用另一双眼睛扫描您的代码总是很有用的,可以帮助您在中断生产之前发现错误。您不必是审阅某人代码的专家。有一定的编程语言经验和复习清单可以帮助您入门。我们整理了一份清单,以供您审查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从而消除了对using的需要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?
我们希望coinCount将1卢比硬币的数量更新为7,因为我们覆盖了equals。但是在HashMap内部检查2个对象的哈希码是否相等,然后才通过equals方法测试相等性。两个不同的对象可能具有或不具有相同的哈希码,但是两个相等的对象必须始终具有相同的哈希码,这由hashCode方法的协定定义。因此,首先检查哈希码是一个提前退出的条件。这意味着必须同时重写equals和hashCode方法来表示相等性。
原文链接:https://codingdict.com/