Go代码优化

1、Go 语言的 if 语句允许在条件之前传递一个语句。

原始代码:

f, contains := factory[string(token)]
if contains {
    // Do something
}

优化:(稍微提高了代码的可读性)

if f, contains := factory[sToken]; contains {
    // Do something
}

2、错误处理

糟糕的实现中的错误处理有些糟糕。 我们可以找到一些潜在错误返回的情况,而第二个参数中的错误甚至没有被处理:

preprocessed, _ := preprocess(string)

优秀的实现处理了每一个可能的错误:

preprocessed, err := preprocess(bytes)
if err != nil {
  return Message{}, err
}

第一个错误是语法错误。根据 Go 的规范,错误字符串既不应该大写,也不应该以标点结束。

第二个错误是因为如果一个错误字符串是一个简单的常量(不需要格式化),使用 errors.New() 更为高效。

优秀的实现看起来是这样的:

if len(in) == 0 {
    return nil, errors.New("input is empty")
}

3、避免嵌套

糟糕的实现:

复制代码
a, err := f1()
if err == nil {
    b, err := f2()
    if err == nil {
        return b, nil
    } else {
        return nil, err
    }
} else {
    return nil, err
}
复制代码

相反,优秀的实现是一个扁平的表示方式,代码可读性提高:

复制代码
a, err := f1()
if err != nil {
    return nil, err
}
b, err := f2()
if err != nil {
    return nil, err
}
return b, nil
复制代码

4、Switch

糟糕实现的错误是在以下开关语句中忘记了默认情况:

复制代码
switch simpleToken.token {
case tokenTitle:
    msg.Title = value
case tokenAdep:
    msg.Adep = value
case tokenAltnz:
    msg.Alternate = value 
// Other cases
}
复制代码

如果开发者考虑了所有不同的情况,那么默认情况可以是可选的。然而,像以下示例中这样捕捉特定情况肯定更好:

复制代码
switch simpleToken.token {
case tokenTitle:
    msg.Title = value
case tokenAdep:
    msg.Adep = value
case tokenAltnz:
    msg.Alternate = value
// Other cases    
default:
    log.Errorf("unexpected token type %v", simpleToken.token)
    return Message{}, fmt.Errorf("unexpected token type %v", simpleToken.token)
}
复制代码

处理默认情况有助于在开发过程中尽快捕获开发人员可能产生的潜在错误。

5、常量管理

糟糕的代码是这样做的:

const (
    AdexpType = 0 // TODO constant
    IcaoType  = 1
)

良好的代码是基于 Go(优雅的)iota 的更优雅的解决方案:

const (
    AdexpType = iota
    IcaoType 
)

它产生完全相同的结果,但减少了潜在的开发人员错误。

6、接收器函数

方案一:意味着必须将消息作为函数的输入参数传递。

复制代码
func IsUpperLevel(m Message) bool {
    for _, r := range m.RoutePoints {
        if r.FlightLevel > upperLevel {
            return true
        }
    }

    return false
}
复制代码

方案二:只是一个带有消息接收器的函数:

复制代码
func (m *Message) IsUpperLevel() bool {
    for _, r := range m.RoutePoints {
        if r.FlightLevel > upperLevel {
            return true
        }
    }

    return false
}
复制代码

对比来看,第二种方法更可取,只需指示消息结构实现了特定的行为。

这也可能是使用 Go 接口的第一步。例如,如果将来我们需要创建另一个具有相同行为(IsUpperLevel())的结构体,初始代码甚至不需要重构(因为消息已经实现了这个行为)。

在这种情况下,更优雅的方法取决于代码的上下文和使用场景。但一般来说,使用方法(方案二)可能更符合面向对象编程(OOP)的概念,因为方法允许操作特定类型的数据,并且更具有封装性和可读性。

使用方法的优势:

1)封装性强:方法与特定类型相关联,可以直接操作该类型的数据,使得数据和操作逻辑更紧密地结合在一起。
2)面向对象特性:符合面向对象编程的设计原则,使代码更模块化和可维护。
3)可读性更好:对于具体类型的操作,使用方法可以更清晰地表达意图,提高代码的可读性和可理解性。

但是,如果操作可能适用于多种不同的数据类型,并且不需要与特定类型直接关联,那么将其实现为一个独立的函数(方案一)可能更合适,这样可以提高代码的灵活性和通用性。

7、注释

这是相当明显的,但糟糕的注释写得很糟糕。

另一方面,我尝试像在实际项目中那样注释良好的代码。尽管我不是喜欢每一行都注释的开发者,但我仍然认为至少对每个函数和复杂函数中的主要步骤进行注释是重要的。

