preface

Recently, I was reading wang Zhengda’s design pattern beauty, which talked about code specifications, which happened to be some points I usually pay attention to, so I made a summary here.

The following will be discussed from the four dimensions of naming, annotation, code style and programming skills

named

It can sometimes be difficult to find the right name, so let’s see if there are some tips that can help us

1, named length selection

In terms of naming length, as far as meaning is concerned, the shorter the name is, of course, the better. In most cases, short names are not as meaningful as long ones, and many books don’t recommend abbreviations.

Although long names can contain more information and convey intent more accurately and intuitively, long names for functions and variables can make for long statements. When code column lengths are limited, it is common for a statement to be split into two lines, which can actually affect code readability.

So sometimes we can use abbreviations for short names

When is it appropriate to use short naming

1. Some defaults are well known and can be abbreviated, such as SEC for second, STR for string, num for number, doc for document, and so on

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

2. Use context to simplify naming

Here’s a chestnut

type User struct {
	UserName      string
	UserAge       string
	UserAvatarUrl string
}
Copy the code

So this struct, for example, we already know that this is a struct for User information. There is no need to prefix the user name,age

After the draft

type User struct {
	Name      string
	Age       string
	AvatarUrl string
}
Copy the code

Of course, this is also useful in database design

3. Name readable and searchable

“Readable” means that you should not name yourself after words that are particularly obscure and difficult to pronounce.

When we write code in ides, we often use “keyword association” methods to auto-complete and search. For example, type an object “.get “and expect the IDE to return all get-starting methods for that object. Another example is to search for array-related functions and methods in the JDK by typing “Array” into the IDE search box. Therefore, when we name, it is best to conform to the naming convention of the whole project. Everyone uses “selectXXX” for queries, so don’t use “queryXXX”; Everyone uses “insertXXX” to mean that you insert a data, you do not need “addXXX”, unified protocol is very important, can reduce a lot of unnecessary trouble.

4. How to name the interface

There are two common ways to name interfaces. One is to prefix “I” to indicate an Interface. For example IUserService, the corresponding implementation is named UserService. The other is unprefixed, such as UserService, and the corresponding implementation suffixes “Impl”, such as UserServiceImpl.

annotation

When we accept a project, we often make fun of the old project annotation is not good, the documentation is not complete, so if the annotation is left to us to write, what is a good annotation

Sometimes we’ll see in books and blogs that if you have a good name you don’t need a comment, that’s the code you need a comment, and if you need a comment, that’s the code you don’t have a good name for, so you need to work on the naming.

This is a bit extreme, naming is good, after all, length constraints, can not be enough detailed, and in this case, comments are a good complement.

1. What should I write in a comment

We write numerical comments to make our code easier to understand. Comments generally cover three aspects: what to do, why to do, and how to do it.

This is the comment in Sync. Map in Golang, and is also a comment in terms of what to do, why, and how to do it

// 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
}
Copy the code

Some people argue that comments are meant to provide additional information that the code doesn’t have, so don’t write “what” or “how” — both of these aspects can be expressed in the code. Just write a clear “why” that indicates the design intent of the code.

However, there are several possible advantages to having comments

1. Comments carry more information than code

If functions and variables are well named, they really do not need to be explained in comments. However, for the structure, contains more information, a simple name is not comprehensive enough. At this point, it makes sense to write “what to do” in the comments.

2, notes play a summary role, the role of the document

In comments, we can write some summary statements about specific code implementation ideas, special case statements. This makes it easier for the reader to read the code by making comments that give the reader an idea of how the code is being implemented.

3. Some concluding comments make the code structure clearer

For logically complex code or long functions that are difficult to extract and break down into smaller function calls, we can use summative comments to make the code structure clearer and more organized.

2. The more notes, the better

Annotations themselves have maintenance costs, so more is not always better. Structs and functions must be commented, and written as comprehensive and detailed as possible, while the internal comments of functions should be relatively few, generally rely on good naming, refining functions, explanatory variables, and summary comments to improve code readability.

