34 How Did Your Code Become So Messy

34 How Did Your Code Get Messy? #

Hello, I am Zheng Ye.

In the previous few lessons, I talked to you about various automations in the development process, from building, testing, to deploying, all of which are viewed from the outside of the software. Starting from this lesson, I am going to lead you into the inside of the software. Today’s topic will start with writing code.

Code that Gradually Decays #

Code is the most direct weapon for programmers to transform the world, but it is also the thing that programmers complain about the most. Why are programmers so dissatisfied with code?

Would you complain about writing a piece of code? You certainly wouldn’t, after all, this is your livelihood, and you still have some basic professional ethics. So what are the complaints about? It’s about maintaining a piece of code.

Why is it so difficult to maintain code? Usually, the code you are maintaining has a certain age, so you always complain that the previous developers didn’t write this code properly.

Now, let’s say you have received a new requirement and need to add a new feature to this piece of code, what would you do? Many people’s approach is to add a new piece of logic to the existing code and then submit the work as completed.

Do you see the problem? You simply completed a task with your head down, but the code has become even worse. If I were to ask you, why did you do this? Your answer might be: “The code is already like this, I dare not make random changes.” or “I just followed someone else’s style when writing, this is how it was written before.”

There is a joke in the industry that the best punishment for programmers is to maintain the code they wrote three months ago. You unwittingly become the person you dislike the most.

In the past, I also believed that many programmers were irresponsible and didn’t write good code from the beginning. Later, I realized that many codes are actually just added bit by bit. You need to understand that once a product has vitality, it will exist for a long time, and the code will gradually decay over time.

And almost every programmer has the same reasons, they are also very aggrieved because they only made small changes.

Is there a solution to this problem? One solution mentioned earlier is refactoring, but the premise of refactoring is that you need to know where the code is heading. For this problem, a better answer is that you need to understand some knowledge about software design.

SOLID Principles #

When it comes to software design, most programmers are familiar with the saying “high cohesion, low coupling.” However, just like the phrase “hoping for world peace,” although it is correct, it doesn’t provide us with a very practical guide for our work.

People have attempted various methods to break down this lofty goal, and one approach that has proven to be more practical is the SOLID principles proposed by Robert Martin, which is an acronym for five design principles:

  • Single Responsibility Principle (SRP)
  • Open-Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)

As early as 1995, Robert Martin proposed the prototypes of these design principles. Later, in his book “Agile Software Development: Principles, Patterns, and Practices,” he provided a more complete explanation of these five principles. Subsequently, he further organized these principles into the current “SOLID” principles.

What is the use of learning these design principles?

Most programmers today start learning software design with design patterns. But I don’t know if you have noticed that while learning design patterns, some patterns seem so similar that it’s hard to remember their subtle differences without careful comparison.

Moreover, when you actually start working, you may only remember a few of the simplest patterns, such as the factory method or observer.

Furthermore, some people often “try too hard to be unhappy with new words” and forcefully use design patterns, which can make the code more complex. You may have the illusion that you learned a fake design pattern, as everyone says it’s good, but you can’t feel it.

When I first started learning design patterns, I was really troubled by this problem. It wasn’t until I read Robert Martin’s “Agile Software Development: Principles, Patterns, and Practices” that I finally understood. This is a book that has been ruined by its name.

At the time of the publication of this book, the Agile Software Development movement was in full swing, and Robert Martin couldn’t help but jump on the bandwagon and include “agile” in the title. In fact, this is a book about software design.

After I saw the five SOLID principles, I finally realized that I had been pursuing the wrong direction. If design patterns are the “techniques,” then design principles are the “guidelines.” Design patterns cannot help you establish a knowledge system, but design principles can.

When I couldn’t understand the “guidelines,” I could only memorize the “techniques,” and the result was inevitably unsatisfactory. After understanding this, I boldly gave up the pursuit of design patterns and just wrote code according to design principles. As a result, I often found myself refactoring code that conforms to a certain design pattern. As for the specific names of patterns, if I don’t consciously seek them out, I can no longer remember them.

Of course, I’m not saying that design patterns are not important. The reason why I can write code according to design principles is that I have put a lot of effort into learning design patterns.

Techniques and guidelines are skills that every programmer should have. Only by putting effort into the “techniques” can you understand the value of the “guidelines.” The “guidelines” can help you establish a more complete knowledge system, so you don’t have to wander around in the low-level realm of “techniques.”

Single Responsibility Principle #

