Some common code specifications

Keywords: Design Pattern

Summary of some common code specifications

preface

Recently, I was looking at the beauty of the design pattern of boss Wang Zheng. It talked about the code specification, which is also some points I usually pay more attention to. Here is a summary.

The following will discuss from four dimensions: naming, annotation, code style and programming skills

name

It's really difficult to choose an appropriate name. Let's see some tips that can help us name

1. Named length selection

As for the length of naming, the shorter the naming, the better, if it can express the meaning. In most cases, short names can express meaning better than long names, and abbreviations are not recommended in many books.

Although long naming can contain more information and express the intention more accurately and intuitively, if the naming of functions and variables is very long, the statements composed of them will be very long. When the length of the code column is limited, a statement is often divided into two lines, which will actually affect the readability of the code.

So sometimes we can use abbreviated short names appropriately

In what scenario is short naming appropriate

1. For some defaults, we all know that abbreviations can be used. For example, sec stands for second, str stands for string, num stands for number, doc stands for document, and so on

2. For variables with small scope, we can use relatively short names, such as temporary variables in some functions. Correspondingly, for variables with large scope, we recommend using long names

2. Simplify naming with context

Look at a chestnut

type User struct {
	UserName      string
	UserAge       string
	UserAvatarUrl string
}

For example, we already know that this is a struct of user information. There is no need to prefix the user with the user's name and age

Revised

type User struct {
	Name      string
	Age       string
	AvatarUrl string
}

Of course, this is also useful in database design

3. Naming should be readable and searchable

"Readable" means not to name some English words that are particularly rare and difficult to pronounce.

When we write code in the IDE, we often use the "keyword Association" method to automatically complete and search. For example, type an object ". Get", and you want the IDE to return all the methods starting with get of this object. For another example, enter "Array" in the IDE search box to search for Array related functions and methods in the JDK. Therefore, when naming, we'd better conform to the naming habits of the whole project. Everyone uses "selectXXX" to express query, so don't use "queryXXX"; Everyone uses "insertXXX" to insert a piece of data. You don't have to use "addXXX". Unified protocol is very important and can reduce a lot of unnecessary trouble.

4. How to name an interface

There are two common ways to name interfaces. One is prefixed with "I", which means an Interface. For example, IUserService, the corresponding implementation is named UserService. The other is not prefixed, such as UserService, and the corresponding implementation is suffixed with "Impl", such as UserServiceImpl.

notes

When we accept a project, we often make complaints about the old projects, and the documents are incomplete. If the annotations are let us write, what notes are good annotations?

Sometimes we see in books or some blogs that if a good name does not need comments, that is, the code is comments. If comments are needed, that is, the code is not named well, and we need to work hard in naming.

This is a bit extreme. No matter how well named, there is a length limit after all, and it is impossible to be detailed enough. At this time, comments are a good supplement.

1. What should I write

We write several comments to make the code easier to understand. Comments generally include three aspects: what to do, why and how to do.

This is the annotation in sync.map in golang. It is also annotated from what to do, why and how to do

// Map is like a Go map[interface{}]interface{} but is safe for concurrent use
// by multiple goroutines without additional locking or coordination.
// Loads, stores, and deletes run in amortized constant time.
//
// The Map type is specialized. Most code should use a plain Go map instead,
// with separate locking or coordination, for better type safety and to make it
// easier to maintain other invariants along with the map content.
//
// The Map type is optimized for two common use cases: (1) when the entry for a given
// key is only ever written once but read many times, as in caches that only grow,
// or (2) when multiple goroutines read, write, and overwrite entries for disjoint
// sets of keys. In these two cases, use of a Map may significantly reduce lock
// contention compared to a Go map paired with a separate Mutex or RWMutex.
//
// The zero Map is empty and ready for use. A Map must not be copied after first use.
type Map struct {
	mu Mutex
	read atomic.Value // readOnly
	dirty map[interface{}]*entry
	misses int
}

Some people think that annotation is to provide additional information that the code does not have, so don't write "what to do and how to do". These two aspects can be reflected in the code. Just write "why" clearly to indicate the design intention of the code.

However, writing notes may have the following advantages

1. Comments carry more information than code

If functions and variables are well named, they really don't have to explain what they do in comments. However, for a structure, it contains more information, and a simple name is not comprehensive and detailed enough. At this time, it is reasonable to write "what to do" in the notes.

2. Notes 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.

3. Some concluding comments can make the code structure clearer

For code with complex logic or long functions, if it is difficult to refine and split them 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

Notes themselves have a certain maintenance cost, so the more the better. Comments must be written for structures and functions, and they should be written as comprehensive and detailed as possible, while there should be relatively few comments inside functions. Generally, it depends on good naming, refining functions, explanatory variables and summary Comments to improve code readability.

Code style

1. How big is the function

It's not good to have too much or too little code for functions

That's too much:

There are thousands of lines in a method and hundreds of lines in a function. The logic is too complicated. When reading the code, it's easy to forget the back and forget the front

It's too short:

When the total amount of code is the same, the divided functions will increase accordingly, and the call relationship will become more complex. When reading a code logic, you need to frequently jump between n methods or n functions, and the reading experience is not good.

How many are the most suitable?

However, it is difficult to give a specific value. In some places, it will be said that it should not exceed the vertical height of a display screen. For example, on my computer, if you want the code of a function to be completely displayed in the IDE, the maximum number of lines of code cannot exceed 50.

2. How long is the most appropriate line of code

There is no complete criterion for this. After all, different languages have different requirements

