偶然看到阿里巴巴居然出书了???趁着满减活动(节约节约....)我赶紧买来准备看看,刚拿到的时候掂量了好多下,总觉得商家给我少发了一本书,结果打开才知道..原来这本书这么小....
别人都说我们是搬砖的码农,但我们知道自己是追求个性的艺术家。也许我们不会过多在意自己的外表和穿着,但在我们不羁的外表下,骨子里追求着代码的美、系统的美、设计的美,代码规范其实就是一个对程序美的定义。―― 引自 序
如果有一天在我们的项目中看到了这样的代码:
或者是这样的代码:
这样美不美呢?或许看着是还挺美的,但是如果需要修改,是不是人傻啦?
那这样的代码呢?
作为一个对自己有一定要求的程序猿,是不是第一反应就是:
- 重写!
- 原作者是谁?锤他!
- 总结:代码规范很重要!
众所周知,互联网公司的优势在于效率,它是企业核心竞争力。体现在产品开发领域,就是够沟通效率和研发效率。对于沟通效率的重要性,可以从程序猿三大 “编码理念之争” 说起:
- 缩进采用空格键,还是 Tab 键
- if 单行语句需要大括号还是不需要大括号
- 左大括号不换行,还是单独另起一行
在美剧《硅谷》中,有这样的一个经典镜头:
- 程序媛:Kid? 我们似乎很久没有一起睡了。
- 程序猿:现在?不可能!我永远不会和使用空格来缩进的人睡在一起!
- 程序媛:(疯狂敲 space 气走了程序猿)
- 程序猿:(甩了一句)一个 Tab 可以代替 8个 空格!
之后程序猿就因为视图一步跨下八个阶梯而摔了....
Tab 键和空格键的争议确实存在,并且在知乎上讨论得火热:写代码时,缩进使用 tab 还是空格?
- 总结:使用 4 个空格好,在《阿里巴巴 Java 开发手册》中也明确支持了这样的做法。下面也引用一张图来调侃一下。
if 单语句是否需要换行,也是争论不休的话题。相对来说,写过格式缩进类编程语言的开发者, 更加习惯于不加大括号。《手册》中明确 if/for 单行语句必须加大括号,因为单行语句的写法,容易在添加逻辑时引起视觉上的错误判断。此外,if 不加大括号还会有局部变量作用域的问题。
左大括号是否单独另起一行?因为 Go 语言的强制不换行,在这点上,“编程理念之争” 的硝烟味似乎没有那么浓。如果一定要给一个理由,那么换行的代码可以增加一行,对于按代码行数考核工作量的公司员工,肯定倾向于左大括号前换行。《手册》明确左大括号不换行!
- 总结: 其实,很多编程方式客观上没有对错之分,一致性很重要,可读性很重要,团队沟通效率很重要。
这一章是对传统意义上的代码规范,包括变量命名、代码风格、控制语句、代码注释等基本的变成习惯,以及从高并发场景中提炼出来的集合处理技巧与并发多线程的注意事项。
- 反例:
_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.class
与 A$B.class
。
就本程序来说,会生成 3 个 class 文件(如果可以编译的话),分别是 User$VIP.class
(顶层类)、User.class
与 User$VIP.class
(User 类的成员类,也就是类 VIP)。由于试图存在两个 User$VIP.class
所以才会报错!
- 类名使用 UpperCamelCase 风格,方法名、参数名、成员变量、局部变量都同意使用 lowerCamelCase 风格,必须遵从驼峰形式。
变量命名全部大写,单词兼用下划线隔开,力求予以表达完整清楚,不要嫌名字太长。
正例:MAX_STOCK_COUNT / PRIZE_NUMBER_EVERYDAY
反例:MAX_COUNT / PRIZE_NUMBER抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类名开始,以 Test 结尾。
反例:定义为基本数据类型
Boolen isDeleted;
的属性,它的方法名称也是isDeleted()
,RPC 框架在反向解析的时候,“误以为” 对应的属性名称是deleted
,导致属性获取不到抛出异常。
说明: 将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
正例:
接口方法签名:void commit();
接口基础变量:String COMPANY = "alibaba";
反例:
接口定义方法:public abstract void commit();
- 说明: 如果 JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默认实现。
- 1):【强制】 对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 后缀与接口区别。
正例: CacheServiceImpl 实现 CacheServcie 接口
2):【推荐】 如果是形容能力的接口名称,取对应的形容词为接口名(通常是 -able 的形式)。
正例: AbstractTranslator 实现 Translatable。
- 说明:
Long a = 2l;
写得是数字的 21 还是 Long 型的 2?
说明: 大而全的变量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在 ConfigConsts 下。
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");