当前位置:首页 > 技术分析 > 正文内容

Go 项目文件命名规范是什么?_go项目目录结构最佳

ruisui883个月前 (02-07)技术分析12

计算机科学中只有两件难事:缓存失效和命名。—— 菲尔·卡尔顿(Phil Karlton)。

在编程世界中,选择正确的命名约定是打开可读和可维护代码大门的重要途径。在使用 Go 语言开发大型项目时,文件命名是构建清晰项目结构的关键一环,一个合理的文件命名规范不仅能提高开发效率,还能降低团队协作中的沟通成本。

在这篇博文中,我们将深入探讨 Go 中命名的最佳实践。

在探讨 Go 文件命名规范之前,我们有必要先来了解下 Go 项目中的目录和包的命名规范是什么。

目录名

关于 Go 目录命名规范,在网上搜索相关资料,基本能找到如下两条共识:

  • 全小写单词。例如 cmdinternal
  • 必要时可以使用中划线分隔。例如 kube-schedulerkube-controller-manager

项目本身也是一个目录,所以项目名也遵循这两条规范。

其实我们可以借鉴流行的 Go 项目,参考这些优秀的项目是如何对目录进行命名的。比如 Kubernetes 项目就非常具有参考价值。

如下是 Kubernetes 项目 /cmd 目录部分截图:

可以发现,Kubernetes 项目目录名都采用全小写单词,并且必要时可以使用中划线分隔。

不过,值得注意的是,有些目录名并没有使用中划线进行分隔,而是直接将多个单词连在了一起。所以何时使用中划线是一个选择问题,而不是固定的强制规范。

我们再看下 Kubernetes 项目 /pkg 目录部分截图:

可以发现,/pkg 目录下所有目录名都没有使用中划线分隔。有意思的是,甚至 kubeapiserver 也连在了一起,回看上面 /cmd 目录中的写法是 kube-apiserver。看来 Kubernetes 项目不同目录有着各自的命名规范。

针对这点,我的理解是:/cmd/kube-apiserver,作为项目中组件的入口文件目录,使用中划线分隔可以起到见名知意的效果;而 /pkg/kubeapiserver 作为包路径不使用中划线分隔,是为了方便包的导入。

如下列举几个目录名正反示例。

正确示例:

cmd
internal
pkg
task
kube-scheduler
kube-controller-manager

反向示例:

CMD
kubeScheduler
KubeScheduler
kube_scheduler

不过凡事总有特例,我之前写过一篇文章《如何设计一个优秀的 Go Web 项目目录结构》,里面介绍了一个 Go 项目的标准布局。其中包含一个外部辅助工具目录 third_party,用来存放 fork 的代码和其他第三方工具。这里 third_party 就采用了下划线命名,这种算是约定俗成的命名,就无需刻意遵循目录命名规范了。

另外,关于项目本身的的命名,其实还有一些额外的规范:

  • 可以是项目功能的描述。例如 userapiredis-gogo-swagger
  • 也可以是一个代号(如神话人物的名字、希腊语、游戏角色等)(公司的基础组件、开源项目,都是比较适合采用代号命名的项目)。例如 kratoskubernetes
  • 项目名要尽量避免重复,如果可能重复要添加必要的前缀或者后缀做区分。例如 ai-userai-infra

如下列举几个项目名正反示例。

正确示例:

user
userapi
redis-go
kubernetes

反向示例:

user_api
Product
AIInfra
AiInfra

以下是我个人的一些建议:

  • 尽量使用单数命名(不过对于约定俗称的名称比如 configs 可以存在特例)。
  • 尽量不要使用中划线,而采用简短的单词。
  • 单词长度尽量控制在 3 个单词以内。

包名

接下来,我们再一起探究下 Go 项目中包名的命名规范是什么。

幸运的是,对于包名,Go 官方博客给出了参考建议,也是最为权威的规范。

在 Package names 这篇 Go 官方博文中,给出了几条好的包命名原则:

  • 好的包名应该简短而清晰的,具有描述性。例如 bufio(带缓冲的 I/O)比 bufbuffer 更好,因为它既简短又具有描述性。
  • 小写单一单词,简短名词,避免冗余。避免蛇形命名(under_scores)或驼峰命名(mixedCaps)。例如 timelisthttp
  • 明智而谨慎的使用缩写,如果缩写包名称会使其产生歧义或不清楚,请不要这样做。例如 strconv(string conversion)、syscall(system call)、fmt(formatted I/O)。
  • 不要从用户那里窃取好名字,避免给包起一个在客户端代码中常用的名字。例如,带缓冲的 I/O 包名叫 bufio,而不是 buf,因为 buf 是表示缓冲区的一个好的变量名,常会出现在客户端程序代码中。
  • 包名以及包所在的目录名,不要使用复数。例如可以是 net/url,而不应是 net/urls
  • 不要用 commonutilshared 或者 lib 这类过于宽泛且无意义的包名。记住,一个包应该只有一个目的,只有一个责任。
  • 避免不必要的包名冲突,不使用常用名或标准库作为包名。
  • 不同目录中的包也可以具有相同的名称。例如 runtime/pprofnet/http/pprof 不会产生歧义,客户端代码在导入包时可以重命名(当重命名导入的包时,本地名称同样应遵循与包名称相同的指导原则(lower case, no under_scores or mixedCaps))。
  • 按照惯例,包路径的最后一个元素是包名,如果不一致,会对代码阅读者产生困惑。例如 import golang.org/x/time/rate,包名为 rate

