Expression principle of design pattern: how to make the source code a logical clue?

Keywords: Design Pattern

Maintaining code is one of the most important daily tasks for programmers. Have you ever encountered the following problems?

  • When I took over the maintenance project, I found that the document was missing and the code was not annotated. In addition, the maintainer resigned, so I can only sort out the code logic by guessing.

  • The code style is too abstract (naming too short, devil numbers, duplicate name methods, etc.), can't understand it, and can't be easily modified.

  • When the running code fails, it does not log or throw exceptions, which leads to the location problem, which takes a long time.

  • Large if else codes are nested and combined, the calling logic is complex and lengthy, the scalability is poor, and the reconstruction and optimization is time-consuming and laborious.

Have you found that the reason for these problems is the poor readability of the code and the failure to connect the internal logic of the code well. The code with poor readability is difficult to understand, which will not only cause many misunderstandings and troubles, but also lead to low project delivery efficiency. Although code readability is a subjective definition, it does ensure that developers can quickly and accurately understand the true meaning of code.

So what should we do to improve the readability of the code? Today, let's learn a principle that can help you quickly improve code readability: expression principle.

Why improve the readability of source code

Improving the readability of source code mainly has the following four benefits.

First, it is easier to maintain. After the code is written, you need to debug, run and repair bugs. Design documents, requirements documents and oral communication can only express the intention of some business logic, while the code can reflect all the real intention of programming and realizing business logic. Highly readable code enables readers to quickly understand the intention of the writer when reading. Even if the logic is complex, it can be accurately analyzed and understood when modifying, greatly saving the time of maintaining and modifying the code.

Second, it is easier to refactor. Now many projects are difficult to refactor because the readability of the code is too poor. When you cannot understand a piece of code, you will skip it. If the whole system is difficult to understand, you may choose to rewrite rather than refactor, because refactoring will inevitably modify the original code, which will introduce certain risks. Once refactoring leads to failure, the maintenance personnel will bear the responsibility. Therefore, the readability determines your willingness to reconstruct to some extent.

Third, it is easier to test. The code needs to be debugged repeatedly when it is modified. If the readability of the code is very poor, it is often necessary to write some additional Mock or test interfaces to test the original code, which is not only a waste of time, but also easy to cause misreading. With highly readable code, the parameters and output are clearer, and the corresponding logic and problem points can be found more accurately during testing.

Fourth, it is easier to apply design patterns. In addition to being used at the beginning of design, design patterns are often used in the process of code refactoring. In your work, you will find that although some codes write a lot of nested if else, the naming and comments are well written, and the logic is easy to read. During refactoring, they can be well optimized through design patterns. Although some codes look very concise, they use many advanced techniques or abbreviations, which is very time-consuming and laborious to understand. For maintenance personnel, they are naturally unwilling to consider using design patterns.

Generally speaking, improving the readability of the code can help us better understand the code. Only by understanding the code can we better maintain the code. In essence, the code is used for maintenance - continuous modification and release; In addition, another important use is to help code readers quickly find the implementation logic of the code.

Expression principle: highlight the internal logic of the code

Although writing documents can express the intention of software development, in fact, you may hate writing documents, because most documents have no direct relationship with the code, and with the continuous debugging and modification of the code, the documents will become more and more difficult to synchronize with the latest real situation.

In addition, you may not have much time to read documents. The daily status of programmers is that they need to go online, Bug repair and multi project concurrency. Because of tight time and heavy tasks, you may have to learn while changing the code. At this time, a logical code is what you really need.

However, many times you may not know how to express your intention in the code. In fact, there is a principle that can help you, that is, the expression principle.

Program Intently and Expressively (PIE for short) originated from agile programming. It means that there should be a clear programming intention during programming and express it clearly through code.

In short, the core idea of expression principle is: code is document. In other words, when writing code, you should help the reader understand the intention you want to express like writing design documents. You should jump out from the perspective of the programmer and write code from the perspective of the user.

From another perspective, if you are a code user, what kind of code do you want to see? Obviously, no one wants to see such code (a piece of bad code from the network):

