Skip to content

高质量编程-编码规范

芯笑

前言:

实际应用场景千变万化,各种语言的特性和语法各不相同,但是高质量编程遵循的原则是相通的 Dave Cheney

编写的代码能够达到正确可靠,简洁清晰的目标可称之为高质量代码。应该具备一下三点。

代码格式:

代码格式化一般coder不需要特别注意。

在Java中,可以使用插件Save Actions,在保存或者切换窗口时便会自动格式化。

Go语言官方提供了gofmt自动格式化工具,在常见的ide中均可配置。同时也可以安装goimports,实际上等于gofmt加上依赖包管理,自动增删依赖包的管理。goimports安装及配置教程

注释:

Good code has lots of comments,bad code requires lots of comments Dave Thomas and Andrew Hunt

解释代码作用

说明公共符号,比如提供对外的函数注释描述它的功能和用途,只有在函数的功能简单而明显时才可以省略这些注释,但同时也要避免过于啰嗦,通过方法名就很容易知道作用的就没有必要加上。

// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

解释代码如和做的

对代码中复杂的,并不明显的逻辑进行说明,适合注释实现过程。

下面这段代码中。显而易见的就可以看出流程,就可以不必使用注释。

//Process every element in the list
for e := range elements{
    process(e)
}

解释代码实现原因

解释代码的外部因素,提供额外的上下文。比如下面代码中的shouldRedirect = false,如果没有注释,便无法搞清楚为什么设置值为false。

if ireq.GetBody == nil && ireq.outgoingLength() != 0 {
	// We had a request body, and 307/308 require
	// re-sending it, but GetBody is not defined. So just
	// return this response to the user instead of an
	// error, like we did in Go 1.7 and earlier.
	shouldRedirect = false
}

解释代码什么情况下会出错

注释应该提醒使用者代码中存在的一些潜在的限制条件或者无法处理的情况。

// parseTimeZone parses a time zone string and returns its length. Time zones
// are human-generated and unpredictable. We can't do precise error checking.
// On the other hand, for a correct parse there must be a time zone at the
// beginning of the string, so it's almost always true that there's one
// there. We look at the beginning of the string for a run of upper-case letters.
// If there are more than 5, it's an error.
// If there are 4 or 5 and the last is a T, it's a time zone.
// If there are 3, it's a time zone.
// Otherwise, other than special cases, it's not a time zone.
// GMT is special because it can have an hour offset.
func parseTimeZone(value string) (length int, ok bool)

公共符号始终要注释

1.包中声明的每一个公共符号:变量、常量、函数以及结构体

2.任何进既不明显也不简短的公共功能必须注释

3.无论长度or复杂程度如和,对库中的函数必须注释

4.不需要注释实现接口的方法

命名规范

Good naming is like a good joke.If you have to explain it,it’s not funny. Dave Cheney

variable:

在我们写for循环的时候,对于变量i仅在for循环的内部使用,就可以适当的缩写。

//Bad
for index := 1;index < 10;index++{}
//Good
for i :=i;i < 10;i++{}

但是对于有特殊含义的,就必须要写清楚。如下代码,将deadline换为t就减少了变量名所携带的信息。

func (c *Client) send (req *Request,deadLine time.Time)
//Bad
func (c *Client) send (req *Request,t time.Time)

function:

思考:以下哪种命名更好(在http包下)

//1.
func Serve() error
//2.func DerveHTTP() error

package:

控制流程:

避免嵌套:

常见的就是if else语句,当两个分支都有return语句时,根据if的执行特点,就可以去掉多余的else

//Bad
if foo {
   return x
}else {
   return nil
}
//Good
if foo {
   return x
}
return nil

保持正常代码路径为最小缩进:

先给出代码,可以观察区别。哪一种更直观不言而喻了吧

//Bad
func OneFunc() error {
	if err != doSomething()
	if err == nil{
		err := doSomethingElse()
		if err == nil {
			return nil
		}
		return err
	}
	return err
}
//Good
func OneFunc() error {
	if err := doSomething();err != nil {
		return err
	}
	if err := doSomethingElse();err != nil {
		return err
	}
	return nil
}

错误及异常处理

简单错误:

// 一个例子
func defaultCheckRedirect(req *Request, via []*Request) error {
    if len(via) >= 10 {
        // 使用errors.New
        return errors.New("stopped after 10 redirects.")
    }
    return nil  // 去掉不必要的else
}

错误判定:

判定一个错误是否为特定错误,用errors.ls,不同于使用==,该方法可以判定错误链上的所有错误是否含有特定的错误

// 一个例子
data, err = lockedfile.Read(targ)
if errors.Is(err, fs.ErrNotExist) {
    return []byte{}, nil
}
return data, err

在错误链上获取特定种类的错误,使用errors.As

// 一个例子
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 {
        fmt.Println(err)
    }
}

Warp和Unwarp:

好处是每一层调用方法可以补充自己对应的上下文,方便跟踪排查问题。

// 一个例子
list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
if err != nil {
    return fmt.Errorf("reading srcfiles list: %w", err)
}

panic:

panic比错误更严重,不建议在业务代码中使用,就不详细展开。

recover:

如果需要更多的上下文信息可以在recover后在log中记录当前的调用栈

多defer语句输出(补)

func main()  {
	if true {
		defer fmt.Println("1")
	}else {
		defer fmt.Println("2")
	}
	defer fmt.Println("3")
}

输出31

引用:

部分代码来自:

https://github.com/golang/go/blob/master/

永不满足,未完待续…

分享
上一篇
高质量编程-性能调优
下一篇
Go语言初探