软件工程

《阿里巴巴 Java 开发手册》读书笔记

2018-04-18  本文已影响0人  我没有三颗心脏

偶然看到阿里巴巴居然出书了???趁着满减活动(节约节约....)我赶紧买来准备看看,刚拿到的时候掂量了好多下,总觉得商家给我少发了一本书,结果打开才知道..原来这本书这么小....

编码规范的重要性

别人都说我们是搬砖的码农,但我们知道自己是追求个性的艺术家。也许我们不会过多在意自己的外表和穿着,但在我们不羁的外表下,骨子里追求着代码的美、系统的美、设计的美,代码规范其实就是一个对程序美的定义。—— 引自 序

如果有一天在我们的项目中看到了这样的代码:

或者是这样的代码:

这样美不美呢?或许看着是还挺美的,但是如果需要修改,是不是人傻啦?

那这样的代码呢?

作为一个对自己有一定要求的程序猿,是不是第一反应就是:

规范不一,就会像下图中的小鸭和小鸡对话一样,语言不通,一脸囧相。鸡同鸭讲也恰恰形容了人与人之间沟通的痛点,自说自话,无法达成一致意见。再举一个生活中的例子,交通规则靠左行驶还是靠右行驶,两者孰好孰坏并不重要,重要的是必须要在统一的方向上通行,表面上限制了自由,但实际上是保障了公众的人身安全。试想,如果没有规定靠右行驶,那样的路况肯定拥堵不堪,险象环生。同样,过分自由随意、天马行空的代码会严重的伤害系统的健康,影响到可扩展性以及可维护性。

关于编码规范的三大圣战

众所周知,互联网公司的优势在于效率,它是企业核心竞争力。体现在产品开发领域,就是够沟通效率和研发效率。对于沟通效率的重要性,可以从程序猿三大 “编码理念之争” 说起:

在美剧《硅谷》中,有这样的一个经典镜头:

Tab 键和空格键的争议确实存在,并且在知乎上讨论得火热:写代码时,缩进使用 tab 还是空格?

if 单语句是否需要换行,也是争论不休的话题。相对来说,写过格式缩进类编程语言的开发者, 更加习惯于不加大括号。《手册》中明确 if/for 单行语句必须加大括号,因为单行语句的写法,容易在添加逻辑时引起视觉上的错误判断。此外,if 不加大括号还会有局部变量作用域的问题。

左大括号是否单独另起一行?因为 Go 语言的强制不换行,在这点上,“编程理念之争” 的硝烟味似乎没有那么浓。如果一定要给一个理由,那么换行的代码可以增加一行,对于按代码行数考核工作量的公司员工,肯定倾向于左大括号前换行。《手册》明确左大括号不换行!


第1章:编程规约

这一章是对传统意义上的代码规范,包括变量命名、代码风格、控制语句、代码注释等基本的变成习惯,以及从高并发场景中提炼出来的集合处理技巧与并发多线程的注意事项。

1.1 命名风格

第一条:【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。

  • 反例:_name / $name / name_ / name$

尽管 $ 可以作为标识符使用,然而我们应该尽量避免对其使用。

package test;

public class User$VIP {
    public static void main(String[] args) {
        User user = new User();
        User.VIP vip = user.new VIP();
        vip.print();
    }
}

class User{
    class VIP{
        void print(){
            System.out.println("成员类");
        }
    }
}

仔细阅读以下,似乎并没有什么问题,代码也比较简单,但正在我们编译的时候,IDEA提示我们:

定义了重复的代码?归根到底,都是 $ 惹的祸!因为 $ 被编译器所使用,在源文件(.java 文件)编译成字节码(.class 文件)后,会称为顶层类型与嵌套类型之间的连接符。例如,如果存在一个顶层类 A,在其内声明了一个成员类 B,那么编译之后就会产生两个 class 文件,分别为 A.classA$B.class

就本程序来说,会生成 3 个 class 文件(如果可以编译的话),分别是 User$VIP.class(顶层类)、User.classUser$VIP.class(User 类的成员类,也就是类 VIP)。由于试图存在两个 User$VIP.class 所以才会报错!