Alright, now let’s expand a bit on the Single Responsibility Principle (SRP) within the SOLID principles. Although this principle may sound simple, there are still many misunderstandings surrounding it.

Firstly, what exactly is the Single Responsibility Principle? If you’ve read “Agile Software Development: Principles, Patterns, and Practices”, you should understand that a module should have only one reason to change.

In 2017, Robert Martin published “Clean Architecture” and modified the definition of SRP to “a module should be responsible for only one type of actor.” Here, actor can be understood as people with a common requirement for the system.

Regardless of the definition, it may not be immediately clear upon first reading. Let me provide an example to help you understand. I will use the example given by Robert Martin himself. In a salary management system, there is an Employee class with three methods:

  • calculatePay(), which calculates the salary and is of interest to the finance department.
  • reportHours(), which tracks working hours and is of interest to the human resources department.
  • save(), which saves data and is of interest to the technical department.

The reason these three methods are within the same class is because some of their behavior is similar. For example, both calculating the salary and tracking working hours require calculating regular working hours. To avoid repetition, the team introduced a new method: regularHours().

Image

Next, the finance department wants to modify the method for calculating regular working hours, but the human resources department does not need to make any changes. The programmer in charge of the modification only sees that calculatePay() calls regularHours(). They complete their task, and the changes are accepted by the finance department. However, after deployment, the human resources department receives inaccurate reports.

This is a real-life case in which the company incurred millions of dollars in losses due to this error.

If you were to ask the programmer why calculatePay() and reportHours() are in the same class, they would tell you it’s because they both make use of the Employee class data.

However, they are serving different actors. So, if any actor has a new requirement, this class needs to be modified, making it susceptible to being the focal point of changes.

More importantly, soon it will become so complex that nobody will know which modules are related to it and who will be affected by changes. Programmers will become increasingly unwilling to maintain this piece of code.

As I mentioned in the “Introduction” of the column, the human brain has limited capacity and cannot comprehend overly complex things. Therefore, the only thing we can do is to simplify complex matters.

I constantly emphasize breaking tasks down into smaller ones in the “Task Decomposition” section, and the same principle applies when writing code. The Single Responsibility Principle provides a guiding principle for decomposing code based on different actors.

Robert Martin proposed a solution to the aforementioned problem, which involves dividing the classes based on different actors. I have included the class diagram below to illustrate the division:

Image

Writing Short Functions #

Okay, now that you have a basic understanding of the Single Responsibility Principle, there’s one more thing worth noting. Let me start by asking you a question: How long do you think a function should be?

There was once someone who proudly boasted to me that they had a high code standard and would never allow functions that exceeded 50 lines.

I have always emphasized the value of being “small” and how seeing just how small you can make something allows you to work at a finer granularity. It’s easy to give an example for the Single Responsibility Principle, but in real-world scenarios, how many actors a module caters to completely depends on your ability to break things down.

Getting back to the earlier question, as far as my personal habits go, my functions usually don’t exceed ten lines, and if it’s a powerful expressive language like Ruby, the functions tend to be even shorter.

So, you can probably imagine how I felt when I heard someone say, “Consider 50 lines of code as a small function.” I know that “function length” is yet another topic that easily sparks debate, and people’s answers to this question depend on how they perceive the problem.

Therefore, discussing the length of functions without considering the context is actually meaningless.

The Single Responsibility Principle can be applied at different levels. When writing a class, you can ask yourself if its methods are serving a single actor. When writing methods, you can question if the code is at a single level. Even when it comes to a service, you need to consider from a business standpoint if it is providing a single type of service. In essence, the finer the granularity you observe, the more issues you’ll be able to discover.

Summary #

Today, I talked about software design. Many coding issues actually stem from insufficient consideration of design.

Many programmers start learning design from design patterns, but this approach often lacks structure and is difficult to master effectively. Design principles, on the other hand, form a better system. By mastering design principles, one can better understand design patterns and techniques. “SOLID,” summarized by Robert Martin, is a relatively comprehensive and easy-to-learn set of design principles.

I will use the Single Responsibility Principle from “SOLID” as an example to elaborate further, but for more information, I recommend reading Robert Martin’s books. Additionally, I have supplemented some dimensions for you, especially emphasizing the “small” aspects, to help you identify problems in your code.

If there is only one thing you can remember from today’s content, please remember this: keep your functions short.

Lastly, I would like you to think about how you understand software design. Feel free to share your thoughts in the comments.

Thank you for reading. If you found this article helpful, please consider sharing it with your friends.