cName = InpList.get(0).replace(",", ".");

                    cCode = InpList.get(1).replace(",", ".");

                    cAlpha2 = InpList.get(2).replace(",", ".");

                    cAbreviation = InpList.get(3).replace(",", ".");

                    dYear = InpList.get(4).replace(",", ".");

                    dPoliticalCompatibility = InpList.get(5).replace(",", ".");

                    dRankPoliticalCompatibility = InpList.get(6).replace(",", ".");

                    dEconomicCompatibility = InpList.get(7).replace(",", ".");

                    dRankEconomicCompatibility = InpList.get(8).replace(",", ".");

                    dMilitaryCompatibility = InpList.get(9).replace(",", ".");

                    dRankMilitaryCompatibility = InpList.get(10).replace(",", ".");

                    dDemoScore = InpList.get(11).replace(",", ".");

                    dRankDemoScore = InpList.get(12).replace(",", ".");

                    dEnvironmentalCompatibility = InpList.get(13).replace(",", ".");

                    dRankEnvironmentalCompatibility = InpList.get(14).replace(",", ".");

                    dSumCompatibility = InpList.get(15).replace(",", ".");

                    dRankCompatibility = InpList.get(16).replace(",", ".");

                    dPoliticalUtility = InpList.get(17).replace(",", ".");

                    dRankPoliticalUtility = InpList.get(18).replace(",", ".");

                    dEconomicUtility = InpList.get(19).replace(",", ".");

                    dRankEconomicUtility = InpList.get(20).replace(",", ".");

                    dMilitaryUtility = InpList.get(21).replace(",", ".");

                    dRankMilitaryUtility = InpList.get(22).replace(",", ".");

                    dEnvironmentalUtility = InpList.get(23).replace(",", ".");

                    dRankEnvironmentalUtility = InpList.get(24).replace(",", ".");

                    dSumUtility = InpList.get(25).replace(",", ".");

                    dRankUtility = InpList.get(26).replace(",", ".");

                    dPoliticalScore = InpList.get(27).replace(",", ".");

                    dRankPoliticalScore = InpList.get(28).replace(",", ".");

                    dEconomicScore = InpList.get(29).replace(",", ".");

                    dRankEconomicScore = InpList.get(30).replace(",", ".");

                    dMilitaryScore = InpList.get(31).replace(",", ".");

                    dRankMilitaryScore = InpList.get(32).replace(",", ".");

                    dEnvironmentalScore = InpList.get(33).replace(",", ".");

                    dRankEnvironmentalScore = InpList.get(34).replace(",", ".");

                    dAggregate = InpList.get(35).replace(",", ".");

                    dRankAggregate = InpList.get(36).replace(",", ".");

Instead, you want to see such code (a code fragment of HttpClient):

/**

 * {@inheritDoc}

 */

@Override

public CloseableHttpResponse execute(

        final HttpUriRequest request,

        final HttpContext context) throws IOException, ClientProtocolException {

    Args.notNull(request, "HTTP request");

    return doExecute(determineTarget(request), request, context);

}

private static HttpHost determineTarget(final HttpUriRequest request) throws ClientProtocolException {

    // A null target may be acceptable if there is a default target.

    // Otherwise, the null target is detected in the director.

    HttpHost target = null;

    final URI requestURI = request.getURI();

    if (requestURI.isAbsolute()) {

        target = URIUtils.extractHost(requestURI);

        if (target == null) {

            throw new ClientProtocolException("URI does not specify a valid host name: "

                    + requestURI);

        }

    }

    return target;

}

Therefore, when developing code, you should pay more attention to whether the intention of code expression is clear, and consider using some methods and skills. Although it will take a little time, on the whole, you will save a lot of communication and interpretation time, so as to really improve coding efficiency.

How to write source code with "logical clues"

To write readable code, you can start from three aspects.

  • Code expression: improvements in naming (variable name, method name, class name), code format, comments, etc.

  • Control flow and logic: try to separate control flow and logic to make the code easier to understand.

  • Inertial thinking: find out some habitual ways of thinking and improve them one by one.

