Docs Vault

开发前,最重要的一件事便是制定合理的代码开发规范。那么该如何指定规范呢?其实并没有一个标准的流程或者规范。很多时候,是企业内团队根据自身的需求、现状来制定的。这里,我总结一些通用的思路和方法来供你参考。首先,规范应该作为一个真正的需求被提出、排期、跟踪和评审,直至完成。


当规范作为一个正式的项目需求被排期后,相关开发者需要做 3 步调研,以确定最终的规范类别:

  1. 团队内调研:在团队内跟领导、同事进行交流,确定团队内对规范的诉求和要求;
  2. 公司内调研:很多公司都有公司级的开发规范,公司内的其他团队,也有制定并分享一些规范。开发者需要调研公司级的规范,以及公司内其他团队的已有规范,结合这些规范,扩展和完善团队内的规范。;
  3. 开发社区调研:开发者还需要调研社区的优秀开发规范,结合团队内的需求,进一步完善团队内的开发规范。

经过这3步调研,开发者最终会有一份规范列表,里面包括了规范类别和每个类别具体有哪些规范项目。下面这些规范是企业开发中,经常会被用到的规范:

  1. 代码规范;
  2. 接口规范;
  3. 日志规范;
  4. 错误规范;
  5. 提交规范;
  6. 版本规范。


当然,我们也可以根据团队需要制定更多的规范。在制定新的规范时,要注意以下几点:

  1. 规范的必要性:过多不必要的规范,不仅达不到规范效果,还会干扰其他规范的落地程度,增加开发者的开发负担;
  2. 规范的可执行性:规范是否能够被有效执行,如果一个规范,不能被有效执行,个人感觉不如不去制定。
  3. 规范的可见性:规范应该放在可以被开发者轻松找到的地方,比如:团队 wiki 中醒目的位置或者容易查找的位置,或者项目仓库的文档目录中。


有了明确的开发规范之后,开发者还需要思考规范如何才能够被很好的遵守并执行。根据我的开发经验,一般可通过以下方法来确保规范被有效的执行:

  1. 自动化工具:自动化工具是确保规范被执行的最有效方法。在实际开发中,靠开发人员自行遵守规范,很多时候,是不靠谱,不确定的,规范并不能被很好的执行。在实际的开发中,一个非常有效的方法是,使用工具来检查代码是否符合预定的规范。我们可以使用社区优秀的静态代码检查工具(例如:golangci-lint),或者公司内的静态代码检查工具,甚至,我们也可以自研静态代码检查工具,来检查现有工具不能检查到的规范项目;
  2. 集成到 CI/CD 流程中:可以将规范检查工具集成在 CI/CD 流程中,确保每一次提交、构建,代码都是符合预定规范的;
  3. 代码审查:在代码合入主干前的代码审查中,核心开发人员,不仅要确认代码功能无 bug,还需要确认代码符合开发规范,如果不符合,需要 PR 提交者重新修改,直至符合开发规范;
  4. 定期团队内宣讲:定期团队内进行规范宣讲,一方面能够使新人能够及时了解开发规范,另一方面,通过定期宣讲,可以加强团队成员的规范意识,最终形成团队内的规范化开发风气。


制定编码规范(涵最佳实践)



提示:因为代码规范和最佳实践,边界并不清晰,所以这里直接合并到一起来讲。



以下是一些社区中比较受关注的代码开发规范(涵最佳实践),可供你参考:

  1. CodeReviewComments
  2. Effective Go
  3. Google Style Guides
  4. Uber Go Style Guide
  5. Kubernetes Code Conventions
  6. Godoc: documenting Go code


制定接口规范


Go 项目开发中,接口类型分为以下 3 种:

  1. HTTP 接口;
  2. RPC 接口;
  3. GraphQL 接口。


实际 Go 项目开发中,用的最多的是 RPC 接口和 HTTP 接口,其中 HTTP 接口用的最多。HTTP 接口当前最佳实 践是采用 REST 接口规范。


REST 接口规范


REST 代表的是表现层状态转移(REpresentational State Transfer),REST 本身并没有创造新的技术、组件或服务,它只是一种软件架构风格,是一组架构约束条件和原则,而不是技术框架。


