Writing neat code is the pursuit of every programmer. “clean code” pointed out that in order to write good code, you must first know what is dirty code, what is neat code; then through a lot of deliberate practice, you can really write neat code.

WTF/min is the only measure of code quality, and Uncle Bob says bad code in the book is wading, which only highlights the fact that we are victims of bad code. There is a more suitable vocabulary in China: Lushan, although not very elegant but more objective, programmers are both victims and injurers.

For the tidy code, the book gives a summary of the masters:

  • Bjarne Stroustrup: elegant and efficient; straightforward; reduce dependencies; just do one thing
  • Grady booch: simple and straightforward
  • Dave thomas: readable, maintainable, unit test
  • Ron Jeffries: Don’t repeat, single responsibility, expressiveness

Among them, my favorite is the description of Expressiveness. This word seems to tell the truth of good code: the function of drawing code in a simple and direct way, no more and no less.

This article records some of the views of individuals who have “same feelings” or “squatting” after reading “clean code.”

 

1, the art of naming

To be frank, naming is a difficult thing, and it takes a lot of work to come up with a just-right naming, especially if our native language is not English that is common to programming languages. But all this is worth it, good naming makes your code more intuitive and expressive.

Good naming should have the following characteristics:

 

1.1 worthy of the name

Good variable name tells you: what is it, why it exists, how to use it

If you need to explain the variables through comments, then you have to be worthy of the name.

Below is a sample code in the book that shows the improvement in code quality by naming.

# bad code
def getItem(theList):
   ret = []
   for x in theList:
      if x[0] == 4:
         ret.append(x)
   return ret

# good code
def getFlaggedCell(gameBoard):
   '''mine game,flagged: turnover'''
   flaggedCells = []
   for cell in gameBoard:
      if cell.IsFlagged():
         flaggedCells.append(cell)
   return flaggedCells

1.2 Avoid misleading

  • Don’t hang sheep’s head and sell dog meat
  • Do not overwrite aboriginal abbreviations

Here I had to see a code two days before the spit, actually used l as the variable name; and, user is actually a list (single and plural are not learned!!)

 

1.3 meaningful distinction

The code is written to the machine for execution and is also for reading, so the concept must be differentiated.

# bad
def copy(a_list, b_list):
    pass

# good
def copy(source, destination):
    pass

 

1.4 Using the words read

If the name doesn’t read, then the discussion will be like a silly bird.

 

1.5 Using a convenient search for naming

The length of the name should correspond to the size of its scope

 

1.6 Avoid thinking mapping

For example, if you write a temp in your code, the reader will have to translate it into its true meaning each time you see the word.

 

2, the comments

The expressive code is uncommentable: The proper use of comments is to compensate for our failure to express ourself in code.

The proper effect of annotations is to make up for the failures we encounter when expressing intents in code, which sounds frustrating, but it does. The truth is in the code, the comment is just second-hand information, the two are not synchronized or not equal to the biggest problem of the comment.

The book gives a very vivid example to show: use code to explain, not comment

bad
// check to see if the employee is eligible for full benefit
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))

good
if (employee.isEligibleForFullBenefits())

 

 

Therefore, when you want to add comments, you can think about whether you can show the intent of the code by modifying the naming or by modifying the abstraction level of the function (code).

Of course, it can’t be ruined. The book points out that some of the following are good comments.

  • Legal information
  • Comment on the intent, why do you want to do this?
  • Warning
  • TODO notes
  • Magnify the importance of seemingly unreasonable things

The most personally endorsed by points 2 and 5, what is easy to do by naming, but why not do it is not intuitive, especially when it comes to professional knowledge and algorithms. In addition, some of the first code that feels “not so elegant” may have its special willingness. Then such code should be annotated to explain why this is the case. For example, in order to improve the performance of critical paths, some code may be sacrificed. Readability.

The worst comment is an outdated or erroneous comment, which is a huge hazard to the code maintainer (perhaps a few months later), but there is no easy way to guarantee code and comments besides code review. Synchronize.

 

3, the function

 

3.1 Single responsibility of the function

A function should only do one thing, and this should be clearly demonstrated by the name of the function. The method of judging is simple: see if the function can also remove a function.

