- Simplicity : eliminate unnecessary expressions
- Readability : easy to understand
- Productivity : easy to cooperate with teammates
Two useful packages.
- Good code has lots of comments, bad code requires lots of comments.
- Both
/* */
and//
are supported. - Should explain what the code does, why it works, why it is needed and what goes wrong.
- Always add comments to the public symbols like
Read
.
/*
A Builder is used to efficiently build a string using Write methods.
It minimizes memory copying. The zero value is ready to use.
Do not copy a non-zero Builder.
*/
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
For more exmaples please refer to https://juejin.cn/post/7189519144897740861.
- Acronyms should be all uppercase, eg
ServeHTTP
. but when it is at the beginning of the variable and does not need to be exported, could be all lowercase, egxmlHTTPRequest
. - The farther a variable is from where it is used, the more contextual information its name needs to carry, eg
deadline
instead oft
.
- function name should be as short as possible and it does not need to carry the information of the package.
- Type information can be omitted when the return type's name is consistent with package name. eg,
package http
// bad
func ServeHTTP(I net.Listener, handler Handler) error
// good
func Serve(I net.Listener, handler Handler) error
- Type information should be added to the function name when the package name is not consistent with the return type's name of the function.
- Consists of lowercase letters only.
- Be short and contain some contextual information.
- Do not use the same name as the standard library.
- Better not to use commonly used variable names like
bufio
instead ofbuf
. - Better to use singular instead of plural like
encoding
instead ofencodings
. - Use abbreviations sparingly.
- Avoid nesting
// bad
if foo {
return x
} else {
return nil
}
// good
if foo {
return x
}
return nil
- Keep normal code paths with minimal indentation
// bad
func OneFunc() error {
err := doSomething()
if err == nil{
err := doAnotherThing()
if err == nil{
return nil
}
return err
}
return err
}
// good
func OneFunc() error {
if err := doSomething(); err != nil{
return err
}
if err := doAnotherThing(); err != nil{
return err
}
return nil
}
- For simple errors that occur few times, use
errors.New()
. - To format errors can use
fmt.Errorf
. - For complicated errors, use Wrap and Unnwrap.
list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
if err != nil {
return fmt.Errorf("reading srcfiles list: %w", err)
}
- Use
errors.Is
instead of==
to determine whether an error is a specific error. Useerrors.As
can get the specific kind of error.
data, err = lockedfile.Read(targ)
if errors.Is(err, fs.ErrNotExist) {
return []byte{}, nil
}else{
// do something
}
if _, err := os.Open("non-existing"); err != nil {
var pathError *fs.PathError
if errors.As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
} else {
// do something
}
}
panic/recover
is not recommended unless it is really a huge problem.recover
should be used indefer
and takes effect only on the current goroutine.
Note that defer
is actually a stack.
func main(){
defer fmt.Printf("1")
defer fmt.Printf("2")
defer fmt.Printf("3")
}
// output : 321
- Better to provide size when
make
a slice. - Better to use
copy
to replace re-slice.
- Better to provide size when
make
a map.
- Use
strings.Builder
to replace+
orbytes.Buffer
. - Better to provide size using
strings.Builder.Grow(size)
.
- Use an empty struct as a placeholder.
m := make(map[int]struct{})
for i:= 0; i < n; i++{
m[i] = struct{}{}
}
map
[key](Empty struct) =set
- Atomic package provides atomic operations which can replace lock with a higher efficiency.
var x int32 = 100
func f_add() {
atomic.AddInt32(&x, 1)
}
func f_sub() {
atomic.AddInt32(&x, -1)
}
func main() {
for i := 0; i < 100; i++ {
f_add()
f_sub()
}
fmt.Printf("x: %v\n", x) // 100
}
- For non-numeric operations, can use
atomic.Value
, which can carry aninterface{}
.