REST 有一系列规范,满足这些规范的 API 均可称为 RESTful API。REST 规范把所有内容都视为资源,也就是说网络上一切皆资源。REST架构对资源的操作包括获取、创建、修改和删除,这些操作正好对应 HTTP 协议提供的 GET、POST、PUT 和 DELETE 方法。如下图所示:


更多关于 REST 接口规范的介绍可参考文章:OneX 项目 REST 接口规范


RPC 接口规范


RPC 接口规范其实没有太多可说的。总得来说就是:

  1. 方法名(接口名)格式规范,符合一定的规范。例如:
  2. 使用大写的驼峰命名法,例如:UpdateUser;
  3. 方法名见名知义,看到方法名,很容易理解接口要实现的功能类别;
  4. 方法参数只能有一个,类型为结构体指针,命名采用大写驼峰式,例如:*UpdateUserRequest;
  5. 方法返回值只能有一个,类型为结构体指针,命名采用大写驼峰式,例如:*UpdateUserResponse;
  6. 为保证接口可以安全的重试,Mutation 接口(Create/Update 等 Mutation 操作)需要保证幂等性;


当然,RPC 接口还有很多其他接口规范,例如:IDL 规范、结构体定义规范、注释规范等。具体可参考:TODO


制定日志规范


日志规范是 Go 项目开发中的一个必备规范。日志规范通常用来解决一下 3 类问题:

  1. 在何处打印日志?
  2. 在哪个日志级别打印日志?
  3. 如何记录日志内容?


以上 3 个问题的解答如下。


  1. 在需要的地方打印日志:

其实在哪里打印日志这个问题,是最没有标准答案的问题。你可以根据需要,在任何你觉得有必要记录日志的位置打印日志。以下是我建议的一些地方:

  1. 在分支语句处打印日志:通常我们可以考虑在分支语句处打印日志,因为分支语句,往往能说明流程的走向;
  2. 不在循环中打印日志:不建议在循环中打印日志,因为循环中打印日志,可能会打印出超多的无用日志;
  3. 在错误产生的最原始位置打印日志:在我的开发生涯中,我发现很多开发者,喜欢层层封装错误。但我不喜欢,我只喜欢在最原始的位置处打印日志即可。因为排障时,绝大部分情况下只根据错误原始发生位置,即可定位到原因。层层封装日志,可能使代码显得很臃肿。我喜欢简洁的代码。在错误的最原始位置打印日志,可以直接通过日志,定位到错误发生的根因;
  4. 在接口请求处打印日志:在我们的项目开发中,经常需要调用外部的 API 接口,这里建议在这些地方也打印日志,记录请求包、请求头、返回包等信息。因为当调用第三方接口出问题时,可以直接借助于这些日志信息,重新发请求测试,或者提供信息给第三方接口负责人,请求协助排查问题。在这些地方记录日志,要考虑加入 Filter 逻辑,因为请求参数、返回值可能会很大,不加过滤,全部记录日志,可能会打爆系统;


  1. 在合适的级别记录日志:

日志使用级别来代表一条日志的严重级别,通常日志包都提供以下几类日志级别(有些日志包不提供,如果需要,可自行封装):

  1. Debug 级别;
  2. Info 级别;
  3. Warn 级别;
  4. Error 级别;
  5. Panic 级别;
  6. Fatal 级别。


在打印日志时,我们需要给日志选择合适的日志级别。


  1. 合理地记录日志:
  2. 在记录日志时,不要输出一些敏感信息,例如密码、密钥等;
  3. 统一日志记录格式。例如:
  4. 成功日志一律为 <动词> + <一些事>;
  5. 失败日志一律为 Fail to <动词> + <一些事>;
  6. 日志内容以大写字母开头,例如 log.Info("Update user function called")。
  7. 根据需要,日志最好包含三个信息:请求ID(RequestID)、用户(User)、行为(Action)
  8. 不要将日志记录在错误的日志级别上。