Of course, there is a general principle: the maximum length of a line of code cannot exceed the width of the IDE display.

It's too long to read the code

3. Make good use of empty lines to split unit blocks

In other words, it is not recommended to write down our code. There is no space left in a line of a function or method. Usually, according to different semantics, the content of a small module is finished and divided by blank spaces.

// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
	read, _ := m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}

	m.mu.Lock()
	// ...
	m.mu.Unlock()
}

The lock code here is blank with the above

Of course, some places will say that there is no space in the first line, which is also right. The empty line in the head of the function is useless.

Programming skills

1. Divide the code into smaller unit blocks

Be good at abstracting the modules in the code, which can facilitate our reading

Therefore, we should have modular and abstract thinking, be good at refining large pieces of complex logic into small methods or functions, shield the details, so that people who read the code will not be lost in the details, which can greatly improve the readability of the code. However, only when the code logic is more complex, we actually suggest refining the corresponding logic.

2. Avoid too many function or method arguments

It is acceptable when the function contains 3 or 4 parameters. When it is greater than or equal to 5, we feel that there are too many parameters, which will affect the readability of the code and is inconvenient to use.

There are two ways to deal with this situation

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

2. Encapsulate the parameters of a function into objects.

Chestnuts

func updateBookshelf(userId, deviceId string, platform, channel, step int) {
	// ...
}

// After modification
type UpdateBookshelfInput struct {
	UserId   string
	DeviceId string
	Step     int
	Platform int
	Channel  int
}

func updateBookshelf(input *UpdateBookshelfInput) {
	// ...
}

3. Do not use function parameters to control logic

Do not use Boolean identification parameters in functions to control internal logic. This logic is used when true and another logic is used when false. This obviously violates the principle of single responsibility and interface isolation.

Can be split into two functions called separately

Chestnuts

func sendVip(userId string, isNewUser bool) {
	// Is a new user
	if isNewUser {
		// ...
	} else {
		// ...
	}
}

// After modification
func sendVip(userId string) {
	// ...
}

func sendNewUserVip(userId string) {
	// ...
}

However, if the function is a private function with limited scope of influence, or the two functions after splitting are often called at the same time, we can consider not splitting as appropriate.

4. Function design should have a single responsibility

For function design, we should also try to have a single responsibility, avoid designing a large and complete function, and split the function according to different function points.

For example, let's check some of our user attributes. Of course, this check is omitted to judge whether it is empty

func validate(name, phone, email string) error {
	if name == "" {
		return errors.New("name is empty")
	}

	if phone == "" {
		return errors.New("phone is empty")
	}

	if email == "" {
		return errors.New("name is empty")
	}
	return nil
}

It has been modified

func validateName(name string) error {
	if name == "" {
		return errors.New("name is empty")
	}

	return nil
}

func validatePhone( phone string) error {
	if phone == "" {
		return errors.New("phone is empty")
	}

	return nil
}

func validateEmail(name, phone, email string) error {
	if email == "" {
		return errors.New("name is empty")
	}
	return nil
}

5. Remove too deep nesting levels

Too deep code nesting is often caused by excessive nesting of if else, switch case and for loops. If the code is too deeply nested, it is not easy to understand. If the code is too deeply nested, it is easy to fold the statements inside the nesting into two lines because the code is indented many times, which will affect the cleanliness of the code.

For the modification of nested code, there are probably four directions to consider

For example, chestnuts:

In this code, some places are inappropriate. Let's analyze it from the following four directions

func sum(sil []*User, age int) int {
	count := 0
	if len(sil) == 0 || age == 0 {
		return count
	} else {
		for _, item := range sil {
			if item.Age > age {
				count++
			}
		}
	}
	return count
}

1. Remove redundant if or else statements

Change to

func sum(sil []*User, age int) int {
	count := 0
	if len(sil) != 0 && age == 0 {
		for _, item := range sil {
			if item.Age > age {
				count++
			}
		}
	}
	return count
}

2. Use the continue, break and return keywords provided by the programming language to exit the nesting in advance

func sum(sil []*User, age int) int {
	count := 0
	if len(sil) != 0 && age == 0 {
		for _, item := range sil {
			if item.Age <= age {
				continue
			}
			count++
		}
	}
	return count
}

3. Adjust the execution order to reduce nesting

func sum(sil []*User, age int) int {
	count := 0
	if len(sil) == 0 || age == 0 {
		return count
	}
	for _, item := range sil {
		if item.Age <= age {
			continue
		}
		count++
	}
	return count
}

4. Encapsulate some nested logic into function calls to reduce nesting

6. Learn to use explanatory variables

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

1. Constants replace magic numbers

func CalculateCircularArea(radius float64) float64 {

	return 3.1415 * radius * radius
}

// After modification
const PI = 3.1415
func CalculateCircularArea(radius float64) float64 {

	return PI * radius * radius
}

2. Use explanatory variables to interpret complex expressions

if appOnlineTime.Before(userId.Timestamp()) {
	appOnlineTime = userId.Timestamp()
}

// After modification
isBeforeRegisterTime := appOnlineTime.Before(userId.Timestamp())
if isBeforeRegisterTime {
	appOnlineTime = userId.Timestamp()
}

reference resources

[beauty of design pattern] https://time.geekbang.org/column/intro/100039001
[summary of some common code specifications] https://boilingfrog.github.io/2021/11/03/ Summary of some common code specifications/

Posted by Donovan on Tue, 02 Nov 2021 21:53:23 -0700