程序开发规范手册 v1.0
版本号 |
开发团队 |
修改时间 | 备注 |
1.0 | burnlord | 2021.07.30 | 初版 |
前言
本手册为聚科智能(伯恩洛德工作室),程序开发标准规范手册。本手册以本工作室长期积累经验进行总结,并将不断的进行维护、修订。
本手册参考了《阿里巴巴 Java开发手册》 ,本手册内容中所属《阿里巴巴 Java开发手册》 内容的归其版权所有。
任何组织、机构、个人都可以借鉴使用及二次修订本手册来形成自己的手册,但不可用于任何商业用途,且需标注以下引用声明:
本手册编订借鉴了《伯恩洛德(逐焰者)工作室开发规范手册》 (burnlord.com)。
一、编程规范
(一)命名风格
1.【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例:_name / __name / $Object / name_ / name$ / Object$
2.【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用。
正例:alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。
反例:DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3
3.【强制】类名使用 UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:DO / BO /DTO / VO / AO
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
4.【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase风格,必须遵从驼峰形式。
正例: localValue / getHttpMessage() / inputUserId
5.【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT
反例:MAX_COUNT
6.【强制】抽象类命名使用 Abstract或 Base开头;异常类命名使用 Exception结尾;测试类命名以它要测试的类的名称开始,以 Test结尾。
7.【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例: 应用工具类包名为 com.alibaba.open.util、类名为 MessageUtils(此规则参考spring的框架结构)
8.【强制】杜绝完全不规范的缩写,避免望文不知义。随意缩写严重降低了代码的可阅读性,甚至有国际缩写的单词时也不建议使用,如advertisement,缩写为AD;除非为国际组织等完整名称过于长的,如National Basketball Association,可缩写为NBA。
反例:AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi
9.【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。
正例:从远程仓库拉取代码的类命名为 PullCodeFromRemoteRepository。
反例:变量 int a; 的随意命名方式。
10.【推荐】如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例:public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
11.【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
正例:
接口方法签名:void f();
接口基础常量表示:String COMPANY = “alibaba”;
反例:接口方法定义:public abstract void f();
12.【参考】枚举类名建议带上 Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
正例:枚举名字为 ProcessStatusEnum的成员名称:SUCCESS / UNKOWN_REASON。
(二)常量定义
1.【强制】不允许任何魔法值(即未经定义的常量)直接出现在代码中。
反例:String key = “Id#taobao_” + tradeId;
cache.put(key, value);
2.【强制】long或者 Long初始赋值时,使用大写的 L,不能是小写的 l,小写容易跟数字 1混淆,造成误解。
说明:Long a = 2l; 写的是数字的 21,还是 Long型的 2?
3.【推荐】不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。
说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts下;系统配置相关常量放在类 ConfigConsts下。
(三)代码格式
1.【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有 else等代码则不换行;表示终止的右大括号后必须换行。
2.【强制】 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。详见
反例:if (空格 a == b空格)
3.【强制】if/for/while/switch/do等保留字与括号之间都必须加空格。
4.【强制】任何二目、三目运算符的左右两边都需要加一个空格。
说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
5.【强制】采用 4个空格缩进,禁止使用 tab字符。
说明:如果使用 tab缩进,必须设置 1个 tab为 4个空格。IDEA设置 tab为 4个空格时,请勿勾选 Use tab character;而在 eclipse中,必须勾选 insert spaces for tabs。
6.【强制】注释的双斜线与注释内容之间有且仅有一个空格。
正例:// 注释内容,注意在//和注释内容之间有一个空格。
7.【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
1) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
2) 运算符与下文一起换行。
3) 方法调用的点符号与下文一起换行。
4) 方法调用时,多个参数,需要换行时,在逗号后进行。
5) 在括号前不要换行,见反例。
8.【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
正例:下例中实参的”a”,后边必须要有一个空格。
method(“a”, “b”, “c”);
9.【推荐】没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐。
10.【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
说明:没有必要插入多个空行进行隔开。
(四)控制语句
1.【强制】在一个 switch块内,每个 case要么通过 break/return等来终止,要么注释说明程序将继续执行到哪一个 case为止;在一个 switch块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。
2.【强制】在 if/else/for/while/do语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式:if (condition) statements;
3.【推荐】函数中用if做是否执行某一大片代码时,不满足条件进行返回与满足条件的执行不做else的分割。
说明:
if(sum!=0){
// 满足要求执行的代码。如果此处代码很多,而且也包括if等嵌套,那么此
处就相当于没必要的多了一次控制语句的嵌套,而控制语句嵌套越多,程序的可读性越差。
}else{
return 101; // 数量不满足要求,返回。
}
正例:
// 数量不满足要求,不向下处理,返回。
if(sum==0){
return 101;
}
// 满足要求,继续处理,代码需要就近解释原则,不符合条件的逻辑代码通常较少,先处理。而从此处向下的代码都是sum条件成立的代码了,不用思考其他的了。
……
4.【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch操作(这个 try-catch是否可以移至循环体外)。
5.【推荐】接口入参保护,这种场景常见的是用于做批量操作的接口。
6.【参考】下列情形,需要进行参数校验:
1) 调用频次低的方法。
2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是 RPC/API/HTTP接口。
5) 敏感权限入口。
7.【参考】下列情形,不需要进行参数校验:
1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。
2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO层与 Service层都在同一个应用中,部署在同一台服务器中,所以 DAO的参数校验,可以省略。
3) 被声明成 private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
(五)OOP规约
1.【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
2.【强制】不能使用过时的类或方法。
3.【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
4.【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。
(六)注释规约
1.【强制】类、类属性、类方法的注释必须使用 Javadoc规范,使用/**内容*/格式,不得使用// xxx方式。
2.【强制】所有的抽象方法(包括接口中的方法)必须要用 Javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
3.【强制】所有的类都必须添加创建者和创建日期。
4.【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
5.【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。
6.【推荐】与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。
反例:“TCP连接超时”解释成“传输控制协议连接超时”,理解反而费脑筋。
7.【推荐】代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后,就失去了导航的意义。
8.【参考】谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。
说明:代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。
9.【参考】对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。
10.【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的
11.【参考】特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
二、MySQL数据库
(一)建表规约
1.【强制】表达是与否概念的字段,必须使用 is_xxx的方式命名,数据类型是 unsigned tinyint( 1表示是,0表示否),或bit。
说明:任何字段如果为非负数,必须是 unsigned。
正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。
2.【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name
3.【强制】表名不使用复数名词。
说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO类名也是单数形式,符合表达习惯。
4.【强制】禁用保留字,如 desc、range、match、delayed等,请参考 MySQL官方保留字。
5.【强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index的简称。
6.【强制】小数类型为 decimal,禁止使用 float和 double。
说明:float和 double在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal的范围,建议将数据拆成整数和小数分开存储。
7.【强制】如果存储的字符串长度几乎相等,使用 char定长字符串类型。
8.【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
9.【强制】表必备三字段:id, time_create, time_edit。
说明:
其中 id必为主键,单表时自增、步长为 1。
time_creat,类型为 timestamp,表示创建时间,可设为默认值:CURRENT_TIMESTAMP
time_edit,类型为 timestamp,表最近修改时间,可设为默认值CURRENT_TIMESTAMP,自动更新。
10.【推荐】表的命名最好是加上“业务名称_表的作用”。
正例:alipay_task / force_project / trade_config
11.【推荐】库名与应用名称尽量一致。
12.【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
13.【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
1)不是频繁修改的字段。
2)不是 varchar超长字段,更不能是 text字段。
正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。
14.【推荐】单表行数超过 500万行或者单表容量超过 2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
15.【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
(二)索引规约
1.【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
2.【强制】超过三个表禁止 join。需要 join的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
说明:即使双表 join也要注意表索引、SQL性能。
3.【强制】在 varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
4.【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tree的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
5.【推荐】如果有 order by的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后E6进行划分,方便根据页面来找图片。
如果应用调用本地的静态图片不多,可以不用过于细致分类,但如果考虑到以后应用的升级、更新等依然会用到大量图片,那这里要进行细致分类划分。
图片等,除非文件很小的图标等,不然尤其是背景图片等建议使用网络地址(CDN的云图片),请一定保证网络图片的地址稳定性,不可使用第三方可能存在广告的图床!!!
(四)命名规范
- 目录、文件名称不可以过长;
- 不可以使用缩写(NBA等全称过长的机构除外)、中文、横线(-)、下划线、字符等进行命名;
- 命名尽量不包含数字,如不建议:vue;
- 允许的便捷表示:
2=to | sql2xls() | sql转xls |
4=for | order4student() | 关于学生的订单 |
- 尽量使用单个单词进行命名,如果单个单词无法表达,可以目录分层,如order/count;如果目录下确定内容过少,可以不分层目录,使用驼峰命名,如orderCount。
- 如果目录下包含子功能目录,则此目录下必须有一个与目录同名的文件作为入口主页,如:me/me.vue,vue则为这个目录下的主要入口文件,如果其为末尾目录可以没有同名文件,但是需要一个符合其环境的文件作为入口。
正例 | me
me.vue phone.vue |
me.vue,是个人信息的一览页面,其有很多个子信息页面,那么me.vue必须存在;
手机页面属于个人中心子信息页面。 |
正例 | me
order add.vue del.vue edit.vue list.vue |
order为me目录的子功能且是末尾目录,那order目录下可以不存在order.vue。
在实际环境中,我们点击跳转order的链接,会进入order列表,order列表页存在创建order、删除order、编辑order的页面跳转,那么order列表(list.vue)显然是入口文件,此时order目录下可以不存在order.vue,而是以list.vue命名,因为order列表是实际的入口页面,显然list.vue更可以准确的表达这个页面的内容。 |
- 某一功能目录下的有很多子功能的页面文件,如果所有子功能都是单一的一个页面文件,可以不为每个子功能独立创建目录,如果其中任何一个子功能是非单一的页面,则必须为此子功能创建一个目录,且其他的所有的子功能无论是否只有一个页面都要有独立的目录。如:
正例 | me
me.vue phone.vue order.vue |
me.vue,功能页包含跳转到phone.vue、order.vue的子功能页的链接,且phone、order都仅有一个相关页面,可以不为order、phone建独立目录。 |
正例 | me
me.vue order list.vue add.vue phone list.vue
|
子页面order、phone有细分页面,为所有子页面独立建目录 |
反例 | me
me.vue phone.vue order list.vue add.vue |
不可以,order是子功能目录,虽然phone仅有一个相关页面,但也要为其创建目录。 |
反例 | me
me.vue phoneList.vue phoneAdd.vue
|
不可以,phone相关的页面有多个,要为phone建一个目录,其目录下放:list.vue、add.vue。
这样归类的页面才不乱。 |
四、接口设计
以下以英文名为“iedkp”项目接口为例,此处皆以前后端分离的设计提供参考。
(一)前缀格式
/iedkp/wx/mini/api/v1
/iedkp/wx/mp/api/v1
/iedkp/h5/api/v1
/iedkp/admin/api/v1
(二)接口来源
格式:/iedkp/api/v1,v1代表版本1
说明:这样有利与接口版本的维护,尤其在接口升级但历史有前端业务还需要使用旧接口时,这时可以创建v2下的新接口,原接口功能还可以保持不变。
五、各语言专属规约
六、日志
七、安全规范
1.【强制】隶属于用户个人的页面或者功能必须进行权限控制校验。
说明:防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改他人的订单。
2.【强制】用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
说明:查看个人手机号码会显示成:158****9119,隐藏中间 4位,防止隐私泄露。
3.【强制】用户输入的 SQL参数严格使用参数绑定或者 METADATA字段值限定,防止 SQL注入,禁止字符串拼接 SQL访问数据库。
4.【强制】用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
- pagesize过大导致内存溢出
- 恶意order by导致数据库慢查询
- 任意重定向
- SQL注入反序列化注入
- 正则输入源串拒绝服务ReDoS
Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
5.【强制】禁止向 HTML页面输出未经安全过滤或未正确转义的用户数据。
6.【强制】表单、AJAX提交必须执行 CSRF安全过滤。
说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在CSRF漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户不知情情况下对数据库中用户参数进行相应修改。
7.【强制】在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。
说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
8.【推荐】发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。
评论 (0)