在记录日志时,还有一些建议想分享给你,这些建议如下:

  1. 持续优化日志:开发调试、测试、现网排障时,不要遗忘一件事情,就是根据排障的过程优化日志打印;
  2. 不多不少:打印日志要“不多不少”,避免打印没有作用的日志,也不要遗漏关键的日志信息;
  3. 支持动态开关 Debug 日志:如果你不介意一些开发成本,并且有必要,你也可以给 API 接口加上动态开关 Debug 日志的功能,通常可以在 Web 中间件中实现;
  4. 总是将日志记录在本地文件:在项目开发中,日志通常会被记录在本地文件、标准输出、异步上报给日志平台。但我比较建议直接将日志记录到本地文件中。记录到本地文件,一方面可以方便查看,另一方面也支持异步采集数据到分布式的日志中心中;
  5. 集中化日志存储处理:现在软件架构越来越多的采用微服务架构,并且考虑到横向扩容、容灾等能力,同一个微服务还要启动多个实例,多个实例还需要打散到不同的服务器上。所以,如果没有一个统一的日志查询,你需要一个一个的登录部署服务器查看日志,排障效率是非常低的。所以,云原生时代,日志基本都会收集到分布式的日志搜索引擎中,例如:elasticsearch;
  6. 结构化日志记录:日志记录格式需要时结构化的,结构化的日志可以支持按字段进行日志过滤、组合查询等。还能够更轻松的进行日志清洗,从中找出有用的数据,进行数据分析等。


更多关于日志规范的介绍可参考文章:OneX 项目日志规范


制定错误规范


同日志规范一样,错误规范也是项目开发中,必须制定的一个规范。在 Go 项目开发中,通常我们期望接口返回错误可以实现以下功能:

  1. 支持返回错误码:错误码用来唯一的标识一个或一类错误,通常我们可以根据错误码快速定位到问题。并且根据错误码,我们还可以很方便的进行错误判断,并进行相应的错误处理;
  2. 自定义错误码:因为 HTTP 状态码有限,不能满足业务不可知数量的错误码需求。所以,在 Go 项目开发中,我们通常需要制定业务自己的错误码;
  3. 易读的错误码:因为错误码会直接展示给用户,所以我们期望错误码应该是易读的,最好见名知义。
  4. 返回错误信息:错误码作为错误标识,通常对长度是有要求的,不能很长。所以包含的错误信息必定有限。这时候如果想让接口调用方了解错误的具体原因,还需要返回更详细的错误信息。但在返回错误信息时,我们通常有以下诉求:
  5. 错误简洁、易理解、易读:错误信息直接面向开发者或用户,一个简洁、易读、易理解的错误信息,不仅可以让开发者或用户获取到有用的信息,定位到错误的问题。还能够提高开发者或用户的使用体验。
  6. 错误数可数:开发者或用户的脑容量很大,但大部分人却不希望花费很多脑容量去记忆你的错误信息,所以在不影响错误信息展示的前提下,可以控制错误信息的返回数量,减少开发者或用户的记忆成本;
  7. 错误信息可扩展:项目返回的错误信息其实很多时候是不可预测的,随着项目的迭代,可能有越来越多的错误信息,所以,我们的错误返回要能够支持自定义错误信息,做到错误返回的可扩展性。


我研究过很多项目的错误码规范,包括尤其公有云厂商的错误规范。因为公有云作为一个技术提供方,本身就很偏重于技术,而且对外提供 SLA 很高的产品,包括接口,所以公有云厂商的错误码规范是非常值得借鉴的。


在调研了很多错误码实现后,我发现错误码的实现通常可以归结为以下 3 种实现(这里我直接忽略了,数目众多的,不合格错误码设计实现):

  1. 始终返回 200 HTTP 状态码,在 HTTP Body 中返回错误码及错误信息;
  2. 根据错误设置 HTTP 状态码,并在 HTTP Body 中返回错误码及简单的错误信息;
  3. 根据错误设置 HTTP 状态码,并在 HTTP Body 中返回错误码及详细的错误信息;


接下来,我来给你详细介绍下每种错误实现及优缺点:


始终返回 200 HTTP 状态码,在 HTTP Body 中返回错误码及错误信息


