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代码审查指南的主要内容,如果未能解决你的问题,请参考以下文章

SonarQube学习入门指南

java代码审查包括哪些内容

markdown 打字稿...编码说明,提示,作弊,指南,代码片段和教程文章

Java程序员注意——审查Java代码的六种常见错误

简单的方法来分享/讨论/协作的代码片段?

java代码走查审查规范