Go Team 成员 David Crawshaw 在 2014 Google I/O talk 中也对包命名规范给出了建议:

  • 保持包名简短而有意义。
  • 不要使用下划线,它们会使包名变长。例如采用 suffixarray 而不是 suffix_array
  • 包的名称是它的类型名和函数名的一部分。例如 bytes 包下存在 Buffer 结构体,本身而言 Buffer 存在二义性,但客户端用户看到的是 buf := new(bytes.Buffer),即包名 + 类型名,解决了二义性的问题。

同目录名规范一样,包名也存在例外的情况:

  • 例如使用代码生成工具生成的代码包名称可能会存在下划线,个人建议是在导入时将其重命名为适合在 Go 代码中使用的名称。
  • _test 结尾的包名是测试代码包。

如下列举几个包名正反示例。

正确示例:

controller
stringset
tabwriter

反例:

MyUtil
util
time // 与标准库重名
tabWriter
TabWriter
tab_writer

文件名

前文分别介绍了 Go 项目中目录名和包名的命名规范,现在终于可以探讨 Go 文件的命名规范了,这也是本文的重头戏。

不过,对于 Go 文件的命名规范,即没有 Go 团队的官方博客作为参考,也没有著名的 Gopher 站出来分享 Go 文件到底改如何命名。

这是一个少有人特意提及的规范项,不过我在 Go 项目的 GitHub 仓库中找到了一条讨论这个问题的 issue doc: filename conventions。

虽然不少人参与了讨论,但遗憾的是,这个问题现在仍然没有定论。不过,这也正是此问题值得探讨的原因,也是本文的意义所在。

其实遇到规范相关问题,我们最先想到的,应该就是参考 Go 源码本身。很不巧,针对 Go 文件的命名规范问题,Go 源码做的也不够好。

正如 doc: filename conventions 问题 issue 提问者列举的几个 Go 文件名示例中,存在很大差异:

  • file.extension.go 风格。例如 cmd/internal/obj/arm/a.out.gocmd/internal/obj/arm64/a.out.go
  • snake_case.go 风格。例如 cmd/internal/obj/abi_string.gointernal/syscall/unix/at_sysnum_linux.go
  • lowercase.go 风格。例如 cmd/compile/internal/ssa/loopreschedchecks.goruntime/mksizeclasses.go
  • CamelCase.go 风格。例如 cmd/compile/internal/ssa/gen/AMD64Ops.gocmd/compile/internal/ssa/gen/WasmOps.go

这就比较有意思了 。

issue 中点赞最多的是 peterbourgon 的评论:

There is a de facto standard for Go source file names: all lowercase with underscore separation when necessary, i.e. snake case. (The exceptions are exceptions.) I'd appreciate having it as a documented standard, too, to answer questions like @carnott-snap notes, especially among junior Gophers. In my opinion it needn't be so formal as entering Effective Go, a short new section on CodeReviewComments is more than sufficient.

这个回答的核心是:all lowercase with underscore separation when necessary, i.e. snake case. (The exceptions are exceptions.)

即大家达成了三点共识:

  • 所有文件名采用小写单词 lowercase.go。例如 mksizeclasses.go
  • 必要时用下划线分隔,即蛇形命名法 snake_case.go。例如 abi_string.go
  • 例外情况除外。

前面两条好理解,例外情况都有哪些?我总结大概有如下几种:

  • _test.go 结尾的测试文件(仅供 go test 编译和运行)。
  • ._ 开头的“隐藏文件”(将被 go tool 忽略,如果你使用 GoLand 创建一个 .a.go 文件,GoLand 会提示 '.a.go' is ignored by the build tool since its name starts with '.')。
  • 具有 OSARCH 特定后缀的文件会遵守特定的约束。例如 name_linux.go 仅在 Linux 上构建,name_amd64.go 仅在 amd64 上构建(这与在文件顶部的 //+build amd64 具有相同作用)。

虽然很多人达成了上面三点共识,但其实文件命名和目录命名规范一样,存在“灰色地带” —— 必要时用下划线分隔。

到底何时是必要的时候

这也是最不统一的一点规范,我发现很多人推荐,在名称出现多个单词时采用下划线分隔。例如 task_status.goweb_shell.go

但还有一小部分人,更推崇不采用下划线的命名方式,例如 taskstatus.gowebshell.go

而我个人的偏好则更倾向于后者。如果单词数量为 2 个,我会倾向于不使用下划线分隔,例如 taskstatus.go;如果单词数量为 3~4 个,则倾向于使用下划线分隔,例如 import_known_versions.godefault_storage_factory_builder.go

但最好的方案,仍然是尽量用简短有意义的单个单词或缩写作为文件名。

NOTE: 我倾向于不使用下划线分隔的几点理由:

与包名对齐

kubernetes 也经常这么干。

你看,testdata 就没有使用下划线分隔 。

... 想到了再说 :)。

