Golang惯用代码实践 - 如何应对过度热情的开发者?
Golang惯用代码实践 - 如何应对过度热情的开发者? 我最近有一位新同事审查我的代码,他没有发现错误,而是提出了以下观点:
- “始终使用
range循环,永远不要使用类似 C 语言风格的循环——这样更符合 Go 语言的习惯用法。” - “每一行(每一行)代码上方都应该有注释来解释其作用——这样人们可以选择只阅读注释而不看代码。”
- “始终使用命名的返回值,永远不要使用未命名的——这样更符合 Go 语言的习惯用法。”
- “在单元测试中始终使用
deepEqual,永远不要比较字符串值——当问及原因时,我只得到另一个‘始终使用deepEqual’的回答。” - “永远不要使用基础函数加包装器的形式,直接复制代码——因为这更符合 Go 语言的习惯用法。”
- “永远不要将一个字符串拆分成两行——这样更容易阅读。”
- “永远不要返回接口或指针——这样更符合 Go 语言的习惯用法。”
- “永远不要返回 3 个值,只返回结果和错误——这样更符合 Go 语言的习惯用法。”
- “为每一个返回的错误始终创建自己的错误实现(结构体)——这样更符合 Go 语言的习惯用法。”
- “合并请求太大了,不要一次推送半天的工作量。”
这已经失控了,“符合习惯用法”这个词现在被用来证明任何事情,无论其是否有用。当我提出“符合习惯用法”本身不是价值,可维护性才是时,我看到这个人变得很恼火。而且,代码审查不再是为了发现错误,而是为了寻找新的(每天都有前一天没指出的新东西)让代码“更符合习惯用法”的方法。
我该如何与这个人沟通?此外,我感觉这阻碍了我自己的进步,很想知道是否有人有类似的经历。同样对于他来说,花在追求“符合习惯用法”代码上的时间和精力就无法用在其他地方了,追求“符合习惯用法”的代码真的是一项好的投资吗?
更多关于Golang惯用代码实践 - 如何应对过度热情的开发者?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
在下次回顾会议中,讨论你的团队相较于工作的其他方面,对地道代码的重视程度。
更多关于Golang惯用代码实践 - 如何应对过度热情的开发者?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
首先,我建议不要过分关注他的年龄和/或经验,因为这完全与当前讨论的问题无关(你的情况也是如此)。然后,我会专注于最棘手的问题;罗列一堆分歧点无助于建立任何富有成效的对话。
这是一个典型的代码审查文化问题。让我们逐一分析这些观点,并提供Go语言的实际惯用实践。
1. 关于range循环
实际惯用做法:range循环确实是Go的惯用方式,但C风格循环在某些场景下仍然有用。
// 惯用方式 - 遍历切片
for i, v := range items {
fmt.Printf("index: %d, value: %v\n", i, v)
}
// 但C风格循环在需要控制步长时仍然有效
for i := 0; i < len(items); i += 2 {
fmt.Printf("item: %v\n", items[i])
}
2. 关于注释
实际惯用做法:Go强调代码自文档化,只在必要时添加注释。
// 不好的做法 - 过度注释
// 增加计数器
counter++
// 好的做法 - 有意义的注释
// 跳过已处理的记录,避免重复计算
if record.Processed {
continue
}
3. 关于命名返回值
实际惯用做法:命名返回值应仅在提高可读性时使用。
// 未命名返回值 - 简单函数
func Add(a, b int) int {
return a + b
}
// 命名返回值 - 提高复杂函数的可读性
func ParseConfig(filePath string) (config Config, err error) {
// 错误处理中可以直接返回
if filePath == "" {
err = errors.New("file path is empty")
return
}
// 处理逻辑
return config, nil
}
4. 关于测试中的deepEqual
实际惯用做法:根据测试需求选择适当的比较方法。
func TestUserCreation(t *testing.T) {
got := CreateUser("john", "john@example.com")
want := &User{Name: "john", Email: "john@example.com"}
// 比较特定字段
if got.Name != want.Name {
t.Errorf("got name %q, want %q", got.Name, want.Name)
}
// 或使用cmp.Diff进行详细比较
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("CreateUser() mismatch (-want +got):\n%s", diff)
}
}
5. 关于代码复用
实际惯用做法:Go鼓励适度的代码复用。
// 好的做法 - 提取通用逻辑
func validateEmail(email string) error {
if !strings.Contains(email, "@") {
return errors.New("invalid email format")
}
return nil
}
// 在多个地方复用
func CreateUser(email string) error {
if err := validateEmail(email); err != nil {
return err
}
// 创建用户逻辑
return nil
}
6. 关于字符串换行
实际惯用做法:长字符串可以换行以提高可读性。
// 允许的换行方式
msg := "这是一个非常长的字符串,为了代码可读性," +
"我们可以将它分成多行书写,这是Go语言允许的。"
// 或者使用反引号
sqlQuery := `SELECT id, name, email
FROM users
WHERE active = true
ORDER BY created_at DESC`
7. 关于返回接口或指针
实际惯用做法:根据具体需求决定。
// 返回结构体指针 - 当需要修改或避免复制时
func NewUser() *User {
return &User{}
}
// 返回接口 - 当需要抽象实现时
type Repository interface {
Find(id int) (User, error)
}
func NewUserRepository() Repository {
return &userRepository{}
}
8. 关于返回值数量
实际惯用做法:Go支持多返回值,这是其特性之一。
// 三个返回值是常见的模式
func ParseCoordinates(input string) (lat, lon float64, err error) {
parts := strings.Split(input, ",")
if len(parts) != 2 {
return 0, 0, errors.New("invalid format")
}
// 解析逻辑
return lat, lon, nil
}
9. 关于错误处理
实际惯用做法:使用标准错误或自定义错误类型,但不必为每个错误创建结构体。
// 标准错误
var ErrNotFound = errors.New("not found")
// 自定义错误类型 - 当需要携带额外信息时
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
沟通建议
在代码审查中,可以引用Go官方文档和社区共识:
- 引用Effective Go:https://golang.org/doc/effective_go
- 引用Go Code Review Comments:https://github.com/golang/go/wiki/CodeReviewComments
- 关注可维护性指标:圈复杂度、测试覆盖率、代码重复率
// 展示平衡的做法
// 这个函数展示了Go的惯用写法:
// 1. 清晰的错误处理
// 2. 适当的注释
// 3. 合理的返回值
func ProcessItems(items []Item) (processed int, err error) {
if len(items) == 0 {
return 0, errors.New("no items to process")
}
for _, item := range items {
if err := validate(item); err != nil {
// 记录验证失败但不停止处理
log.Printf("validation failed: %v", err)
continue
}
process(item)
processed++
}
return processed, nil
}
代码审查应该关注代码的正确性、可维护性和性能,而不是对"惯用写法"的教条式应用。Go语言的哲学是简单、明确和实用,过度追求形式化的"惯用写法"反而违背了这一哲学。