举个例子:

复制代码
// Split each line in a goroutine
for _, line := range in {
    go mapLine(line, ch)
}

msg := Message{}

// Gather the goroutine results
for range in {
    // ...
}
复制代码

除了函数注释之外,一个具体的例子也可能非常有用:

// Parse a line by returning the header (token name) and the value. 
// Example: -COMMENT TEST must returns COMMENT and TEST (in byte slices)
func parseLine(in []byte) ([]byte, []byte) {
    // ...
}

这样具体的例子可以帮助其他开发人员更好地理解现有项目。

最后但同样重要的是,根据 Go 的最佳实践,包本身也应进行注释。

复制代码
/*
Package good is a library for parsing the ADEXP messages.
An intermediate format Message is built by the parser.
*/

package good
复制代码

8、事务处理

示例代码:

复制代码
tx, err := g.DB().Begin()
if err != nil {
   return errors.New("启动事务失败")
}

defer func() {
   if err != nil {
      tx.Rollback()
   } else {
      tx.Commit()
      //定义钩子函数
      afterCommmit()
   }
}()
复制代码
1、首先启动事务时一定要做错误判断
2、建议在启动事务之后马上写defer方法
3、在defer方法内对err进行判断,如果全局中有err!=nil就回滚
4、全局中err都为nil则提交事务
5、在提交事务之后我们可以定义一个钩子函数afterCommit,来统一处理事务提交后的逻辑。

什么是钩子函数

复制代码
钩子函数(Hook Function)是一种在软件开发中常见的编程技术,用于在特定事件发生前或发生后执行自定义代码。这种机制允许开发者在程序执行的特定点注入自定义逻辑,常见于事件驱动的架构或者框架中。
钩子函数通常分为两种类型:
1)前置钩子(Pre-hooks): 在某个事件发生之前执行的函数。在这个钩子中,你可以修改即将发生的事件、检查输入数据的有效性、拦截请求等。
2)后置钩子(Post-hooks): 在某个事件发生之后执行的函数。这种钩子允许你处理事件的结果、清理资源、记录日志等。
钩子函数的使用有助于提高代码的灵活性和可扩展性,因为它们允许在不改变原有逻辑的情况下,增加、修改或者取消某个事件的处理。常见的应用场景包括:
1)Web 开发中,请求处理前后的拦截和处理;
2)数据库操作前后的验证、审计和记录;
3)触发器(Triggers):在特定的数据库操作前或后执行。
总的来说,钩子函数可以让你在代码的特定点上“挂载”自定义行为,增强了程序的灵活性和可维护性。
复制代码

 9、mysql可以加唯一索引的表一定要加唯一索引,而不是通过程序去判断值是否存在,不存在再去插入。

重复的值检查在高并发情况下可能会出现问题,例如两个并发的操作检查到数据不存在,然后尝试插入,最终导致重复插入的情况。而使用唯一索引可以避免这种情况发生。

使用数据库的唯一索引是保证数据一致性和避免并发问题的一种较好的方式,特别是在多个项目操作同一个表的情况下。

如果出于某种原因,不能在数据库层面设置唯一索引,那么可以考虑采用分布式锁、队列等机制来协调多个项目之间的数据操作,但这往往会引入更多的复杂性和开销。

10、Redis分库

首先Redis分库有必要,要约定每个库的使用规则,不能没有规范的滥用。

可以根据不同的业务分库,也可以根据不同的功能分库,但是,必须有规则。

11、配置文件

合理的使用配置文件,不要把敏感信息写到项目中,一定要写到配置文件中。

从配置文件中的取值方式如下:

g.Cfg().GetString("xx.xxx")

 12、使用结构体传值,而不是map传值

不管是请求api,还是模块之间进行传值,都建议定义结构体进行传值,可复用的结构体之间建议使用分层设计的思路复用,减少重复代码。

GoFrame代码优化(使用gconv类型转换,避免重复定义map)

总结

在我看来,很难给出糟糕代码和良好代码的一般定义。在一个上下文中的代码可能被认为是好的,而在另一个上下文中可能被认为是糟糕的。

良好代码的第一个明显特征是根据给定的功能需求提供正确的解决方案。如果代码不符合需求,即使它很高效,也是相当无用的。

同时,对于开发人员来说,关心简单、易维护和高效的代码也很重要。

写出一个优雅的 Go 项目,就是用“最佳实践”的方式去实现这三个部分:编写高质量的 Go 应用、高效管理项目、编写高质量的项目文档。

posted @   李若盛开  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示