Java编码中的典型错误

这篇文章包含了我所看到和我一起工作的人在java编码中出现的最典型错误。静态分析(我们用qulice)不能捕获所有显而易见的错误,这就是为什么我决定在这里把它们列出来。
如果你想在这里看到别的补充请告诉我,我很乐意效劳。
列出的所有错误总得来说和面向对象编程有关,特别是java。

类名

你的类应该是一个没有“验证”、“控制器”、“管理者”等等的现实生活实体的一个抽象。如果你的类名以“-er”结尾——它是一个糟糕的设计。顺便说一句,这是一个好对象的七个优点。此外,这篇文章更加详尽的阐述了这个观点:Don’t Create Objects That End With -ER
而且,当然工具类是反例,比如Apache的StringUtils、FileUtils和IOUtils。这些是可怕设计的完美例子。读这篇文章继续跟进:OOP Alternative to Utility Classes
当然,不要添加后缀或者前缀来区分接口和类。举个例子,这些命名全都是极其错误的:IRecordIfaceEmployee 或者
RecordInterface。通常,接口的名称是一个现实生活实体的名称,而类名应该解释它的实现细节。如果关于实现没有具体的要讲的,把它命名为DefaultSimple
或者类似的。例如:

class SimpleUser implements User {};
class DefaultRecord implements Record {};
class Suffixed implements Name {};
class Validated implements Content {};

方法名

方法可以有返回值可以没有返回值,如果有返回值,那么它的名字应该释返回的是什么。例如(永远不要使用 get 作为前缀):

boolean isValid(String name); 
String content();
int ageOf(File file);

如果没有返回值,那么它的名字应该解释它做了什么。例如:

void save(File file); 
void process(Work work);
void append(File file, String line);

你可以在书《Elegant Objects》的2.4节中读到更多关于这个观点。对于刚才提到的规则只有一个例外——JUnit测试方法。下面将对它们进行解释。

测试方法名

在JUnit测试中,方法名应该为没有空格的英语句子。很容易用例子解释:

/**
 * HttpRequest can return its content in Unicode.
 * @throws Exception If test fails
 */ 
 @Test
 public void returnsItsContentInUnicode() throws Exception { } 

重点是你的JavaDoc第一句话应该以类名开始,正在测试的内容跟在 can 后面,这样,你的第一句话总是类似于“某人能做某事”。
方法名声明完全一致,只是没有主语。如果我在方法名前加上一个主语,那么我应该得到一个完整的英语句子,就像上面的例子:“HttpRequest returns its content in Unicode”。 注意测试方法不能以 can 开头。只有JavaDoc的注释用
can 开始。此外,方法名不能用动词开始。 这是一个很好的习惯,在声明测试方法的同时抛出异常。

变量名

避免合成变量的名称,像timeOfDayfirstItem 或者 httpRequest ,我指的既有类的变量又有方法内部。变量名应该足够长从而避免在其作用域范围内造成混淆,但尽量不要太长。名称应该是单数或复数形式的名词或恰当的缩写。更多关于在这篇文章: A Compound Name Is a Code Smell。 例如:

List<String> names;
void sendThroughProxy(File file, Protocol proto);
private File content;
public HttpRequest request;

有时,当构造函数在实例化对象中保存传入数据时构造函数的参数和类的属性可能会产生冲突。这种情况下,我个人建议通过去掉元音字母的方式来创建缩写(看看美国邮局是怎样缩写街道名的)。

另一个例子:

public class Message {
  private String recipient;
  public Message(String rcpt) {
    this.recipient = rcpt;
  }
}

很多情况下,变量名的最佳提示可以通过读取它的类名来确定。只要把它写成小写字母,就应该可以了:

File file;
User user;
Branch branch;

然而,千万不要用相同的方式处理原始数据类型,比如 Integer 或者 String
你也可以使用一个形容词,当存在多个具有不同特征的变量时。例如:

String contact(String left, String right);

构造函数

不出意外的话,应该只有一个构造函数将数据存储在对象变量中。所有其他构造函数使用不同的参数调用这个函数。例如:

