When I first came to my current company, and when I was at another company trying to optimize outsourced code. There are always some problems, I believe you will also encounter these problems like me:

  1. I took over a project where documentation was missing, code was not commented at all, and the person who took over maintenance had left, so I basically had to guess my way through the logic of the code
  2. The code style is too abstract (named abbreviation and the meaning is not clear, directly with letters or numbers named, such as Student S, int num1, int num2, the same name method, etc.), can not understand, also can not easily modify.
  3. Run code, no log is printed, info log is not, error log is not. SQL does not print. Or in the distributed case, there is no logid to do global tracing
  4. Code that’s written too low, if else nesting n levels, or a method that’s written over a hundred lines, maybe even hundreds of lines. Scalability is poor, and reconstruction optimization is time-consuming and laborious. Generally not easy optimization
  5. Business code and logic code are not separated, all write together, service layer basically no role, all operations in the controller layer

The list above, do you feel, do you notice, is all the problems you have encountered. In fact, the root cause is that the code is not readable and does not properly connect the internal logic of the code. Poorly readable code is not only difficult to understand, it is also a pain to maintain, and ultimately leads to less efficient delivery.

How can I quickly improve the readability of my code

Why improve code readability

There are four main benefits to improving the readability of your source code.

First, it’s easier to maintain. After the code is written, it needs to be debugged, run, and Bug fixed. Design documents, requirements documents, and verbal communication can only express part of the intent of the business logic, while the code can reflect all the true intent of the programming to implement the business logic. Readable code allows readers to quickly understand the intent of the writer when they read it, and even if the logic is complex, they can accurately analyze and understand when they modify it, greatly saving the time of maintaining and modifying the code.

Second, it is easier to refactor. Many projects today are hard to refactor because the code is so unreadable. When you don’t understand a piece of code, you skip it, and if the whole system is hard to understand, you may choose to rewrite rather than refactor, because refactoring will inevitably change the original code, which introduces a certain risk that if a failure occurs due to refactoring, the maintenance person will be held responsible. So readability, to some extent, determines how much you want to refactor. (I believe most people here are really met in taking other people’s code, maybe it’s because that person in you before leaving, may also be handover with you because he didn’t handover complete it in a hurry leaving, cause you to existing code is not familiar with, the company’s business development, and raised new requirements require you to change the original code, but you don’t understand at this time, So you have to override a method and then call the interface you override, which I’m sure most people have done, and I’ve done it too.)

Third, it’s easier to test. Code changes require repeated debugging, and if the code is not readable enough, many times you will need to write additional mocks or test interfaces to test the original code, which is not only a waste of time but also prone to misreading. More readable code, clearer parameters and output, and more accurate logic and problem spots when testing.

Fourth, it is easier to apply design patterns. In addition to being used at the beginning of design, design patterns are more often used during code refactoring. In your work, you’ll find code that has a lot of nested if-else’s, but is well named and commented, the logic is easy to read, and is well optimized by design patterns when refactoring. Some code that looks simple, but uses a lot of advanced techniques or abbreviated names, is time-consuming and laborious to understand, and it’s natural for maintainers not to consider using design patterns.

While writing documentation can express software development intent, in fact, you may hate writing documentation because most of it is not directly related to the code, and as the code is debugged and modified, the documentation becomes harder and harder to keep up with the latest reality.

Plus, you probably don’t have much time to read the documentation; requirements are up, bugs are fixed, and multi-project concurrency is a daily reality for programmers these days. Because time is tight and tasks are heavy, you may have to change code and learn at the same time, when a logical piece of code is what you really need.

Think about it another way: if you were a user of code, what kind of code would you want to see? Obviously, no one wants to see code like this

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(",".".");

Copy the code

Instead, you want to see code like this (a snippet of HttpClient code) :

/ * * * {@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;

}
Copy the code

Therefore, when developing code, you should pay more attention to whether the intention of the code is clear, and consider using some methods and techniques. Although it may cost a little time, on the whole, you will save a lot of time in communication and explanation, and really improve the coding efficiency.

How to write source code with “logical clues”

To write readable code, you can start with three areas.

  • Code representation: improvements in naming (variable names, method names, class names), code formatting, comments, and so on.
  • Control flow and logic: Try to separate control flow and logic to make the code easier to understand.
  • Conventional thinking: Find out some of the common ways of thinking and improve one by one.

Let me explain it in detail.

1. Optimize code presentation

Naming is very important in programming. Whether it’s variable names, class names, or method names, good names convey the meaning quickly and accurately, while abbreviations and custom names can make code difficult to understand. Let’s look at some code first:

public class T {

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

    private int s = 0;

    private Boolean f(String n) {returnpns.contains(n); }int getS(a) {returns; }int s(List<T> ts, String n) {

        for (T t :ts) 

            if (t.f(n)) 

                return t.getS();

        return 0; }}Copy the code

