Java编程中的一些常见问题汇总_java

本文列举了我在周围同事的Java代码中看到的一些比较典型的错误。显然,静态代码分析(我们团队用的是qulice)不可能发现所有的问题,这也是为什么我要在这里列出它们的原因。

如果你觉得少了什么,请不吝赐教,我会很乐意把它们加上。

下面列出的所有这些错误基本都与面向对象编程有关,尤其是Java的OOP。

类名

读下这篇短文“什么是对象”。类应该是真实生活中的一个抽象实体,而不是什么“validators”,“controller”, “managers”这些东西。如果你的类名以”er”结尾的话——那它就是个糟糕的设计。

当然了,工具类也是反模式,比如说Apache的StringUtils, FileUtils, 以及IOUtils。上面这些都是糟糕设计的代表。延伸阅读:OOP中工具类的替代方案。

当然,不要使用前缀或者后缀来区分类和接口。比方说,这些名字就是错误的:IRecord, IfaceEmployee, 或者RecordInterface。通常来说,接口名应该是真实生活中的实体的名字,类名应该可以说明它的实现细节。如果这个实现没有什么特别可说明的,可以把它叫作Default, Simple或者类似的什么。比如说:

复制代码 代码如下:

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

方法名

方法可以返回值也可以返回void。如果方法返回值的话,它的名字应该能说明它返回了什么,比如说(永远也不要使用get前缀):

复制代码 代码如下:

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

如果它返回void,那么它的名字应该要说明它做了什么。比如:

复制代码 代码如下:

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

刚才提到的这些规则只有一个例外——JUnit的test方法不算。下面将会说到这个。

test方法的名字

在JUnit的测试用例中,方法名应该是没有空格的英文语句。用一个例子来说明会更清楚一些:

复制代码 代码如下:

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

你的JavaDoc里的第一句话的开头应该是你要测试的那个类的名字,然后是一个can。因此,你的第一句话应该是类似于“somebody can do something”。

方法名也是一样的,只是没有主题而已。如果我在方法名中间加一个主题的话,我就能得到一个完整的句子,正如上面那个例子中那样:“HttpRequest returns its content in unicode”。

请注意test方法的名字是不以can开头的。只有JavaDoc里的的注释会以can开头。除此之外,方法名不应该以动词开头。

实践中最好将测试方法声明为抛出Exception的。

变量名

避免组合的变量名,比如说timeOfDay, firstItem,或者httpRequest。类变量及方法内的变量都是如此。变量名应该足够长,避免在它的可见作用域内产生歧义,但是如果可以的话也不要太长。名字应该是单数或复数形式的名词,或者是一个适当的缩写。比如:

复制代码 代码如下:

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 number或者String 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());
  }
}

一次性变量

无论如何都应该避免使用一次性变量。这里我所说的“一次性“指的是只使用一次的变量。比如下面这个:

复制代码 代码如下:

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

上述的变量只会使用一次,因此这段代码可以重构成这样:

复制代码 代码如下:

return new File("data.txt");

有的时候,比较罕见的情况中——主要是为了格式更好看些——可能会用到一次性变量。然而,还是应当尽量避免这种情况。

异常

毋庸赘言,永远不要自己吞掉异常,而是应该当它尽量往上传递。私有方法应该始终把受检查异常往外面抛。

不要使用异常来进行流程控制。比方说下面这段代码就是错误的:

复制代码 代码如下:

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

那如果IOException提示“磁盘已满”的话该怎么办?你还会认为这个文件大小为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"
);

多余的常量

当你希望在类的方法中共享信息的时候,应当使用类常量,这些信息应该是你这个类所特有的。不要把常量当作字符串或数值字面量的替代品来使用——这是非常糟糕的实践方式,它会对代码造成污染。常量(正如OOP中的任何对象一样)应当在真实世界中有它自己的含义。看下这些常量在真实生活中的意思是什么:

复制代码 代码如下:

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”。

为了避免这种情况,你最好还是引入一个变量。

时间: 2024-10-27 21:05:42

Java编程中的一些常见问题汇总_java的相关文章

Java编程中正则表达式的用法总结_java

1. 字符串中的正则表达式使用正则表达式可以对字符串进行查找.提取.分割.替换等操作.String类当中提供了如下几个特殊方法: boolean matches(String regex):判断该字符串是否匹配指定的正则表达式. String replaceAll(String regex, String replacement):将该字符串中所有匹配regex的子串替换成replacement. String[] split(String regex):以regex作为分隔符,把该字符串分割成

