We can enumerate the manifestations of a lot of poor-quality code, such as unintelligible names, super large classes, super large methods, repetitive code, difficult code, difficult code modification... Among them, the two manifestations that most affect the quality of the code are naming untrue names. , Logical scalability is poor. When a newcomer reads the code, he sometimes finds that the method naming does not match the actual logic, which makes people very confused. This phenomenon is not uncommon in normal work; the other is the poor logical scalability. After the new business requirements were put forward, it was discovered that there were many changes to be made, and there were more business logics that needed to be returned, resulting in low R&D efficiency.
The problems mentioned in section 1 are summarized and sorted out, and 6 types of problems are roughly sorted out and explained separately.
Naming problem: Naming problem is a very headache, it is not so easy to choose a name that is worthy of the name and easy to understand. When it comes to variable naming, method naming, and class naming, there are two common naming problems: one is incomprehensible; the other is misnomer. Naming is incomprehensible when a person looks at it at first time and does not know what it means. The fundamental reason is that they did not think of a suitable vocabulary to abstract the problem; the naming name is not the same as the actual logic intended to express. Such naming will be misleading. people.
Code structure problem: When a person looks at the engineering code for the first time, before looking at the code logic in depth, the overall code quality can be felt from the module division, class division, and method division. If a class has several thousand lines of code, one There are hundreds of lines in the method. I believe that not many people are willing to look at this kind of logic, and the complexity is relatively high. The hierarchical structure of a good code is very clear, just like reading a beautiful book, there is a pleasing feeling.
Programming paradigm problem: There are three programming paradigms: table mode, transaction script mode, and domain design mode. The transaction script mode that everyone uses the most is the transaction script mode. This mode is most in line with the way people do things, step by step, the biggest problem of this mode It just takes on the responsibilities that shouldn't be undertaken by oneself. It seems more logical, but in fact there are more problems. People usually like to call it "noodle-type code".
Readability issues: In addition to achieving business functions, the code must also have good readability. Some codes do not have any comments; some code formats are not uniform; some are to show off technology, large sections of Lambda expressions (and Not to say that Lambda expressions are not good, the key is to control the depth of the hierarchy), such code looks concise, and the readability is not very good.
Scalability issue: Scalability issue is a commonplace issue. It is not so easy to achieve good scalability. Generally, there is no abstract problem. For example, if a shop displays Tabs in the store, the noodle-type code is to directly define a List. , And then add a Tab object to it, what if you need to add another Tab? Typically, the principle of opening and closing is not satisfied.
No design problem: The entire code looks relatively plain, and others can't learn from it after reading it. Generally, this kind of problem is that there is no in-depth analysis of the problem, only the problem is solved, without considering how to better solve the problem, such as whether the work of the repeated processing flow can be abstracted into a general template class, and whether different processing classes can be passed through the factory class. Obtain specific strategies, whether asynchronous processing can be processed in event mode, whether newly added capabilities can be discovered through automatic registration...
Next, analyze the reasons why the code is bad. There are external and internal reasons for this problem. The main external reasons are: the project is urgently scheduled and there is not much time to design; the shortage of resources and the lack of manpower can only come as quickly as possible; emergency problem repairs, temporary solutions quickly... The main internal reasons are: my own skills are low, how I have not mastered the skills, such as Lamda expressions, commonly used tools, advanced framework usage, etc.; the spirit of infinite pursuit, just complete the requirements, stability, scalability, performance, Data consistency, etc. are not considered...
The author believes that the most critical issue is the internal problems, the root causes are two: low self-demand; no feedback channel. If you don’t have high requirements for yourself, you will stop developing just to meet your requirements. It is difficult to write high-quality code. In addition, without external feedback, it is difficult to improve your skills. The author’s previous supervisor was very strict and reviewed the code written by everyone carefully. A variable name and a logical way of writing were repeatedly modified. This is actually the fastest way to improve skills.
The author likes to use three methods to improve code quality: domain modeling, design principles, and design patterns. I mainly talk about how to use it.
Analysis stage: When you get a requirement, don't worry about how to implement this function. This mode is easy to fall into the transaction script. What to analyze? It is necessary to analyze the purpose of the requirements and which entities are required to complete the function. The core of this step is to find entities. Take the above example of the tab display in the shop. It has two key entities: navigation bar and Tab. The navigation bar contains several Tabs.
Design stage: After analyzing the entities, analyze how responsibilities are assigned to specific entities. This requires some design principles to guide. GRASP mentions some principles of responsibilities assignment. Interested students can go and see in detail. . Going back to the above example, there are two main responsibilities of Tab: one is whether the Tab can be displayed, which is its own responsibility, as the logic of the new Tab display is that there are new products on the store within 30 days; the other is the Tab specification The construction of information is also its own responsibility. The navigation bar has two responsibilities: one is to accept Tab registration; the other is to display. The unreasonable assignment of responsibilities does not satisfy the characteristics of high cohesion and low coupling.
Polishing stage: At this stage, choose the appropriate model to implement. Everyone will understand what it does when they see the model. For example, when you see the template class, you will know to handle the general business process, and the specific changes are handled in the subclass. . In the above example, two design patterns are used: one is the subscriber mode, which is the process of automatic tab registration; the other is the template mode, which first judges whether the Tab can be displayed, and then constructs the Tab specification information. Although the process is simple, it can also be used. The general process is abstracted out, and the subclass only needs to simply rewrite 2 methods.
The entry barrier for domain modeling is relatively high and contains some difficult concepts. This article will not talk about how to model (you can communicate privately). The author finds that getting everyone to accept domain modeling is far more important than knowing how to model. After you know the role of domain modeling, you will think about each Kind of way to learn. The following is a description of some actual cases experienced by the author, so that everyone does not sound so empty.
I joined a financial company after working for a year. At that time, I didn’t know anything about finance. After I came into contact with terms such as subject matter, debt, debt transfer, financing guarantee, and non-financing guarantee, I felt at a loss for a while, and I had to learn a lot of new things every day. content.
Two months later, my supervisor shared with us and took a ppt. It contains the entities in the domain and the relationship between the entities. I immediately understood how the entire business is. Fun. The role of the model is to simplify people's understanding of things. If we are caught in the code details at the beginning, it is difficult to see the full picture of the business, and the code is to achieve business capabilities. When you know the business, look at the code again. Much faster.
In the company, there are research and development, products, operations, testing...When we communicate together, everyone's default language is not unified. Development often talks about how to operate this database table, and products often talk about business models...this As a result, everyone’s understanding is not uniform.
That was one night. Just after confirming the interaction process with the interactive classmates, she suddenly asked a question: Let the seller move similar pages to the same folder, is this easy to achieve? After listening, I told him that I couldn’t. When the interactive students heard that it was reasonable, why couldn’t it be realized? I started to talk to her about the current system process, and found that she was stunned, and immediately found the problem. I used the language developed to describe the problem. I immediately changed the method and found a pen and a pen. A piece of paper, I drew interactive students what our domain model is and what the interaction between business entities is. After finishing the lecture, the interactive students immediately understood the reason why it could not be realized.
Some students feel that domain modeling is hollow and imaginary. In fact, in addition to simplifying and unifying understanding, domain modeling may also guide code design. For example, the store navigation Tab example mentioned above, the author uses domain modeling to design Yes, although it is a small requirement, it does not hinder the application of domain modeling. In the figure below, you can clearly see that the navigation bar contains several Tabs, and one Tab contains specification information and click operation information. After the business model is drawn, the corresponding code will also have the above concepts, and there is a mapping relationship between reality and code. The model is the code, and the code is the model. If your model can’t reflect reality, the module can only be considered a garish. Teacher Fan Gang summed up three sentences on this: What things are in reality, and what objects are there; what are the behaviors of real things, and what methods are there to correspond to the objects; Real things What is the connection, and what is the connection with the corresponding object.
For design principles, we generally talk about SOLID, which contains five design principles:
Single responsibility principle: A class should have one, and only one, reason to change, a class can only be modified for one reason.
Principle of opening and closing: Entities should be open for extension, but closed for modification, open for extension, and closed for modification.
Richter substitution principle: Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it, subclasses can replace parent classes.
Interface isolation principle: A client should not be forced to implement an interface that it doesn't use. The client should not be forced to implement an interface that it doesn't use. The interface should be as small as possible.
Dependence inversion principle: Abstractions should not depend on details. Details should depend on abstractions, abstractions do not depend on details, and details depend on abstractions.
We have basically heard or understood about the SOLID principles, but why do we have these design principles? In order to answer this question, we derive from the goal. The goal of software development is high cohesion and low coupling. This sentence is often difficult to measure. For example, to answer: What is high cohesion? What is low coupling? How high is high cohesion? How low should low coupling be? These four questions are not easy to answer.
Think about it in reverse, what if our code is not highly cohesive and low-coupling? That is, the scene of low cohesion and high coupling. If the code is low cohesive and highly coupled, there will be changes to one logic, which will lead to multiple code changes. This is not what we want to see, especially when modifying the original logic, it is easy to have bugs, such as before the author Modifying one problem and changing the rules in another place seems to be no problem, but it affects a business side. This is why the opening and closing principle proposes to close the modification. Modifying the original logic is risky.
The ideal situation is that the modification is only limited to a certain local scope, so that the scope of impact is limited, so we require a single logic, not containing multiple responsibilities. Think further down: Why do we want to modify it? In addition to bugs in the original logic that need to be repaired and code refactoring, an important reason is that the requirements have changed, and the changes have caused us to modify the original logic. If there are no modified scenarios, there is no such thing as high cohesion and low coupling. Therefore, the underlying logic of the design principle is to allow the software to better respond to changes and reduce costs and increase efficiency.
Design principles are just a guideline, and there is still a long way to go. Some students say that I understand the design principles, but I still can’t apply them. In fact, the essence of this problem is that you have no understanding of the underlying logic of the design principles, and no insight into the focus of change. How to solve this problem? The answer given by design patterns: find changes, encapsulate changes.
Please refer to the article written by the author before for the design pattern.
When the called interface has different implementations (the input parameters, output parameters, and interfaces are different), a layer of anti-corrosion layer needs to be abstracted. How to implement it? Next, let's look at two cases respectively. The two cases have different focuses. One is the abstraction of partial behavior, and the other is the abstraction of partial structure.
The store needs to query the store brand information, but the interface of Lazada and AE is different, how to abstract the anti-corrosion layer?
First of all, the simplest solution is easy to think of, which is to define an interface, and then there are two implementations. Its advantage is that the level is simple, and everyone can understand it after reading it. Its shortcomings are also obvious. In the two implementation classes, the responsibilities are not single, and two responsibilities are assumed: one is to realize the query of the store brand, and the other is the data conversion.
According to the shortcomings mentioned in the first scheme, it is easy to think of using the adapter pattern to split the previous class into two classes: one class is to call the corresponding brand service; the other is to do data adaptation and conversion. However, there is another disadvantage of the method at this time. In the internationalization scenario, the isolation between multi-tenants must be considered. For example, Lazada has multiple sites. How to achieve more fine-grained differences? Scheme 3 is based on these thoughts.
The third option is to introduce a multi-tenant framework, which can support multi-tenant scenarios.
There is a "golden oil" development model: assembling parameters, calling interfaces, and parsing response results. You will find that this model is too versatile and suitable for all scenarios. This development model is also "transaction script mode" or "noodle type" Code".
For the coupon query case, use the domain modeling model to first think about what entities are there. The essence of coupon query: query through xx conditions to return a set of coupons that meet the conditions. For coupons, two types of information are essential. One is the specification information of the coupon, such as the name of the coupon, the discount amount, the validity period, etc.; the other is the restriction conditions of the coupon. When inquiring, whether to check store coupons, fan coupons, or commodity coupons... Therefore, two abstract coupons are divided: one is the coupon query request; the other is the coupon specification entity.
If you follow this design, there is a disadvantage that the business side understands that the complexity will increase, it is a low-level implementation, and it is not simple to use. Coupons focus on product delivery rather than just function delivery. Therefore, the product components are abstracted on top of the underlying implementation, so that the business side is relatively simple to use.