I feel quite apprehensive while writing this article, as the topic covers a vast range. I am afraid that I might not be able to handle such a broad subject. In my practical work experience, I have maintained various types of code, including those written by senior engineers. Everyone has decent demand capabilities and can quickly meet the needs of the product. However, few people pay attention to the cleanliness of the code. In many cases, after multiple people maintain the code, it becomes impossible to make any changes, and eventually, a significant amount of time is spent on refactoring. Therefore, I decided to write an article to "briefly discuss" the code literacy that software engineers should possess. I hope it will be helpful to everyone. Please feel free to correct any inadequacies due to my limited level.
When we often talk about "literacy," it refers to a kind of cultivation formed by individual practice and training in a professional field. It is manifested differently in various fields. For example, in the music field, "musical literacy" refers to a person's degree of sensitivity to music, control of pitch and rhythm, and appreciation of different music genres. In the programming field, there are also different kinds of literacy, reflecting aspects such as basic skills, code cleanliness, and professional attitude. In short, "code literacy" refers to whether the code is elegant, beautiful, and maintainable.
Absolutely perfect code does not exist, and code literacy is not about perfectionism. In the field of translation, there are standards of "faithfulness, expressiveness, and elegance." The reason "elegance" is placed last is that it requires a relatively high level of expertise and accumulated experience to achieve it. Analogous to the programming field, when we program, the first thing we think about is how to implement the business logic, not how to write the code elegantly. Therefore, there is no absolute elegance in writing code. However, as a professional front-end engineer, or more precisely, a professional software engineer, the pursuit of elegant code should be maintained at all times. It is more like a benchmark, just as everyone knows what they should and should not do, the so-called principles, the so-called bottom line, reflecting the so-called "code literacy."
The Broken Window Theory, in its original sense, refers to the situation where a building's window is broken and no one takes care of it. People let the window continue to deteriorate, and eventually, they participate in destructive activities themselves, such as graffiti on the exterior walls, allowing garbage to accumulate, and ultimately leading to the building's collapse.
The Broken Window Theory is very likely to occur in practice. Often, when the first person writes poor code, the second person will have a similar mindset like "since he has already written it this way, I have no choice but to do the same." This leads to the code becoming more and more cluttered as it is maintained, and eventually collapsing, turning into garbage that no one wants to maintain.
Clean code is like beautiful prose. Imagine reading a good book, where you can follow the author's ups and downs, filled with vivid imagery, and invoking your emotions. Although code does not have such climaxes, clean code should be full of tension, capable of using this tension to push the plot to a climax at a certain moment.
I prefer to compare writing code to writing articles or telling stories. Writing code is a creative process, where the author needs to express their ideas through the form of code. Clean code is like storytelling, captivating and engaging, while poorly written code leaves readers feeling lost and confused, not knowing what the story is about.
In modern front-end development, there are many automated tools to help us write standardized code, such as ESLint, TSLint, and other auxiliary validation tools. Well-known standards like Google's and Airbnb's also provide constraints and guidance from various details, helping us form a reasonable and standardized code style.
In this section, we will not repeat language-level code styles. Instead, based on actual refactoring projects, we will summarize a series of principles that need to be kept in mind during the development process, arranged in order of importance.
As a software engineer, I believe everyone has heard of the most basic DRY principle. Many design patterns, including object-oriented programming itself, are efforts based on this principle.
As the name suggests, DRY means "don't repeat yourself." It actually emphasizes an abstraction principle. If the same or similar code snippets appear more than once, they should be abstracted into a common method or file, and imported as dependencies where needed. This ensures that when changes are made, only one place needs to be adjusted, and all other places will be changed accordingly, rather than having to find and modify the corresponding code in each location.
In my actual work, I have seen two types of code that go to extremes on this principle:
● One type is completely lacking in abstraction, with duplicate code scattered everywhere. What's even stranger is that there is some abstraction, but more repetition. For example, a data.js file is extracted under the common folder for data processing, and some pages reference this file, while many more pages copy several different methods from this file. The author's intention is somewhat amusing - they think that since only a small part of the code is used, there is no need to import the entire file. Regardless of whether modern front-end build layers can solve this problem, even if the entire large file is imported, the extra code won't cause much performance loss after gzip compression. However, this copy-pasting behavior doubles the subsequent maintenance cost.
● Another reason for this behavior is the short project duration and the fear of causing issues on the live site if the previous code is changed. So, they copy the same logic for modification. For example, the original payment logic has a separate UI layer and single payment purchase. Now, the product proposes a "package purchase" requirement, and the original code logic is relatively complex, resulting in an "unchangeable" phenomenon. So, the entire UI layer and several files of purchase logic are copied, modified slightly, and formed into a new "package purchase" module. Later, when the product proposes a "per item purchase," another "per item purchase" module is copied following the "unchangeable" principle. As a result, the calling logic becomes redundant and repetitive, requiring different UI components and payment logic to be imported based on different purchase methods. If new requirements are added, such as supporting "installment payments," many files will need to be changed. The saddest part is that the person who wants to refactor the code into a unified call will face the pressure of three "unchangeable" codebases, needing to compare and analyze the similarities in many logics. The workload can no longer be measured in multiples, and this kind of workload often cannot be recognized and understood by the product team.
● The other extreme is over-designing, abstracting every logic when writing it, which greatly reduces the code's readability. Even a simple for loop needs to be reused, and even variable definitions. Maintaining this kind of code is also quite costly. Additionally, excessively abstracting entirely different logics makes the abstract methods very complex, often causing a "domino effect." This behavior is also undesirable.
This is the reason why this principle is ranked first. The refactoring workload caused by this behavior is the largest. Maintaining good code maintainability is not only a literacy but also a responsibility. If you evade or slack off in this regard, you will double the workload for yourself or others in the future.
SRP is a well-known design principle - Single Responsibility. In object-oriented programming, it is believed that a class should have a single responsibility, and there should be only one motivation for a class to change.
For front-end development, the most important idea to implement is that functions should maintain a single responsibility. A function should do only one thing. This ensures the reusability of the function, as more focused functions have stronger reusability, and it also makes the overall code framework clearer, with details encapsulated in small functions. Another point related to single responsibility is side-effect-free functions, also known as pure functions. We should try to ensure the number of pure functions as much as possible. Impure functions are inevitable, but their number should be minimized.
The SRP principle is ranked second because it is crucial. No one wants to see a tangled mess of logic. When maintaining code, if there is no clear logical structure, and all the details of data definitions, data processing, DOM operations, etc., are put in one function, it makes the function very lengthy and instinctively repulsive, making people unwilling to examine the internal logic.
Although the function does not maintain a single responsibility, the internal process logic is clearly demonstrated through a "segmented" form. This kind of code looks much better than having all the details mixed in one function.
Maintaining single responsibility can be quite challenging, mainly due to the division of responsibilities. Sometimes, overly detailed divisions of responsibility can also make reading more difficult. In such cases, we can still use writing as a comparison. Single responsibility is equivalent to a "paragraph" in an article. For an article, each paragraph has its central idea, which can be described in one sentence. If you find that the central idea of a function is vague or requires a lot of language to describe it, then perhaps it already has multiple responsibilities that should be divided.
The LKP principle is the Least Knowledge Principle, also known as the "Demeter" principle, which means that an object should have the least knowledge about another object. It doesn't matter how complex your internals are; I only care about the calling location.
This UI component exposes a lot of methods, including business logic, view logic, and tool methods. At this time, it will cause great trouble to the maintainers. Instinctively, they think that these exposed methods are being used, so it is difficult to reconstruct some of them. In fact, the externally called methods are just show.
A good package, no matter how complex it is internally, must expose the most concise and practical interface, and the internal logic is independently maintained. For example, the above code, as a UI component, can provide the most basic show/hide method. If necessary, it can add the update method to self update without exposing many details, causing problems for callers and maintainers.
The basic theorem of readability: "Code should be written in such a way that the time required for others to understand it is minimized".
Code style and principles are not uniform. We often need to choose some coding principles and schemes, such as the choice of ternary expressions. When we think that both schemes are reasonable, the only criterion is the basic theorem of readability. No matter how superb the writing technique, the best code is still the code that people can understand at the first time.
The readability of code mostly depends on the naming of variables and functions. A good name can help maintainers understand logic pointedly, just like the "writing style" in an article. Those who have excellent writing style can always tell stories in a fascinating way.
However, it is still difficult to get a good name, especially since we are not native speakers of English, which adds a layer of barriers. Some people think that entanglement in the name will lead to low efficiency, and the development of requirements should be completed as soon as possible. That's right. We should focus on functional logic in the development process, but don't completely ignore naming. The so-called "writing style" needs practice. The more we think about it, the more natural it will be. Later, it will not affect work efficiency.
Here we recommend the Boy Scout Rules mentioned by Uncle Bob. Every time we look at our own code, we need to reconstruct it. The simplest reconstruction is to rename it. Maybe at the beginning, the naming is more appropriate, but the more logic is written, the less consistent with the original naming. When reviewing the code, we can easily rename variables and methods. Modern editing tools can also easily do this.
This function is named starting with check *, originally intended to detect whether the course time conflicts, but the internal logic includes the entire payment process. At this point, for the caller, if they do not carefully examine the internal logic, they may mistakenly believe that the check function has no side effects, leading to accidents.
Annotations are a controversial topic, and some people believe that readable function variables are clear and do not require additional annotations. Annotations are also non maintainable, such as:
//1-PC, 2-android hand QH5, 3-android APP, 4-ios&non hand QH5, 5-IOS APP
Var platform=isAndroidApp? 3: isIosApp? 5: 4;
In fact, the meaning of this field has already changed, but due to the fact that the modifier only modified the logic and did not pay attention to this line of annotation, the old annotation provided incorrect information. At this point, the annotation not only became invalid, but also led to misunderstandings by the maintainer, resulting in bugs.
For this situation, either maintain annotations or include interface documentation in the annotations. In other cases, appropriate annotations are necessary. For complex logic, having a concise annotation can greatly help with code readability, but some unnecessary annotations can be removed.
The six principles of code writing mentioned in this article, the first three lean towards code maintainability, and the last three lean towards code readability. The overall maintainability and readability constitute the basic literacy of code. As a front-end development engineer, if you want to have good code literacy, first of all, you need to make your code maintainable and not bring huge costs and workload to others' maintenance. Secondly, try to ensure that the code is beautiful and readable, and clean code is loved by everyone, just like reading a good book, which makes you feel happy.
”Code literacy "is an attitude that programmers who truly love programming will not lack. We usually refer to "writing code" as "programming", not "programming". The term "design" reflects that our code is a work of art, perhaps not as exquisite as "art", but it is also not a piece of rough cloth. If we are writing code with a wild imagination, muddling along, and holding the idea of as long as we can achieve functionality, So this "work" is not worth watching, it not only reflects the code writer's "unprofessional" attitude, but also reflects the attitude towards programming. The cleanliness and maintainability of the code depend on whether you truly "care" about your code. Every programmer may not necessarily love programming, but please treat your profession with a "serious" attitude.
Uncle Bob, the author of 'clean code', mentioned that someone once gave him a wristband with the words' Test Observed 'on it. He realized that he could no longer remove it after wearing it, not only because the wristband was tight, but also because it was a spiritual curse. When programming, can we subconsciously look at our wrists and discover an invisible wristband?