这是最常见的错误实现方式。

{
  "error": {
    "message": "Syntax error \"Field picture specified more than once. This is only possible before version 2.1\" at character 23: id,name,picture,picture",
    "type": "OAuthException",
    "code": 2500,
    "fbtrace_id": "xxxxxxxxxxx"
  }
}

上面是 Facebook 的错误返回实现。可以看到,在错误返回中,有以下 3 个字段:

  1. message:介绍了错误的返回详情;
  2. code:指定了错误码。这里的错误码是整数。通常 0代表接口成功,非0代表接口失败。
  3. type、fbtrace_id:其他一些返回信息。


在我的职业生涯中,一提到错误码,很多开发者,脑海中的第一个实现方式便是使用整数来实现。在 2017、2018 年我所开发的一些项目中,当时就采用了整数的错误码,后来证明这种错误码并不是最优解。


整数固然天然可以用来作为唯一 ID,但存在以下问题:

  1. 信息量少:看到整数 ID,你只是看到了一串数字而已,并不能从数字中分析出什么有用的信息;
  2. 可能返回敏感数据:很多整数 ID,是单调递增的,如果用整数 ID 来指代错误,可能会透传出系统中的错误数量,有时候这是敏感数据。


所以,现在很多项目都使用字符串来指定错误 ID(也就是错误码)。国内外各大公有云厂商,也都是采用字符串来指定错误码。例如,以下是腾讯云容器服务的一些错误码:


可以看到,通过字符串类型的错误码,可以很轻松的就知道错误的类别,这很有助于开发者判断失败的原因。


另外,这种错误返回方式有一个很大的弊端:不论请求成功与否,都需要解析错误返回,查看其中的业务码是否为 0,进而判断请求是否成功。也就是说,你不仅要判断 HTTP 状态码,还要 HTTP 返回体中的错误返回信息。


根据错误设置 HTTP 状态码,并在 HTTP Body 中返回错误码及简单的错误信息


下 面 是第 2 种错误返回方式。

HTTP/1.1 400 Bad Request
x-connection-hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
set-cookie: guest_id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Date: Thu, 01 Jun 2017 03:04:23 GMT
Content-Length: 62
x-response-time: 5
strict-transport-security: max-age=631138519
Connection: keep-alive
Content-Type: application/json; charset=utf-8
Server: tsa_b

{
  "errors": [
    {
      "code": 215,
      "message": "Bad Authentication data."
    }
  ]
}


可以看到,上面的错误返回设计中,当出现业务处理错误时,会设置对应的 HTTP 状态码。这种方式,相比于第 1 种方式,更方便客户端处理错误。因为,客户端代码中必然会判断 HTTP 状态码,当 HTTP 状态码为 200时,说明 HTTP Transport 层成功,也说明了业务层是成功的(因为,业务层如果不成功,会设置 HTTP 状态码非 200)。也就是说,在请求成功时,相比于第 1 种方法,少了解析并判断业务错误返回这一步。从而使得客户端处理错误返回更加简单高效。


一、根据错误设置 HTTP 状态码,并在 HTTP Body 中返回错误码及详细的错误信息


下面是第 3 种返回方式。

HTTP/1.1 400
Date: Thu, 01 Jun 2017 03:40:55 GMT
Content-Length: 276
Connection: keep-alive
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
X-Content-Type-Options: nosniff

{
  "SearchResponse": {
    "Version": "2.2",
    "Query": {
      "SearchTerms": "api error codes"
    },
    "Errors": [
      {
        "Code": 1001,
        "Message": "Required parameter is missing.",
        "Parameter": "SearchRequest.AppId",
        "HelpUrl": "http://msdn.microsoft.com/en-us/library/dd251042.aspx"
      }
    ]
  }
}

第 3 种方法,基于第 2 种方法进行了更多的错误信息返回。例如,当接口失败时,会在错误信息中返回以下类型的字段:

  1. 参数:失败发生的位置(参数名);
  2. URL:指定对应的解决方案文档。


当然,你还可以根据需要添加更多的返回信息。


二、如何设计合理的错误返回规范


