20 programming specifications to quickly improve code quality

Posted by markjohnson on Tue, 01 Mar 2022 03:47:41 +0100

20 programming specifications to quickly improve code quality

It is mainly divided into three parts:

Naming and Comments

Naming
  1. How long is the most appropriate name?

    There are two extremes of named length: 1 Full name nomenclature 2 Familiar with word abbreviations and nomenclature

    1. Advantages: more accurate and intuitive

      Disadvantages: when the code length is limited, a statement will often be divided into two lines, which will actually affect the readability of the code

    2. Advantages: concise and clear

      Disadvantages: lack of expressiveness

    For those with small scope, it is recommended to use relatively short names, such as temporary variables in some functions.

    For class names with large scope, it is recommended to use the long naming method.

    When naming, developers should learn to empathize. Assuming they are not familiar with the code, consider whether the naming is intuitive enough from the perspective of code readers.

  2. Simplify naming with context

    Class name and function name are context information

    For attribute naming in a class, developers can accurately understand the meaning of the attribute with the help of the class object.

    For the naming of function parameters, developers can name parameters with the help of function names.

  3. The naming should be readable and searchable

    Readability: it means not to name some English words that are particularly rare and difficult to pronounce

    Searchable: when developers write code in IDE, they often use the method of "keyword Association" to automatically complete and search. When naming, it is best for developers to conform to the naming habits of the whole project.

    It is very important to unify the statute, which can reduce a lot of unnecessary trouble.

  4. How to name interfaces and abstract classes?

    Two methods of interface naming: 1 Interface name prefixed with "I" 2 The interface name is not prefixed with "I", and the corresponding implementation class is suffixed with "Impl"

    Two naming methods of abstract classes: 1 Abstract class name prefixed with "abstract" 2 Abstract class name without prefix "abstract"

    For the naming of interfaces and abstract classes, no matter which naming method, as long as it can be unified in the project.

Comments
  1. What exactly should the notes say?

    The contents of the notes mainly include the following three aspects:

    1. Do what?
    2. Why?
    3. How do you do it?
    /**
    * (what) Bean factory to create beans. 
    * 
    * (why) The class likes Spring IOC framework, but is more lightweight. 
    *
    * (how) Create objects from different sources sequentially:
    * user specified object > SPI > configuration > default object.
    */
    public class BeansFactory {
      // ...
    }
    

    The main points of this note are as follows:

    • Comments carry more information than code: the naming of class names is not detailed enough, so "what to do" should be written in the comments, because classes contain more information. Function naming is different. A good name can accurately express the function of a function. At this time, there is no need to explain what it does in the comments.
    • Comments play the role of summary and documentation: in the comments, we can write some summary descriptions and descriptions of special situations about the specific code implementation ideas. In this way, people who read the code can roughly understand the implementation idea of the code through comments, and it will be easier to read. In fact, for some complex classes or interfaces, we may also need to write "how to use" clearly in the comments, and give some simple examples of quick start, so that users can quickly know how to use it without reading the code.
    • Some summary comments can make the code structure clearer: for code with complex logic or long functions, if it is difficult to refine and split into small function calls, we can use summary Comments to make the code structure clearer and more organized.
  2. Are the more comments the better?

    There are some problems with too many and too few comments. Too many comments means that the code is not readable enough, and too many comments will affect the readability of the code to a certain extent. The maintenance cost is high, and code changes forget to synchronize and modify comments, which will confuse code readers.

    According to my experience, classes and functions must write comments as comprehensive and detailed as possible, while the comments inside functions should be relatively few. Generally, it depends on good naming, refining functions, explanatory variables and summary Comments to improve the readability of the code.

Code Style

  1. How big are functions and classes?

    The number of lines of code for the function should not exceed the size of one screen, such as 50 lines. The size limit of the class is difficult to determine.

  2. How long is the most appropriate line of code?

    It is best not to exceed the width of the IDE display. Of course, the limit should not be too small. Too small will cause many slightly longer statements to be folded into two lines, which will also affect the cleanliness of the code and is not conducive to reading.

  3. Make good use of blank lines to divide unit blocks

    For long functions, in order to make the logic clearer, you can use blank lines to divide each code block. Inside the class, between member variables and functions, between static member variables and ordinary member variables, between functions, and even between member variables, you can add empty lines to make the boundaries between codes of different modules more clear.

  4. Four or two indents?

    I personally recommend using two grid indentation, which can save space, especially when the code nesting level is deep. In addition, it is worth emphasizing that whether you use two or four indents, you must not use the tab key to indent.

  5. Do braces need another line?

    Personally, I prefer to put braces on the same line as the previous statement, which can save the number of lines of code. However, there is also an advantage of starting another line of braces, that is, the left and right braces can be vertically aligned, and which code belongs to which code block is more clear at a glance.

  6. Order of members in class

    In the Google Java programming specification, dependency classes are arranged alphabetically from small to large. Class, write the member variable first and then the function. Between member variables or functions, write static member variables or functions first, and then ordinary variables or functions, and arrange them in order according to the size of the scope.

