你好,我是孔令飞。今天,我们来聊聊开发应用中需要用到的那些规范。
无规矩不成方圆,生活如此,软件开发也是如此。一个应用基本都是多人协作开发的,但不同人的开发习惯、方式都不同。如果没有一个统一的规范,就会造成非常多的问题,比如:
/v1/users/colin
,但是修改密钥接口为/v1/secret?name=secret0
,难以理解和记忆。因此,在设计阶段、编码之前,我们需要一个好的规范来约束开发者,以确保大家开发的是“一个应用”。一个好的规范不仅可以提高软件质量,还可以提高软件的开发效率,降低维护成本,甚至能减少 Bug 数,也可以使你的开发体验如行云流水一般顺畅。所以,在编码之前,有必要花一些时间和团队成员一起讨论并制定规范。
那么,有哪些地方需要制定规范,这些规范又该如何制定呢?
一个 Go 项目会涉及很多方面,所以也会有多种规范,同类规范也会因为团队差异而有所不同。所以,在这门课中我只给你讲一些开发中常用的规范。为了便于你记忆,根据是否跟代码相关,我将它们分为非编码类规范和编码类规范:
为了便于你记忆,我将这些规范整理成了下面一张图:
这一讲,我们先来说说开源规范、文档规范和版本规范,因为 Commit 规范比较多,我们放到下一讲。至于其他规范,会在后面内容中介绍。例如日志规范,因为和日志设计结合比较紧密,我会放在日志包设计中一起讲。
首先,我们来介绍下开源规范。
其实业界并没有一个官方的开源规范,实际开发中,也很少有人提这个。那么,我们为什么一定要知道开源规范呢?
原因主要有两方面:一是,开源项目在代码质量、代码规范、文档等方面,要比非开源项目要求更高,在项目开发中按照开源项目的要求来规范自己的项目,可以更好地驱动项目质量的提高;二是,一些大公司为了不重复造轮子,会要求公司团队能够将自己的项目开源,所以提前按开源标准来驱动 Go 项目开发,也会为我们日后代码开源省去不少麻烦。
一个开源项目一定需要一个开源协议,开源协议规定了你在使用开源软件时的权利和责任,也就是规定了你可以做什么,不可以做什么。所以,开源规范的第一条规范就是选择一个合适的开源协议。那么有哪些开源协议,如何选择呢?接下来,我来详细介绍下。
首先要说明的是,只有开源项目才会用到开源协议,如果你的项目不准备开源,就用不到开源协议。但先了解一下总是没错的,以后总能用得上。
业界有上百种开源协议,每种开源协议的要求不一样,有的协议对使用条件要求比较苛刻,有的则相对比较宽松。我们没必要全都记住,只需要知道经常使用的 6 种开源协议,也就是 GPL、MPL、LGPL、Apache、BSD 和 MIT 就可以了。至于它们的介绍,你可以参考 开源协议介绍 。
那具体如何选择适合自己的开源协议呢?你可以参考乌克兰程序员 Paul Bagwell 画的这张图:
在上图中,右边的协议比左边的协议宽松,在选择时,你可以根据菱形框中的选择项从上到下进行选择。为了使你能够毫无负担地使用 IAM 项目提供的源码,我选择了最宽松的 MIT 协议。
另外,因为 Apache 是对商业应用友好的协议,使用者也可以在需要的时候修改代码来满足需要,并作为开源或商业产品发布/销售,所以大型公司的开源项目通常会采用 Apache 2.0 开源协议。
那我们在参与开源项目,或者按照开源项目的要求来规范代码时,需要关注哪些方面的规范呢?
其实,在我看来,一切能让项目变得更优秀的规范,都应该属于开源规范。
开源项目的代码,除了要遵守上面所说的编码类规范和非编码类规范之外,还要遵守下面几个规范。
第一,开源项目,应该有一个高的单元覆盖率。这样,一方面可以确保第三方开发者在开发完代码之后,能够很方便地对整个项目做详细的单元测试,另一方面也能保证提交代码的质量。
第二,要确保整个代码库和提交记录中,不能出现内部 IP、内部域名、密码、密钥这类信息。否则,就会造成敏感信息外漏,可能会对我们的内部业务造成安全隐患。
第三,当我们的开源项目被别的开发者提交 pull request、issue、评论时,要及时处理,一方面可以确保项目不断被更新,另一方面也可以激发其他开发者贡献代码的积极性。
第四,好的开源项目,应该能够持续地更新功能,修复 Bug。对于一些已经结项、不维护的开源项目,需要及时地对项目进行归档,并在项目描述中加以说明。
在我看来,上面这些,是开源规范中比较重要的几点。如果你想了解详细的开源规范包括哪些内容,可以看我放在 GitHub 上的 这份资料 。
最后提醒你两件事:第一件,如果有条件,你可以宣传、运营开源项目,让更多的人知道、使用、贡献代码。比如,你可以在掘金、简书等平台发表文章,也可以创建 QQ、微信交流群等,都是不错的方式。第二件,如果你英文好、有时间,文档最好有中英文 2 份,优先使用英文,让来自全球的开发者都能了解、使用和参与你的项目。
工作中我发现,很多开发者非常注重代码产出,但不注重文档产出。他们觉得,即使没有软件文档也没太大关系,不影响软件交付。我要说的是,这种看法是错误的!因为文档属于软件交付的一个重要组成部分,没有文档的项目很难理解、部署和使用。
因此,编写文档是一个必不可少的开发工作。那么一个项目需要编写哪些文档,又该如何编写呢?我认为项目中最需要的 3 类文档是 README文档、项目文档和 API 接口文档。
下面,我们一一来说它们的编写规范。
README文档是项目的门面,它是开发者学习项目时第一个阅读的文档,会放在项目的根目录下。因为它主要是用来介绍项目的功能、安装、部署和使用的,所以它是可以规范化的。
下面,我们直接通过一个 README模板,来看一下 README 规范中的内容:
# 项目名称
<!-- 写一段简短的话描述项目 -->
## 功能特性
<!-- 描述该项目的核心功能点 -->
## 软件架构(可选)
<!-- 可以描述下项目的架构 -->
## 快速开始
### 依赖检查
<!-- 描述该项目的依赖,比如依赖的包、工具或者其他任何依赖项 -->
### 构建
<!-- 描述如何构建该项目 -->
### 运行
<!-- 描述如何运行该项目 -->
## 使用指南
<!-- 描述如何使用该项目 -->
## 如何贡献
<!-- 告诉其他开发者如果给该项目贡献源码 -->
## 社区(可选)
<!-- 如果有需要可以介绍一些社区相关的内容 -->
## 关于作者
<!-- 这里写上项目作者 -->
## 谁在用(可选)
<!-- 可以列出使用本项目的其他有影响力的项目,算是给项目打个广告吧 -->
## 许可证
<!-- 这里链接上该项目的开源许可证 -->
更具体的示例,你可以参考 IAM 系统的 README.md 文件 。
这里,有个在线的README生成工具,你也可以参考下:readme.so。
项目文档包括一切需要文档化的内容,它们通常集中放在/docs 目录下。当我们在创建团队的项目文档时,通常会预先规划并创建好一些目录,用来存放不同的文档。因此,在开始 Go 项目开发之前,我们也要制定一个软件文档规范。好的文档规范有 2 个优点:易读和可以快速定位文档。
不同项目有不同的文档需求,在制定文档规范时,你可以考虑包含两类文档。
为了方便全球开发者和用户使用,开发文档和用户文档,可以预先规划好英文和中文 2 个版本。
为了加深你的理解,这里我们来看下实战项目的文档目录结构:
docs
├── devel # 开发文档,可以提前规划好,英文版文档和中文版文档
│ ├── en-US/ # 英文版文档,可以根据需要组织文件结构
│ └── zh-CN # 中文版文档,可以根据需要组织文件结构
│ └── development.md # 开发手册,可以说明如何编译、构建、运行项目
├── guide # 用户文档
│ ├── en-US/ # 英文版文档,可以根据需要组织文件结构
│ └── zh-CN # 中文版文档,可以根据需要组织文件结构
│ ├── api/ # API文档
│ ├── best-practice # 最佳实践,存放一些比较重要的实践文章
│ │ └── authorization.md
│ ├── faq # 常见问题
│ │ ├── iam-apiserver
│ │ └── installation
│ ├── installation # 安装文档
│ │ └── installation.md
│ ├── introduction/ # 产品介绍文档
│ ├── operation-guide # 操作指南,里面可以根据RESTful资源再划分为更细的子目录,用来存放系统核心/全部功能的操作手册
│ │ ├── policy.md
│ │ ├── secret.md
│ │ └── user.md
│ ├── quickstart # 快速入门
│ │ └── quickstart.md
│ ├── README.md # 用户文档入口文件
│ └── sdk # SDK文档
│ └── golang.md
└── images # 图片存放目录
└── 部署架构v1.png
接口文档又称为 API 文档,一般由后台开发人员编写,用来描述组件提供的 API 接口,以及如何调用这些 API 接口。
在项目初期,接口文档可以解耦前后端,让前后端并行开发:前端只需要按照接口文档实现调用逻辑,后端只需要按照接口文档提供功能。
当前后端都开发完成之后,我们就可以直接进行联调,提高开发效率。在项目后期,接口文档可以提供给使用者,不仅可以降低组件的使用门槛,还能够减少沟通成本。
显然,一个有固定格式、结构清晰、内容完善的接口文档,就非常重要了。那么我们该如何编写接口文档,它又有什么规范呢?
接口文档有四种编写方式,包括编写 Word 格式文档、借助工具编写、通过注释生成和编写 Markdown 格式文档。具体的实现方式见下表:
其中,通过注释生成和编写 Markdown 格式文档这 2 种方式用得最多。在这个专栏,我采用编写 Markdown 格式文档的方式,原因如下:
API 接口文档又要遵循哪些规范呢?其实,一个规范的 API 接口文档,通常需要包含一个完整的 API 接口介绍文档、API 接口变更历史文档、通用说明、数据结构说明、错误码描述和 API 接口使用文档。API 接口使用文档中需要包含接口描述、请求方法、请求参数、输出参数和请求示例。
当然,根据不同的项目需求,API 接口文档会有不同的格式和内容。我以这门课的实战项目采用的 API 接口文档规范为例,和你解释下。
接口文档拆分为以下几个 Markdown 文件,并存放在目录 docs/guide/zh-CN/api 中:
这里我拿 user.md 接口文档为例,和你解释下接口文档是如何写的。user.md
文件记录了用户相关的接口,每个接口按顺序排列,包含如下 5 部分。
HTTP 方法 请求路径
,例如 POST /v1/users
。在 通用说明中的请求方法部分,会说明接口的请求协议和请求地址。如果掌握了这些内容之后,你还想了解更详细的 API 接口文档规范,可以参考这个 链接 。
在做 Go 项目开发时,我建议你把所有组件都加入版本机制。原因主要有两个:一是通过版本号,我们可以很明确地知道组件是哪个版本,从而定位到该组件的功能和代码,方便我们定位问题。二是发布组件时携带版本号,可以让使用者知道目前的项目进度,以及使用版本和上一个版本的功能差别等。
目前业界主流的版本规范是语义化版本规范,也是 IAM 系统采用的版本规范。那什么是语义化版本规范呢?
语义化版本规范(SemVer,Semantic Versioning)是 GitHub 起草的一个具有指导意义的、统一的版本号表示规范。它规定了版本号的表示、增加和比较方式,以及不同版本号代表的含义。
在这套规范下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。语义化版本格式为:主版本号.次版本号.修订号(X.Y.Z)
,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。
版本号可按以下规则递增:
例如,v1.2.3
是一个语义化版本号,版本号中每个数字的具体含义见下图:
你可能还看过这么一种版本号:v1.2.3-alpha
。这其实是把先行版本号(Pre-release)和版本编译元数据,作为延伸加到了主版本号.次版本号.修订号
的后面,格式为 X.Y.Z[-先行版本号][+版本编译元数据]
,如下图所示:
我们来分别看下先行版本号和版本编译元数据是什么意思。
先行版本号意味着,该版本不稳定,可能存在兼容性问题,格式为:X.Y.Z-[一连串以句点分隔的标识符]
,比如下面这几个例子:
1.0.0-alpha
1.0.0-alpha.1
1.0.0-0.3.7
1.0.0-x.7.z.92
编译版本号,一般是编译器在编译过程中自动生成的,我们只定义其格式,并不进行人为控制。下面是一些编译版本号的示例:
1.0.0-alpha+001
1.0.0+20130313144700
1.0.0-beta+exp.sha.5114f85
注意,先行版本号和编译版本号只能是字母、数字,且不可以有空格。
语义化版本控制规范比较多,这里我给你介绍几个比较重要的。如果你需要了解更详细的规范,可以参考 这个链接 的内容。
说了这么多,我们到底该如何确定版本号呢?
这里我给你总结了这么几个经验:
第一,在实际开发的时候,我建议你使用 0.1.0 作为第一个开发版本号,并在后续的每次发行时递增次版本号。
第二,当我们的版本是一个稳定的版本,并且第一次对外发布时,版本号可以定为 1.0.0。
第三,当我们严格按照 Angular commit message 规范提交代码时,版本号可以这么来确定:
一套好的规范,就是一个项目开发的“规矩”,它可以确保整个项目的可维护性、可阅读性,减少 Bug 数等。
一个项目的规范设计主要包括编码类和非编码类这两类规范。今天我们一起学习了开源规范、文档规范和版本规范,现在我们回顾一下重点内容吧。
今天示范用到的项目规范示例,我把详细版放在这里,方便你随时查看:开源规范 、 README规范 、 API接口文档规范 。
期待在留言区看到你的思考和答案,也欢迎和我一起探讨关于规范设计的问题,我们下一讲见!
评论