Here are some tips for writing code that is easy to maintain / test.
By the way, the language is written in Java (please read other languages)
Also, you may understand that the degree of coupling is wrong. In that case, please point out. (Because it is different from other things written by books and sites ... (´ ・ ω ・ `))
Also known as module strength. A measure of how much code is focused on the division of responsibility for the class. ** Classes that exist only for one role (all methods exist only for that role) are highly cohesive. ** ** On the contrary, the classes that have no commonality in each method and play various roles have a low degree of cohesion.
When the degree of cohesion is low, the number of member variables tends to increase. Since member variables are the state of the class itself, if there are many member variables, the patterns of states that the instance can take will increase, the complexity will increase, and maintainability will be significantly reduced. It also makes testing harder and more patterns.
This class exists just to "handle strings" and has no other role. String class
Consider a class called Price. That class only has methods that handle price data.
public class Price {
private final int value;
public Price(int value) {
this.value = value;
}
public Price() {
this(0);
}
/**Add price*/
public Price add(Price other) {
return new Price(this.value + other.value);
}
/**Spending the price*/
public Price multiply(int amount) {
return new Price(this.value * amount);
}
/**Discount*/
public Price discount(double percentage) {
return new Price(this.value * percentage);
}
/**Format and return the price*/
public String format() {
return NumberFormat.getCurrencyInstance().format(this.value);
}
public void print() {
System.out.println(this.format());
}
}
//User side
Price price = new Price(1000);
price.print(); // ¥1,000
System.out.println(price.format()); // ¥1,000
System.out.println(price.add(new Price(500)).format()); // ¥1,500
System.out.println(price.discount(0.9).format()); // ¥900
//Supplement:Price is immutable and the initially set value does not change
//Value when calling the last discount=Calculation is done at 1000
Simply put, "** how much the class or method of the user and the user knows (how much depends on) **".
The higher the degree of coupling, the less maintainable it is and the more difficult it is to test.
The degree of coupling increases in the following order.
It only calls methods with no arguments and has no return value. The ideal shape.
public class Main {
private final Price price;
public Main(Price price) {
this.price = price;
}
public void output() {
this.price.print();
}
}
Price price = new Price(1000);
Main main = new Main(price);
main.output(); // ¥1,Is displayed as 000
In the above, the Main
class and the Price
class do not share data, and Main # output ()
does not depend on the implementation ofprint ()
.
The Price
class doesn't know anything about the Main
class, nor does the Main
class care about whatPrice # print ()
is doing inside. ** For example, even if you change the implementation of the print ()
method to print a string to a file, even if you don't actually do anything, Main # output
doesn't care. ** **
Such joins are also called ** message passing **, and interactions between such objects are called ** messaging **. It was named "messaging" because it completely delegates the work to the other object, and it's just like telling someone "this is finally done".
Only pass simple data.
//Data join example
public class NameFormatter {
public String format(String firstName , String familyName) {
return new StringBuilder().add(familyName).add(" ").add(firstName).toString();
}
}
public class Main {
private final String firstName;
private final String familyName;
public Main(String firstName, String familyName) {
this.firstName = firstName;
this.familyName = familyName;
}
public void printName() {
NameFormatter formatter = new NameFormatter();
System.out.println(formatter.format(this.firstName, this.familyName));
}
}
Main main = new Main("Kazuki", "Oda");
main.printName(); //Kazutaka Oda
The combination with NameFormatter
inMain # printName ()
above is a data combination.
In data binding, ** share only "necessary information" **. The "information" referred to here corresponds to the following.
The data join will always have the same output for the same input. Also, since only the necessary information is passed to NameFormatter
,printName ()
just uses the value finally returned from NameFormatter # format
, and what kind of implementation is done internally. You don't have to know if you are there.
In general, data joins pass data as an argument [immutable](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%9F%E3%83%A5%E3%83] % BC% E3% 82% BF% E3% 83% 96% E3% 83% AB) or Primitive Type AA% E3% 83% 9F% E3% 83% 86% E3% 82% A3% E3% 83% 96% E5% 9E% 8B) Or refers to a join when passing an interface (I think). However, even in the case of passing a reference type object, it may correspond to data binding (for example, only the necessary information is disclosed by the called method).
Pass a structure of data (that is, an instance with multiple member variables) as an argument.
public class Item {
//In the body that there is a constructor, getter and setter respectively
private String name;
private String group;
}
public class CashRegister {
public String format(Item item, Price price) {
return item.getName() + ":" + price.format();
}
}
CashRegister regi = new CashRegister();
System.out.println(regi.format(new Item("Smash Bra", "game"), new Price(6800))); //Smash Bra:¥6,800
In the case of stamp binding, data that the callee does not use can be referenced in the structure (that is, the callee also knows unnecessary information). In the above case, group
is not used, but if you want to do it, you can refer to any of them in CashRegister # format
.
It doesn't matter if it's just a reference, but in some cases it is possible to rewrite the value of the data in the called method. So ** In the case of stamp join, when the caller wants to use it correctly, there are cases where it is necessary to know (know) to some extent what data is handled in the method of the callee. ** **
The above is a data join with the caller if CashRegister is set as follows. However, the caller and Item, and the caller and Price are stamped together </ font>
public class CashRegister {
public String format(String item, int price) {
return item + ":" + price;
}
}
CashRegister regi = new CashRegister();
Item item = new Item("Smash Bra", "game");
Price price = new Price(6800);
System.out.println(regi.format(item.getName(), price.format())); //Smash Bra:¥6,800
Thus, stamp binding cannot be easily eliminated as long as the class is used. By using the interface, it is possible to reduce the degree of coupling to data binding or message binding, but it will be longer, so I will omit it.
A case where the return value changes depending on the content of the given argument.
public class Item {
//In the body that there is a constructor, getter and setter respectively
private String name;
private Price unitPrice;
}
public class SaleService {
public Price fixPrice(Item item, int amount, boolean discount) {
Price newPrice = item.getUnitPrice().multiply(amount);
if (discount == false) {
return newPrice;
}
return newPrice.discount(0.90);
}
}
Item item = new Item("Smash Bra", new Price(6800));
SaleService service = new SaleService();
System.out.prinln(service.fixPrice(item, 2, false)); // ¥13,600
System.out.prinln(service.fixPrice(item, 2, true)); // ¥12,240
In this control combination, the return value differs by the number of patterns even if a similar value is entered as an argument depending on the patterns that can be taken. In other words, ** the patterns that can be taken depend on the arguments. ** **
This control coupling can be reduced to less than the stamp coupling by using polymorphism. (However, I personally think that control coupling can be tolerated if it is about 3 to 4 patterns)
Outer join refers to the state in which multiple modules refer to one global resource. From here on, if you feel free to allow this join, it will be very difficult to test.
The global resources refer to the following, for example.
For these, the value changed in other instances etc. affects the entire class referenced by other. Also, if the structure of those resources changes, it will be necessary to modify all the calling classes.
** In order to write testable code, it is important (I think) how to reduce the number of bindings after this outer join. ** **
A state in which various classes refer to multiple data of global resources. It will be more chaotic than the outer join.
A join that knows even the unpublished methods and variable names of other classes and directly references / calls them. In content combination, if the internal implementation (implementation that is not published) of the dependent class changes, it will be directly affected, so even if the variable name of the dependency is changed, it will be affected.
By keeping the following in mind, the degree of coupling can be kept low.
Simply put, the number of "if statements / for statements / while statements / switch statements / try statements / catch statements" and so on. If there are none of them, it will be 1, and as the number of branches increases, it will increase by 1. For example, the cyclomatic complexity of the following method is 4.
public void hogehoge(int test) {
if (test > 5 ) {
// doSomething1
} else if (test < 1) {
// doSomething2
}
for (int i = 0; i < 5; i++) {
// doSomething3
}
}
Of course, there are more test patterns. In addition, readability is reduced and maintenance becomes difficult.