Let me explain it in detail.

1. Optimize code expression

Naming is very important in programming. Whether it is variable name, class name or method name, a good name can quickly and accurately convey the meaning to be expressed, while abbreviations and user-defined names will make the code difficult to understand. Let's start with a code:

public class T {

    private Set<String> pns = new HashSet();

    private int s = 0;

    private Boolean f(String n) {return pns.contains(n);}

    int getS() {return s;}

    int s(List<T> ts, String n) {

        for (T t :ts) 

            if (t.f(n)) 

                return t.getS();

        return 0;

    }

}

What exactly does this code do? Probably no one can answer. If the writer wasn't me, I certainly wouldn't understand this code. Just looking at the code, you can hardly understand what the real meaning of this code is.

In fact, this class is used to obtain the team's game score. In addition to directly obtaining the game score through the team, you can also find the corresponding score through a player in the team. The specific modifications are as follows:

/**

 * Get team game points

 **/

public class Team {

    private Set<String> playerNames = new HashSet(); //Ensure that the names are not repeated

    private int score = 0; //The default is zero

    

    /**

     * Determine whether players are included

     * @param playerName

     * @return

     */

    private Boolean containsPlayer(String playerName) {

        return playerNames.contains(playerName);

    }

    

    /**

     * Know the team and get the score directly

     * @return

     */

    public int getScore() {

        return score;

    }

    

    /**

     * Find team scores by team member name

     * @param teams Support multiple teams

     * @param playerName 

     * @return The bottom of the pocket is 0, and there is no negative score

     */

    public int getTeamScoreForPlayer(List<Team> teams, String playerName) {

        for (Team team :teams) {

            if (team.containsPlayer(playerName)) {

                return team.getScore();

            }

        }

        return 0;

    }

}

From the optimized code, you can intuitively see that the "named optimization plus Annotation" makes the logic of the source code clear at once. Even if you haven't learned programming, you can roughly understand the logic and function of this code.

2. Improve control flow and logic

Let's start with an example. The specific code is as follows:

public List<User> getUsers(int id) {

    List<User> result = new ArrayList<>();

    User user = getUserById(id);

    if (null != user) {

        Manager manager = user.getManager();

        if (null != manager) {

            List<User> users = manager.getUsers();

            if (null != users && users.size() > 0) {

                for (User user1 : users) {

                    if (user1.getAge() >= 35 && "MALE".equals(user1.getSex())) {

                        result.add(user1);

                    }

                }

            } else {

                System.out.println("Failed to get employee list");

            }

        } else {

            System.out.println("Failed to obtain leadership information");

        }

    } else {

        System.out.println("Failed to get employee information");

    }

    return result;

}

The meaning of this code is: you want to query the employee's information through the id. if the id cannot be found, you can query the employee's leader, and then find it through the employee information under his leadership. At this time, you also need to judge that the employee is older than 35 years old and male.

This is the most commonly used logical implementation method, commonly known as arrow code, but with the gradual increase of judgment conditions, nesting will increase. The more code logic, the easier it is for you to get confused about what logic is, because when you see the innermost layer of code, you have forgotten what the conditional judgment of each previous layer is.

So, how should we optimize it? In fact, it is very simple to change the control flow, judge the failure conditions first, and give priority to launch once they occur. The optimized code is as follows:

public List<User> getStudents(int uid) {

    List<User> result = new ArrayList<>();

    User user = getUserByUid(uid);

    if (null == user) {

        System.out.println("Failed to get employee information");

        return result;

    }

    Manager manager = user.getManager();

    if (null == manager) {

        System.out.println("Failed to obtain leadership information");

        return result;

    }

    List<User> users = manager.getUsers();

    if (null == users || users.size() == 0) {

        System.out.println("Failed to get employee list");

        return result;

    }

    for (User user1 : users) {

        if (user1.getAge() > 35 && "MALE".equals(user1.getSex())) {

            result.add(user1);

        }

    }

    return result;

}

