很多开发者从Java转向Go语言的过程中,发现很多团队都会面临一个有趣的现象:一些新接触Go的开发者会不自觉地沿用Java那套编码习惯,其中最明显的就是在文件命名上使用xxxService.goxxxDao.goxxxController.go这样的约定。这里根据我的经验来聊聊,在Go语言中是否真的需要这样做。

Go语言的设计哲学

与Java等语言不同,Go语言从语法层面就强制统一了代码风格。一些对于其他语言的编译器完全忽视的问题,在Go编译器前就会被认为是编译错误。

Go语言很可能是第一个将代码风格强制统一的语言。这种设计哲学体现了Go团队对简洁和一致的追求。

Go语言的命名规范

在Go世界中,命名规范有着明确的准则:

包名必须与目录名一致,尽量采取有意义、简短的包名,不要与标准库名称一样。包名应小写,没有下划线,可以使用中划线隔开。

文件名应该小写,组合词用下划线分割。Go语言明确宣告了拥护骆驼命名法而排斥下划线法

该不该使用Java风格的文件命名?

那么,回到我们的核心问题:该不该在Go项目中使用xxxService.goxxxDao.goxxxController.go这样的命名方式?

答案是不推荐。原因如下:

1. 违背了Go的简洁理念

Go语言鼓励简单的设计。在Go中,通常更倾向于按领域模型组织代码,而不是按技术角色划分。例如,一个用户相关的功能,更倾向于将用户的数据模型、业务逻辑和存储操作放在user.go中,而不是分别放在UserService.goUserDao.goUserController.go中。

2. Go的包设计原则不同

在Java中,我们经常会根据技术角色(如Controller、Service、Dao)来划分包结构。但在Go中,包应该是按功能领域划分的。一个包应该包含一组相关的功能,而不是一个技术层级。

3. 命名一致性更重要

Go语言社区已经形成了自己的一套命名习惯。例如,测试文件以_test.go结尾,接口名通常以er结尾。遵循这些社区约定比沿用Java习惯更重要。

更地道的Go项目结构

那么,地道的Go项目应该怎样组织代码呢?以下是一个推荐的结构:

project/
├── cmd/                 // 可执行程序
│   └── myapp/
│       └── main.go
├── internal/            // 内部包,外部项目无法导入
│   ├── user/           // 按领域划分,不是按技术角色
│   │   ├── user.go     // 用户领域的主要代码
│   │   ├── store.go    // 用户存储相关
│   │   └── service.go  // 如果有必要,可以单独文件
│   └── order/
│       ├── order.go
│       └── store.go
├── pkg/                // 可被外部项目导入的代码
│   └── util/
│       └── stringutil.go
└── go.mod

在这种结构中,你会注意到:

  • 按领域模型组织,而不是按技术角色
  • 文件命名简洁明了,不需要冗余的后缀
  • 同一个领域的内容在同一个包中,减少不必要的导入

何时应该拆分文件?

当一个领域的代码量较大时(建议单个文件不超过800行,单个函数不超过80行),我们可以按逻辑功能将代码拆分到多个文件中。但即使在这种情况下,也不推荐使用Java风格的技术后缀。

更地道的做法是:

// 不推荐
user_service.go
user_controller.go  
user_repository.go

// 推荐
user.go          // 主要类型定义和核心方法
user_store.go    // 存储相关
user_api.go      // API处理相关

总结

Go语言不是Java,它有自己独特的设计哲学和编码规范。虽然刚开始从Java转Go时会有些不习惯,但尊重并遵循Go语言的约定是写出地道Go代码的关键。

代码必须是本着写给人阅读的原则来编写,只不过顺便给机器执行而已。在Go语言中,简洁、可读性和一致性远比遵循其他语言的约定更重要。