A preface.
Everyone has come across “family code” in their daily work.
- Thousands of lines for a class, hundreds of thousands of lines for a method, and a severe anemia model
- Method internal business logic chaos, if/else everywhere
- The critical business logic is uncommented and magic values are everywhere
- Duplicate code is everywhere
- .
At the beginning of the year, bloggers join a new product line to develop a new product, recruited or recruited from other departments. Requirements were iterated for about half a year before I got involved in development. Because it was a new product and we were a new team, everyone’s code style was very different. Every time I have to iterate on requirements on the corresponding module, I feel sick just looking at the code. Clearly just a line of code annotation, puzzling led to a number of bugs, want to change are not moving. After the end of this year, I communicated with my leaders about the unification of the technical framework and formulated some relevant RESEARCH and development guidelines. Today I’m going to share with you how to decouple systems and fix bad smells in code. If have wrong place, welcome to point out, common progress ~
2. Entity class
Entity classes as data carriers, you will definitely come across in your daily work, but do you really use them correctly?
Tell me about the code I saw in the previous project. The data carrier obtained by data query, the data carrier of service layer interaction, the data carrier of RPC layer interaction and the data carrier of Web layer interaction are all concentrated in one entity. This will not be a big problem in a very simple business scenario, but in a larger business application. That would be a problem.
For example
User table a four field, user ID, user name, phone number, user password. You now need to display the user data on the User Administration menu page. If there is only one entity and the data I query from the database has four fields, it is definitely not appropriate to pass the password to the front end. Desensitization. Leave the password blank. But you can still see it in the front-end messages
{
"userId":1
"userName":"admin"
"mobile":"13888888888"
"password":null
}
Copy the code
Obviously this is not reasonable, the data returned to the front-end should be
{
"userId":1
"userName":"admin"
"mobile":"13888888888"
}
Copy the code
Obviously, the layering of each layer is very important for data carriers in Java. I usually layer the data carrier as follows
PO: Persistent object, entity attributes and table field one-to-one mapping, generated in the DAO layer, used in the Service layer.
BO: business object, aggregation PO layer data, can also be multi-table associated data query aggregation, there will be internal attributes of the business logic processing method. The DAO/Service layer is generated and used by the Service layer.
DTO: data transfer object, commonly used service layer, RPC layer, controller layer, used for array transfer carrier, internal no logic
VO: Data presentation layer, used for controller layer, here I am used to the method of the parameter, used to fit the structural differences between DTO and VO layer.
Query: Query parameter, controller layer method input parameter, receive front-end Query type parameter.
Command: indicates a Command parameter, such as a data carrier that a user adds or modifies.
Description:
1. I often mix DTO and VO. If the data transmission carrier will only be assembled and used in the Controller presentation layer, it is also ok to directly return it to the front end. They’re just data carriers.
2.Command and DTO/VO. Some bloggers on the Internet will take VO or DTO as the input parameter of the Web layer to add, delete and modify data. There is no problem with structure and definition, but this is somewhat irrelevant with data carriers with instructions. My understanding of DTO and VO is that they are result-type data and the products of business logic processing. Command is the Command data, through the Command type parameters, through the BO layer business logic, the data is mapped to the PO layer and interacts with the database.
3. The Query parameter is similar to the Command parameter. DTO or VO is often used to pass data.
Anemia model
I wouldn’t say 90%, but I’d say at least 70% of r&d likes to aggregate a lot of logic in the service layer. As a result, the business logic of the entire service layer is not prominent enough and semantic is weak, which makes it difficult for secondary developers to get started. The BO layer logic in the corresponding Service method is empty.
About anemia model and congestion model is common in DDD domain modeling concept, this article only on the MVC model of congestion model transformation, the classmate of DDD perceptual to this concept can be read: zhuanlan.zhihu.com/p/147879821
For example
The figure above shows some of the core logic of the log-in interface made by bloggers. If you write the above logic in a login method, then the checksum of the miscellaneous checksum methods must be at least a hundred lines. It’s hard to put the logic together without adding the necessary comments.
Now only in view of the above process to say about the blogger slim strategy. After the LoginCommand command is sent, the LoginBO (similar to the aggregation root in DDD, but not exactly the same) maps the data and extracts the logic of the minimal node method, such as validating login parameters to define a method. Call the BO layer methods one by one in the Service layer. 4 small methods can also be aggregated in a method, logical clear. Take a look at the pseudocode.
Anemia model
// Check 1 // check 2.... // Set tokenCopy the code
Congestion model
Bo layer Verification Method 1{} Verification method 2{} Verification method 3{} Verification method 4{} Total verification {Verification Method 1 Verification method 2 Verification method 3 Verification method 4}.... Other minimum processing logic service layer login{bo. Total verification BO. Generate token return login result}Copy the code
4. If/else the land
I’m sure you’ve come across a method that has a bunch of if/else logic judgments, and the worst part is that it doesn’t have comments. I’ve summarized the if/else scenarios that people use a lot
- Into the reference check
- Internal judgment of business logic
4.1. Input verification
Method input validation is unavoidable. Error parameter verification results in one of two ways:
1. Directly report an error
For direct error reporting, I suggest you read the blogger’s global exception handling article: juejin.cn/post/696540…
Input verification failure, direct mapping business error code, using validationUtil.istrue () semantic form of strong expression, easy to read the code.
2. Return a null value
Flat if judgment, clear expression of hierarchy.
Don’t recommend
if(true){
//doSomething
}
Copy the code
advice
if(false){
return;
}
//doSomething
Copy the code
4.2. Internal judgment of business logic
I read a good article about eliminating if/else: juejin.cn/post/691387…
Here is a blogger for some simple logic if/else judgment commonly used a functional tool class, simplify the code
/** * Public final class JavaUtil {/** * public final class JavaUtil {/** * singleton */ private static JavaUtil javaUtil; Public static JavaUtil getJavaUtil() {if (JavaUtil == null) {synchronized (javautil.class) { if (javaUtil == null) { javaUtil = new JavaUtil(); } } } return javaUtil; } private JavaUtil() { super(); @param consumer * @param <T> * @return */ public <T> JavaUtil acceptIfCondition(boolean condition, T value, Consumer<T> consumer) { if (condition) { consumer.accept(value); } return this; } /** * Parameter consumption based on whether the condition is valid * @param condition * @param trueValue parameter consumption when the condition is valid * @param falseValue parameter consumption when the condition is not valid * @param falseValue parameter consumption Public <T> JavaUtil acceptDependCondition(Boolean condition, T trueValue) T falseValue, Consumer<T> consumer) { consumer.accept(condition ? trueValue : falseValue); return this; } /** * The consumption function is determined based on the condition being established * @param condition * @param Value the consumption parameter * @param consumerTrue The consumption parameter when the condition is established * @param consumerFalse Public <T> JavaUtil consumeDependCondition(Boolean condition, T value) <T> JavaUtil consumeCondition (Boolean condition, T value) Consumer<T> consumerTrue, Consumer<T> consumerFalse) { if(condition){ consumerTrue.accept(value); }else { consumerFalse.accept(value); } return this; } @param condition * @param supplier returns a value from the supplier function * @param consumer <T> * @param return */ public <T> JavaUtil acceptSupplierIfCondition(boolean condition, Supplier<T> supplier, Consumer<T> consumer) { if (condition) { consumer.accept(supplier.get()); } return this; } /** * consumption parameter If the provider function is empty * @param supplier provider function as the judgment input * @param consumer consumption function * @param defValue consumption input * @param <T> * @return */ public <T> JavaUtil acceptValueIfNull(Supplier<T> supplier, Consumer<T> consumer, T defValue) { return acceptIfCondition(supplier.get() == null, defValue, consumer); } /** * public <T> JavaUtil ** @param consumer * @param <T> * @return */ public <T> JavaUtil acceptIfNotNull(T value, Consumer<T> consumer) { if (value ! = null) { consumer.accept(value); } return this; } @param value @param consumer consumer function @return */ public JavaUtil acceptIfNotEmpty(String) value, Consumer<String> consumer) { if (StringUtils.hasText(value)) { consumer.accept(value); } return this; } /** * source not null; @param mapFunction consumes the non-empty input parameter and returns the consumption parameter. @param consumer consumes the parameter. @param <T> * @param <R> * @return */ public <T, R> JavaUtil mapAndAcceptIfNonnull(T source, Function<T, R> mapFunction, Consumer<R> consumer) { if (source ! = null) { R apply = mapFunction.apply(source); consumer.accept(apply); } return this; } /** * List< object > converts to List< some property of the object >, And assign it to another class * @param list entry * @param consumer consumption * @param mapFunction mapping * @param <T> original object * @param <R> some property in the object * @return */ public <T, R> JavaUtil mapAndAcceptIfNotEmpty(List<T> list, Function<T, R> mapFunction, Consumer<List<R>> consumer) { if (CollectionUtils.isNotEmpty(list)) { List<R> mapList = list.stream().map(mapFunction).collect(toList()); consumer.accept(mapList); } return this; }}Copy the code
Repeat logic
Friends who use IDEA development should use the Ali development code plugin. When you write logic in a project that has a lot of repeated lines of code, the beginning of the repeated lines of code is highlighted with a wavy underline. We’re all programmers. We’re all obsessive-compulsive. I tried to eliminate the hint, but later I found that they were just similar in structure, but the logic of setting the value inside was different, which could not be stripped as a public method.
For example, user.getName() in method 1 and employee.getName() in method 2.
What’s a good way?
One of the big features of JDK8 is that functions are interfaces. Use functional interfaces and generics to solve the problem.
Here’s an example:
Now there is a method to write the user and Employee data to a file named user and Employee that will not duplicate.
The conventional way of writing it must report code duplication
Public static void createExportFiles(String tempExportPath, List<User> datas) { if (CollectionUtil.isEmpty(datas)){ return; } datas.forEach(data -> { String fileName = user.getName() + ".json"; FileWriter writer = new FileWriter(tempExportPath + File.separator + fileName); try { writer.write(GsonUtil.gsonToString(data)); } catch (Exception e) { log.error(e.getMessage()); }}); } public static void createExportFiles(String tempExportPath, List<Employee> datas) { if (CollectionUtil.isEmpty(datas)){ return; } datas.forEach(data -> { String fileName = data.getName() + ".json"; FileWriter writer = new FileWriter(tempExportPath + File.separator + fileName); try { writer.write(GsonUtil.gsonToString(data)); } catch (Exception e) { log.error(e.getMessage()); }}); }Copy the code
Functions are combined with generics
public static <T,R> void createExportFiles(String tempExportPath, List<T> datas, Function<T, R> function) { if (CollectionUtil.isEmpty(datas)){ return; } datas.forEach(data -> { String fileName = function.apply(data) + ".json"; FileWriter writer = new FileWriter(tempExportPath + File.separator + fileName); try { writer.write(GsonUtil.gsonToString(data)); } catch (Exception e) { log.error(e.getMessage()); }}); User: createExportFiles("/root",List< user > users, user ::getName) Employee: createExportFiles("/root",List<Employee> employees,Employee::getName)Copy the code
Magic value/constant/enumeration
I’m sure you’ll come across this code a lot
if(state==1){
//doSomething
}else if(state==2){
//doSomething
}else{
//doSomething
}
Copy the code
What does one, two, three mean?
A little better for you to note, and a little better for you to define constants. But the right thing to do is to define an enumeration that defines exactly what 1, 2, and 3 do.
For example, define a gender enumeration
Public enum SexEnum {MAN (1, "male" and "MAN"), the WOMAN (0, "female", "WOMAN"),; @Getter private Integer key; @Getter private String value; @Getter private String name; SexEnum(Integer key, String value,String name) { this.key = key; this.value = value; this.name = name; } public static SexEnum getByKey(String key) { for (SexEnum e : values()) { if (Objects.equals(key, e.key)) { return e; }} throw new RuntimeException(" no gender enumeration found: "+ key); }}Copy the code
When making gender judgments
If (objects.equals (sex,SexEnum. Man.getkey ())){}Copy the code
7. Tool class chaos
Search any StringUtil in an old project and you’ll find a bunch of custom StringUtil. The methods are mostly the same, which is unnecessary duplication of code for a project.
It is recommended to pull a separate base project and put it into a common utility class that is not business related, where the public methods are maintained. A base module is defined in the service. If necessary services need to be extended to the tool classes in the Base project, the service implements the tool classes by inheriting the tool classes in the Base project. The judgment by type is provided by the tool class of the Base project.
8. Confusion in data logic management
8.1. Data in the DAO layer is chaotic
If you think of a single application as a large distributed application. Then each internal service layer is the corresponding microservice. The DAO layer corresponding to each microservice is the database corresponding to the microservice. Different microservices cannot be invoked across libraries. Therefore, it is forbidden to add, delete, or modify data other than the service layer in the corresponding Dao layer of the service layer.
8.2. The modification and query of irrelevant data of the main business flow are chaotic
In the daily operation of data, it is very likely that the data of an object is from mysql in version 1, mysql and Redis aggregation in version 2, and ES, mysql and Redis aggregation in version 3.
As versions evolve, the source of the data changes, and each change is made to the main business logic. This leads to a lot of data aggregation in the code as the business evolves, but essentially. For example, if I want to query a user’s information, I don’t care how many data sources your user information is aggregated from, I only care what my user data is. Think of the DAO layer as a powerful warehouse, where you tell it what data you want and leave it to the warehouse to source and aggregate the data. It is similar to merging the logic of the DAO layer and the data operation of the Service layer into a small service.
So the master logic is clear.
Too much concatenation logic
Too much logic is defined in the logic that is not strongly associated with the business.
For example, we define the permission system as follows: User 1: n Role 1: N permission
Now let’s develop an interface to delete the user. Therefore, the permission association between users and roles needs to be deleted.
The way we code in general
Public void delete(Long userId){Delete a user. Delete the association between a user and a role. Delete... }Copy the code
Have you found the problem? Clearly I am deleting users, but I am breaking the business boundary of deleting users. And if there are more functions associated with the user, should I call them one by one? This is obviously unreasonable, and the code is difficult to read and maintain. Decoupling is a must here, and one of the important things that comes to mind for decoupling in a microservice scenario is MQ. Business application isn’t that the event listening mechanism?
Modify the code
Public void delete(Long userId){Delete a user Publish a user delete Event} // Role association function public void Listener (Event Event){Delete the association between a role and a user} // Notify other micro-services Public void listener(Event Event){send an MQ message to inform other microservice users of the deletion}Copy the code
Ten. Contact me
If you think the article is well written, you can like it and comment + follow it
Nailing: louyanfeng25
WeChat: baiyan_lou