laitimes

Ali's colleague, the code written is really TMD elegant!

author:Prose thinks with the wind

In this article, you will learn how important clean code is for your project, your company, and you, and how to write clean code.

Through the four chapters of naming, classes, functions, and tests, we make our code tidy.

1. Why should I keep my code clean?

When untidy code increases over time, productivity decreases.

As a result:

  • The code doesn't extend easily or can cause other problems
  • The program crashes
  • overtime
  • Increase company costs (plus people)
  • It may even lead to the closure of the company

A picture is worth a thousand words

Ali's colleague, the code written is really TMD elegant!

1.1. So keep it tidy from the beginning

Therefore, it is necessary to write clean code at the beginning, and if there is untidy code, it must be rectified in time. Never have the idea of changing it later and talking about it later, because!

later equal never

           

Think about whether this is the truth, how many things you will talk about later and change later, you have forgotten about it.

If it's a must-do, do it sooner!

1.2. How to write neat code?

So the question now is, what kind of code is clean code:

  • Readability: The code should be as elegant and easy to read as prose
  • Reject duplicate codes
  • Meet the design pattern principles
  • Single responsibility
  • Open-closed principle
  • Richter substitution principle
  • Rely on the inversion principle
  • Interface isolation principle
  • Dimmit's Law
  • The law of synthetic reuse

2. Naming

Good naming can improve the readability of the code, make the code understandable, reduce the cost of understanding, improve efficiency, and reduce overtime.

2.1. Bad naming

1. Naming without any meaning;

public interface Animal {
    void abc();
}

           

Now we have an animal interface with a method abc() in it, and it's confusing to look at it, and the person calling this method doesn't know what it's all about, because its name doesn't make sense

Meaningful naming:

public interface Animal {
    void cry();
}

           

We'll name the method cry, and the person calling it will know what the method does.

Therefore, the name must be meaningful and meaningful.

1. Mismatch between the names of the names;

This situation is reflected in the fact that it is the same behavior, but it has different names, which is inconsistent and confusing.

public interface StudentRepository extends JpaRepository<AlertAll, String> {
    Student findOneById(
            @Param("id") String id
    );

    List<Student> queryAllStudent(
    );

}

           

The above two methods are query xxx, but the naming will be called query and will be called find, this situation should be standardized, consistent, modified:

public interface StudentRepository extends JpaRepository<AlertAll, String> {
    Student findOneById(
            @Param("id") String id
    );

    List<Student> findAll(
    );

}

           

1. Naming jokes;

This is reflected in the fact that there are a lot of unnecessary components in the name, and this "nonsense" does not help distinguish between them, such as adding the word Variable to the name of a variable, and adding the word Table to the name of a table.

Therefore, do not use redundant words in the naming, and agree on the naming convention in advance.

// 获取单个对象的方法用get做前缀
getXxx();
//获取多个对象用list做前缀
listXxxx();

           

3. Classes

A neat class should satisfy the following:

  • Single responsibility
  • Open-closed principle
  • High cohesion

3.1. Single responsibility

Classes should be short, classes or modules should have only one reason to change them, and if a class is too large, it has too much responsibility.

Merit:

  • Reduce the complexity of your class
  • Improve the readability of your classes
  • Improve the maintainability of the system
  • Reduce the risk of change

How do you tell if a class is short enough?

If you can't give a class an exact name, the class is probably too long, and the more ambiguous the class name, the more responsibilities it may have.

For an example of too many responsibilities, you can see that there are two responsibilities in the following class:

public abstract class Sql {
    // 操作SQL的职责
    public abstract void insert();
    // 统计SQL操作的职责
    public abstract void countInsert();

}

           

Extract the responsibility of statistics to another class

public abstract class CountSql {

    public abstract void countInsert();

}

           

3.2. The principle of opening and closing

Open-Closed Principle: Closed for modification, open for extension.

Modification-oriented shutdown means that adding new logic does not modify the original code, reducing the possibility of errors.

Extension-oriented is to improve the extensibility of the code, and it is easy to add new code logic.

Examples of non-compliance with the open-close principle:

public abstract class Sql {
    public abstract void insert();
    public abstract void update();
    public abstract void delete();
}

           

If we want to add a query operation now, we need to modify the SQL class, and we don't do it for modification-oriented

After refactoring:

public abstract class Sql {
    public abstract void generate();
}

public class CreateSql extends Sql {

    @java.lang.Override
    public void generate() {
        // 省略实现
    }
}
public class UpdateSql extends Sql {

    @Override
    public void generate() {
        // 省略实现
    }
}

           

It can be easily extended when we want to add a removal method.

Using a large number of short classes may seem like an increase in the amount of work (more classes added) than using a small number of large classes, but is that really the case? Here's a good analogy:

