Go语言“正统”在中国?这6点教你写好Go代码!
👉导读
👉目录
01
1.1 单一职责原则
type Trade struct {TradeID intSymbol stringQuantity float64Price float64}type TradeRepository struct {db *sql.DB}func (tr *TradeRepository) Save(trade *Trade) error {_, err := tr.db.Exec("INSERT INTO trades (trade_id, symbol, quantity, price) VALUES (?, ?, ?, ?)", trade.TradeID, trade.Symbol, trade.Quantity, trade.Price)if err != nil {return err}return nil}type TradeValidator struct {}func (tv *TradeValidator) Validate(trade *Trade) error {if trade.Quantity <= 0 {return errors.New("Trade quantity must be greater than zero")}if trade.Price <= 0 {return errors.New("Trade price must be greater than zero")}return nil}
1.2 开闭原则
type TradeProcessor interface {Process(trade *Trade) error}type FutureTradeProcessor struct {}func (ftp *FutureTradeProcessor) Process(trade *Trade) error {// process future tradereturn nil}type OptionTradeProcessor struct {}func (otp *OptionTradeProcessor) Process(trade *Trade) error {// process option tradereturn nil}
1.3 里氏替换原则
type Trade interface {Process() error}type FutureTrade struct {Trade}func (ft *FutureTrade) Process() error {// process future tradereturn nil}
1.4 接口隔离原则
type Trade interface {Process() error}type OptionTrade interface {CalculateImpliedVolatility() error}type FutureTrade struct {Trade}func (ft *FutureTrade) Process() error {// process future tradereturn nil}type OptionTrade struct {Trade}func (ot *OptionTrade) Process() error {// process option tradereturn nil}func (ot *OptionTrade) CalculateImpliedVolatility() error {// calculate implied volatilityreturn nil}
1.5 依赖倒置原则
type TradeService interface {Save(trade *Trade) error}type TradeProcessor struct {tradeService TradeService}func (tp *TradeProcessor) Process(trade *Trade) error {err := tp.tradeService.Save(trade)if err != nil {return err}// process tradereturn nil}type SqlServerTradeRepository struct {db *sql.DB}func (str *SqlServerTradeRepository) Save(trade *Trade) error {_, err := str.db.Exec("INSERT INTO trades (trade_id, symbol, quantity, price) VALUES (?, ?, ?, ?)", trade.TradeID, trade.Symbol, trade.Quantity, trade.Price)if err != nil {return err}return nil}type MongoDbTradeRepository struct {session *mgo.Session}func (mdtr *MongoDbTradeRepository) Save(trade *Trade) error {collection := mdtr.session.DB("trades").C("trade")err := collection.Insert(trade)if err != nil {return err}return nil}
02
2.1 单例设计模式
var once sync.Oncevar instance interface{}func GetInstance() *singleton {once.Do(func() {instance = &singleton{}})return instance}
2.2 工厂模式
package main// Factory interfacetype simpleInterest struct {principal intrateOfInterest inttime int}type compoundInterest struct {principal intrateOfInterest inttime int}// Interfacetype InterestCalculator interface {Calculate()}func (si *simpleInterest) Calculate() {// logic to calculate simple interest}func (si *compoundInterest) Calculate() {// logic to calculate compound interest}func NewCalculator(kind string) InterestCalculator {if kind == "simple" {return &simpleInterest{}}return &compoundInterest{}}func Factory_Interface() {siCalculator := NewCalculator("simple")siCalculator.Calculate() // Invokes simple interest calculation logicciCalculator := NewCalculator("compound")ciCalculator.Calculate() // Invokes compound interest calculation logic}
2.3 代理模式
其他对象提供一种代理以控制对这个对象的访问。类图:
// zkClient backend request struct.type zkClient struct {ServiceName stringClient client.Clientopts []client.Option}// NewClientProxy create new zookeeper backend request proxy,// required parameter zookeeper name service: trpc.zookeeper.xxx.xxx.func NewClientProxy(name string, opts ...client.Option) Client {c := &zkClient{ServiceName: name,Client: client.DefaultClient,opts: opts,}c.opts = append(c.opts, client.WithProtocol("zookeeper"), client.WithDisableServiceRouter())return c}// Get execute zookeeper get command.func (c *zkClient) Get(ctx context.Context, path string) ([]byte, *zk.Stat, error) {req := &Request{Path: path,Op: OpGet{},}rsp := &Response{}ctx, msg := codec.WithCloneMessage(ctx)defer codec.PutBackMessage(msg)msg.WithClientRPCName(fmt.Sprintf("/%s/Get", c.ServiceName))msg.WithCalleeServiceName(c.ServiceName)msg.WithSerializationType(-1) // non-serializationmsg.WithClientReqHead(req)msg.WithClientRspHead(rsp)if err := c.Client.Invoke(ctx, req, rsp, c.opts...); err != nil {return nil, nil, err}return rsp.Data, rsp.Stat, nil}
2.4 观察者模式
package mainimport "fmt"type Item struct {observerList []Observername stringinStock bool}func newItem(name string) *Item {return &Item{name: name,}}func (i *Item) updateAvailability() {fmt.Printf("Item %s is now in stock\n", i.name)i.inStock = truei.notifyAll()}func (i *Item) register(o Observer) {i.observerList = append(i.observerList, o)}func (i *Item) notifyAll() {for _, observer := range i.observerList {observer.update(i.name)}}
03
3.1 声明
错误使用 util 命名的包,不容易正常识别功能的用途,导致 util 包越来越臃肿。 slice 的创建使用 var arr []int,初始化切片使用 var s []string 而不是 s := make([]string),初始化,如果确定大小建议使用 make 初始化。 import . 只能用于测试文件,且必须是为了解决循环依赖,才能使用。
3.2 函数定义
不要通过参数返回数据。
尽量用 error 表示执行是否成功,而不是用 bool 或者 int。
多使用指针接收器,尽量避免使用值接收器。
除0、1、“”不要使用字面量。
if else 通常可以简写为 if return。
尽量将 if 和变量定义应该放在一行。
err := r.updateByAttaIDs(fMd5OneTime, sMd5OneTime)if err != nil {
不要添加没必要的空行。
使用 == "" 判断字符串是否为空。
通过 %v 打印错误信息,%v 建议加:。
Fail Fast 原则,如果出现失败应该立即返回 error,如果继续处理,则属于特殊情况需要添加注释。
array 和 map 的变量命名时,添加后缀 s。 _, xxx for xxxs 一般要求 xxx 相同。 正则表达式变量名以 RE 结尾。 不要用注释删除代码。 TODO 格式:TODO(rtx_name): 什么时间/什么时机,如何解决。19.导出的函数/变量的职责必须与包&文件职责高度一致。
时间类型尽量使用内置定义,如,time.Second,不要使用 int。
建议所有不对外开源的工程的 module name 使用 xxxxxx/group/repo ,方便他人直接引用。
应用服务接口建议有 README.md。
3.6 安全问题
代码中是否存在 token 密码是否加密。
日志中是否输出用户敏感信息。
PB 是否开启 validation。
字符串占位符,如果输入数据来自外部,建议使用 %q 进行安全转义。
04
4.1 值拷贝
package mainimport ("fmt")func main() {x := [3]int{1, 2, 3}func(arr [3]int) {arr[0] = 7fmt.Println(arr)}(x)fmt.Println(x) // 1 2 3}
4.2 管道操作
管道操作,谨记口诀:“读关闭空值,读写空阻塞,写关闭异常,关闭空、关闭已关闭异常”。个人建议管道除非在一些异步处理的场景建议使用外,其它场景不建议过多使用,有可能会影响代码的可读性。
检测管道关闭示例:
func IsClosed(ch <-chan T) bool {select {case <-ch:return truedefault:}return false}
4.3 匿名函数变量捕获
type A struct {id int}func main() {channel := make(chan A, 5)var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()for a := range channel {wg.Add(1)go func() {defer wg.Done()fmt.Println(a.id) // 输出的数字是无法确定的,输出依赖具体的调度时机。// go vet 提示 loop variable a captured by func literal}()}}()for i := 0; i < 10; i++ {channel <- A{id:i}}close(channel)wg.Wait()}
4.4 defer 执行流程
package mainimport ("fmt")func main() {fmt.Println("c return:", *(c())) // 打印结果为 c return: 2}func c() *int {var i intdefer func() {i++fmt.Println("c defer2:", i) // 打印结果为 c defer: 2}()defer func() {i++fmt.Println("c defer1:", i) // 打印结果为 c defer: 1}()return &i}
package mainimport "fmt"func main() {fmt.Println(test())}func test() (result int) {defer func() {result++}()return 0 // result = 0// result++}
4.5 recover 正确执行方式
// 无效func main() {recover()panic(1)}// 无效func main() {defer recover()panic(1)}// 无效func main() {defer func() {func() { recover() }()}()panic(1)}// 有效func main() {defer func() {recover()}()panic(1)}
4.6 sync.Mutex 错误传递
package mainimport ("fmt""sync""time")type Container struct {sync.Mutex // <-- Added a mutexcounters map[string]int}func (c Container) inc(name string) {c.Lock() // <-- Added locking of the mutexdefer c.Unlock()c.counters[name]++}func main() {c := Container{counters: map[string]int{"a": 0, "b": 0}}doIncrement := func(name string, n int) {for i := 0; i < n; i++ {c.inc(name)}}go doIncrement("a", 100000)go doIncrement("a", 100000)// Wait a bit for the goroutines to finishtime.Sleep(300 * time.Millisecond)fmt.Println(c.counters)}
05
5.1 go vet
asmdecl report mismatches between assembly files and Go declarationsassign check for useless assignmentsatomic check for common mistakes using the sync/atomic packagebools check for common mistakes involving boolean operatorsbuildtag check that +build tags are well-formed and correctly locatedcgocall detect some violations of the cgo pointer passing rulescomposites check for unkeyed composite literalscopylocks check for locks erroneously passed by valuehttpresponse check for mistakes using HTTP responsesloopclosure check references to loop variables from within nested functionslostcancel check cancel func returned by context.WithCancel is callednilfunc check for useless comparisons between functions and nilprintf check consistency of Printf format strings and argumentsshift check for shifts that equal or exceed the width of the integerslog check for incorrect arguments to log/slog functionsstdmethods check signature of methods of well-known interfacesstructtag check that struct field tags conform to reflect.StructTag.Gettests check for common mistaken usages of tests and examplesunmarshal report passing non-pointer or non-interface values to unmarshalunreachable check for unreachable codeunsafeptr check for invalid conversions of uintptr to unsafe.Pointerunusedresult check for unused results of calls to some functions
5.2 goimports
5.3 gofmt
06
CR 的目的是让我们的代码更具有规范、排查出错误、代码设计的统一,从而降低不好代码所带来的误解、重复、错误等问题。无论是 contributor 或者是 codereviewer,都有职责去执行好 CR 的每个环节,这样我们才能写出更好更优秀的代码。
前置工作
发起人自己先做一次 review。
做好单测、自测,不要依赖 CodeReview 机制排查问题。
是否有现成的依赖包、工具、复用的代码使用。
仓库配置相应的 CodeCC、单测覆盖率检测流水线。
发起 Codereview
准备好本次 CR 的背景知识,如 TAPD、设计文档等。
COMMIT 里详细介绍本次改动的目的。
控制规模,一次提交最好能在30分钟内 review 完成。
CodeReviewer
友好语气。
认真提出合理的建议与改进方案,是对代码编写者的尊重。
避免纯主观判断。
不要高高在上。
不要吝啬称赞。
适度容忍、没有必要必须完美。
无重要的设计、bug 可以先 approve,后续有时间修改。
冲突解决
寻求第三人评估。
组内讨论。
07