对引用使用==(而不是.equals)
很多Java开发者都知道使用==比较原生类型数据,使用.equals比较引用类型数据。这是一条很容易记住的简单原则,Java开发者这么用也没什么问题。有时使用==来比较标准的Java类型引用(String、Integer、Long等等)也没问题,不过这要取决于被缓存的值的大小,因此这么做并不是一个好的做法。有时,我们需要检查标识的相等性而不是内容的相等性,在这种情况下使用==来比较引用就很适合了。相对而言,我更喜欢Groovy的处理方式,==类似于.equals,而===则是更加严格地比较标识。同理,使用!=来比较两个引用也是一个“危险信号”,因为如果待比较的两个对象不共享相同的标识(内存地址),即便他们拥有相同的内容也总是会返回true。
对枚举使用.equals(而不是==)
坦率地说,对于枚举,Java开发者使用==还是.equals都没有太大关系。不过,我更倾向于对枚举使用==。这么做最重要的原因就是对枚举使用==可以防止不小心将枚举与不相关的对象进行比较(永远不会相等)。Object.equals(Object)方法可以接收任意对象,这意味着编译器并不会强制限定传进来的对象要与被比较的对象是相同的类型。一般来说,我更喜欢静态的编译期问题检测而非动态运行期的问题检测,对枚举使用==可以满足这个要求。同理,在比较枚举时,!=与!.equals也是一样的。
魔数与字符串字面值
我经常会在Java代码中看到有人使用“魔数”和字符串字面值。他们对于未来的维护来说是一种“危险信号”,让我十分怀疑应用的正确性。在单个位置处将其标识为常量(如果可能用枚举来表示更佳),这么做可以改善未来的维护,并且让我可以更加自信地相信使用这些值的所有代码都在使用着相同的值。除此之外,在一个地方定义好常量与枚举可以更方便地使用IDE的“查找使用”特性来找到所有使用这些常量的地方。
字符串常量
在看到有限的相关字符串常量时,我就在想使用枚举应该更加适合。对于高度内聚的字符串常量的情况来说更是如此,因为枚举可以更好地表达出这些字符串所表示的概念。相比于字符串常量来说,枚举提供了编译期的静态类型安全与潜在的性能优势。对于程序的正确性来说,编译期的安全是最吸引我的地方。
equals(Object)与hashCode()方法的不匹配
虽然我认为每个Java类都应该重写toString()方法,但对于equals(Object)与hashCode()方法来说却并不这么认为。我觉得只有在需要这些方法的场合下才应该重写类中的这两个方法,因为他们的存在暗示着其设计与开发某种程度上的完全改变。特别地,equals与hashCode方法要能满足其意图与契约(位于Object类的API文档),并且需要保持一致。大多数IDE与分析工具都会在其中一个方法重写而另一个没有重写的情况下给出提示。然而,我要确保equals与hashCode使用的是相同的属性,并且在这两个方法中属性的顺序要保持一致。
源代码中的注释
虽然接口(通常是Javadoc)中缺乏注释是一种危险信号,不过方法体和其他源代码中的注释也可能是个危险信号,会导致潜在的问题。如果需要为代码添加注释以帮助人们理解代码的行为,那就很有可能是代码的复杂性过大(“注释是恶臭代码的芳香剂”,这句话有些滑稽,不过在一定程度上却真的如此)。就像文中提到的众多“危险信号”一样,有时我也认为源代码中的注释是有意义的,可以提供很多信息,这时就没有必要删除这些注释了。然而,我认为代码中的注释(不是接口描述的Javadoc注释)应该尽量少一些,而且主要应该关注于“为什么”这么写而不是“如何做”。代码自身应该能够说明“如何”这一问题,不过很多时候是无法清楚地解释出“为什么”(由于客户/管理方向、设计决策、正式的接口需求、正式的算法需求等等)。
实现继承(extends)
我看到很多时候使用extends(实现继承)都很不错,也适合于相应的场景。但很多时候也存在使用不当的情况,实现继承会导致更多的麻烦。Allen Holub曾说到extends是邪恶的,四人帮的设计模式一书也用了很大的篇幅解释为何说组合要比实现继承更好。随着编写Java代码时间的不断增长,更为重要的是随着我维护Java代码时间的不断增长,我越来越觉得组合相对于实现继承的优势了。我确实看到了实现继承所带来的好处,不过更多的时候类是被“硬塞”到继承体系当中的,子类只不过是对UnsupportedOperationExceptions打洞或是“空实现”,因为他们所继承下来的方法并不适用于他们自己。加上Effective Java一书中所提及的继承问题(比如说具体类的equals与hashCode方法实现等),这真就成了一个大问题了。实现继承并不总是糟糕的,不过很多人经常没有正确地使用他们,因此我觉得这是个“危险信号”。
无作用的代码
程序中出现用不上的代码永远都不是一件好事。人们很快就会忘记它是怎么用的,为什么要这么用。不久之后,开发者就想知道这是不是出于某个原因被扔在那里的。无作用的代码不仅会无意义地增加代码长度,增加维护的复杂度,对于IDE来说,无作用的代码还有可能会被不小心调用,这种情况说明代码基出现了问题。
注释掉的代码
注释掉的代码可能不像可执行的、无意义的代码那么糟糕,因为至少它不会被不小心地调用,显然,这些代码是不会被用到的,不过这仍旧是个危险信号,因为它表明可能会出现潜在的代码基问题。就像无意义的可执行代码一样,注释掉的代码越多,人们就越难理解代码为何会出现在那里、为什么会被注释掉、不使用了为什么不将其删除掉。开发者不敢删除他们,因为留下来肯定是有原因的,只不过没人能够记起来原因是什么了。
使用StringBuffer而非StringBuilder
坦白地说,这只是个小问题而已,不过却能标识出过时的Java代码(StringBuffer是JDK 1.0引入的,而StringBuilder则是J2SE 5引入的)或是开发者并没有真正理解他们之间的区别。在大多数情况下,这两者之间的性能差别对于应用来说是微乎其微的,但由于StringBuilder更适合于大多数使用了StringBuffer的场景,因此我们还是可以从StringBuilder获得性能上的微小提升。 我很少发现使用StringBuffer而不能使用StringBuilder的情况。方法与构造方法中使用了过多的参数
当方法与构造方法拥有太多的参数时,我总是担心客户端无法正确地使用他们,特别是有些参数是相同类型的情况。如果一个方法接收了3个字符串和3个布尔值,那么客户端就很容易搞混传递进去的值。编译器在这种情况下也无能为力,检测问题的唯一办法就是在运行期(通过单元测试或是其他测试)查看调用结果。过多的参数表明了不恰当的设计。
很多Java开发者都知道使用==比较原生类型数据,使用.equals比较引用类型数据。这是一条很容易记住的简单原则,Java开发者这么用也没什么问题。有时使用==来比较标准的Java类型引用(String、Integer、Long等等)也没问题,不过这要取决于被缓存的值的大小,因此这么做并不是一个好的做法。有时,我们需要检查标识的相等性而不是内容的相等性,在这种情况下使用==来比较引用就很适合了。相对而言,我更喜欢Groovy的处理方式,==类似于.equals,而===则是更加严格地比较标识。同理,使用!=来比较两个引用也是一个“危险信号”,因为如果待比较的两个对象不共享相同的标识(内存地址),即便他们拥有相同的内容也总是会返回true。
对枚举使用.equals(而不是==)
坦率地说,对于枚举,Java开发者使用==还是.equals都没有太大关系。不过,我更倾向于对枚举使用==。这么做最重要的原因就是对枚举使用==可以防止不小心将枚举与不相关的对象进行比较(永远不会相等)。Object.equals(Object)方法可以接收任意对象,这意味着编译器并不会强制限定传进来的对象要与被比较的对象是相同的类型。一般来说,我更喜欢静态的编译期问题检测而非动态运行期的问题检测,对枚举使用==可以满足这个要求。同理,在比较枚举时,!=与!.equals也是一样的。
魔数与字符串字面值
我经常会在Java代码中看到有人使用“魔数”和字符串字面值。他们对于未来的维护来说是一种“危险信号”,让我十分怀疑应用的正确性。在单个位置处将其标识为常量(如果可能用枚举来表示更佳),这么做可以改善未来的维护,并且让我可以更加自信地相信使用这些值的所有代码都在使用着相同的值。除此之外,在一个地方定义好常量与枚举可以更方便地使用IDE的“查找使用”特性来找到所有使用这些常量的地方。
字符串常量
在看到有限的相关字符串常量时,我就在想使用枚举应该更加适合。对于高度内聚的字符串常量的情况来说更是如此,因为枚举可以更好地表达出这些字符串所表示的概念。相比于字符串常量来说,枚举提供了编译期的静态类型安全与潜在的性能优势。对于程序的正确性来说,编译期的安全是最吸引我的地方。
equals(Object)与hashCode()方法的不匹配
虽然我认为每个Java类都应该重写toString()方法,但对于equals(Object)与hashCode()方法来说却并不这么认为。我觉得只有在需要这些方法的场合下才应该重写类中的这两个方法,因为他们的存在暗示着其设计与开发某种程度上的完全改变。特别地,equals与hashCode方法要能满足其意图与契约(位于Object类的API文档),并且需要保持一致。大多数IDE与分析工具都会在其中一个方法重写而另一个没有重写的情况下给出提示。然而,我要确保equals与hashCode使用的是相同的属性,并且在这两个方法中属性的顺序要保持一致。
源代码中的注释
虽然接口(通常是Javadoc)中缺乏注释是一种危险信号,不过方法体和其他源代码中的注释也可能是个危险信号,会导致潜在的问题。如果需要为代码添加注释以帮助人们理解代码的行为,那就很有可能是代码的复杂性过大(“注释是恶臭代码的芳香剂”,这句话有些滑稽,不过在一定程度上却真的如此)。就像文中提到的众多“危险信号”一样,有时我也认为源代码中的注释是有意义的,可以提供很多信息,这时就没有必要删除这些注释了。然而,我认为代码中的注释(不是接口描述的Javadoc注释)应该尽量少一些,而且主要应该关注于“为什么”这么写而不是“如何做”。代码自身应该能够说明“如何”这一问题,不过很多时候是无法清楚地解释出“为什么”(由于客户/管理方向、设计决策、正式的接口需求、正式的算法需求等等)。
实现继承(extends)
我看到很多时候使用extends(实现继承)都很不错,也适合于相应的场景。但很多时候也存在使用不当的情况,实现继承会导致更多的麻烦。Allen Holub曾说到extends是邪恶的,四人帮的设计模式一书也用了很大的篇幅解释为何说组合要比实现继承更好。随着编写Java代码时间的不断增长,更为重要的是随着我维护Java代码时间的不断增长,我越来越觉得组合相对于实现继承的优势了。我确实看到了实现继承所带来的好处,不过更多的时候类是被“硬塞”到继承体系当中的,子类只不过是对UnsupportedOperationExceptions打洞或是“空实现”,因为他们所继承下来的方法并不适用于他们自己。加上Effective Java一书中所提及的继承问题(比如说具体类的equals与hashCode方法实现等),这真就成了一个大问题了。实现继承并不总是糟糕的,不过很多人经常没有正确地使用他们,因此我觉得这是个“危险信号”。
无作用的代码
程序中出现用不上的代码永远都不是一件好事。人们很快就会忘记它是怎么用的,为什么要这么用。不久之后,开发者就想知道这是不是出于某个原因被扔在那里的。无作用的代码不仅会无意义地增加代码长度,增加维护的复杂度,对于IDE来说,无作用的代码还有可能会被不小心调用,这种情况说明代码基出现了问题。
注释掉的代码
注释掉的代码可能不像可执行的、无意义的代码那么糟糕,因为至少它不会被不小心地调用,显然,这些代码是不会被用到的,不过这仍旧是个危险信号,因为它表明可能会出现潜在的代码基问题。就像无意义的可执行代码一样,注释掉的代码越多,人们就越难理解代码为何会出现在那里、为什么会被注释掉、不使用了为什么不将其删除掉。开发者不敢删除他们,因为留下来肯定是有原因的,只不过没人能够记起来原因是什么了。
使用StringBuffer而非StringBuilder
坦白地说,这只是个小问题而已,不过却能标识出过时的Java代码(StringBuffer是JDK 1.0引入的,而StringBuilder则是J2SE 5引入的)或是开发者并没有真正理解他们之间的区别。在大多数情况下,这两者之间的性能差别对于应用来说是微乎其微的,但由于StringBuilder更适合于大多数使用了StringBuffer的场景,因此我们还是可以从StringBuilder获得性能上的微小提升。 我很少发现使用StringBuffer而不能使用StringBuilder的情况。方法与构造方法中使用了过多的参数
当方法与构造方法拥有太多的参数时,我总是担心客户端无法正确地使用他们,特别是有些参数是相同类型的情况。如果一个方法接收了3个字符串和3个布尔值,那么客户端就很容易搞混传递进去的值。编译器在这种情况下也无能为力,检测问题的唯一办法就是在运行期(通过单元测试或是其他测试)查看调用结果。过多的参数表明了不恰当的设计。