深入解析Java编程中final关键字的作用_java

final class当一个类被定义成final class,表示该类的不能被其他类继承,即不能用在extends之后.否则在编译期间就会得到错误. package com.iderzheng.finalkeyword; public final class FinalClass { } // Error: cannot inherit from final class PackageClass extends FinalClass { } Java支持把class定义成final,似乎违背了面

整理Java编程中字符串的常用操作方法_java

字符一般情况下,当我们处理字符时,我们用原始数据类型 char. 示例 char ch = 'a'; // Unicode for uppercase Greek omega character char uniChar = '\u039A'; // an array of chars char[] charArray ={ 'a', 'b', 'c', 'd', 'e' }; 然而在开发中,我们会遇到需要使用对象而不是原始数据类型的情况.为了达到这个需求.Java 为原始数据类型 char 提

详解Java编程中super关键字的用法_java

通过用static来定义方法或成员,为我们编程提供了某种便利,从某种程度上可以说它类似于C语言中的全局函数和全局变量.但是,并不是说有了这种便利,你便可以随处使用,如果那样的话,你便需要认真考虑一下自己是否在用面向对象的思想编程,自己的程序是否是面向对象的. 好了,现在开始讨论this&super这两个关键字的意义和用法.在Java中,this通常指当前对象,super则指父类的.当你想要引用当前对象的某种东西,比如当前对象的某个方法,或当前对象的某个成员,你便可以利用this来实现这个目的,当

简单了解Java编程中对异常处理的运用_java

Java异常是一个描述在代码段中发生的异常(也就是出错)情况的对象.当异常情况发生,一个代表该异常的对象被创建并且在导致该错误的方法中被抛出(throw).该方法可以选择自己处理异常或传递该异常.两种情况下,该异常被捕获(caught)并处理.异常可能是由Java运行时系统产生,或者是由你的手工代码产生.被Java抛出的异常与违反语言规范或超出Java执行环境限制的基本错误有关.手工编码产生的异常基本上用于报告方法调用程序的出错状况. Java异常处理通过5个关键字控制:try.catch.th

详解Java编程中对线程的中断处理_java

1. 引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时--都需要通过一个线程去取消另一个线程正在执行的任务.Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制. 如果对Java中断没有一个全面的了解,可能会误以为被中断的线程将立马退出运行,但事实并非如此.中断机制是如何工作的?捕获或检测到中断后,是抛出InterruptedException还是重设中断状态以及在方法中吞掉中断状态会有什么后果?Thread.st

深入解析Java编程中final关键字的使用_java

在Java中声明属性.方法和类时,可使用关键字final来修饰.final变量即为常量,只能赋值一次:final方法不能被子类重写:final类不能被继承.1.final成员声明 final 字段有助于优化器作出更好的优化决定,因为如果编译器知道字段的值不会更改,那么它能安全地在寄存器中高速缓存该值.final 字段还通过让编译器强制该字段为只读来提供额外的安全级别.  1.1关于final成员赋值1)在java中,普通变量可默认初始化.但是final类型的变量必须显式地初始化.   2)fin

详解Java编程中向量(Vector)的应用_java

Vector(向量)是 java.util 包中的一个类,该类实现了类似动态数组的功能. 向量和数组相似,都可以保存一组数据(数据列表).但是数组的大小是固定的,一旦指定,就不能改变,而向量却提供了一种类似于"动态数组"的功能,向量与数组的重要区别之一就是向量的容量是可变的. 可以在向量的任意位置插入不同类型的对象,无需考虑对象的类型,也无需考虑向量的容量. 向量和数组分别适用于不同的场合,一般来说,下列场合更适合于使用向量: 如果需要频繁进行对象的插入和删除工作,或者因为需要处理的对

浅谈Java编程中的单例设计模式_java

写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上线后还可以帮助你分析数据.但是Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,甚至像findbugs等代码检查工具还会认为使用System.out.println()是一个bug. 为什么作为Java新手神器的System.out.println(),到了真正项目开发当中会被唾弃呢?其实只要细细分析,你就会发现它的很多弊端.比如不可控制,所有的日志都会在项目上线后照常打印,从而降低运