Skip to content

java guides

雷霹雳的爸爸 edited this page Aug 1, 2018 · 10 revisions

概述

以阿里巴巴开发手册1.4.0版本为索引,结合广为流传的Google Java Style Guide,以及标杆经典著作 Effective Java,交叉参考形成此评述性文档

参考

内容

以阿里的开发手册1.4版(以下简称手册)中的编程规约和异常日志部分为蓝本及基础,并结合其他较为知名的guidelines,形成一下文本

如果遵循阿里开发手册中的意见(page 1-22),这里不再特别列出,以下,仅记录对手册的修改和补充部分

对手册中内容的调整和补充

(一) 1 关于标识符的名称规则

原文:

【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。 反例:name / name / $name / name / name$ / name

说明:

  • 按说这个规则应该是没有争议的,拎出来是内部讨论时候有提到下划线是会被用到的,所以这里需要进行补充说明
  • 美元符号实际上已经认为是内部类等系统内部使用了
  • 下划线有很大一部分是源于其他语言,比如python,在java中不适用
  • 有听过老程序员提到,有代码生成工具通过下划线来生成类似setter或getter这类的代码,java世界有如果有,现在也已经废弃了
  • 这条guideline没有对中间的下划线做出规定,从后面的对于驼峰命名法的几条约束来看,它不鼓励蛇形命名法及其和驼峰的混用,但在Google Java Style Guide 中提到JUnit的test method有一种例外,混合驼峰和蛇形命名法是被接受的命名模式

(一)3 当缩写遇到驼峰命名法

原文:

【强制】类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID 等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

修改为:

  • 【强制】类名使用UpperCamelCase风格,遇到缩写和不常见结构体时,遵循google java style guides中规则

说明:

阿里这条比较拧巴,为什么TCP UDP就要按驼峰而这些例外不要?豁免权的来源完全搞不清楚,估计是其内部对现状的妥协,这么去落实,实际上是在违反一致性原则;如果真是遗留代码的问题,应当给一个对旧代码的整体例外约束比较好,这一点在Google的 C++ Style Guide中有明确的章节来说明,所以说java还是年轻?

(四)14,split和空字符串的问题

原文:

【推荐】使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException 的风险。

说明:

String str = "a,b,c,,";  
String[] ary = str.split(",");  
// 预期大于 3,结果是 3 
System.out.println(ary.length);

修改并补充:

  • 【推荐】使用索引访问用 String 的 split 方法得到的数组时,要对数组长度做检查,否则会有抛IndexOutOfBoundsException 的风险
  • 【推荐】如果允许split结果数组中有空字符串,那么可以使用split(regex,-1)来进行切分来达到设计目的

说明:

  • 手册意图比较明确,但是说错了;实际上应该检查的是数组长度,也就是生产代码中如何防范这种非受控异常,手册说明部分本意是这个问题
  • 手册应当还有一个意图,就是它期望能取到最后那个空字符串,这时靠检查是没用,而应当对正则的匹配限定limit,补充部分强调的就是这部分
  • 另外不要对split加limit为-1做出限定,否则就会失去一个功能,split(regex)是两个参数split的一个特例,详见JDK的Java doc

(四)15,方法重载的顺序问题

原文:

【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读,此条规则优先于第 16 条规则。

修改:

  • 【推荐】重载方法要放置在一起,中间不要插入其他任何内容,此条规则优先于第 16 条规则。

说明:

  • 阿里的同学真的怕重载是个大家不懂的名词?
  • 推荐按顺序又没给出应该按什么顺序,参数的字母顺序?参数数量?和关联的16条一看大概明白他的意思了,这两条合在一起应该都是给一个通用准则的狗尾续貂

(四)16,类内要素的顺序问题

原文:

【推荐】 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。

说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类 关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个 黑盒实现;因为承载的信息价值较低,所有 Service 和 DAO 的 getter/setter 方法放在类体最后

修改:

  • 【推荐】 类的成员要按照逻辑规则进行组织

说明:

这个问题也是容易引起争议的地方,主要是具体的规则都非常难以操作;考虑到手册仅仅是推荐,因此不对它这里缺少原则、涉及内容不全面、令人疑惑的说明做出修改描述(手册很难修正了,只能重写这条规则)

