Very detailed, Wanzi teaches you how to improve the quality of Java code

As programmers, our job is to write code. Today, let's learn how to improve the quality of Java code

1 readability

1.1 Accurate naming

Each programming language has its own naming convention, and the styles of different languages ​​vary greatly. Let's take Java as an example:

The overall naming style of Java is UpperCamelCase or lowerCamelCase. Whether it is a class or a variable name, it must be well-known, and do not use abbreviations or Chinese; the style is uniform, try to use English nouns, and do not mix Chinese and English; try to avoid the same name as the class library that comes with Java, and do not use Java keywords name.

  • Package naming convention

Use lowercase English nouns for package naming, use "." to separate, and try to have only one noun for each divided unit. The naming convention is:

域名.公司/个人名称.项目名称.模块名称

  • class naming convention

Classes use the UpperCamelCase naming style, and some special abbreviations can be in all uppercase, such as XML. Class naming uses nouns to describe what a class does.

  • Interface naming convention

The interface adopts the UpperCamelCase naming style. Since the interface defines a class of functions or actions, the naming of the interface generally uses adjectives or verbs to describe the behavior of the interface.

  • Abstract class naming convention

In addition to satisfying the UpperCamelCase style, abstract classes generally need to be prefixed with Abstract.

  • Exception class naming convention

In addition to satisfying the UpperCamelCase style, the exception class generally needs to be suffixed with Exception or Error, and the noun is used to describe the exception or error.

  • Enum class naming convention

In addition to satisfying the UpperCamelCase style, the enumeration class generally adds the Enum suffix. The enumeration value in the enumeration class adopts all uppercase styles, and "_" is used to separate words.

  • Method naming convention

Method naming adopts lowerCamelCase style, generally using verb + noun to name, the more common ones are doXxx, handleXxx, findXxxx.

  • Variable naming convention

The variable naming adopts the lowerCamelCase style. Generally, nouns are used to describe the function of variables. It should be noted that it is different from constants. Try not to use special symbol prefixes or use "_" to separate symbols.

  • Constant naming convention

Constants are named in all uppercase, and "_" is used to separate words.

1.2 Code style

In daily project development, a project is jointly developed by many people. Everyone uses different development tools, such as VS Code and idea, which are commonly used by everyone. Different development tools or code habits will also lead to inconsistent code styles. We are developing When you may habitually format the code, it will lead to a lot of changes in the entire class, and it is easy to conflict when the code is merged.

We can add .editorconfig files to the project to unify the code style.

root = true
[*.{adoc,bat,groovy,html,java,js,jsp,kt,kts,md,properties,py,rb,sh,sql,svg,txt,xml,xsd}]charset = utf-8
[*.{groovy,java,kt,kts,xml,xsd}]indent_style = tab              #tab键缩进,可选"space"、"tab"indent_size = 4                 #缩进空格为4个end_of_line = lf                #结尾换行符,可选"lf"、"cr"、"crlf"charset = utf-8                 #文件编码trim_trailing_whitespace = true #不保留行末的空格insert_final_newline = true     #文件末尾增加一个空行curly_bracket_next_line = false #大括号不另起一行spaces_around_operators = true  #运算符两边都有空格indent_brace_style = 1tbs       #条件语句格式是1tbs

1.3 Annotation Convention

class annotation

The class annotation adopts /**...*/, and there must be necessary annotation information at the head of each class, including: author, creation time, class function description

/** * 简单分流算法实验, 每次分流只用考虑当前的桶, 不用回溯历史版本 * {@link https://duapp.yuque.com/team_tech/confluence-data-iwskfg/dzmogk} * @author hufei * @date 2021/6/8 7:59 下午 */

interface annotation

The interface annotation adopts /**...*/. On the basis of satisfying the class annotation, the interface annotation should include the purpose of the interface and how to use it.

/** * AB分桶算法接口规范 * 对外暴露实验桶计算接口,该接口有一个抽象实现类AbstractBucketAlgorithm,具体的分桶算法实现这个抽象类 * @author hufei * @date 2021/6/8 6:06 下午 */

method annotation

Method comments use /**...*/ to describe the function, input, output and return value description of the method

/** * 计算实验层的桶信息 * @param layerId 分层 id * @param expId 实验 Id * @param expRatio 新的实验占层流量比例 * @param existsLayerBucket 老的层流量实验配比 * @return 新的层流量实验配比 * @throws BucketAlgorithmException */

method internal comment

What the code does and why, especially the complex logic processing part, should be given as detailed comments as possible.

global variable annotation

