After years of writing code, I always feel that how to write clean and elegant code is not an easy thing. According to the theorem of 10000 hours of deliberate training, it takes about five years to become a master, assuming that 8 hours a day, 20 days a month, 12 months a year. In fact, we can’t really spend eight hours writing code in our daily work, and most of the time is to complete the task. When the business pressure is very high, the goal we may want to achieve is how to make the function work as soon as possible. Whether the code is clean and elegant is not always the first priority, but how fast and how to do it.
Under such circumstances, it is very easy to owe technical debt. For a long time, such code basically cannot be maintained and can only be pushed back. This cost is very high. It’s only a matter of time before we have to pay off our debts, and when we have to pay off our debts, we have to pay extra interest. It may be you or your successor who pays the debt, but it’s the team that pays the debt. Therefore, from the perspective of the team, it is very necessary to write good code. How to write clean and elegant code is a very difficult topic. I haven’t found a universal solution. What’s more, some trade off can be discussed a little.
Is the code written for people or machines?
In most cases, I think the code is written for people to see. Although the last executor of the code is the machine, in fact, the code is more often shown to people. Let’s take a look at the life cycle of a piece of code: Development — > Unit test > code review > function test > performance test > online > operation and maintenance, bug repair > online test > offline retirement. The time from development to online may be weeks or months, but the cycle of online operation and maintenance and bug repair can be years.
In the past few years, it’s almost impossible for the original author to maintain it. How successors can understand the previous code logic is extremely critical. If they can’t maintain it, they have to do it again. So in the project, we often see that when we see the code of our predecessors, we all think it’s rubbish and messy. I’d better rewrite it myself. Even in the process of development, you need someone else to code Review, if they can’t understand the code, how can they do the review. And you don’t want to call for help when you’re on vacation because other people can’t understand your code. I’m very impressed by this. I remember when I was not long working, I went back to my hometown for a vacation. Suddenly, my colleague called me and asked me how to solve the problem. At that time, there was a roaming charge for the phone, which was very expensive, but I had to support it until my phone bill was used up.
thereforeCode is mainly written for people to see, is our way of communication。 Those very good open source projects have documents, but we still look at their source code. If the code in the open source project is hard to read, the project will not be popular. Because code is the basic way for our developers to communicate. We can even discuss things that are not clear verbally through code. I think code readability comes first. It is estimated that each company has its own code specifications. The first step is to follow the relevant specifications and keep the code style unified (google code specifications and Microsoft code specifications are recommended). The specification generally includes how to name variables, classes and functions, how to keep functions as short as possible and atomic, how to do many things, the basic design principles of classes and so on. Another suggestion is to learn more about the code in open source projects.
KISS （Keep it simple and stupid）
Generally, the working memory capacity of the brain is 5-9. If there are too many or too complex things, most people can’t directly understand and process them. Usually we need some auxiliary means to deal with complex problems, such as taking notes and drawing pictures, which is a bit similar to borrowing external memory when there is not enough memory.
CS students know that the speed of external memory access is certainly not as fast as memory access. In addition, generally speaking, in the case of complex logic, the probability of error is far greater than that in the case of simple logic. In the case of complex logic, there may be many branches of code. It is difficult for us to consider each case. In order to make the code more reliable and easy to understand, the best way is to keep the code simple. When dealing with a problem, try to use simple logic instead of too many variables.
But the real problem is not always so simple, so how to deal with complex problems? Instead of borrowing external memory, I prefer to abstract complex problems hierarchically. Network communication is a very complex thing. There are countless kinds of devices used in the middle (mobile phones, various IOT devices, desktop computers, laptops, routers, switches…). OSI protocol abstracts each layer and greatly simplifies the situation that each layer needs to deal with. Through the decomposition and abstraction of complex problems, the problems we need to solve at each level are simplified. In fact, it is also similar to the divide and conquer algorithm. Complex problems should be disassembled into small problems to simplify the solution.
Kiss has another meaning, “don’t add substance if it’s unnecessary” (Okam’s razor principle). There is a sentence “all problems in computer science can be solved by another level of indirection” in CS. In order to expand the system and support some possible changes in the future, we often introduce an indirect layer or add an intermediate interface. When making these decisions, we should consider whether it is really necessary. The advantage of adding an extra layer is that it is easy to expand, but it also increases the complexity, making the system more incomprehensible. For the code, it is very likely that I called an API here, and I don’t know where the actual trigger is, which may make it more difficult to understand and debug.
Kiss itself is a trade off. We need to simplify complex problems by abstraction and separation. However, we need to carefully consider whether we need to do more abstraction of indirection in order to keep changes.
DRY （Don’t repeat yourself)
In order to realize a function quickly, if you know there is a similar one before, just copy the code and modify it. It may be the fastest way. But copy code is often the source of many problems and bugs. One kind of problem is that the code from copy contains some other logic, which may not be required by this part, so there may be redundancy or even some additional risks.
Another kind of problem is that during maintenance, we actually don’t know how many other places need to be repaired after repairing one place. In my past projects, there was such a problem. One problem was fixed before. A few days later, another customer proposed another path for similar problems. The same logic should only appear in one place as far as possible, so that problems can be repaired once and for all. This is also an abstraction. For the same logic, it is abstracted into a class or a function, which is also conducive to the readability of the code.
Do you want to write a comment
My personal view is that most of the code should not be annotated. Code itself is a communication language, and generally speaking, programming language is more accurate than our daily spoken language. Keeping the code logic simple and using good naming conventions, the code itself is clear and may be a good article to read. Especially in OO language, the addition of object (noun) and operation (verb in general) can already explain what you are doing. Again, putting the noun of this operation in the comment does not increase the readability of the code. And in subsequent maintenance, the code will be modified, but the comments will not be modified. I’ve seen this in a lot of code reviews I’ve done. Try to write the code understandable, not through comments.
Of course, I don’t object to all comments. Comments are required on public APIs. The pre and post conditions of the API should be listed to explain how to use the API. This can also be used for automatic product API documentation. It is necessary to explain some special optimization logic and algorithm.
Do it right once, don’t believe it will refactoring later
Generally speaking, if you write todo in the code and wait for refactoring or improvement in the future, there will be no future. We can search todo in our code base to see how many and how many years ago. I believe this result will surprise you (welcome to leave a message and share your search results).
Try to do it right once, and don’t believe that you will come back to refactoring the code in the future. Everyone is lazy. Once the current task is completed, the probability of moving on and coming back to deal with it is very small, unless you really need to modify the code next time. If it is said that it will not come back, then todo is meaningless. Don’t leave this question if you really need it. I have seen some people leave a todo, throw a not implemented exception, and then a few days later other students bring the code online and hang up directly. Try not to do todo, do it all at once.
Do you want to write unit tests?
The personal point of view is that you have to, unless you’re just doing prototype or fast iterations of discarded code.
Unit tests are typically automated tests written and run by software developers to ensure that a section of an application (known as the “unit”) meets its design and behaves as intended. In procedural programming, a unit could be an entire module, but it is more commonly an individual function or procedure. In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.
Unit testing is to ensure that the code we write is really the logic we want to express. When our code is integrated into a large project, the subsequent integration test, functional test and even E2E test cannot cover every line of code. If you don’t do enough unit testing, you will leave some black holes in the code that you don’t know. When the caller changes something and goes to an unusual branch, he may hang up. A similar situation has occurred in the projects I brought with me before. The code has been online for several years. Once I changed the parameters of the caller slightly. I thought it was a small change, but when I went online, I hung up because I encountered a branch that no one had tested before. Unit testing is to ensure that the code we write is implemented according to the logic we want. We need to achieve high coverage as far as possible to ensure that there are no black holes left in our own code. I’d like to have a separate discussion on testing, so let’s talk about it briefly.
It is not easy to write good code. We need to consider correctness, readability, robustness, testability, scalability, portability and performance. The previous discussion is just a few points that I think are more important for me to get started. If I want to write good code, I need to deliberate consideration and practice to really achieve the goal!
The first cloud based micro Service Conference
PC address of the first cloud native microservice conference:https://developer.aliyun.com/topic/microservices2020#/
“Alibaba cloud nativeFocus on micro service, Serverless, container, Service Mesh and other technology areas, focusing on cloud native technology trends, cloud native large-scale landing practice, do the best understanding of the official account of cloud developers.