关于规范
说明
目的
规范化可以让我们的工程师训练有素,以此来提高软件交付的质量。
另一方面,团队的项目经验能够得到继承,在实战中不断进行总结和摸索,找到兼备开发效率、程序执行效率、扩展性和安全性的最佳实践,最终实现团体智慧的延续和精进。
优势
规范有以下优点:
- 高效编码 —— 避免了过多的选择造成的『决策时间』浪费;
- 风格统一 —— 最大程度统一了开发团队成员代码书写风格和思路,代码阅读起来如出一辙;
- 减少错误 —— 减小初级工程师的犯错几率;
- 提高团队战斗力 —— 在多人协作的工作中,做到 1 +1 大于 2。
开发哲学
因为篇幅原因,本规范无法涉及到项目里每一块代码的编写标准,所以此处重点说明下此规范遵循的『开发哲学』,开发中请把其当做指明灯,来指引你做决策:
- DRY ——「Don’t Repeat Yourself」不写重复的逻辑代码;
- 约定俗成 ——「Convention Over Configuration」,优先选择框架以及社区提倡的做法,不过度配置;
- KISS ——「Keep it Simple, Stupid」提倡简单易读的代码,不写高深、晦涩难懂的代码,不过度设计;
- 主厨精选 —— 让有经验的人来为你选择方案,不独创方案;
- 官方提倡 —— 优先选择官方推崇的方案。
设计理念
以下是一些优秀的『程序设计理念』:
- MVC - Model, View, Controller ,以 MVC 为核心,严格控制 Controller 的可读性和代码行数;
- Restful - 利用『资源化概念』和标准的 HTTP 动词来组织你的程序。
在此规范中,我们会将使用这两套理念作为程序设计基础。
这些设计理念为我们设计程序提供了依据,遵循这些理念,能让程序变得清晰易读。
注意事项
1、过于灵活是一件糟糕的事情。
对于框架设计而言,灵活是件好事,能提供给开发者不同的选项,能让框架适用更多的用户场景。但对于团队开发来说,大部分时候,更多的选项反而是累赘。因为每个人都可能写出不一样的代码,这无疑增加了项目维护的难度,影响效率。
2、能愿动词
为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:
- 必须(Must)—— 只能这样子做,请无条件遵循,没有别的选项;
- 绝不(Must Not)—— 严令禁止,在任何情况下都不能这样做;
- 应该(Should)—— 强烈建议这样做,但是不强求;
- 不应该(Should Not) —— 强烈建议不这样做,但是不强求;
- 可以(May) —— 选择性高一点,在这个文档内,此词语使用较少;
参考:RFC 2119
在这份规范里,有些内容里会解释『这样做的理由』,这样做的目的是为了达成共识。
请不要以此『理由』的准确性来怀疑规范的权威性,规范就是规范,可以讨论改正,但在执行的时候 必须 严格遵守。
请把『团队项目开发』想象就是在行军打仗,对于规范要绝对服从。要有大局观,做到团结一致,把个人的喜好放一边,把整个团队的执行效率放在第一位。
项目规范
版本选择和更新
版本选择
版本更新
避免技术债务
对于长期维护的项目,要特别注意技术债务的累积。主动做到尽早更新框架,而不是等被逼无奈才去升级。
升级框架好处多多。从程序的执行效率上来讲,新版本的框架也会不断优化、提高运行速度。升级框架相当于免费让程序得到加速,何乐而不为。
从团队技术能力上讲,主动使用新框架,意味着要学习新东西,可以养成团队对新技术灵敏的嗅觉。
自动化测试
项目需要具备一定的测试代码,更新框架时才能更有自信。所以,从今天开始为你的项目写测试代码吧。
开发和线上环境
环境说明
一般情况下,一个项目 应该 有以下三个基本的项目环境:
- dev - 开发环境
- test - 测试环境
-
Prod 生产环境
出于安全考虑,线上环境 必须 只开放以下端口:
80 HTTP
- 443 HTTPS
-
Dev 开发环境
Test 测试环境
除了域名等其他独立应用配置以外,环境 必须 跟 Prod 保持高度一致性,可以的话 应该 与 Prod 使用同台机器。
软件版本
服务器:Centos 7
- 数据库:MySQL 5
- JDK:1.8
项目文档编写规范
说明
每一个项目都 必须 包含一个 readme.md 文件,readme 里书写这个项目的简单信息。作用主要有两个,一个是团队新成员可从此文件中快速获悉项目大致情况,另一个是部署项目时可以作为参考。
1. 排版规范
文档页面排版 必须 遵循 中文文案排版指北 ,在此基础上:
- 中文文档请使用全角标点符号;
- 必须 遵循 Markdown 语法,勿让代码显示错乱;
- 原文中的双引号(“”)请代换成中文的引号(『』符号怎么打出来见 这里)。
所有的 「加亮」、「加粗」和「链接」都需要在左右保持一个空格。
2. 行文规范
readme.md 文档 应该 包含以下内容:
项目概述 - 介绍说明项目的一些情况,类似于简单的产品说明,简单的功能描述,项目相关链接等,500 字以内;
- 运行环境 - 运行环境说明,系统要求等信息;
- 开发环境部署 - 一步一步引导说明,保证项目新成员能最快速的,没有歧义的部署好开发环境;
- 架构说明 - 最好能有服务器架构图,从用户浏览器请求开始,包括后端缓存服务使用等都描述清楚(主要体现为软件的使用),配合「运行环境」区块内容,可作为线上环境部署的依据;
-
工具统一
工具的统一,是为了方便工作流的统一,还有工具使用经验的传承。
团队里的成员,经常需要互相使用对方电脑来讨论问题、查看某段代码、Debug 某个功能,工具统一起来后,你会发现,虽然是别人的电脑,工具使用起来是熟悉的,用起来就跟自己的电脑一样顺手,自然的工作效率就会提高。 硬件:RMBP 13 寸
- 系统:Mac 版本 10.10 以上
- 编辑器:Idea、VS code
- 编辑器代码格式化:EditorConfig
- 命令行工具:iTerm2
- 浏览器:Chrome
- 虚拟机:VirtualBox
- MySQL 数据库查询工具:TablePlus
- Redis 管理工具:TablePlus
- MongoDB 管理工具:TablePlus
编码规范
代码风格
环境变量
API 设计规范
参考资料
首先请熟悉以下的两个文档:
API 设计上有无法抉择的地方,应该参考 GitHub 的 API 文档:
- Resources in the REST API - GitHub Docs
GitHub 的 RESTful API 设计是业内比较知名的。
API 版本控制
所有的 API,早期设计时都 必须 考虑版本控制。
随着业务的发展,需求的不断变化,API 的迭代是必然的,很可能当前版本正在使用,而我们就得开发甚至上线一个不兼容的新版本,为了让旧用户可以正常使用,为了保证开发的顺利进行,我们需要控制好 API 的版本。
将版本号直接加入 URL 中:
https://api.example.com/v1
https://api.example.com/v2
https://api.example.com/v3
RESTful API
开发 API 时,必须使用 RESTful 规范来架构 API。
具体规则下面罗列出来。
- 使用 URL 定位资源
必须使用 URL 定位资源的规则。
在 RESTful 的架构中,所有的一切都表示资源,每一个 URL 都代表着一种资源,资源应当是一个名词,而且大部分情况下是名词的复数,尽量不要在 URL 中出现动词。
先来看看 GitHub 的 例子:
GET /issues 列出所有的 issue
GET /orgs/:org/issues 列出某个项目的 issue
GET /repos/:owner/:repo/issues/:number 获取某个项目的某个 issue
POST /repos/:owner/:repo/issues 为某个项目创建 issue
PATCH /repos/:owner/:repo/issues/:number 修改某个 issue
PUT /repos/:owner/:repo/issues/:number/lock 锁住某个 issue
DELETE /repos/:owner/:repo/issues/:number/lock 解锁某个 issue
例子中冒号开始的代表变量,例如 /repos/summerblue/larabbs/issues
在 GitHub 的实现中,我们可以总结出:
- 资源的设计可以嵌套,表明资源与资源之间的关系。
- 大部分情况下我们访问的是某个资源集合,想得到单个资源可以通过资源的 id 或 number 等唯一标识获取。
- 某些情况下,资源会是单数形式,例如某个项目某个 issue 的锁,每个 issue 只会有一把锁,所以它是单数。
❌ 错误的例子:
POST https://api.example.com/createTopic
GET https://api.example.com/topic/show/1
POST https://api.example.com/topics/1/comments/create
POST https://api.example.com/topics/1/comments/100/delete
✅ 正确的例子:
POST https://api.example.com/topics
GET https://api.example.com/topics/1
POST https://api.example.com/topics/1/comments
DELETE https://api.example.com/topics/1/comments/100
- 使用 HTTP 动词描述操作
必须使用 HTTP 动词来描述操作,绝不单一的使用 POST 来处理所有逻辑。
HTTP 设计了很多动词,来表示不同的操作,RESTful 很好的利用的这一点,我们需要正确的使用 HTTP 动词,来表明我们要如何操作资源。
先来解释一个概念,幂等性,指一次和多次请求某一个资源应该具有同样的副作用,也就是一次访问与多次访问,对这个资源带来的变化是相同的。
常用的动词及幂等性
动词 | 描述 | 是否幂等 |
---|---|---|
GET | 获取资源,单个或多个 | 是 |
POST | 创建资源 | 否 |
PUT | 更新资源,客户端提供完整的资源数据 | 是 |
PATCH | 更新资源,客户端提供部分的资源数据 | 否 |
DELETE | 删除资源 | 是 |
为什么 PUT 是幂等的而 PATCH 是非幂等的,因为 PUT 是根据客户端提供了完整的资源数据,客户端提交什么就替换什么,而 PATCH 有可能是根据客户端提供的参数,动态的计算出某个值,例如每次请求后资源的某个参数减 1,所以多次调用,资源会有不同的变化。
另外需要注意的是,GET 请求对于资源来说是不安全的,绝不 通过 GET 请求改变(更新或创建)资源。
真实使用中,为了方便统计类的数据,会有一些例外情况,例如帖子详情,记录访问次数,每调用一次,访问次数 +1。这种情况下可以考虑页面展示成功后,再次调用一个 POST 请求去更新阅读数。
- 使用 HTTP 状态码进行通讯
必须利用 HTTP 状态码和客户端进行通讯。
有一些 API 的设计,不论接口的状态成功与否,都会返回 200 ,然后使用自定的状态码,例如说 :
这种方法是不可取的。{
// 数据不存在
code: 30404
}
HTTP 状态码是行业标准,意味着成千上万开发者都在认同和使用这套规则,意味着他们写出来的 HTTP 通讯程序(类库)也在使用这套规则。所以没有必要,也不该重新发明自己的一套规则。
HTTP 提供了丰富的状态码供我们使用,正确的使用状态码可以让响应数据更具可读性。
- 200 OK - 对成功的 GET、PUT、PATCH 或 DELETE 操作进行响应。也可以被用在不创建新资源的 POST 操作上
- 201 Created - 对创建新资源的 POST 操作进行响应。应该带着指向新资源地址的 Location 头
- 202 Accepted - 服务器接受了请求,但是还未处理,响应中应该包含相应的指示信息,告诉客户端该去哪里查询关于本次请求的信息
- 204 No Content - 对不会返回响应体的成功请求进行响应(比如 DELETE 请求)
- 304 Not Modified - HTTP 缓存 header 生效的时候用
- 400 Bad Request - 请求异常,比如请求中的 body 无法解析
- 401 Unauthorized - 没有进行认证或者认证非法
- 403 Forbidden - 服务器已经理解请求,但是拒绝执行它
- 404 Not Found - 请求一个不存在的资源
- 405 Method Not Allowed - 所请求的 HTTP 方法不允许当前认证用户访问
- 410 Gone - 表示当前请求的资源不再可用。当调用老版本 API 的时候很有用
- 415 Unsupported Media Type - 如果请求中的内容类型是错误的
- 422 Unprocessable Entity - 用来表示校验错误
- 429 Too Many Requests - 由于请求频次达到上限而被拒绝访问
强制 User-Agent
强制客户端在请求时,必须 发送 User-Agent 信息。
User-Agent 信息包含两部分,客户端信息 + 版本,使用斜杆分隔:
User-Agent: Mixin Bot iOS/2.1.37
User-Agent: Mixin Bot Android/2.1.22
User-Agent: MixPay PHP SDK/2.1.22
User-Agent: MixPay GO SDK/2.1.22
API 后端接收到 User-Agent 数据后可以暂时不做处理,但是后续有特殊的业务需求时,可以针对某个客户端具体到版本,进行特殊的数据处理。
常见的使用场景,是废弃客户端:例如一个银行 APP,升级了交易时的加密算法,低于 5.0 版本的客户端因为安全原因,必须废弃。针对此情况,可通过后端 API 判断 User-Agent 标头,对低于 5.0 的版本的客户端请求,返回专属的数据,如 APP 首页的第一个 Banner 显示请升级客户端,安全升级无法使用的提示。
现实生产中,有些客户端用户会关闭系统的应用自动更新功能,多版本客户端是无法避免的问题。有了 User-Agent ,我们可以更加灵活的做针对性处理。
单数 or 复数?
资源路由路由 URI 必须 使用复数形式,如:
/photos/create
/photos/{photo}
错误的例子如:
/photo/create
/photo/{photo}