What does this code actually do? Probably no one can answer that. If it wasn’t written by me, I certainly wouldn’t understand this code. It’s almost impossible to understand what the code really means just by looking at it.

In fact, this class is used to get a team’s game score. In addition to getting a team’s game score directly, it can also be used to find the corresponding score by a player on the team.

/** * Get your team's game score **/

public class Team {

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

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

    

    /** * Determine whether to include player *@param playerName

     * @return* /

    private Boolean containsPlayer(String playerName) {

        return playerNames.contains(playerName);

    }

    

    /** * Know the team, get points * directly@return* /

    public int getScore(a) {

        return score;

    }

    

    /** * Find the team score by name *@paramTeams supports multiple teams *@param playerName 

     * @returnThe bottom pocket is 0 points, no negative points */

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

        for (Team team :teams) {

            if (team.containsPlayer(playerName)) {

                returnteam.getScore(); }}return 0; }}Copy the code

From the optimized code, you can see intuitively that “named optimizations plus comments” makes the logic of the source code immediately clear, and even if you have no programming experience, you will have a general understanding of the logic and function of the code.

2. Improve control flow and logic

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("Failure to get leadership message"); }}else {

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

    }

    return result;

}

Copy the code

If you want to query the information of the employee by id, if you cannot find the id, you can query the employee’s leader, and then find the information of the employee under his leadership. In this case, you need to judge that the employee is over 35 years old and male.

This is the most common implementation of logic, known as arrow code, but as the criteria increase, the nesting increases. The more logic there is in the code, the easier it is to lose track of what the logic is, because by the time you look at the innermost layer of code, you have forgotten what the conditions at each previous layer are.

So, how do we optimize? In fact, it is very simple, is to change the control flow, the first judgment will appear failure conditions, once the emergence of priority. 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 obtain employee information.");

        return result;

    }

    Manager manager = user.getManager();

    if (null == manager) {

        System.out.println("Failure to get leadership message");

        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;

}
Copy the code

Now, is the logic clear? While this quick-fail method is simple, it is very effective. In fact, fast failure is a good practice of the KISS principle to ensure that the logic of conditional judgments is simple and clear. As long as there are more than three levels of if nesting, you can apply this principle to improve the flow of control and make the logic more understandable.

3. Avoid conventional thinking

In addition to improving the surface layer and logic, we should also try to avoid some of the conventional thinking when designing code. Here I have summarized the “five avoid”, and let’s analyze them in detail.

First, avoid one-time code. The biggest disadvantage of a one-time code is that once it needs to be changed, it will have to be changed multiple times, and there is a risk of omission if it is changed multiple times. One of the fundamental reasons that disposable code is appearing in more and more software code is that more and more people are co-developing. Since programming is a non-standardized thing, different programmers may have completely different understandings of the same logic, and once everyone writes code from their own point of view once, the code in the same system can quickly become redundant and confusing.

Second, avoid copying and pasting code. On the one hand, different people may have different coding styles, which creates a certain cognitive burden on the reader’s understanding (the need to switch back and forth between criteria). On the other hand, there is the risk of unknown bugs. The copied code pays more attention to the input and output. Once the code is running properly, it pays less attention to the internal logic of the code. However, when problems occur, it becomes more difficult to sort out the logic (because you don’t know the detailed implementation logic).

Third, avoid writing long code. The biggest problem with very long code is that there are too many jumps between functions and methods when reading the code, and it is easy to get confused, especially for methods with the same name but different parameters. From a writer’s point of view, you may write very long code because it is easier to maintain it in a single file. But for the reader, he may not know how you divide the responsibilities of the code, more often he will think that a class is a responsibility, but in fact, once there are multiple responsibilities, plus a lot of logical jump, the reader will basically give up reading.

Fourth, avoid oversimplifying names and expressions. When the development task is heavy, we usually choose some simplified naming methods, such as num1, num2, num3, and so on. While we may remember what these variables mean while we’re writing code, after a while, without comments or explanations, it’s almost impossible to know what they do directly from their names, with the help of context code, which is time-consuming and can lead to misinterpretation.

Fifth, avoid “what is” comments. The name and structure of the code should not be commented out if it directly reflects “what is”, because it will be clear at a glance. For example, the names of the methods that get user information — get and getFromUserInfo — are used to retrieve user information.

We should write more “why” comments, for example, why to add an additional adaptation method, the reason may be caused by online XXX problem, or temporary fix Bug, later may be discarded with XXX interface adjustment, etc. In many good open source frameworks, we have seen the author write a lot of “why” instructions on the interface, just to help us quickly catch the logical clues of the code.

In addition, writing “why” comments has the added benefit of providing an optimized entry point for future maintainers, especially during early, rapid iterations, without leaving the code unreadable and unmoved by the maintainers.