综合上面的 3 种错误返回设计,这里给出一个我觉得好的设计方案。


首先,错误返回码采用字符串,采用字符串最大的好处是,通过错误返回码很多时候,能够直接知道错误返回的类型和原因,协助开发者进行问题修复。


接着,当业务接口失败时,为了方便客户端的错误处理,我们还要设置对应的 HTTP 状态码。而且 HTTP 状态码不建议映射的很多,因为越多的状态码,就意味着客户端越多的判断分支。


接着,返回简洁的错误信息。不太建议返回复杂的错误信息,例如:帮助文档。个人感觉只返回错误信息即可,例如:message字段。因为返回更多的错误信息字段,意味着开发侧更多的维护成本和客户端更多的理解成本。最重要的是,在我的项目开发经验中,几乎没有开发者会用心维护更多的错误字段,例如:接口失败时,返回的 HelpUrl。所以,这里简洁的指出问题发生的详情即可。


下面是 OneX 项目的错误返回规范,供你参考:

HTTP/1.1 409 status code 409
Content-Length: 152
Content-Type: application/json
Date: Mon, 18 Sep 2023 23:49:29 GMT
Trace-Id: 754942ead2c206da531d8cc727c215e7

{
  "metadata": {
    "username": "colin"
  },
  "message": "User already exists",
  "reason": "UserAlreadyExists",
  "code": 409
}


上面的错误返回中,当接口失败时,设置了 HTTP 状态码为 409,返回了错误原因,英文版本的 Code UserAlreadyExists以及错误详情 User already exists。


更多错误规范,请参考:OneX 项目错误规范


制定提交规范


当某个修复、功能、重构、优化等代码开发完成后,我们还需要提交我们的代码到 Git 仓库。代码提交操作每天都会发生。开发者 通过提交信息,能够简单、明了的知道某次代码变更的内容,这对于项目的排障、维护等帮助都很大。所以,想要开发一个高质量的 Go 项目,我们还需要制定提交规范。


什么是提交规范呢?直接点说就是执行 git commit时的提交信息编写规范。当前,的提交规范基本都是采用 Angular 提交规范。


Angular项目通常遵循特定的提交信息规范,以确保提交历史清晰、结构化,并且能够被自动化工具有效解析。这种提交信息规范通常被称为提交信息约定(Commit Message Convention)。典型的Angular提交规范实际上基于更广泛使用的Conventional Commits规范。


在 Angular 规范中,Commit Message 包含三个部分,分别是 HeaderBodyFooter,格式如下:

<type>[optional scope]: <description>
// 空行
[optional body]
// 空行
[optional footer(s)]


其中,header 是必需的,body 和 footer 可以省略。在以上规范中,scope 必须用括号 () 括起来, <type>[<scope>] 后必须紧跟冒号,冒号后必须紧跟空格,2 个空行也是必须的。


Header


Header 部分只有一行,包括三个字段:type(必选)、scope(可选)和 subject(必选)。type、scope、subject说明如下:

  1. 类型 type,表示提交变更的目的。常见的类型包括:



  1. 范围 scope,可选,它表示改动的影响范围,例如功能模块、页面、组件等。它有助于识别是哪一部分(或多个部分)代码库进行了更改。例如:
  2. fix(user-auth): correct auth token check
  3. feat(router): add navigation guard
  4. 描述 subject,是一行简单明了的变更描述,应该:
  5. 使用祈使句,例如 “change” 而不是 “changed” 或 “changes”;
  6. 不要以大写字母开头;
  7. 末尾不要加句号。


Body


Header 对 commit 做了高度概括,可以方便我们查看 Commit Message。那我们如何知道具体做了哪些变更呢?答案就是,可以通过 Body 部分,它是对本次 commit 的更详细描述,是可选的。


正文部分是对变更的详细说明。它包括变更的动机和目的,以及变更的具体描述。正文应该尽量帮助其他开发人员了解这次提交的意图和具体实现细节。每行最多72个字符。例如:

fix(auth): ensure tokens are validated correctly

Previously, the token validation logic was only checking expiration dates.
This commit adds additional claims verification to ensure the tokens were
actually issued by our server.