Code style.

1. How big is the function

Too much or too little code for functions is not good

Too much:

With thousands of lines of logic for a method and hundreds of lines for a function, it’s easy to look at the back of the code and forget the front

Too little:

When the total amount of code is the same, the number of divided functions will increase correspondingly, and the call relationship will become more complex. When reading a certain code logic, it is necessary to frequently jump between N methods or N functions, and the reading experience is not good.

How many are the most appropriate?

It’s hard to give an exact value, but some places say no more than the vertical height of a display. For example, on my computer, the maximum number of lines of code for a function to be fully displayed in the IDE is 50.

2. How long is best for a line of code

There is no perfect criterion for this, because different languages have different requirements

There is, of course, a general rule: one line of code should not exceed the width of the IDE display.

It’s too long to read the code

3. Use empty lines to divide unit blocks

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

// 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()
}
Copy the code

The lock code here is whitespace

Of course, there are places where the first line is not whitespace, and that’s also true, so a blank line at the head of a function is useless.

Programming skills

1. Break your code up into smaller chunks

Good at abstracting the modules in the code, to facilitate our reading

Therefore, we need to be modular and abstract thinking, good at refining large pieces of complex logic into small methods or functions, shielding the details, so that readers do not get lost in the details, which can greatly improve the readability of the code. However, it is only when the code logic is complex that we actually recommend distilling it.

2, avoid functions or methods with too many parameters

A function with three or four arguments is acceptable, but a function with five or more arguments is considered too many, which affects the readability of the code and makes it difficult to use.

There are two ways to handle this situation

1. Consider whether a function has a single responsibility and whether it can be divided into multiple functions to reduce the number of arguments.

2. Encapsulate function arguments as objects.

chestnuts

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

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

func updateBookshelf(input *UpdateBookshelfInput) {
	// ...
}
Copy the code

3. Do not use function arguments to control logic

Do not use Boolean identifiers in functions to control internal logic. If true, use one piece of logic and if false, use another. This clearly violates the single responsibility principle and interface isolation principle.

You can split it into two functions and call them separately

chestnuts

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

func sendNewUserVip(userId string) {
	// ...
}
Copy the code

However, if the function is private and has limited scope, or if the split function is often called at the same time, we can consider not splitting at our discretion.

4, function design to a single responsibility

For the design of functions, we should try to avoid designing a large and complete function. We can split the function according to different function points.

For example, let’s check some of our user attributes, and of course the check is omitted to check if they are null

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
}
Copy the code

It’s 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
}
Copy the code

5. Remove too deep nesting levels

Deep nesting of code is often caused by excessive nesting of if-else, switch-case, and for loops. Too deep nesting, in addition to the code is not easy to understand, because the code is indented many times, resulting in more than one line of nested statements and broken into two lines, affecting the cleanliness of the code.

There are roughly four directions to consider when modifying nested code

Here’s an example:

There are some things in this code that don’t quite fit, so let’s look at them 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
}
Copy the code

1. Remove redundant if or else statements

Modified 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
}
Copy the code

2. Exit nesting early using the continue, break, and return keywords provided by the programming language

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
}
Copy the code

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
}
Copy the code

4. Reduce nesting by encapsulating part of the nested logic as function calls

Learn to use explanatory variables

There are two common ways to use explanatory variables to improve code readability

1. Constants replace magic numbers

func CalculateCircularArea(radius float64) float64 {

	return 3.1415 * radius * radius
}

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

	return PI * radius * radius
}
Copy the code

2. Use explanatory variables to interpret complex expressions

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

/ / modified
isBeforeRegisterTime := appOnlineTime.Before(userId.Timestamp())
if isBeforeRegisterTime {
	appOnlineTime = userId.Timestamp()
}
Copy the code

reference

The beauty of design patterns time.geekbang.org/column/intr 】…