先细说一下手册的问题:

  • 只涉及了方法顺序,没涉及成员变量,那就更不用提静态初始化块儿的了
  • 可能是阿里得同学都不用包可见级别
  • getter/setter的顺序解释很奇特,按说阿里这份guidelines是很注重工程实践的,比如拆装箱部分,就对effective java中的item做出了很有实践意义的加强,所以这里写成这样确实很难理解,这里详细说下
    • 这个世代需要在service或这DAO这两层类型写accessor这种方法的情况应该已经很少了,通常是基于注解并通过DI框架来完成功能,如果是为了保持一种纯粹性,注解使用@Resource
    • 大量需要getter/setter方法的类通常也只有getter/setter(典型的贫血模型+事务脚本这种实现模式)
    • 大量需要getter/setter方法的类是值对象,贫血模型,通常现在应当鼓励使用类似Lombok或者AutoValue这类类库来减少撰写和维护时无谓的工作量,当然损失掉的是这部分类型源码和字节码一一对应这个Java特性

唯一能确定的顺序是顺序必须按照某种类内逻辑关系来组织,但是问题就在于这种逻辑关系实际上一般不具有普适性,所以很难具体规定出来,这里摘抄翻译的 google style guide关于这里的建议如下:

3.4.2 类内部定义的顺序

类中成员和初始化函数顺序的选择对于该类设计的理解上有很大的影响。然而是没有唯一正确的守则来确定如何去做;不同的类可能用不同的顺序来组织他们内容的顺序才是合适的 重要的是每个类要使用某种确定的逻辑顺序来进行组织,他的维护者要能解释清楚这种逻辑顺序;例如,新的方法不能习惯性的在类的最下面一放就完事了,因为这样就变成了“明摆着的按时间添加”顺序,而不是逻辑顺序 3.4.2.1 重载:绝不要拆开 当一个类有多个构造函数,或者多个方法使用了相同的方法名,那么他们应该紧挨着撰写,中间不要插入任何代码(就算是私有的成员也不要不要)

这种原则性描述基本上够了,再往上添加很有可能变成立场之争,浪费生命;回过头来再看一下手册的说明,能探出它基本思路是鼓励封装和隔离的,但是理由阐述的并不充分甚至可能是错的和混乱的:

  • 调用者和维护者立场不同最关心的不可能,也不应该一样:
    • 维护者最关心对外接口要甚于实现细节?
    • 对外接口视图难道不应该是interface去集中表达?
    • 调用者难道不该使用接口?
  • 等到最后service/DAO的accessor之论已经乱套的一塌糊涂了,这个上面已经罗列过了

如果需要一个更有可操作性的指导,可以考虑在满足更具内在逻辑表达的情况下,可见性高的放前面,如果有public的因为其他public的method关联性更强的private挤到好几屏以后了,那应该考虑内聚性的问题,该拆类了

五(6)关于Java泛型的协变问题

原文:

【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。

说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内 容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。

修改:

  • [推荐]遵从Effective Java 2nd Edition 的 Item 28: Use bounded wildcards to increase API flexibility,如果你能看懂它的话

说明:

  • 能完全看懂原书这条的不一定要奉为天人,也可以肃然起敬了
  • 这里手册的问题是,本来就难懂的问题,经过手册这么一番不说人话,彻底凌乱了
  • extend的时候,add 不可以是因为只知道里面是T或T派生的类型,但是不知道具体是哪种类型,可能会引发在原始初始化类型被引用的其他场合是否合适;
  • super时候,get当然也是可以的,只是get出来的对象是不能给T型变量赋值的,而只能给他的超类,
  • 只是说这个有什么意义呢?编译器就可以帮助检查的
  • 按说Java泛型不支持协变,然后搞出一个有限通配符类型来指导实践,是为了实现更具灵活性的API,但是成了java 基本语法里面最难理解的规则之一,我个人的倾向最好的办法其实还是不考虑协变设计,甚至应当较少的考虑使用参数化类型来设计自己的API,除非真的知道自己在干什么;但这是一种设计退化的思路,偷偷的做还行,不能编为正式guideline去提倡的
  • 阿里手册写了这么一条,不知用意何在,实际上Effective Java 2nd Edition泛型这章Item 23、24、25都是比较好理解且值得贯彻的,从item 26开始看不懂不应该踩

手册内容的补充

Clone this wiki locally