第三至第六条:【强制】

正例:MAX_STOCK_COUNT / PRIZE_NUMBER_EVERYDAY
反例:MAX_COUNT / PRIZE_NUMBER

第八条:【强制】 POJO 类中布尔类型的变量都不要加 is 前缀,否则部分框架解析会引起序列化错误。

反例:定义为基本数据类型 Boolen isDeleted; 的属性,它的方法名称也是 isDeleted() ,RPC 框架在反向解析的时候,“误以为” 对应的属性名称是 deleted ,导致属性获取不到抛出异常。

第十二条:【推荐】 如果模块、类、方法使用了设计模式,应在命名时体现出具体模式

正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;

第十三条:【推荐】 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的间接性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,必须是与接口方法相关的,并且是整个应用的基础变量。

正例:
接口方法签名: void commit();
接口基础变量: String COMPANY = "alibaba";
反例:
接口定义方法: public abstract void commit();

第十四条:接口和实现类的命名规则

正例: CacheServiceImpl 实现 CacheServcie 接口

正例: AbstractTranslator 实现 Translatable。

1.2 常量定义

第二条:【强制】 long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l。小写的 l 容易跟数字 1 混淆,造成误解。

第三条:【推荐】 不要使用一个常量类维护所有变量,要按常量功能进行归类,分开维护。

正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在 ConfigConsts 下。

1.3 代码格式

public static void main(String[] args){
    // 注释的双斜线与注释内容之间有且仅有一个空格
    // 缩进 4 个空格
    String say = "hello";
    // 运算符的左右必须有 1 个空格
    int flag = 0;
    // 关键字 if 与括号之间必须有 1 个空格,括号内的 f与左括号、
    // 0 与右括号之间不需要空格
    if (flag == 0) {
        System.out.println(say);
    }
    // 左大括号前加空格且不换行;左大括号后换行
    if (flag == 1) {
        System.out.println("world");
    // 右大括号前换行,右大括号后有 else,不用换行
    } else {
        System.out.println("ok");
    // 在右大括号后直接结束,则必须换行
    }
}

第八条:【强制】 方法参数在定义和传入时,多个参数逗号后边必须加空格。

正例:下例中实参的“one”,后边必须要有一个空格。
method("one", "two", "three");

1.4 OOP 规约

第二条:【强制】 所有的复写方法,必须加 @Override 注解。

打脸

第七条:【强制】 所有相同类型的包装类对象之间值得比较,全部使用 equals 方法

第十二条:【强制】 POJO 类必须写 toString 方法。在使用 IDE 中的工具 source>generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。

1.5 集合处理

第七条:【强制】 不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。

// 正例
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (删除元素的条件) {
        iterator.remove();
    }
}
// 反例
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
    // 如果把 1 改为 2 再试一下看看是否相同
    if ("1".equals(item)) {
        list.remove(item);
    }
}
打脸

第十一条:【推荐】 高度注意 Map 类集合 K/V 能不能存储 null 值得情况

1.6 并发处理

第三条:【强制】 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

打脸

1.7 控制语句

第二条:【强制】 在 if / else / for / while / do 语句中,必须使用大括号。即使只有一行代码,也应该避免采用单行的编码方式:if (condition) statements;

第三条:【强制】 在高并发场景中,避免使用 “等于” 判断作为终端或退出的条件

反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。

第四条:【推荐】 在表达异常的分支时,尽量少用 if-else 方式

// 正例:超过 3 层的 if-else 逻辑判断代码可以使用卫语句、策略模式
// 状态模式等来实现,其中卫语句实例如下:
public void today() {
    if (isBusy()) {
        System.out.println("change time,");
        return;
    }

    if (isFree()) {
        System.out.println("go to travel.");
        return;
    }
    System.out.println("stay at home to learn Java");
    return;
}

1.8 注释规约

第一条:【强制】 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /**内容*/ 格式,不得使用 //xxx 方式

第二条:【强制】 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。

第三条:【强制】 所有的类都必须添加创建者和创建日期。

打脸

1.9 其他

第三条:【强制】 后台输送给网页的变量必须加$!{var}——中间是感叹号