如下列举几个文件名正反示例。

正确示例:

router.go
middleware.go
webshell.go

反面示例:

routers.go // 复数
fooBar.go
Service.go

总结

计算机科学中只有两件难事:缓存失效和命名。本文探讨的是后者。

Go 语言虽然是后起之秀,但在项目布局、命名规范等的确没有一个统一的标准。不过社区还是给出了一些共识性建议:

  • 采用 lowercase,而非 camelCasePascalCase
  • 必要时,目录使用 kebab-case,文件使用 snake_case
  • lowercase 以及 kebab-case/snake_case 的选择存在模糊的“灰色地带”。

希望本文能对你有所启发。

延伸阅读

  • The Go Blog —— Package names: https://go.dev/blog/package-names
  • Effective Go —— Package names: https://go.dev/doc/effective_go#names
  • Organizing Go code: https://go.dev/talks/2014/organizeio.slide#6
  • Naming Conventions in Golang: https://www.mohitkhare.com/blog/go-naming-conventions/
  • Module naming conventions: https://www.reddit.com/r/golang/comments/rowff0/module_naming_conventions/
  • 9 Golang Name Conventions Gophers should follow!: https://blog.devgenius.io/golang-name-convention-gophers-should-follow-e4397fba5dce
  • Go Style Decisions: https://google.github.io/styleguide/go/decisions.html#naming
  • What are conventions for filenames in Go?: https://stackoverflow.com/questions/25161774/what-are-conventions-for-filenames-in-go
  • What is the correct convention for giving filenames in go: https://www.reddit.com/r/golang/comments/16frdsi/what_is_the_correct_convention_for_giving/
  • doc: filename conventions #36060: https://github.com/golang/go/issues/36060
  • 如何设计一个优秀的 Go Web 项目目录结构: https://jianghushinian.cn/2023/02/25/how-to-design-a-good-go-web-project-directory-structure/
  • go: https://github.com/golang/go
  • Kubernetes: https://github.com/kubernetes/kubernetes
  • Kratos: https://github.com/go-kratos/kratos

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1764.html

标签: pprof
分享给朋友:

“Go 项目文件命名规范是什么?_go项目目录结构最佳” 的相关文章

GitLab-合并请求

描述合并请求可用于在您对项目进行的其他人员之间交换代码,并轻松与他们讨论更改。合并请求的步骤步骤1-在创建新的合并请求之前,GitLab中应该有一个创建的分支。您可以参考本章来创建分支-步骤2-登录到您的GitLab帐户,然后转到“ 项目”部分下的项目 -步骤3-单击“ 合并请求”选项卡,然后单击“...

Python 幕后:Python导入import的工作原理

更多互联网精彩资讯、工作效率提升关注【飞鱼在浪屿】(日更新)Python 最容易被误解的方面其中之一是import。Python 导入系统不仅看起来很复杂。因此,即使文档非常好,它也不能让您全面了解正在发生的事情。唯一方法是研究 Python 执行 import 语句时幕后发生的事情。注意:在这篇文...

深度解析!AI智能体在To B领域应用,汽车售后服务落地全攻略

在汽车售后服务领域,AI智能体的应用正带来一场效率和专业度的革命。本文深度解析了一个AI智能体在To B领域的实际应用案例,介绍了AI智能体如何通过提升服务顾问和维修技师的专业度及维修效率,优化汽车售后服务流程。上周我分享了AI智能体+AI小程序To C的AI应用场景《1000%增长!我仅用一个小时...

全新斯柯达柯珞克Karoq深度评测:大众替代品

“斯柯达柯珞克是一款出色的全能家庭 SUV,具有许多有用的功能”价格36,605 英镑- 49,190 英镑优点方便的 VarioFlex 后排座椅非常适合家庭入住驾驶乐趣缺点保修期短保守的内饰性格比Yeti少结论——斯柯达柯珞克是一辆好车吗?斯柯达柯珞克是在辉煌的七座 斯柯达柯迪亚克之后推出的,因...

vue-router是如何解析query参数呢? #前端

vue-router 中的 query 解析。1. 大家好,我是龙仔。今天来分享 vue-router 是如何解析快乐参数的,因为使用 vue 路由会传 query 参数和快乐参数,所以从 vue 的角度来看如何解析传递的快乐参数。2. 基础知识大家应知道,快乐参数结构如:a、b、c、a、b、c、a...

Vue从入门到实践 丨Vue-router基本使用

1. 什么是 vue-routervue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。vue-router 的官方文档地址:https://router.vuejs.org/zh/2. vue-router 安装...