很多开发者从Java转向Go语言的过程中,发现很多团队都会面临一个有趣的现象:一些新接触Go的开发者会不自觉地沿用Java那套编码习惯,其中最明显的就是在文件命名上使用xxxService.go、xxxDao.go、xxxController.go这样的约定。这里根据我的经验来聊聊,在Go语言中是否真的需要这样做。
Go语言的设计哲学
与Java等语言不同,Go语言从语法层面就强制统一了代码风格。一些对于其他语言的编译器完全忽视的问题,在Go编译器前就会被认为是编译错误。
Go语言很可能是第一个将代码风格强制统一的语言。这种设计哲学体现了Go团队对简洁和一致的追求。
Go语言的命名规范
在Go世界中,命名规范有着明确的准则:
包名必须与目录名一致,尽量采取有意义、简短的包名,不要与标准库名称一样。包名应小写,没有下划线,可以使用中划线隔开。
文件名应该小写,组合词用下划线分割。Go语言明确宣告了拥护骆驼命名法而排斥下划线法。
该不该使用Java风格的文件命名?
那么,回到我们的核心问题:该不该在Go项目中使用xxxService.go、xxxDao.go、xxxController.go这样的命名方式?
答案是不推荐。原因如下:
1. 违背了Go的简洁理念
Go语言鼓励简单的设计。在Go中,通常更倾向于按领域模型组织代码,而不是按技术角色划分。例如,一个用户相关的功能,更倾向于将用户的数据模型、业务逻辑和存储操作放在user.go中,而不是分别放在UserService.go、UserDao.go和UserController.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语言中,简洁、可读性和一致性远比遵循其他语言的约定更重要。