Do you want to put your tools in a toolbox with many drawers, each with well-defined and labeled components, or do you want a handful of drawers where you can throw everything in?

Final Verdict:

系统应该由许多短小的类而不是少量巨大的类组成,每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为.

           

3.3. Cohesion

The more variables a method operates, the more it will be cohesed to the class. If every variable in a class is used by every method, then the class has the greatest cohesion.

We should keep the cohesion of the class high. High cohesion means that methods and variables are interdependent and combine with each other to form a logical whole.

Insertion: If you are preparing to change jobs in the interview in the near future, it is recommended to brush the questions online in ddkk.com, covering 10,000+ Java interview questions, covering almost all mainstream technical interview questions.

Why maintain high cohesion?

Maintaining cohesion will result in many short classes and the more satisfying a single duty will be.

What to do if the cohesion is low?

If the cohesiveness of the class is not high enough, the original class is split into new classes and methods.

4. Functions

To keep your functions clean, you should make sure that:

  • Do only one thing
  • Good naming
  • Neat parameters
  • Pay attention to the content returned

4.1. Do only one thing

what?

The first rule of the function is short, and the second rule is shorter, short enough to do one thing. (Yes, it's very similar to the principle of classes)

why?

The shorter the function, the more it satisfies a single responsibility.

how?

Here's the code before the refactoring, This method has three responsibilities, and the method is very long to 80 + 50 + 5 = 135 lines

public class PicService {

    public String upload(){
        // 校验图片的方法 伪代码80行

        // 压缩图片的方法 伪代码50行

        // 返回成功或失败标识 0,1 伪代码5行
        return "0";
    }
}

           

The original upload method did a lot of things, but after refactoring, it did only one thing: split the larger concept (in other words, the name of the function) into a series of steps on another abstraction layer:

public String upload(){
        // 校验图片的方法
        check();
        // 压缩图片的方法
        compress();
        // 返回成功或失败标识 0,1
        return "0";
    }

           

Each method has its own responsibilities (checking images, compressing images, and returning results).

4.2, Function Naming

1. The name of the function should be known by the name;

Functions have descriptive names, don't be afraid of long names.

Bad naming:

public String addCharacter(String originString, char ch);

           

This function, at first glance, is not bad, and from the functional surface it is to add a character to a certain string. But is it added at the beginning of the original string, or at the end of the original string? Or is it inserted in a fixed position? The real intent of this function is completely unclear from the name of the function, and you can only know by reading the specific implementation of this function below.

The following names are much better than the above:

// 追加到末尾
public String appendCharacter(String originString, char ch);   

// 插入指定位置
public String insertCharacter(String originString, char ch, int insertPosition); 

           

1. The function should have no side effects;

Functions should be side-effect-free, meaning that the function should do only one thing, but do something else that has side effects.

For example, when the password is verified, the session is initialized, resulting in the loss of the session. If you can't remove this side effect, you should show it in the method name to prevent the user from misusing checkPasswordasswordAndInitializeSession, and the side effect should be reflected in the name.

4.3. Parameters

1. The fewer parameters, the better;

The fewer the parameters, the easier it is to understand, the parameters can be encapsulated if there are more than three parameters, and the parameters should be encapsulated according to the semantics of the parameters, not necessarily encapsulated into a large and complete parameter, which can be encapsulated into multiple, and the principle is to supplement according to the semantics;

Example:

public List<Student> findStudent(int age, String name, String country, int gender);

//封装参数
public List<Student> findStudent(Student student);

           

1. Do not use identification parameters;

The identification parameter is a parameter of type Boolean, which the user passes true or false. Don't use identity arguments, as this means that your function is violating a single duty (true, false, and two sets of logic).

The correct way to do this is to split it into two ways:

//标识参数方法
render(Boolean isSuite);

//重构为两个方法
reanderForSuite();
renderForSingleTest();

           

1. Don't use output parameters;

What are Output Parameters?

Passing a variable as a parameter to the method and outputting the variable is the output parameter

public void findStudent(){
Student student = new Student();
doSomething(student);
return student;
}

int doSomething(Student student){
// 省略一些student逻辑
return student;
}

           

Why shouldn't there be an output parameter?

Because of the increased cost of understanding in it, we need to look at what exactly doSomething does to the student. Is student an input or output parameter? are not clear.

Reconstitution:

// 将doSomething()方法内聚到student对象本身
student.doSomething();

           

4.4. Return value

1. Separate instructions and interrogation;

Sample code:

Pulic Boolean addElement(Element element)

           

The command is to add an element, asking if it is successful,

The downside of this is that there is no single responsibility, so it should be split into two approaches

public void addElement(Element element);
public Boolean isAdd(Element element);

           

1. Use an exception substitution to return an error code;

Throwing an exception instead of returning an error code for judgment can make the code more concise. This is because there is a risk of multiple layers of nested fragments using error codes

Code examples:

// 使用错误码导致多层嵌套...
public class DeviceController{

 public void sendShutDown(){
  DeviceHandle handle=getHandle(DEV1);
   //Check the state of the device 
  if (handle != DeviceHandle.INVALID){
   // Save the device status to the record field 
   retrieveDeviceRecord(handle);
   // If nat suspended,shut down
   if (record.getStatus()!=DEVICE_SUSPENDED){
     pauseDevice(handle);
     clearDeviceWorkQueue(handle);
     closeDevice(handle);
   }else{
    logger.log("Device suspended. Unable to shut down"); 
   }
  }else{
   logger.log("Invalid handle for: " +DEV1.tostring()); 
 }
} 
           

After refactoring:

//  将代码拆分为一小段一小段, 降低复杂度,更加清晰
public class DeviceController{

 public void sendShutDowm(){ 
  try{
   tryToShutDown();
  } catch (DeviceShutDownError e){ 
   logger.log(e);
  }

 private void tryToShutDown() throws DeviceShutDownError{
   DeviceHandle handle =getHandle(DEV1);
   retrieveDeviceRecord(handle);
   pauseDevice(handle);
   clearDeviceWorkQueue(handle);
   closeDevice(handle);
 }

 private DeviceHandle getHandle(DeviceID id){
              // 省略业务逻辑
  throw new DeviceShutDownError("Invalid handle for:"+id.tostring()); 
 }
}

           

4.5. How to write such a function?

Nobody can start with perfect code, write code that meets the functionality, and then refactor it

Why immediately after?

因为later equal never!

4.6. Code quality scanning tool

Using SonarLint can help us find problems with our code and also provide solutions accordingly.

For each problem, SonarLint gave examples and corresponding solutions, taught us how to modify them, and greatly facilitated our development

For example, try to use LocalDate, LocalTime, LocalDateTime for date types, and there are also problems such as duplicate code, potential null pointer exceptions, nested loops, and so on

With code specification and quality inspection tools, many things can be quantified, such as bug rate, code repetition rate, etc.

5. Testing

Testing is important to help us verify that the code we write is okay, and the same test code should be kept clean.

5.1、TDD

TDD stands for Test-Driven Development, which is a core practice and technology in agile development, and is also a design methodology

Advantages: At any development node, you can come up with a product that can be used, contains a small number of bugs, has certain functions and can be released.

Insertion: If you are preparing to change jobs in the interview in the near future, it is recommended to brush the questions online in ddkk.com, covering 10,000+ Java interview questions, covering almost all mainstream technical interview questions.

Disadvantages: Increased code volume. Test code is twice as much or more than system code, but at the same time saves time debugging and troubleshooting.

how?

1. Write tests before developing code;

2. You can only write unit tests that just can't pass, and you can't compile them.

3. The development code should not exceed the test;

Explanation of 2: The single test is synchronized with the production code, and the production code is written when a non-compilable single test is written, and so on, and the single test can contain all the production code.

5.2. FIRST principle

The FIRST principle is a principle that guides the writing of unit tests

fast The execution of a single test should be done quickly

independent 独立 单测之间相互独立

Repeatable Single tests are environment-agnostic and can be run anywhere

The self validating program can self-verify through the output Boolean, instead of manually verifying it (see the log output, compare two different files, etc.)

Timely Single-test is written before the code is produced

Unit testing is the basic test in code testing, FIRST is an important principle for writing good unit tests, which requires our unit tests to be fast, independent, repeatable, self-verifying, and timely/complete.

5.3. Test the code pattern

You can use the given-when-then pattern for development and testing code

given manufacturing simulation data

when executing test code

Then verify the test results

Code samples

/**
  * If an item is loaded from the repository, the name of that item should 
  * be transformed into uppercase.
  */
@Test
public void shouldReturnItemNameInUpperCase() {
 
    // Given
    Item mockedItem = new Item("it1", "Item 1", "This is item 1", 2000, true);
    when(itemRepository.findById("it1")).thenReturn(mockedItem);
 
    // When
    String result = itemService.getItemNameUpperCase("it1");
 
    // Then
    verify(itemRepository, times(1)).findById("it1");
    assertThat(result, is("ITEM 1"));
}
           

使用give-when-then 模式可提高测试代码的可读性.

5.4. Automatically generate a single test

Introducing two plug-ins for IDEA to automatically generate single tests

  • Squaretest plugin (for a fee)
  • TestMe Plugin (Free)

6. Concluding remarks

Writing clean code allows us to improve the readability of the code, make the code more extensible, improve our development efficiency, reduce overtime, and improve our level.

Every developer should take a look at <代码整洁之道>< > to improve our coding skills as well as programming ideas.

Again, we should write clean code in a timely manner, rather than trying to fix it after the fact

Ali

Read on