第四条:【强制】 注意 Math.random() 这个方法返回的是 double 类型,取值的范围 0≤x<1(能够取到零值,注意除零异常),如果向获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法。

第六条:【推荐】 不要在视图模板中加入任何复杂的逻辑。


第4章:安全规约

“安全生产,责任重于泰山。” 这句话同样适用于软件生产,本章主要说明编程中需要注意的比较基础的安全准则。

第一条:【强制】 隶属于用户个人的页面或者功能必须进行权限控制校验

第二条:【强制】 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。

第三条:【强制】 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。

打脸

第四条:【强制】 用户请求传入的任何参数必须做有效性验证

打脸

第五条:【强制】 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。

第六条:【强制】 表单、AJAX 提交必须执行 CSRF 安全过滤

第七条:【强制】 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。


第5章:MySQL 数据库

5.1 建表规约

第二条:【强制】 表名、字段名必须使用小写字母或数字 , 禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。

正例: getter _ admin , task _ config , level 3_ name
反例: GetterAdmin , taskConfig , level 3 name

第四条:【强制】禁用保留字,如 desc 、 range 、 match 、 delayed 等,请参考 MySQL 官方保留字。

第五条: 【强制】主键索引名为 pk_ 字段名;唯一索引名为 uk _字段名 ; 普通索引名则为 idx _字段名。

第六条:【强制】小数类型为 decimal ,禁止使用 float 和 double 。

第八条:【强制】 varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text ,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

第九条:【强制】表必备三字段: id , gmt _ create , gmt _ modified

第十条: 【推荐】表的命名最好是加上“业务名称_表的作用”。

正例: tiger _ task / tiger _ reader / mpp _ config

第十五条:【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。

对象 年龄区间 类型 表示范围
150 岁之内 unsigned tinyint 无符号值:0 到 255
数百岁 unsigned smallint 无符号值:0 到 65535
恐龙化石 数千万年 unsigned int 无符号值:0 到约 42.9 亿
太阳 约 50 亿年 unsigned bigint 无符号值:0 到约 10 的 19 次方

5.2 索引规约

第五条: 【推荐】如果有 order by 的场景,请注意利用索引的有序性。 order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file _ sort 的情况,影响查询性能。

正例: where a =? and b =? order by c; 索引: a _ b _ c
反例:索引中有范围查找,那么索引有序性无法利用,如: WHERE a >10 ORDER BY b; 索引 a _ b 无法排序。

第九条: 【推荐】建组合索引的时候,区分度最高的在最左边。

正例:如果 where a =? and b =? , a 列的几乎接近于唯一值,那么只需要单建 idx _ a 索引即可。

5.3 SQL 语句

第一条:【强制】不要使用 count( 列名 ) 或 count( 常量 ) 来替代 count( * ) , count( * ) 是 SQL 92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

第六条: 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。

打脸

第八条: 【强制】数据订正时,删除和修改记录时,要先 select ,避免出现误删除,确认无误才能执行更新语句。

打脸

5.4 ORM 映射

整个规约对自己来说都挺有用的,因为正好涉及到这方面,幸好感觉脸不怎么疼。

第一条:【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。

第二条:【强制】 POJO 类的 布尔 属性不能加 is ,而数据库字段必须加 is _,要求在 resultMap 中进行字段与属性之间的映射。

第三条:【强制】不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义 ; 反过来,每一个表也必然有一个与之对应。

第七条:【强制】更新数据表记录时,必须同时更新记录对应的 gmt _ modified 字段值为当前时间。

第九条:【参考】@ Transactional 事务不要滥用。事务会影响数据库的 QPS ,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。


总结

浏览了一遍,还是学习到了很多东西吧,上面也仅仅只是总结了对我自己比较收益,现阶段我能吸收能实际感受得到的规约,如果想要 PDF 版的可以在这里下载:戳这里

欢迎转载,转载请注明出处!
简书ID:@我没有三颗心脏
github:wmyskxz
欢迎关注公众微信号:wmyskxz_javaweb
分享自己的Java Web学习之路以及各种Java学习资料

上一篇 下一篇

猜你喜欢

热点阅读