Now, is the code logic clear? Although this quick failure method is simple, it is very effective. In fact, quick failure is a good practice of KISS principle, which can ensure that the logic of conditional judgment is simple and clear. As long as the if is nested more than three layers, you can apply this principle to improve the control flow and make the logic clearer and easier to understand.

3. Avoid habitual thinking

In addition to improving the surface layer and logic, we should try our best to avoid some habitual thinking when designing code. Here I have summarized the "five avoidance". Let's analyze it in detail below.

First, avoid one-time code. The biggest disadvantage of one-time coding is that once it needs to be modified, multiple parts have to be modified, and repeated modifications may lead to the risk of omission. One time code appears in more and more software codes. An essential reason is that there are more and more cases of multi person collaborative development. Because programming is a non standardized thing, different programmers may have completely different understanding of the same logic. Once everyone only writes one-time code from their own point of view, the code in the same system will soon become redundant and chaotic.

Second, avoid copying and pasting code. On the one hand, different people may have different coding styles, which will cause a certain cognitive burden on readers' understanding (it is necessary to switch judgment criteria back and forth). On the other hand, it also brings the risk of unknown bugs. The copied code pays more attention to input and output. Once the code runs normally, it rarely pays attention to the internal logic of the code. However, after problems occur, it becomes more difficult to sort out the logic (because we don't know the detailed implementation logic).

Third, avoid writing super long code. The biggest problem caused by super long code is that when reading the code, there are too many jumps between function methods, and the thinking is easy to be confused. Especially for some methods with the same name but different parameters, it is easy to make modification errors. From the writer's point of view, when you write super long code, you may find it more convenient to maintain the code in a file; But for the reader, he may not know how you divide the responsibilities of the code. More often, he will think that there is one responsibility in a class, but in fact, once there are multiple responsibilities and many logical jumps, the reader will basically give up reading.

Fourth, avoid over simplifying naming and expressions. When the development task is heavy, we usually use some simplified naming methods, such as num1, num2 and num3. Although we may remember the meaning of these variables when writing code, after a period of time, if there is no comment or description, it is almost impossible to know their role directly through the name, and we have to use the context code, which is not only time-consuming, but also misunderstood.

Fifth, avoid writing "what is" notes. If the name and structure of the code can directly reflect "what is", we should not express it with comments, because we can understand it at a glance. For example, the method names for obtaining user information - get and getFromUserInfo.

We should write more "why" comments. For example, why should we add an adaptation method? The reason may be caused by online xxx problems or temporarily repaired bugs, which may be discarded with the adjustment of xxx interface, etc. In many excellent open source frameworks, we can see that the author will write many "why" explanations on the interface interface to help us quickly grasp the logical clues of the code.

In addition, writing "why" comments has another advantage: especially in the early rapid iteration process, it can provide an optimized entry point for later maintainers, so as not to make the maintainers unable to understand and dare not move after handing over the code.

summary

The core idea of expression principle is to clearly express our true intention through code.

Although there are many documents in the software development process, such as architecture design documents, process documents, PRD documents, etc., these documents are not enough to help us correctly understand how the code runs and organizes. Many times, we can only master the real operation of the software by reading the code.

The reason why we should improve code readability as the first priority is that we read code much more than we write code, including the code you are writing.

Today, we learned three main improvement methods: first, the improvement of the surface layer, starting with naming, expressions, variables and comments to improve the readability of the code; The second is to improve the code logic, such as replacing too many nested if else codes with policy patterns; Third, improve thinking habits. The improvement of habits requires more persistence over time.

After class thinking

After learning the expression principles, you must realize the importance of expressing intention in programming. Should you just pay attention to expression and ignore algorithms and data structures? What else do you think is important but often overlooked in programming?

Welcome to leave a message and share. I'll reply to you as soon as possible.

In the next lecture, I will share with you the related content of "responsibility principle: how to separate responsibilities in code design". Remember to attend the class on time!

Posted by mrkite on Mon, 25 Oct 2021 18:12:52 -0700