作者:HW-SQH
1. 在产品的Java代码里,经常有泛滥的常量使用
更糟糕的是,有一种大泥球像ModeContstant这样的XxxConstant类,里面包含了各种常量定义,例如HANDOFF,OTHER的。 还有一种是方法定义,其返回值数值或字串常量的。示例1:
这种方法返回特定数值或字串,其还包含错误码的情形,这叫带内错误指示器[1]。
常见的例子有:
- 一个有效的对象,或者为null
- 正整数表示有效返回值,-1表示有错误发生
这样的代码易使程序员忽略检查程序的错误码,或者使用错误的返回值进行后续的计算。可读性也不太好,程序员要特别小心地分析各种具体值的处置。 好在Java 1.5引入了枚举enum,Java 1.8引入了Optional,虽然晚了些。
在Java语言通用编程规范中,关于枚举有这样的描述:
由于枚举类是类,因此适用于格式化类的所有其他规则。 Java的枚举比较灵活强大,而且与switch/case结合较好,只要可能,优先使用。 枚举的使用场景:
- 布尔型的2元素值,例如isCelsius = true | false来表示摄氏|华氏,可用
- 变量值仅在一个固定范围内变化用 enum 类型来定义。例如 建议8.6.3的Keyboard例子
- 整数或字符串的枚举模式,蕴含有某种名称空间的,例如上面的Suit 例子,或者其它语言的ComparisonResult,避免-1、0、1的数字比较:
上面的示例1,可以优化为更加Clean的代码。如下所示,当不能构造一个枚举常量时返回empty Optional,就像JDK 12中的 Optional<Enum.EnumDesc<E>> describeConstable()。
Java编程规范中也有这一条:建议8.6.2 Java 8使用Optional代替null作为返回值或者可能的缺失值。
调用方然后可以通过Optional的ifPresent(action)和orElse(other)来抽取有效值处理。也可以在枚举类型中增加一个更加有韧性的工厂方法来查询枚举单例:
2. 虽然没有直接写if/else,但是流水账似地罗列了所有的条件动作
示例2,粘滞性(Viscosity):做正确的事情比做错误的事情更难(实现或变更一个需求时,生硬的方法很简单,保持系统设计的方法很难;或开发环境迟钝低效时,开发人员会倾向于做不会导致大规模重编译的改动,即使那些改动不再保持设计)。
进一步地,示例2,good:
上面valueOf(enumType, name)方法,可能抛出IllegalArgumentException。如果API提供者不能确信外部参数在合法范围,则要处理异常。
3. 小结
在《重构》第2版中,描述了24种代码坏味道。其中包括 基本类型偏执(Primitive Obsession)和重复的switch (Repeated Switches)语句。作者建议以子类取代类型码Type Code加上以多态取代条件语句的组合将它换掉。
这种枚举 + 接口的方式,在《Effective Java》中也有举例。实际上,它是更一般的函数式编程的代数数据类型,在Scala和Kotlin中是基于密封类+模式匹配,更加优雅。JDK 13有个预览特性,会把Switch语句增强为Switch表达式。
这篇介绍Rust语言的文章沅有芷兮:类型系统的数学之美,描述了Optional的好处:
表面上看它似乎和上面第一种方式没有区别,但一个形式化完备的类型让很多事情变成了可能。这个函数可以被 pipe,被 compose,调用者不必担心类型的泄露 —— 所有信息都已经在 type signature 里面了,编译器可以做更合适更严格的检查,也可以适当优化 —— 更重要的是,围绕着这个类型,我们可以把一堆原本不断出现在用户代码中的对结果判断的 if else / try catch 抽象出来,成为 Option 类型的一组 behavior,这样让用户代码变得清晰。同样的思路,在 Rust 里,exception 被抛弃,取而代之的是是 Result,也是一个 sum type。
[1] 《Java编码指南 - 编写安全可靠程序的75条建议》,Item 52:要避免使用带内错误指示器(in-band error indicator)





































苏公安网备 32011402010933号