关于规范

说明

这是一套严格的 Java 团队开发规范。

目的

规范化可以让我们的工程师训练有素,以此来提高软件交付的质量。
另一方面,团队的项目经验能够得到继承,在实战中不断进行总结和摸索,找到兼备开发效率、程序执行效率、扩展性和安全性的最佳实践,最终实现团体智慧的延续和精进。

优势

规范有以下优点:

  • 高效编码 —— 避免了过多的选择造成的『决策时间』浪费;
  • 风格统一 —— 最大程度统一了开发团队成员代码书写风格和思路,代码阅读起来如出一辙;
  • 减少错误 —— 减小初级工程师的犯错几率;
  • 提高团队战斗力 —— 在多人协作的工作中,做到 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

在这份规范里,有些内容里会解释『这样做的理由』,这样做的目的是为了达成共识。

请不要以此『理由』的准确性来怀疑规范的权威性,规范就是规范,可以讨论改正,但在执行的时候 必须 严格遵守。

请把『团队项目开发』想象就是在行军打仗,对于规范要绝对服从。要有大局观,做到团结一致,把个人的喜好放一边,把整个团队的执行效率放在第一位。

项目规范

版本选择和更新

版本选择

选择稳定版本,如Java8。

版本更新

避免技术债务

对于长期维护的项目,要特别注意技术债务的累积。主动做到尽早更新框架,而不是等被逼无奈才去升级。
升级框架好处多多。从程序的执行效率上来讲,新版本的框架也会不断优化、提高运行速度。升级框架相当于免费让程序得到加速,何乐而不为。
从团队技术能力上讲,主动使用新框架,意味着要学习新东西,可以养成团队对新技术灵敏的嗅觉。

自动化测试

项目需要具备一定的测试代码,更新框架时才能更有自信。所以,从今天开始为你的项目写测试代码吧。

开发和线上环境

环境说明

一般情况下,一个项目 应该 有以下三个基本的项目环境:

  • dev - 开发环境
  • test - 测试环境
  • prod - 线上生产环境

    Prod 生产环境

    出于安全考虑,线上环境 必须 只开放以下端口:

  • 80 HTTP

  • 443 HTTPS
  • 22 SSH

    Dev 开发环境

    统一使用域名 .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 文档:

API 版本控制

所有的 API,早期设计时都 必须 考虑版本控制。
随着业务的发展,需求的不断变化,API 的迭代是必然的,很可能当前版本正在使用,而我们就得开发甚至上线一个不兼容的新版本,为了让旧用户可以正常使用,为了保证开发的顺利进行,我们需要控制好 API 的版本。
将版本号直接加入 URL 中:

  1. https://api.example.com/v1
  2. https://api.example.com/v2
  3. https://api.example.com/v3

RESTful API

开发 API 时,必须使用 RESTful 规范来架构 API。
具体规则下面罗列出来。

  1. 使用 URL 定位资源
    必须使用 URL 定位资源的规则。
    在 RESTful 的架构中,所有的一切都表示资源,每一个 URL 都代表着一种资源,资源应当是一个名词,而且大部分情况下是名词的复数,尽量不要在 URL 中出现动词。

先来看看 GitHub 的 例子:

  1. GET /issues 列出所有的 issue
  2. GET /orgs/:org/issues 列出某个项目的 issue
  3. GET /repos/:owner/:repo/issues/:number 获取某个项目的某个 issue
  4. POST /repos/:owner/:repo/issues 为某个项目创建 issue
  5. PATCH /repos/:owner/:repo/issues/:number 修改某个 issue
  6. PUT /repos/:owner/:repo/issues/:number/lock 锁住某个 issue
  7. DELETE /repos/:owner/:repo/issues/:number/lock 解锁某个 issue

例子中冒号开始的代表变量,例如 /repos/summerblue/larabbs/issues

在 GitHub 的实现中,我们可以总结出:

  • 资源的设计可以嵌套,表明资源与资源之间的关系。
  • 大部分情况下我们访问的是某个资源集合,想得到单个资源可以通过资源的 id 或 number 等唯一标识获取。
  • 某些情况下,资源会是单数形式,例如某个项目某个 issue 的锁,每个 issue 只会有一把锁,所以它是单数。

❌ 错误的例子:

  1. POST https://api.example.com/createTopic
  2. GET https://api.example.com/topic/show/1
  3. POST https://api.example.com/topics/1/comments/create
  4. POST https://api.example.com/topics/1/comments/100/delete

✅ 正确的例子:

  1. POST https://api.example.com/topics
  2. GET https://api.example.com/topics/1
  3. POST https://api.example.com/topics/1/comments
  4. DELETE https://api.example.com/topics/1/comments/100
  1. 使用 HTTP 动词描述操作
    必须使用 HTTP 动词来描述操作,绝不单一的使用 POST 来处理所有逻辑。

HTTP 设计了很多动词,来表示不同的操作,RESTful 很好的利用的这一点,我们需要正确的使用 HTTP 动词,来表明我们要如何操作资源。

先来解释一个概念,幂等性,指一次和多次请求某一个资源应该具有同样的副作用,也就是一次访问与多次访问,对这个资源带来的变化是相同的。

常用的动词及幂等性

动词 描述 是否幂等
GET 获取资源,单个或多个
POST 创建资源
PUT 更新资源,客户端提供完整的资源数据
PATCH 更新资源,客户端提供部分的资源数据
DELETE 删除资源

为什么 PUT 是幂等的而 PATCH 是非幂等的,因为 PUT 是根据客户端提供了完整的资源数据,客户端提交什么就替换什么,而 PATCH 有可能是根据客户端提供的参数,动态的计算出某个值,例如每次请求后资源的某个参数减 1,所以多次调用,资源会有不同的变化。

另外需要注意的是,GET 请求对于资源来说是不安全的,绝不 通过 GET 请求改变(更新或创建)资源。
真实使用中,为了方便统计类的数据,会有一些例外情况,例如帖子详情,记录访问次数,每调用一次,访问次数 +1。这种情况下可以考虑页面展示成功后,再次调用一个 POST 请求去更新阅读数。

  1. 使用 HTTP 状态码进行通讯
    必须利用 HTTP 状态码和客户端进行通讯。
    有一些 API 的设计,不论接口的状态成功与否,都会返回 200 ,然后使用自定的状态码,例如说 :
    1. {
    2. // 数据不存在
    3. code: 30404
    4. }
    这种方法是不可取的。

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 信息包含两部分,客户端信息 + 版本,使用斜杆分隔:

  1. User-Agent: Mixin Bot iOS/2.1.37
  2. User-Agent: Mixin Bot Android/2.1.22
  3. User-Agent: MixPay PHP SDK/2.1.22
  4. 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 必须 使用复数形式,如:

  1. /photos/create
  2. /photos/{photo}

错误的例子如:

  1. /photo/create
  2. /photo/{photo}