Footer


Footer 部分不是必选的,可以根据需要来选择,主要用来说明本次 commit 导致的后果。在实际应用中,Footer 通常包括:

  1. 问题追踪(Issue):例如 Closes #1234 表示关闭了编号为 1234 的问题。
  2. BREAKING CHANGE:列出重大变更。


Footer 格式如下:

BREAKING CHANGE: <breaking change summary>
// 空行
<breaking change description + migration instructions>
// 空行
// 空行
Fixes #<issue number>


例如:

fix(auth): ensure tokens are validated correctly

Previously, the token validation logic was only checking expiration dates.
This commit adds additional claims verification to ensure the tokens were
actually issued by our server.

Closes #1234

BREAKING CHANGE: The 'validateToken' method now requires an additional 'issuer' parameter.

如果这次提交解决了特定的问题或是进行了一些重大变更(如不兼容变更),可以在脚注中提及。通常包括:

  1. 问题追踪(Issue):例如 Closes #1234 表示关闭了编号为 1234 的问题。
  2. BREAKING CHANGE:列出重大变更。


更多提交个规范,请参考:OneX 项目提交规范


制定版本号规范


版本号规范在 Go 项目开发中,并不是必须的,但强烈建议,项目能够制定并遵循版本号规范。因为,版本号及规范,可以让项目的发布情况更加清晰,并且有助于开发人员进行排障,在某些情况下,还可以进行发布校验,使发布更加安全。


当前的版本号基本都是遵循语义化版本规范(SemVer)


语义化版本规范介绍


语义化版本规范(SemVer,Semantic Versioning)是 GitHub 起草的一个具有指导意义的、统一的版本号表示规范。它规定了版本号的表示、增加和比较方式,以及不同版本号代表的含义。


在这套规范下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。语义化版本格式为:主版本号.次版本号.修订号(X.Y.Z),其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。版本号可按以下规则递增:

  1. 主版本号(MAJOR):当做了不兼容的 API 修改;
  2. 次版本号(MINOR):当做了向下兼容的功能性新增及修改。这里有个不成文的约定需要你注意,偶数为稳定版本,奇数为开发版本;
  3. 修订号(PATCH):当做了向下兼容的问题修正。


例如,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


注意,先行版本号和编译版本号只能是字母、数字,且不可以有空格


语义化版本控制规范


语义化版本控制规范比较多,这里我给你介绍几个比较重要的。如果你需要了解更详细的规范,可以参考 这个链接 的内容。

  1. 标记版本号的软件发行后,禁止改变该版本软件的内容,任何修改都必须以新版本发行。
  2. 主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变,这样的公共 API 不应该被视为稳定版。1.0.0 的版本号被界定为第一个稳定版本,之后的所有版本号更新都基于该版本进行修改。
  3. 修订号 Z(x.y.Z | x > 0)必须在只做了向下兼容的修正时才递增,这里的修正其实就是 Bug 修复。
  4. 次版本号 Y(x.Y.z | x > 0)必须在有向下兼容的新功能出现时递增,在任何公共 API 的功能被标记为弃用时也必须递增,当有改进时也可以递增。其中可以包括修订级别的改变。每当次版本号递增时,修订号必须归零。
  5. 主版本号 X(X.y.z | X > 0)必须在有任何不兼容的修改被加入公共 API 时递增。其中可以包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须归零。


如何确定版本号?


说了这么多,我们到底该如何确定版本号呢?这里我给你总结了这么几个经验。

第一,在实际开发的时候,我建议你使用 0.1.0 作为第一个开发版本号,并在后续的每次发行时递增次版本号。

第二,当我们的版本是一个稳定的版本,并且第一次对外发布时,版本号可以定为 1.0.0。

第三,当我们严格按照 Angular commit message 规范提交代码时,版本号可以这么来确定:

  1. fix 类型的 commit 可以将修订号 +1;
  2. feat 类型的 commit 可以将次版本号 +1;
  3. 带有 BREAKING CHANGE 的 commit 可以将主版本号 +1。


更多版本号规范,请参考:OneX项目版本号规范