Including descriptions of variable functions, value ranges, precautions, etc.

/** * 代表kafka收到消息的答复数,0就是不要答复,爱收到没收到.1就是有一个leader broker答复就行,all是所有broker都要收到才行 * 0: Producer不等待kafka服务器的答复,消息立刻发往socket buffer,这种方式不能保证kafka收到消息,设置成这个值的时候retries参数就失效了,因为producer不知道kafka收没收到消息,所以所谓的重试就没有意义了,发送返回值的offset全默认是-1. * 1: 等待leader记录数据到broker本地log即可.不等待leader同步到其他followers,那么假如此时刚好leader收到消息并答复后,leader突然挂了,其他fowller还没来得及复制消息呢,那么这条消息就会丢失了. * all:等待所有broker记录消息.保证消息不会丢失(只要从节点没全挂),这种方式是最高可用的 acks默认值是1. */private String acks = "0";

local variable annotation

The main local variables must have comments, and no comments can be added if there is no special meaning.

2. Reliability

2.1 Enhance robustness

Use recursive algorithms with caution

The recursive algorithm is very simple to write, but it is easy to cause stack overflow and infinite loop problems if it is not used well. Therefore, try not to use recursive algorithms. If you want to use them, you need to pay attention to the following issues:

  1. Put the exit condition at the top of the function, which is clearer and prevents the program from always failing to meet the exit condition and causing stack overflow

public int recursiveAlgorithm(){
   
       if (退出条件)        return 0;    ......}
  1. Avoid overly large local variables in recursive functions, which will speed up the consumption of stack space

public int recursiveAlgorithm(){
   
       char buf[] = new char[1024];}
  1. Increase a maximum recursion depth to prevent stack overflow from an infinite loop

Use parameter validation

There are many interfaces provided to the front end in the project. Some parameters cannot be empty in the subsequent logical processing and no non-null check is done. The parameters cannot be empty by verbal agreement. If the front end does not pass parameters when calling, a null pointer will appear. abnormal. The best solution is to unify the parameter verification framework, restrict the input parameters of the interface to be non-empty, and throw an exception uniformly if it is empty.

@Valids({
   
           @Valid(names = "request.expStatus",required = true,regex = "[1,2]",error = "实验状态必须为1或者2"),        @Valid(names = "request.weekDateList,request.type" , required = true)})

Idempotent check

Our system should do a good job of idempotent verification, which is equal to the same business operation, no matter how many times it is called, the result is the same. For example, users repeatedly place orders, MQ messages repeatedly consume, front-end repeatedly submit forms, etc. These are all idempotent operations that need to be guaranteed.

2.2 Start well and end well

exception handling

When we handle exceptions, we must put the statements that must be executed in the finally block. For example, when reading and writing files, close the IO connection in the finally block.

BufferedReader br = null;try {
   
       br = new BufferedReader(new FileReader(new File("")));} catch (FileNotFoundException e) {
   
       e.printStackTrace();} finally {
   
       if (br != null) {
   
           try {
   
               br.close();        } catch (IOException e) {
   
               e.printStackTrace();        }    }}

lock release lock

We often encounter the problem of resource competition in our daily development. At this time, we need to lock the competing resources. If we forget to release the lock, other requests will be blocked.

Lock lock = new ReentrantLock();lock.lock();......lock.unlock();

resource release

We use database or network resources in our daily development. At this time, we need to establish a database connection or network connection. If we forget to release the connection, the database resource or network resource will be occupied until the connection fails. Although we do not directly create connections but use connection pools in our daily development, if the maximum number of connections is set too large, it will also lead to resource exhaustion.

ResultSet resultSet = null;Statement statement = null;Connection connection = null;try {
   
       Class.forName("com.mysql.cj.jdbc.Driver");    connection = DriverManager.getConnection(url,user,pwd);    statement = connection.createStatement();    resultSet = statement.executeQuery("select * from student");} catch (SQLException e) {
   
       e.printStackTrace();} catch (ClassNotFoundException e) {
   
       e.printStackTrace();} finally {
   
       try {
   
           if (resultSet != null) {
   
               resultSet.close();        }    } catch (SQLException e) {
   
           log.error(e.getMessage(),e);    }    try {
   
           if (statement != null) {
   
               statement.close();        }    } catch (SQLException e){
   
           log.error(e.getMessage(),e);    }    try {
   
           if (connection != null) {
   
               connection.close();        }    } catch (SQLException e){
   
           log.error(e.getMessage(),e);    }}

2.3 Exception handling

Use unchecked exceptions whenever possible

