疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

学习Java 8的编码风格的好处


 

 
表现力是功能风格编程的好处之一,但是这对你的代码意味着什么?在本文中,我们比较命令式和功能式代码的例子,识别表达性和简洁性。你会看到这些特质是如何支持可读性的,你会考虑一个相反的例子:当简洁的要求导致无用的代码。最后,我们考察Java 8在函数合成中垂直对齐点的习惯。虽然这个约定对某些Java开发人员可能并不熟悉,但是一个简单的例子说明了它的价值。
 
关于java8
Java 8是对Java语言最重要的更新,自启动以来,包含了大量新功能,您可能会想知道从哪里开始。在这个系列中,作者和教育家Venkat Subramaniam提供了Java 8的一种惯用方法:简短的探索,邀请您重新思考您认为理所当然的Java惯例,同时逐渐将新技术和语法集成到您的程序中。
 
令人惊讶的结果
在Java 8发布大约一年之后,我在我的网站上发布了一个简短而不受控制的调查,邀请开发者参与。每个参与者都在命令式或功能式中呈现一段代码,然后要求确定代码的行为。我测量了每个访问者产生响应的时间,比较了两个不同代码示例的结果。这次调查是开放了48小时,当时有超过1100人参加。结果包含了一些惊喜。
大多数开发人员,都有很多丰富的编程风格经验。尽管功能风格已经存在了很长时间,但是对于大多数Java程序员来说却并不熟悉。了解到这一点,82%的接受命令式编码的调查对象能够确定其正确的行为并不奇怪。与此同时,只有百分之七十五的获得功能风格代码的受访者得到了正确的答案。
 
然而,令我感到意外的是受访者了解这两个代码样本的时间。计算命令式代码的中位时间比功能式代码的中位时间长30秒。
 
在家尝试这个实验
如果写得很好,功能风格的代码比命令风格的代码更具表现力和简洁性。
 
我们来看下面这2个例子
 
命令性代码示例
 
List<String> names = Arrays.asList("Jack", "Jill", "Nate", "Kara", "Kim", "Jullie", "Paul", "Peter");
List<String> subList = new ArrayList<>();
for(String name : names) {
 if(name.length() == 4)
   subList.add(name);
}
StringBuilder namesOfLength4 = new StringBuilder();
for(int i = 0; i < subList.size() - 1; i++) {
 namesOfLength4.append(subList.get(i));
 namesOfLength4.append(", ");
}
if(subList.size() > 1)
 namesOfLength4.append(subList.get(subList.size() - 1));
System.out.println(namesOfLength4);
你需要多少时间来弄清这个代码?如果花费的时间比您预期的要长,请不要感到惊讶。这不代表你的能力,代码质量差。
 
现在用Java 8支持的功能风格编写等效示例:
 
List<String> names = Arrays.asList("Jack", "Jill", "Nate", "Kara", "Kim", "Jullie", "Paul", "Peter");
 
System.out.println(
 names.stream()
   .filter(name -> name.length() == 4)
   .collect(Collectors.joining(", ")));
需要多少时间来弄清楚这个代码?显然,你已经确定了目的,所以这不是一个真正的实验。如果你想真的比较样本,请问几个同事找出一个代码样本或其他,然后比较他们的响应时间。
 
为什么功能风格的编码很重要
如果您熟悉Java 8,那么您可能无需弄清楚第2个例子中的代码。即使您对Java 8不熟悉,也可以使用描述性方法名称。您还可以快速理解代码,因为它比例子1简洁得多。
从本质上讲,代码说:给定一个名称集合,只选择长度为4的名称,然后用逗号连接它们。
 
这个例子是人为设计的,但它确实说明了编码的简洁性和表达性的价值。我们在函数式代码中比在命令式代码中看到更多这些特性。
 
编写可读的代码
功能风格的代码是表达性和简洁性的,这导致程序既短又易于阅读。再看一个例子:
 
命令性代码示例
int result = 0;
for(int e : numbers) {
 if(e > 3 && e % 2 == 0 && e < 8) {
   result += e * 2;
 }
}
System.out.println(result);
给定列表调用numbers,这个代码执行一个大于3小于8的偶数的和,然后打印结果。代码由七条线组成,我们可以减少一两条线。
 
现在用Java 8支持的功能风格编写等效示例:
System.out.println(
numbers.stream()
  .filter(e -> e  > 3)
  .filter(e -> e % 2 == 0)
  .filter(e -> e < 8)
  .mapToInt(e -> e * 2)
  .sum());
这也是七行代码,但在这种情况下,进一步减少代码是没有帮助的。
功能风格的代码并不总是比命令风格的代码短。更重要的是它具有表现力。代码简洁但难于阅读是没有帮助的。
 
不要犯这个错误
功能风格的代码被设计成比命令式代码更简洁,但是不能确保它更具可读性。看看下面这个例子就知道了。
 
System.out.println(
names.stream().
filter(name -> name.startsWith("J")).
filter(name -> name.length() > 3)
 .map(name -> name.toUpperCase()).collect(Collectors.joining(", ")));
这个例子中,filter,map和其它功能元件增加了代码的表现力。但是你可能会注意到这个代码更简洁。
 
虽然只有两行,但这些代码仍然需要相当大的努力来阅读和理解。你的眼睛紧张,看看一个函数调用结束,下一个开始。代码非常简短,但是写得很简单。编写这种无用的代码只有一个理由:开发人员必须憎恨与他们一起工作的每个人。
 
使你的代码简洁,不简单
在编程中,我们很容易忽视表现力和可读性的价值。Java 8通过惯例鼓励这些特性,这表明我们为了函数组合而垂直对齐点。
 
不幸的是,即使在多次提醒之后,程序员经常忽略这个约定。更有经验的程序员应该在代码审查期间强制执行这个约定。
 
下面这个例子使用对齐约定重写上面例子的代码时会发生什么情况:
 
Java中的函数组合:
System.out.println(
names.stream()
     .filter(name -> name.startsWith("J"))
     .filter(name -> name.length() > 3)
     .map(name -> name.toUpperCase())
     .collect(Collectors.joining(", ")));
这里我们看到例5中的简洁代码,但这些点是垂直对齐的,而且我们拒绝将多个条件组合成一个参数的冲动。因此,每条路线都是凝聚力:狭隘和专注,只有一个明确的任务。
一个有用的约定
虽然这看起来可能是可有可无的,但遵循Java 8的对齐约定是非常有益的。
 
遵循这个约定的代码更容易阅读,理解和解释。在详细考察每个部分之前,我们可以迅速掌握总体目标。
元素清晰,易于定位,支持更快的修改。如果我们想要包含另一个条件,或者删除或修改一个现有条件,找到这条线并进行更改将会相对简单。
代码更容易维护,这表示我们关心我们团队中的其他开发人员。编写有用的代码可以极大地影响团队的士气,除了使代码更容易维护。
结论
保持代码的每一行简明扼要是一个很好的做法,但是过度会导致代码变得简单而难以阅读。为了提高表达能力,问问自己代码是否容易理解。为了提高可读性,应用Java 8的垂直排列点约定。使用这些简单的技巧,您将创建简洁,富有表现力和可读性的功能风格的代码。