-
Clean Code is self-explanatory, and reading Code should be like reading a good article, so you can immediately understand the general functions of the Code. The code should be readable first, and the function should be implemented second.
-
As a developer, in today’s team-oriented way of working, specifications are far more important than features, and the features may not exist in the release iteration, but the specifications are always there.
-
For clean code, there are some common team practices that must be followed. Messy code that can’t even be named will only increase maintenance costs for subsequent successors
Chapter 1 Clean Code
1.1-1.9
This chapter mainly describes that bad code does not do much to improve the progress of the project, just like a pile of firewood in danger, the subsequent developers will not understand the relationship and often directly throw other firewood on it, the final result is a collapse and cannot be maintained. So keep the code as clean as possible from the start.
The so-called clean code, in my personal understanding, probably meets the following requirements: 1. The code format should be consistent with a standard, such as indentation, blank lines, Spaces, etc. 2. Use meaningful names, including class names, variable names, function names, etc., to basically see the operation to be completed at a glance; 3. Classes and functions are simple and simple to avoid overcrowding and excessive nesting;
Chapter two meaningful naming
2.1-2.6
Variable names that can read the meaning of the word and meet the practical use of the code make the reading process more pleasant, even concise code once poorly named will increase ambiguity, difficult to read the meaning.
2.7-2.18
Class and object names are usually nouns/noun phrases, and method names are usually verbs/verb phrases. Naming commands based on context relevance can generate meaningful context. For example, when used together with words such as “score”, “student” and “exam”, it is easy to imagine that “score” represents a student’s score in an exam. Of course, necessary prefixes can also be added to add context, such as “examScore”
Chapter III Functions
3.1-3.3
The first rule of the function is short, the second rule is shorter, the length of the function had better not exceed a screen, otherwise need to scroll back and forth, reading inconvenient;
Each function has a single function, nested no more than 2 layers, and does only one thing. Do it well. To determine whether the function is single, you can see if you can extract another function;
The shorter the function, the more simple the function, the more specific the name;
Reading rules from the top down, one level for each function and calling the next level function;
3.4-3.5
Switch statements are best used at a lower level of abstraction to create polymorphisms. Use descriptive names and don’t be afraid to make them too long. Long, descriptive names are better than short, confusing ones.
3.6-3.7
The optimal number of parameters is zero, and there is no good reason not to use three or more parameters, not only adding to the cost of understanding, but also making parameter combinations a hassle to write unit tests from a testing standpoint. When the number of parameters is too large, consider incorporating some parameters into class objects.
Some functions perform processing that may hide operations that do not match the name of the function, which is destructive and violates the rule of uniformity. For example, if the session.initialize () Session is hidden in the void checkPassword(String username, String password) function, the referrers may only want to check whether the username and password are correct. Results led to the current normal conversation there is a problem, can the right thing to do is the function named checkPasswordAndSessionInitialize (String username, String password)
The above two points are also easy to make mistakes in ordinary programming: the excessive number of parameters, on the one hand, increases the time cost of reading and understanding the function name, in reading the specific implementation, but also pay attention to the specific reference position and effect of each parameter; Doing too many things in an API with names that are not well described can lead to unknown errors after calls from people who don’t know the implementation;
3.8-3.15
Eliminate duplicate code. Duplication is the root of evil in software, and many principles and practices were created to eliminate duplication. Remove too much redundancy, so that the code logic at a glance, but also make subsequent maintenance more simple modification.
As with writing a piece of code, the first draft may be clunky and unorganized, so you hone it until you get what you want.
Chapter IV Notes
4.1-4.3
One of the motivations for writing comments is to decide to add comments because of bad code, but it’s best to clean up the code rather than add unnecessary comments. Good code is self-explanatory.
Comments can be misleading, inapplicable, or provide incorrect information, perhaps from comments that were misstated in the first place, comments that were not updated in a timely manner when code was changed, and so on.
For obscure return values that refer to some third-party library files that cannot be modified, annotate them appropriately to explain their meanings.
For relatively complex processes, comments explaining the author’s intent can provide useful information for understanding the implementation.
Writing a public API means writing a good standard Java Doc specification for it.
4.4-4.5
Bad comments are as follows: 1. Redundant comments: for simple and clear functions, the function comment description is unnecessary, and the time to read the comment may be more than the time to read the function code;
2. Misleading notes: the notes are not accurate enough, and there are situations inconsistent with the actual operation, which mislead users to conduct wrong processing;
3. Logging comments: It is no longer necessary to add an annotated log at the beginning of each module for each change. Many code maintenance tools now provide better logging of changes.
E.g. Private int dayofMonth; The day of the month;
5. Canonical annotations: Javadoc standard annotations for every function or variable are not necessary unless you are writing a public API;
When functions and variables are self-explanatory, don’t add any comments. The purpose of a comment is to explain code that doesn’t explain itself, and it’s useless if the comment itself needs to be explained.
Chapter 5 Format
5.1
The format of the code is important, because the readability of the code has a profound effect on the behavior that can be modified later. I used to think that “making the code work” was the most important thing, and it would turn out that the features you wrote would be changed in the next version and no longer exist, but the code format specification did live on. Good code formatting promotes understanding and communication, and in this collaborative world, it’s important to pay attention to specifications.
5.2-5.5
Everyone may prefer different code formats, but in teamwork it is important to agree on a good code format that everyone works on. For the vertical format of the code, such as the class file as simple as possible, pay attention to the split can be split, lest the class will become bloated and difficult to maintain. Notice on function description can understand the function point of main processing probably from the name. For horizontal formatting of code, a line of code should not exceed the size of one screen.
Chapter 6 Objects and data structures
6.1-6.5
This chapter focuses on the advantages and disadvantages of objects and data structures. Objects mainly provide interfaces and hide their internal implementation, while data structures provide pure data reads and writes without any other meaningful functional operations.
On my understanding: procedural programming is mainly used alone business class to deal with all kinds of data structure, which leads to the various methods of parameter statement for the Object and then need to use to judge every types of the corresponding data structure, convenient to do according to the different data structure types of the incoming to return different results. The advantage of this approach is that a new operation function is added to the business class, and there is no need to modify the existing data structure class. However, if a new data structure type is added, the judgment of the structure type needs to be added to each operation function in the business class.
Object-oriented because of the respect for interface programming, if the interface is not considered clearly at the beginning, then adding or reducing the interface will lead to all implementation classes need to be modified, but compared to object-oriented convenience in creating new class objects, does not affect the operation interface of other objects, because the implementation is polymorphic. The former is convenient to modify existing operation functions but difficult to add classes, while the latter is convenient to add classes but difficult to modify existing interfaces. There is no one better than the other. You have to decide which one to use.
Chapter 7 Error Handling
7.1-7.2
The first is to use an exception instead of a return code. The second is to write a try-catch-finally statement for the exception to be caught. For the first book said that in order to write code in the process of not need to judge each call to return a variety of error codes, affecting code cleanliness will disturb the code logic. But sometimes the actual use process, often have the error code is defined as the return value, it is convenient to judge callers as a result, the general error of this kind of situation will have a lot of kinds of types, even the existence of different error codes may be as a successful treatment, simply use the way an exception is sometimes not so good. For the second, this is a good code specification. For functions that need to catch errors, in theory try starts and finally ends.
7.3-7.10
If the underlying API has been widely used and a new exception needs to be thrown, it is recommended that the internal encapsulation be handled properly to avoid the need to re-throw or catch the new exception in each link of the upper layer. According to the actual situation, you are advised to classify the user-defined exceptions that are actively thrown and carry an exception description for easy troubleshooting. In the handling of functions, it is best to avoid passing in null and returning NULL. To avoid references, add non-NULL judgments, affecting cleanliness.
Chapter 8 Boundaries
8.1-8.7
Using third-party code or open source libraries, avoid direct validation functions in production code, you can try to write the corresponding unit tests to become familiar with the method of use, understanding provided by the API interface, such as open source library version changes, can quickly according to write unit tests to verify before function will be affected. For the interface provided by the open source library or third party, you can try to encapsulate another layer and use the interface name that is convenient for you to understand, so that the code does not directly reference the interface provided by the open source library. In this way, even if the interface is replaced by a different open source library or the open source library interface changes, the change can be minimized.
Chapter 9 Unit Tests
9.1-9.3
These sections focus on the importance of unit testing in parallel with, or even before, production code to ensure that the project is completed with a unit testing system that covers all aspects. Just because it’s a unit test, you can’t double standards and not pay attention to the cleanliness of unit tests. A good set of unit tests can give you a free modification, the framework of the existing code is also to make big changes to your hand, and as well as production code, neat unit testing is necessary, otherwise once the complete set of unit tests bloated difficult to maintain, can lead to the unit test cannot keep pace with the update of the production code, eventually become a drag on efficiency, Once abandoned can not guarantee the stability of the main function.
In practice, unit testing is often neglected, and I feel that this is not just because of the project cycle, but because we did not schedule the unit testing cycle in the first place. Because of this habitual neglect, it is often necessary to spend more time to self-test each time.
9.4-9.7
Unit tests should use as few assertions as possible and ensure that test cases cover only one point. Clean test cases conform to the “First” principle: Fast, Fast test;
Independent: Use cases are Independent of each other and do not affect or depend on each other. Repeatable, the test can be used repeatedly in task environment, such as network, no network, production environment or quality control environment; Self-validating, use cases should not require additional manual validation, such as viewing log files, to determine if they are valid. The use case itself should be judged for success directly through assertions. Timely, test cases should be written in a Timely manner and preferably prior to production code. Otherwise, if you write production code first, you will often find it difficult to test.Copy the code
Chapter ten classes
10.1 10.2.1
The rules for classes are similar to those for functions in that the first rule is short and the second rule is still short. The shorter the class, the more consistent it is with the single responsibility principle. At a certain point in the project, classes can become bloated and need to be broken up into small classes with a single responsibility. A clean system should be made up of many small classes rather than a few large ones.
10.2.2-10.4 –
The design of a class should be cohesive, that is, the elements of a module should be closely related to each other. Member variables in a class should be associated with method names. Member variables in a class should be operated by as many class methods as possible, which will make methods and variables more cohesive. High cohesion means that methods and variables depend on each other and combine with each other into a single whole, and the responsibilities are more single. If some variables in a class are shared by only a few methods, it means that these methods and variables can be split into a separate small class, otherwise piling up more and more of these variables will lead to a decrease in cohesion.
Chapter XI System
11.1-11.2
This section discusses the benefits of “separating construction and use from each other”, which is a little difficult to understand in the book. It basically means that the decoupling of construction and use reduces the coupling between classes, and facilitates subsequent implementation changes. Here’s a relatively simple example:
public class HandlerImpl {
public void handler() { Component component= new component(); component.handle(); }}Copy the code
In this case, since there is no separation between construction and usage, i.e. the HandlerImpl is both the constructor of the Component (which calls the constructor) and the executor of the Component (which calls the method), it is assumed that the implementation of the Component needs to be replaced with an interface implementation class, or some other class. Component2 Component = New ComponentImpl(); Component2 Component = new Component2(); This modification is too much, the coupling degree is too high, affecting subsequent maintenance.
Similar to “separate build and use” can be used with similar factory pattern + interface oriented programming, such as:
public class HandlerImpl {
public void handler() {
Component component= ComponentFactory.getComponent();
component.handle();
}
}
public class ComponentFactory {
public static Component getComponent() {
Component component= new componentImpl1();
return; }}Copy the code
To replace the Component implementation later, or to add a Component conditional, you only need to modify the constructor ComponentFactory without changing everything: ComponentFactory (ComponentFactory); ComponentFactory (ComponentFactory);
public class ComponentFactory {
public static Component getComponent() {
if(...). {return new componentImpl1();
}
if(...). {returnnew componentImpl2(); }}}Copy the code
11.3-11.12
This section mainly describes the system clean, to achieve the system clean, need to pay attention to the module function division, pay attention to the simple Been object is not coupled with the business logic, do a single responsibility. At the initial stage of the project, all aspects of the future system will be considered. It is unnecessary to provide a lot of APIS or function points that are not required at present. What we need to do is to divide each function module well, and pay attention to the scalability and isolation of each module. This is convenient for continuous expansion and reconstruction optimization, rapid iterative and agile development. Cross-plane of concern? Persistent? AOP? EJB? Is this chapter a little confusing?Copy the code
Chapter 12 superposition
There are actually four rules to follow to achieve a simple, clean design, in order of importance: 1. Run all the tests: This means writing unit tests, which many people lack. Full unit tests force you to focus on testability during the coding process, which means dividing up module functionality and adhering to a single responsibility. Good testing is also the foundation for constant refactoring.
2. Eliminate repetition: Repetition is the enemy of good design, and good design patterns are designed to eliminate repetition. It’s not just the same snippet of code that is repeated, but also other behaviors that are repeated, such as supporting the same effect functionally (like int getSize() and Boolean isEmpty(), when isEmpty() is not necessary), too much duplication can be unnecessarily risky and add extra complexity.
3. Express clear intention: this requires that the name should be accurate and clear, with strong context, and be able to read the author’s intention.
4. Minimize the number of classes and methods: The fragmentation of design patterns and functions tends to be accompanied by an increase in the number of classes and methods, but it is not always the case that more classes and methods are better. Some things that can be avoided should be avoided, such as creating interface classes for every class is not necessary.
Chapter 13 Concurrency
13.1-13.3
Concurrent orchestration decouples timing and destination, unlike single threads that tightly couple the two. The goal of decoupling 2 is to significantly improve the throughput and structure of your application. Structured more like multiple computers working at the same time rather than one big loop, the system is therefore easier to understand and better able to slice the focus. Concurrent programming is a double-edged sword that can significantly improve performance in most cases, but there are caveats: 1. After data sharing, there is synchronization problem of multi-thread access modification. 2. Based on the situation, you can use the data copy to replace the synchronized data block to avoid sharing data. 3. Concurrency adds some overhead in performance and writing extra code; 4. Each thread runs in its own world as much as possible and does not share data with other threads
13.4-13.11
Explains that concurrent programming can lead to deadlocks, mutexes, waits, etc. To write good concurrent code, you need to follow the single responsibility principle. Pojos that split the system into thread-dependent and thread-independent code, ensuring simple code and a focus of purpose. Need to understand and learn the knowledge of concurrent libraries, learn to analyze the causes of concurrent problems. Take care to lock the code area as small as possible to avoid performance degradation. Testing multithreading requires attention to different platforms, different numbers of threads, and the use of various wait, sleep, yield, and Priority devices to change the running order of the code, in order to find various problems as early as possible.
Chapter 14 progressive improvement
As mentioned in the previous chapter, good code, like good prose, is enjoyable to read. Prose often needs to go through a variety of draft stages before it can be finalized, and code is the same. It needs continuous optimization and modification to conform to the path of cleanliness. This chapter focuses on step-by-step improvement. Each step of improvement should be followed by a complete run of the various test cases that accompany the production code to ensure that each step of modification does not leave a problem and does not affect the next step of modification. Gradual improvement should be followed to complete the overall refactoring of the code. Again, good test cases are important.
Chapter 15 Inside JUnit
This chapter takes several unit test cases as examples to explain the process of changing use cases from messy and bloated to clean and reasonable. This is an application of the clean code approach explained in the previous chapters: methods with single responsibilities, elegant naming, and refactoring clean code from methods, variables, and typography
Chapter 16 refactoring SerialDate
Here’s another example to illustrate the modification process: perfecting test cases, modifying test cases step by step; Remove some redundant comments and use enumerations instead of ints for weeks to avoid the need for valid value judgments; The factory pattern is used to complete entity creation and realize the separation of construction and use. Optimization method and variable naming to remove duplicate code, eliminate the existence of magic number;
Chapter 17 Taste and Inspiration
Section 17.1 to 17.4
Summarize the areas that need to be optimized:
For comments: 1. Inappropriate information, such as modifying history; 2. Obsolete comments, i.e. comments that are out of date, irrelevant or incorrect; 3. Redundant comments, such as Javadoc with nothing but the function signature; 4. Commented out code blocks, bad comments that don’t know what they mean;
For functions: 1. As few parameters as possible, no more than 3; 2. 2. Avoid output parameters. Use return values or parameter object status instead. 3. Avoid identifying parameters, such as Boolean parameters. 4. Remove uncalled methods.
General problems: 1. Removing duplicate code blocks; 2. Ensure the correct execution of boundary code; 3. Correct level of abstraction; 4. Polymorphism replaces multiple judgment operations; 5. Name clearly expresses intention; , etc.
Section 17.5 to 17.9
In Java: 1. Use wildcards to avoid long import lists, such as import package. 2. Avoid inheriting constants from the lowest inheritance system; 3. Use appropriate enumerations instead of constants;
Name: 1. Use accurate descriptive name and unified specification; 2. Avoid ambiguous names and don’t be afraid to use longer names;
Testing: 1. Test case first, one step before production code; 2. Perfect boundary test; 3. Tests that don’t ignore small details; 4. The test speed should be fast enough; 5. Conduct more comprehensive tests for failed test cases;
Appendix A Concurrent Programming
A.1 – A.2
How to effectively improve server throughput in high concurrency network communication performance testing depends on how the server processes I/O, which can be achieved by creating as many threads as possible, but also by limiting the number of threads that the JVM allows. If this is due to CPU limitations, it can only be solved by upgrading the hardware. Common processing areas: I/O- using sockets, linking to databases, waiting for virtual memory swap, etc. Processor — numerical calculation, regular expression processing, garbage collection, etc. In addition, when writing server code, you need to pay attention to the division of each function responsibility, avoid piling together.
Under high concurrency also need to pay attention to the problem code execution path, unlocked code in a multithreaded access conditions, tend to get the results of the previous best, largely normal Java statements generated after the bytecode is often a Java corresponding to many other bytecode, concurrent programming, threads access is possible in each byte code under the steps.
A.3 Understand class libraries – A.4 Dependencies between methods can break concurrent code
Java provides a number of concurrent libraries, such as the Executor thread pool framework, which can be used directly to support simple concurrent operations. 2.Synchronized locking and CAS can largely ensure the thread safety of concurrent resource access, and CAS is more efficient than Synchronized in most cases. 3. Some class operations are not thread-safe, such as database connections, partial container operations, etc. 4. Multiple single-method thread-safe operations are not necessarily safe in combination, and dependencies between methods can break concurrent code, as in the following two examples:
Case 1:
There are two thread-safe operations for a HashTable map: public Synchronize Boolean containsKey(key); public synchronize void put(key, value); But when combined to implement operations that allow adding a map if it does not exist:if(! map.containsKey(key)) { map.put(key, valie); }Copy the code
There’s no problem with single-thread execution, but with high concurrency there’s a chance that thread 1 passes containsKey, and then there’s a CPU time slice, and then thread 1 waits, and then thread 2 passes containsKey, and then both thread 1 and thread 2 queue up to put, Not as expected.
Solutions:
The lock can be consolidated into an API operation: public Synchronize void putIfAbsent(key, value) {if(! map.containsKey(key)) { map.put(key, valie); }} or manually lock a code block before being called: synchronize (map) {if(!map.containsKey(key)) {
map.put(key, valie);
}
}
Copy the code
Case 2:
public class IntegerIterator {
Integer value = 0;
public synchronize boolean hasNext() {if (value < 100){
return true;
}
return false;
}
public Integer Integer next() {
if(value == 100) {
throw new xxxException();
}
returnvalue++; }}Copy the code
Use:
IntegerIterator iterator = new IntegerIterator();
while(iterator.hasNext()) {
int value = iterator.next();
// ...
}
Copy the code
This example is also prone to problems with high concurrency, but it can take a long time to occur and is difficult to track because value = 99 is needed to reboundary, and multiple threads pass hasNext judgment at the same time and then throw the corresponding exception. The solution is similar to case 1
A.5 – A.10
The contention for limited resources in multithreading is easy to cause deadlock, mutual exclusion, circular waiting and other abnormal situations. In practice, we can optimize the code to reduce the occurrence of this kind of situation, such as meeting the thread resource preemptive mechanism, planning the order of each thread resource acquisition, and so on. Because concurrency problems are difficult to detect and simulate, try borrowing tools such as IBM ConTest for testing