Coding Tips

  1. Divide the code into smaller unit blocks

    Developers need to have modular and abstract thinking, be good at refining large blocks of complex logic into classes or functions, shield the details, so that code readers will not be lost in the details, which can greatly improve the readability of the code. Only when the code logic is complex, we actually suggest refining it into classes or functions; On the contrary, if the extracted function contains only two or three lines of code, you have to skip the reading code when reading the code, which increases the reading cost.

  2. Avoid too many function parameters

    When the function contains 3 or 4 parameters, it is acceptable. When it is greater than or equal to 5, the number of parameters is a little too many, which will affect the readability of the code and is inconvenient to use.

    For the case of too many parameters, there are two processing methods:

    • Consider whether the function has a single responsibility and whether the parameters can be reduced by splitting into multiple functions.

      public User getUser(String username, String telephone, String email);
      
      // Split into multiple functions
      public User getUserByUsername(String username);
      public User getUserByTelephone(String telephone);
      public User getUserByEmail(String email);
      
    • Encapsulate the parameters of a function into objects.

      public void postBlog(String title, String summary, String keywords, String content, String category, long authorId);
      
      // Encapsulate parameters into objects
      public class Blog {
        private String title;
        private String summary;
        private String keywords;
        private Strint content;
        private String category;
        private long authorId;
      }
      public void postBlog(Blog blog);
      

    If the function is a remote interface exposed to the outside world, encapsulating the parameters into objects can also improve the compatibility of the interface.

  3. Do not use function parameters to control logic

    Do not use Boolean identification parameters in functions to control internal logic. For example, when true, use this logic and when false, use another logic. It is suggested that the separation of functions into two principles, namely, the principle of readability and the principle of readability, should be better.

    public void buyCourse(long userId, long courseId, boolean isVip);
    
    // Split it into two functions
    public void buyCourse(long userId, long courseId);
    public void buyCourseForVip(long userId, long courseId);
    

    In addition to the case where Boolean types are used as identification parameters to control logic, there is also a case where logic is controlled "according to whether the parameter is null". In this case, we should also split it into multiple functions. The responsibilities of the split function are more clear, and it is not easy to use it wrong.

    public List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {
      if (startDate != null && endDate != null) {
        // Query transactions in two time intervals
      }
      if (startDate != null && endDate == null) {
        // Query all transactions after startDate
      }
      if (startDate == null && endDate != null) {
        // Query all transactions before endDate
      }
      if (startDate == null && endDate == null) {
        // Query all transactions
      }
    }
    
    // Split into multiple public functions, which is clearer and easier to use
    public List<Transaction> selectTransactionsBetween(Long userId, Date startDate, Date endDate) {
      return selectTransactions(userId, startDate, endDate);
    }
    
    public List<Transaction> selectTransactionsStartWith(Long userId, Date startDate) {
      return selectTransactions(userId, startDate, null);
    }
    
    public List<Transaction> selectTransactionsEndWith(Long userId, Date endDate) {
      return selectTransactions(userId, null, endDate);
    }
    
    public List<Transaction> selectAllTransactions(Long userId) {
      return selectTransactions(userId, null, null);
    }
    
    private List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {
      // ...
    }
    
  4. Function design should have a single responsibility

    Not only the design of classes and modules needs to meet the principle of single responsibility, but also the design of functions should meet the principle of single responsibility. Compared with classes and modules, the granularity of functions is relatively small and the number of lines of code is small. Therefore, when applying the principle of single responsibility, it is not as ambiguous as that applied to classes or modules, and the more single it can be, the more single it can be.

    public boolean checkUserIfExisting(String telephone, String username, String email)  { 
     if (!StringUtils.isBlank(telephone)) {
       User user = userRepo.selectUserByTelephone(telephone);
       return user != null;
     }
     
     if (!StringUtils.isBlank(username)) {
       User user = userRepo.selectUserByUsername(username);
       return user != null;
     }
     
     if (!StringUtils.isBlank(email)) {
       User user = userRepo.selectUserByEmail(email);
       return user != null;
     }
     
     return false;
    }
    
    // Split into three functions
    public boolean checkUserIfExistingByTelephone(String telephone);
    public boolean checkUserIfExistingByUsername(String username);
    public boolean checkUserIfExistingByEmail(String email);
    
  5. Remove too deep nesting levels

    Too deep code nesting is often caused by excessive nesting of if else, switch case and for loops. I personally suggest that nesting should not exceed two layers. After more than two layers, we should think about whether nesting can be reduced. Too deep nesting itself is hard to understand. In addition, too deep nesting is easy to break into two lines because the code is indented many times, resulting in the length of more than one line, which affects the cleanliness of the code.

    The methods to solve the problem of too deep nesting are also relatively mature. There are four common ideas below.

    • **Remove redundant if or else statements** The code example is as follows:

      // Example 1
      public double caculateTotalAmount(List<Order> orders) {
        if (orders == null || orders.isEmpty()) {
          return 0.0;
        } else { // else here can be removed
          double amount = 0.0;
          for (Order order : orders) {
            if (order != null) {
              amount += (order.getCount() * order.getPrice());
            }
          }
          return amount;
        }
      }
      
      // Example 2
      public List<String> matchStrings(List<String> strList,String substr) {
        List<String> matchedStrings = new ArrayList<>();
        if (strList != null && substr != null) {
          for (String str : strList) {
            if (str != null) { // It can be combined with the following if statement
              if (str.contains(substr)) {
                matchedStrings.add(str);
              }
            }
          }
        }
        return matchedStrings;
      }
      
    • **Use the continue, break and return keywords provided by the programming language to exit the nesting in advance** The code example is as follows:

      // Code before refactoring
      public List<String> matchStrings(List<String> strList,String substr) {
        List<String> matchedStrings = new ArrayList<>();
        if (strList != null && substr != null){ 
          for (String str : strList) {
            if (str != null && str.contains(substr)) {
              matchedStrings.add(str);
              // Here are 10 lines of code
            }
          }
        }
        return matchedStrings;
      }
      
      // Refactored Code: use continue to exit early
      public List<String> matchStrings(List<String> strList,String substr) {
        List<String> matchedStrings = new ArrayList<>();
        if (strList != null && substr != null){ 
          for (String str : strList) {
            if (str == null || !str.contains(substr)) {
              continue; 
            }
            matchedStrings.add(str);
            // Here are 10 lines of code
          }
        }
        return matchedStrings;
      }
      
    • **Adjust the execution order to reduce nesting** Specific code examples are as follows:

      // Code before refactoring
      public List<String> matchStrings(List<String> strList,String substr) {
        List<String> matchedStrings = new ArrayList<>();
        if (strList != null && substr != null) {
          for (String str : strList) {
            if (str != null) {
              if (str.contains(substr)) {
                matchedStrings.add(str);
              }
            }
          }
        }
        return matchedStrings;
      }
      
      // Reconstructed Code: execute the empty judgment logic first, and then execute the normal logic
      public List<String> matchStrings(List<String> strList,String substr) {
        if (strList == null || substr == null) { //Empty first
          return Collections.emptyList();
        }
      
        List<String> matchedStrings = new ArrayList<>();
        for (String str : strList) {
          if (str != null) {
            if (str.contains(substr)) {
              matchedStrings.add(str);
            }
          }
        }
        return matchedStrings;
      }
      
    • **Encapsulate some nested logic into function calls to reduce nesting** Specific code examples are as follows:

      // Code before refactoring
      public List<String> appendSalts(List<String> passwords) {
        if (passwords == null || passwords.isEmpty()) {
          return Collections.emptyList();
        }
        
        List<String> passwordsWithSalt = new ArrayList<>();
        for (String password : passwords) {
          if (password == null) {
            continue;
          }
          if (password.length() < 8) {
            // ...
          } else {
            // ...
          }
        }
        return passwordsWithSalt;
      }
      
      // Refactored Code: extract some logic into functions
      public List<String> appendSalts(List<String> passwords) {
        if (passwords == null || passwords.isEmpty()) {
          return Collections.emptyList();
        }
      
        List<String> passwordsWithSalt = new ArrayList<>();
        for (String password : passwords) {
          if (password == null) {
            continue;
          }
          passwordsWithSalt.add(appendSalt(password));
        }
        return passwordsWithSalt;
      }
      
      private String appendSalt(String password) {
        String passwordWithSalt = password;
        if (password.length() < 8) {
          // ...
        } else {
          // ...
        }
        return passwordWithSalt;
      }
      
  6. Learn to use explanatory variables

    There are two common cases where explanatory variables are used to improve the readability of code.

    • **Constants replace magic numbers** The example code is as follows:

      public double CalculateCircularArea(double radius) {
        return (3.1415) * radius * radius;
      }
      
      // Constant instead of magic number
      public static final Double PI = 3.1415;
      public double CalculateCircularArea(double radius) {
        return PI * radius * radius;
      }
      
    • **Use explanatory variables to interpret complex expressions** The example code is as follows:

      if (date.after(SUMMER_START) && date.before(SUMMER_END)) {
        // ...
      } else {
        // ...
      }
      // After introducing explanatory variables, the logic is clearer
      boolean isSummer = date.after(SUMMER_START)&&date.before(SUMMER_END);
      if (isSummer) {
        // ...
      } else {
        // ...
      } 
      

Uniform Coding Standards

That is, projects, teams and even companies must formulate unified coding specifications and supervise the implementation through Code Review, which has an immediate effect on improving code quality.

Note: the content of this article is extracted from the column of "the beauty of design patterns" of geek time

Topics: Java Design Pattern