public class Server {
  private String address;
  public Server(String uri) {
    this.address = uri;
  }
  public Server(URI uri) {
    this(uri.toString());
  }
}

更多关于它的在There Can Be Only One Primary Constructor

一次性变量

不惜一切代价的避免一次性变量。我指的“一次性”是说变量只被使用过一次。就想这个例子:

String name = "data.txt";
return new File(name);

上面的代码变量只使用过一次,那么代码需要被重构:

return new File("data.txt");

有时,在非常罕见的情况下-主要是因为更好的格式,一次性变量也是可以使用。尽管如此,还是应当不惜一切代价的去尽量避免这样的情况。

异常

不必说,你永远都不应该把异常吞咽掉,而是应该让它们尽可能的往上冒。私有方法应该总是把Checked异常抛出去。
不要为流操作使用异常。例如,这个代码是错误的:

int size;
try {
  size = this.fileSize();
} catch (IOException ex) {
  size = 0;
}

说真的,如果那次异常是因为“磁盘满了”,难道你依然认为文件的大小为零,然后继续进行吗?

缩进

对于缩进,主要的规则是,括号要么在一行的结尾要么在该行就被关闭(相反的规则适用于闭括号)。举个例子,下面的代码是不正确的因为它的第一个括号没有在同一行被关闭,而且有符号跟在它后面。第二个括号也出了漏子,因为在它的前面有符号,而且它也不是在同一行打开的:

final File file = new File(directory,
  "file.txt");

正确的说缩进应该看起来像这样:

StringUtils.join(
  Arrays.asList(
    "first line",
    "second line",
    StringUtils.join(
      Arrays.asList("a", "b")
    )
  ),
  "separator"
);

第二条重要的规则是说:你应该尽可能多地放在一行内-在80个字符的限制范围内。上面的例子是无效的,因为它可以被压缩:

StringUtils.join(
  Arrays.asList(
    "first line", "second line",
    StringUtils.join(Arrays.asList("a", "b"))
  ),
  "separator"
);

注:通常遇到这种情况,我一般会按以下方式写:

StringUtils.join(
  Arrays.asList(
    "first line", 
    "second line",
    StringUtils.join(Arrays.asList("a", "b"))
  ),
  "separator"
);

当一个方法调用的参数不能全部放在同一行时,我更愿意每行放相等个数的参数,这样便于我再次阅读代码时能够一眼看清参数,不会遗漏1

冗余常量

类的常量应该被用于当你想要在类的方法之间共享信息,而且这个信息是你的类的一个特性时。不要用常量作为字符串或者数字的替代——这是一个会导致代码污染的不好现象。常量(和面向对象程序中的所以对象一样)在真是的世界中有某种意义。这些常量在现实世界中有什么意义:

class Document {
  private static final String D_LETTER = "D"; // bad practice
  private static final String EXTENSION = ".doc"; // good practice
}

另一个常见的错误是在单元测试中,使用常量去避免重复的字符串/数字在测试方法中。千万别这样做!每一个测试方法都应该使用它自己的一组输入值。

在每一个新的测试方法中使用新的文本和数字。他们是独立的。那么,为什么他们要共享相同的输入常量呢?

测试数据耦合

这是一个在测试方法中数据耦合的例子:

User user = new User("Jeff");
// maybe some other code here
MatcherAssert.assertThat(user.name(), Matchers.equalTo("Jeff"));

在最后一行,我们与第一行有共同的字符串“Jeff”。如果,几个月后,有人想要改变第三行的值,他/她不得不花额外的时间去查找在这个方法中有用到“Jeff”的其他地方。
为了避免这样的数据耦合,你应该引入一个变量。这儿有更多关于:A Few Thoughts on Unit Test Scaffolding

本文是一篇译文,点击《Typical Mistakes in Java Code》查看原文,如有翻译不当的地方欢迎指出。如需转载,请标明原文和译文的出处,谢谢。


微信扫一扫,查看更多内容
微信扫一扫 查看更多内容


  1. 非原文中提到,只是我本人的观点和习惯。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值