The function either does do_sth, or queries what query_sth. The most disgusting thing is that the function name means only query_sth, but in fact it will do_sth, which makes the function have side effects. Such as the example in the book

public class UserValidator {
    private Cryptographer cryptographer;
    public boolean checkPassword(String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.NULL) {
            String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize();
                return true;
            }
        }
        return false;
    }
}

 

3.2 Abstract level of function

Each function has an abstraction level, and the statements in the function are all at the same level of abstraction. Different levels of abstraction cannot be put together. For example, if we want to put an elephant in the refrigerator, it should look like this:

def pushElephantIntoRefrige():
    openRefrige()
    pushElephant()
    closeRefrige()

The three lines of code in the function describe the three steps involved in the order of placing the elephant in the refrigerator at the same level (height). Obviously, this step of pushElephant may contain many substeps, but at the level of push Elephant Into Refrige, there is no need to know too much detail.

When we want to understand a new project by reading the code, we generally adopt a breadth-first strategy, read the code from top to bottom, first understand the overall structure, and then go deep into the details of interest. Without a good abstraction of the implementation details (and condensing a veritable function), the reader is easily lost in the details of the ocean.

To some extent, this is similar to the pyramid principle.

Each level is to demonstrate its superior level of view, but also requires the next level of support; multiple arguments between the same level need to be sorted by some logical relationship. pushElephantIntoRefrige is the central argument, which requires the support of multiple substeps, and there is also a logical order between these substeps.

 

3.3 function parameters

The more parameters the function has, the more input the combination will be, and the more test cases you need, the easier it will be.

The output parameters are difficult to understand compared to the return value. This is deeply felt, and the output parameters are really unintuitive. From the perspective of the function caller, the return value can be seen at a glance, and it is difficult to identify the output parameters. The output parameters usually force the caller to check the function signature, which is really unfriendly.

Passing Boolean to a function (called Flag Argument in the book) is usually not a good idea. In particular, the behavior after passing in True or False is not two sides of one thing, but two different things. This obviously violates the single responsibility constraint of the function. The solution is very simple, that is, using two functions.

 

3.4 Dont repear yourself

At the level of the function, it is the easiest and most intuitive to implement reuse. Many IDEs are also difficult to help us to reconstruct a function from a piece of code.

However, in practice, there will be a situation where a piece of code is used in multiple methods, but it is not exactly the same. If you abstract into a generic function, you need to add parameters and add if else. This is a bit embarrassing, seemingly reconfigurable, but not perfect.

Some of the problems caused by the above problems are because this code also violates the principle of single responsibility, and does more than one thing, which leads to poor reuse. The solution is to subdivide the method to better reuse. You can also consider the template method to handle the differences.

 

4, test

What is very embarrassing is that in the projects I have experienced, the tests (especially the unit tests) have not received enough attention and have not tried TDD. Because of the lack, it is more precious to test well.

We often say that good code needs to be readable, maintainable, and extensible. Good code and architecture need constant reconstruction and iteration, but automated testing is the basis for ensuring this. There is no high coverage. Automated unit testing, regression testing, no one dares to modify the code, can only let it rot.

Even if you write unit tests for the core module, it is generally very casual, think that this is just test code, not worthy of the status of production code, thinking that as long as you can run through. This results in poor readability and maintainability of the test code, which then makes it difficult for the test code to be updated, evolved, and ultimately invalidated by the production code. So, the dirty test – equivalent to – did not test.

Therefore, the three elements of the test code: readability, readability, readability.

The principles and guidelines for testing are as follows:

  • You are not allowed to write any production code unless it is to make a failing unit test pass. Do not write any function code before testing.
  • You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. Only write test code that happens to reflect a failure.
  • You are not allowed to write any more production code than is sufficient to pass the one failing unit test. Only write the function code that just passed the test.

FIRST guidelines for testing:

  • Fast testing should be fast enough and automated as much as possible.
  • Independent testing should be independent. Do not depend on each other
  • Repeatable tests should be repeated in any environment.
  • The Self-Validating test should have bool output. Don’t judge the test by passing the inefficient way of checking the log.
  • Timely testing should be written in time and written before the corresponding production code