Java代码审查指南
Posted Java攻城师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java代码审查指南相关的知识,希望对你有一定的参考价值。
用另一双眼睛扫描您的代码总是很有用的,可以帮助您在中断生产之前发现错误。您不必是审阅某人代码的专家。有一定的编程语言经验和复习清单可以帮助您入门。我们整理了一份清单,以供您审查Java代码时应注意的事项。继续阅读!
1.遵循Java代码约定
遵循语言约定有助于快速浏览代码并理解代码,从而提高可读性。例如,Java中的所有程序包名称均以小写形式编写,所有大写字母中的常量,CamelCase中的变量名称,等等。在此处查找约定的完整列表。
有些团队会制定自己的约定,因此在这种情况下要灵活一些!
2.用Lambda和Streams替换命令性代码
如果您使用的是Java 8+,则用流和lambda替换循环和极其冗长的方法会使代码看起来更简洁。Lambda和流使您可以用Java编写功能代码。以下代码段以传统的命令式方式过滤了奇数:
List<Integer> oddNumbers = new ArrayList<>();
for (Integer number : Arrays.asList(1, 2, 3, 4, 5, 6)) {
if (number % 2 != 0) {
oddNumbers.add(number);
}
}
这是过滤奇数的功能方法:
List<Integer> oddNumbers = Stream.of(1, 2, 3, 4, 5, 6)
.filter(number -> number % 2 != 0)
.collect(Collectors.toList());
3.当心 NullPointerException
在编写新方法时,请尽量避免返回null。它可能导致空指针异常。在下面的代码段中,如果列表没有整数,则最高的方法将返回null。
class Items {
private final List<Integer> items;
public Items(List<Integer> items) {
this.items = items;
}
public Integer highest() {
if (items.isEmpty()) return null;
Integer highest = null;
for (Integer item : items) {
if (items.indexOf(item) == 0) highest = item;
else highest = highest > item ? highest : item;
}
return highest;
}
}
在直接在对象上调用方法之前,建议您检查空值,如下所示。
Items items = new Items(Collections.emptyList());
Integer item = items.highest();
boolean isEven = item % 2 == 0; // throws NullPointerException
boolean isEven = item != null && item % 2 == 0 //
但是,在代码中到处都有null检查可能非常麻烦。如果您使用的是Java 8+,请考虑使用Optional该类来表示可能没有有效状态的值。它使您可以轻松定义替代行为,并且对于链接方法很有用。
在下面的代码段中,我们使用Java Stream API通过返回的方法来找到最大的数字Optional。请注意,我们正在使用Stream.reduce,它返回一个Optional值。
public Optional<Integer> highest() {
return items
.stream()
.reduce((integer, integer2) ->
integer > integer2 ? integer : integer2);
}
Items items = new Items(Collections.emptyList());
items.highest().ifPresent(integer -> { //
boolean isEven = integer % 2 == 0;
});
或者,您也可以使用诸如@Nullable或的注释,@NonNull如果在构建代码时存在空冲突,则会产生警告。例如,将@Nullable参数传递给接受@NonNull参数的方法。
4.直接将客户代码中的引用分配给字段
即使该字段是最终字段,也可以操纵公开给客户代码的引用。让我们通过一个例子更好地理解这一点。
private final List<Integer> items;
public Items(List<Integer> items) {
this.items = items;
}
在以上代码段中,我们直接将客户端代码中的引用分配给字段。客户可以轻松地更改列表的内容并按如下所示操作我们的代码。
List<Integer> numbers = new ArrayList<>();
Items items = new Items(numbers);
numbers.add(1); // This will change how items behaves as well
相反,请考虑克隆引用或创建新引用,然后将其分配给字段,如下所示:
private final List<Integer> items;
public Items(List<Integer> items) {
this.items = new ArrayList<>(items);
}
返回引用时,将应用相同的规则。您需要保持谨慎,以免暴露内部可变状态。
5.谨慎处理异常
捕获异常时,如果您有多个捕获块,请确保捕获块的顺序最不特定。在下面的代码段中,由于Exception该类是所有异常的源,因此该异常永远不会被捕获在第二个块中。
try {
stack.pop();
} catch (Exception exception) {
// handle exception
} catch (StackEmptyException exception) {
// handle exception
}
如果情况是可恢复的,并且可以由客户端(您的库或代码的使用者)处理,则可以使用检查的异常。例如,IOException是一个已检查的异常,它会强制客户端处理场景,并且如果客户端选择重新抛出该异常,则应该有意识地呼吁忽略该异常。
6.关于数据结构选择的思考
Java集合提供ArrayList,LinkedList,Vector,Stack,HashSet,HashMap,Hashtable。重要的是要了解每种方法的利弊,以便在正确的上下文中使用它们。一些提示可以帮助您做出正确的选择:
Map:如果您具有键,值对形式的无序项目并且需要有效的检索,插入和删除操作,则很有用。HashMap,Hashtable,LinkedHashMap是所有实现Map的接口。
List:非常常用于创建项目的有序列表。此列表可能包含重复项。ArrayList是List接口的实现。使用可以使列表成为线程安全的,Collections.synchronizedList因此无需使用Vector。以下是一些有关为什么Vector实际上已过时的信息。
Set:类似于列表,但不允许重复。HashSet实现Set接口。
7.曝光前要三思而后行
在Java中public,有很多访问修饰符可供选择- protected,,private。除非要向客户端代码公开方法,否则可能要保留所有private默认设置。公开API后,就再也没有回头路了。
例如,您有一个Library具有以下方法的类,用于按名称签出一本书:
public checkout(String bookName) {
Book book = searchByTitle(availableBooks, bookName);
availableBooks.remove(book);
checkedOutBooks.add(book);
}
private searchByTitle(List<Book> availableBooks, String bookName) {
...
}
如果searchByTitle默认情况下不将方法设置为私有,并且最终将其公开,则其他类可能会开始使用它,并在该方法之上构建您可能希望加入Library该类的逻辑。这可能会破坏Library类的封装,或者可能在以后不破坏其他人的代码的情况下进行还原/修改。有意识地暴露!
8.接口代码
如果您对某些接口(例如ArrayList或LinkedList)有具体的实现,并且直接在代码中使用它们,则可能导致高度耦合。坚持使用该List接口,您可以在将来的任何时间切换实现,而不会破坏任何代码。
public Bill(Printer printer) {
this.printer = printer;
}
new Bill(new ConsolePrinter());
new Bill(new htmlPrinter());
在上面的代码片段中,使用Printer接口可以使开发人员移至另一个具体的类HTMLPrinter。
9.不要强行安装接口
看一下以下界面:
interface BookService {
List<Book> fetchBooks();
void saveBooks(List<Book> books);
void order(OrderDetails orderDetails) throws BookNotFoundException, BookUnavailableException;
}
class BookServiceImpl implements BookService {
...
创建这样的界面有好处吗?此接口是否有范围由另一个类实现?这个接口是否通用到足以由另一个类实现?如果所有这些问题的答案都是“否”,那么我绝对建议您避免使用此不必要的接口,以后您将必须维护该接口。马丁•福勒(Martin Fowler)在他的博客中对此进行了很好的解释。
那么,接口的好用例是什么?假设我们有一个class Rectangle和一个class Circle具有计算周长的行为。总结一下,如果需要所有形状的周长(一个多态的用例),那么拥有接口将更有意义,如下所示。
interface Shape {
Double perimeter();
}
class Rectangle implements Shape {
//data members and constructors
@Override
public Double perimeter() {
return 2 * (this.length + this.breadth);
}
}
class Circle implements Shape {
//data members and constructors
@Override
public Double perimeter() {
return 2 * Math.PI * (this.radius);
}
}
public double totalPerimeter(List<Shape> shapes) {
return shapes.stream()
.map(Shape::perimeter)
.reduce((a, b) -> Double.sum(a, b))
.orElseGet(() -> (double) 0);
}
10.覆盖等于时覆盖hashCode
由于值相等而相等的对象称为值对象。例如金钱,时间。equals如果值相同,则此类必须重写该方法以返回true。equals其他库通常使用该方法进行比较和相等性检查。因此,equals有必要进行覆盖。每个Java对象还具有一个将其与另一个对象区分开的哈希码值。
class Coin {
private final int value;
Coin(int value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Coin coin = (Coin) o;
return value == coin.value;
}
}
在上面的示例中,我们仅覆盖的equals方法Object。
HashMap<Coin, Integer> coinCount = new HashMap<Coin, Integer>() {{
put(new Coin(1), 5);
put(new Coin(5), 2);
}};
//update count for 1 rupee coin
coinCount.put(new Coin(1), 7);
coinCount.size(); // 3 why?
学习资料免费领取群:3907814
我们希望coinCount将1卢比硬币的数量更新为7,因为我们覆盖了equals。但是在HashMap内部检查2个对象的哈希码是否相等,然后才通过equals方法测试相等性。根据hashCode方法的约定,两个不同的对象可能具有或不具有相同的哈希码,但是两个相等的对象必须始终具有相同的哈希码。因此,首先检查哈希码是一个提前退出的条件。这意味着必须同时重写equals和hashCode方法来表达相等性。
以上是关于Java代码审查指南的主要内容,如果未能解决你的问题,请参考以下文章