Flaws of checked exceptions:

  • Checked exceptions make interface declarations fragile

For example, we define an interface

interface User {
   
       public void changePassword() throws MySecurityException;}

With the development of the business, the number of exceptions thrown by the interface increases. For example, if a RejectChangeException is added, the User interface needs to be modified, which will cause all the callers of the User interface to additionally handle the RejectChangeException exception.

  • Checked exceptions make code less readable

If a method adds a checked exception, the caller must handle the exception, such as calling an unchecked exception:

public static void main(String[] args) {
   
       userImpl.changePassword();}

It's not the same when calling checked exceptions:

public static void main(String[] args) {
   
       try {
   
           userImpl.changePassword();    } catch(Exception e){
   
           e.printStackTrace();    }}

If a method uses a checked exception, the caller must handle it. Especially in the case of multiple exceptions, adding multiple catch blocks for processing will increase the code complexity.

  • Checked exceptions increase development effort

Don't handle return values ​​in finally blocks

Returning in a finally block can cause the following problems:

  • Override the return return value in the try code block, such as the following method, we pass in 100 and the return result is also -1.

public static void main(String[] args) {
   
       calculate(100);}public static int calculate(int number) throws Exception {
   
       try {
   
           if (number < 100) {
   
               throw new DataFormatException("数据格式错误");        } else {
   
               return number;        }    } catch (Exception e) {
   
           throw e;    } finally {
   
           return -1;    }}
  • Shield exception

When we throw an exception in the try block, when the exception thread monitors that an exception occurs, it will register the current exception type as DataFormatException in the exception table, but when the executor executes the finally code block, it will reset the method Assignment is to tell the caller that "the method is executed correctly and no exception is generated". For example, if we call the above method and pass in -1, no exception will be thrown.

Exception encapsulation

Java provides an exception handling mechanism to ensure the robustness of the program, but Java provides general exceptions, and we need to encapsulate some business exceptions in the development of the project.

Unified exception handling

In project development, you can use aspect-based unified exception handling or rely on SpringMVC's ControllerAdvice to return error information to the front end in a unified project format, so that you only need to throw exceptions during the development process.

Forbid swallowing exceptions directly

Swallowing exceptions will make it difficult to troubleshoot problems that occur during the running of the program, and exceptions should be thrown upwards.

2.4 Pay attention to compilation warnings

The compilation warnings in the program are easy to be ignored, because even if there are warnings, the source file can still be edited and run, especially when we use IDE during the development process, but these warnings often hide some potential problems.

2.5 Expose problems as early as possible

When a bug is discovered during the development, self-test, testing, and release stages of the project, the repair cost is different. The later the repair cost is higher, especially when it goes online, it may cause certain capital losses. In the process of project development and self-testing, attention should be paid to code quality, self-testing or using bug scanning tools to find problems as early as possible.

SpotBugs

SpotBugs provides static byte code analysis, which uses static analysis to find more than 400 bug patterns, such as null pointer dereferences, infinite recursive loops, incorrect use of Java libraries, and deadlocks.

3. Maintainability

3.1 Logging

  • All backgrounds must have operation logs and data change logs

  • The log needs to be configured with asynchronous write to disk

  • Only WARN and ERROR level logs are kept online

  • All logs must have traceId

  • The exception log must have a stack, input parameters, and information that can clearly explain what is wrong

  • When printing logs, it is forbidden to directly use JSON tools to convert objects into Strings

3.2 Clear error message

In the use of the product, we will prompt some error messages to users, but if we provide general error messages, it may confuse users, such as: "The service is temporarily unavailable", especially we cannot prompt: "There is an internal error in the system, please contact the system Admin!" to the user, which reduces the user's trust in the product. We can prompt specific error messages, such as "xxx information is not filled, please fill it out first."

3.3 Keep the code concise

Avoid nested if/else

Null value judgments and logical judgments are often performed in the code, and if/else nesting will make the code logic look very complicated.

public  static String getDepartmentNameOfUser(String username) {
   
       Result<User> result = getUserByName(username);    if (result != null) {
   
           User user = result.getData();        if (user != null) {
   
               Department department = user.getDepartment();            if (department != null) {
   
                   return department.getName();            }        }    }    return "未知部门";}

Try to avoid nested if/else, you can write like this:

public static String getDepartmentNameOfUser(String username) {
   
       Result<User> result = getUserByName(username);    if (result == null) {
   
           return "未知部门";    }    User user = result.getData();    if (user == null) {
   
           return "未知部门";    }    Department department = user.getDepartment();    if (department == null) {
   
           return "Department为空";    }    return department.getName();}

Extract class, method

Make the responsibilities of classes or methods clearer, and don't write all the logic into one method.

public boolean addExp(ExpVO expVO){
   
       //校验参数是否正确,如果失败直接抛出异常    checkParamValidate(expVO);    //新增实验信息    addExp(expVO);    //新增实验组    expGroupService.addExpGroup(expVO);    //新增实验层    expLayerService.addExpLayer(expVO);    //计算实验流量    List<ObjectFLowEntity> flowEntityList = flowService.calculateFlow(expVO);    //保存流量信息    flowService.saveFlow(flowEntityList);}

don't use magic

Do not use magic values ​​in your code, as they will be missed if the value changes later.

public boolean addExp(){
   
       //服务端实验    expEntity.setExpType(1);}

Enums can be used:

public boolean addExp(){
   
       expEntity.setExpType(ExpTypeEnum.SERVER.getExpType());}

3.4 Using open source tools

Using some open source tools can reduce our repeated wheel creation, and commonly used open source tools have complete unit test coverage, which can effectively reduce the occurrence of bugs.

Google Guava

Guava is a set of core Java libraries from Google, including collections, caching, primitive types, concurrency, common annotations, basic string operations, I/O, and more.

For example, it is very convenient to use Google Guava for the intersection, union, and search of sets.

Set<Integer> sets = Sets.newHashSet(1, 2, 3, 4, 5, 6);Set<Integer> sets2 = Sets.newHashSet(3, 4, 5, 6, 7, 8, 9);//交集SetView<Integer> intersection = Sets.intersection(sets, sets2);//差集SetView<Integer> diff = Sets.difference(sets, sets2);//并集SetView<Integer> union = Sets.union(sets, sets2);

Apache Commons

Apache Commons is an extension to JDK, which includes many open source tools. The following are the tools commonly used in our projects:

Commons Lang3: A tool class for processing Java basic object methods, providing operations on basic objects such as characters and arrays.

Commons Codec: Provides common encoding and decoding methods, such as DES, SHA1, Base64.

Commons BeanUtils: Provides dynamic generation of beans.

Commons HttpClient: Simplifies various communications between HTTP clients and servers.

Log4j

The most used logging framework in major open source frameworks and projects.

4. Scalability

The code we write is for specific requirements, but these requirements are not static. When the requirements change, if the scalability of our code is very good, we may only need to simply add or delete modules. The performance is not good, and all the code may need to be rewritten, so it is necessary to provide code scalability. When we write code, we can use design patterns to make the code have good scalability.

For example, the AB shunt algorithm, the shunt algorithm has different implementations according to the different needs of different scenarios, we define the interface of the algorithm, and different shunt algorithms implement this interface, then we only need to consider which algorithm to use when using it. Need to care about the implementation of the algorithm.

/** * AB分桶算法 * @author hufei * @date 2021/6/8 6:06 下午 */public interface BucketAlgorithmTemplate {
   
   
    /**     * ab分桶算法     *     * @param layerId     * @param expId        实验id     * @param expRatio     实验占层流量比     * @param expGroups    实验组详情     * @param existsBucket 已存在的实验层和实验组流量详情,第一次创建传null     * @return     * @throws BucketAlgorithmException     */    public Map<Integer, JSONObject> calculateBucket(Integer layerId, Integer expId, Integer expRatio, Map<Integer, Integer> expGroups, Map<Integer, List<JSONObject>> existsBucket) throws BucketAlgorithmException;}

/** * AB分桶算法 * @author hufei * @date 2021/6/8 6:39 下午 */public abstract class AbstractBucketAlgorithm implements BucketAlgorithmTemplate {
   
       /**     * @param expId        实验id     * @param expRatio     实验占层流量比     * @param expGroups    实验组详情     * @param existsBucket 已存在的实验层和实验组流量详情,第一次创建传null     * @return     * @author hufei     * @description ab分桶算法     * @date 2021/6/8 6:31 下午     */    public Map<Integer, JSONObject> calculateBucket(Integer layerId, Integer expId, Integer expRatio, Map<Integer, Integer> expGroups, Map<Integer, List<JSONObject>> existsBucket) throws BucketAlgorithmException {
   
           calculateVerify();        Map<Integer, JSONObject> bucketMap = new HashMap<>();        JSONObject layerObj = new JSONObject();        JSONObject expObj = new JSONObject();        if (existsBucket == null) {
   
               //如果不存在历史分流信息,说明层是新建,实验也是新建            layerObj = newLayer(layerId, expId, expRatio);            expObj = newExp(expGroups);        } else if (existsBucket.get(layerId) != null && existsBucket.size() == 1) {
   
               //只有层的历史记录,但是没有实验的历史记录,说明层已经存在实验是新建            layerObj = calculateLayer(layerId, expId, expRatio, existsBucket.get(layerId));            expObj = newExp(expGroups);        } else if (existsBucket.get(layerId) != null && existsBucket.get(expId) != null) {
   
               //有层和实验的历史记录,说明层不是新建并且实验也不是新建            layerObj = calculateLayer(layerId, expId, expRatio, existsBucket.get(layerId));            expObj = calculateExp(expGroups, existsBucket.get(expId));        }        bucketMap.put(layerId, layerObj);        bucketMap.put(expId, expObj);        return bucketMap;    }    /**     * @param layerId     * @param expId     * @param expRatio     * @return     * @author hufei     * @description 新建层     * @date 2021/6/10 11:24 上午     */    public JSONObject newLayer(Integer layerId, Integer expId, Integer expRatio) {
   
           return new JSONObject();    }    public JSONObject calculateLayer(Integer layerId, Integer expId, Integer expRatio, List<JSONObject> layerHistoryFlow) {
   
           return new JSONObject();    }    /**     * @return     * @author hufei     * @description 新建实验     * @date 2021/6/10 11:24 上午     */    public JSONObject newExp(Map<Integer, Integer> expGroups) {
   
           return new JSONObject();    }
    public JSONObject calculateExp(Map<Integer, Integer> expGroups, List<JSONObject> expHistoryFlow) {
   
           return new JSONObject();    }}

/** * 简单分流算法实验, 每次分流只用考虑当前的桶, 不用回溯历史版本 * {@link https://duapp.yuque.com/team_tech/confluence-data-iwskfg/dzmogk} * @author hufei * @date 2021/6/8 7:59 下午 */public class SimpleBucketAlgorithm extends AbstractBucketAlgorithm {
   
       /**     * @param layerId     * @param expId     * @param expRatio     * @return     * @author hufei     * @description 新建层     * @date 2021/6/10 11:24 上午     */    @Override    public JSONObject newLayer(Integer layerId, Integer expId, Integer expRatio) {
   
           ......    }    @Override    public JSONObject calculateLayer(Integer layerId, Integer expId, Integer expRatio, List<JSONObject> layerHistoryFlow) {
   
           ......    }    /**     * @return     * @author hufei     * @description 新建实验     * @date 2021/6/10 11:24 上午     */    @Override    public JSONObject newExp(Map<Integer, Integer> expGroups) {
   
           ......    }    /**     * 实验组流量变更:     * 1.优先从右边空白开始分配     * 2.先增后减     *     * @param expGroups     * @param expHistoryFlow     * @return     */    @Override    public JSONObject calculateExp(Map<Integer, Integer> expGroups, List<JSONObject> expHistoryFlow) {
   
           .......    }    /**     * 递归计算     */    private void calculateExpRecursion(JSONObject currentExpFlow, Map<Integer, Long> groupCountMap, List<Integer> positiveRatioList, List<Integer> negativeRatioList, Map<Integer, Long> groupNeedAddOrReduceRatioMap) {
   
               }}

5. Efficiency

5.1 Code optimization

loop optimization

for (int i=0;i<list.size;i++) {
   
   ...}for (int i=0,size=list.size();i<size;i++){
   
   ...}

Don't create objects in loops

collection optimization

When initializing a collection, try to specify a predictable collection size to reduce the number of collection expansions.

5.2 Introducing concurrency

Concurrency can improve the execution time of the program very well, but it can also cause many problems if it is not used well. If there is no correlation between tasks, we execute tasks concurrently to shorten the overall time.

CompletableFuture[] cfs = tailorEntry.getValue().values().stream().map(layerExtraInfo -> CompletableFuture.supplyAsync(() -> layerCalculate(layerExtraInfo, userHitWhiteListMap, request.getUserId(), needGroupId, request.getCurrentGroupParm()), asyncFlowServiceExecutor).whenComplete((r, e) -> {
  
      if (!r.isEmpty()) {
   
           hitGroupList.addAll(r);        r.forEach(g -> {
   
               needGroupId.add(g.getId());        });    }})).toArray(CompletableFuture[]::new);CompletableFuture.allOf(cfs).join();

Improving code quality is a complex and continuous work, and the explanation of an article is also very limited. We need continuous iterative optimization in the development of the project to ensure the quality of the code.

Guess you like

Origin blog.csdn.net/Java_LingFeng/article/details/128654722