1 前言
在实际的学习和工作(我没工作过)中,总是避免不了把自己的代码给别人阅读以及回顾自己以前的代码,或者是对现有项目进行升级。这时候就需要有一个良好的规范来约束自己的代码,以得到更好的可维护性。
本文内容学习自:《Building Maintainable Software, Java Edition》详情见末尾参考文献
2 简单的代码单元
代码单元上一篇文章已经说过了,这里不再赘述。
那么简单要如何来衡量呢?书中提到了一种常用的方式:计算代码中可能路径的数量。而这里的路径数量可以通过计算分支点数量来得到。问题又来了,啥是分支点?
分支点是指根据不同条件会得到不同执行结果的语句,也就是我们编程语言中常见的分支结构的语句。分支点的数量,就是覆盖所有分支点产生的分支路径的最小数量,也叫做分支覆盖率。
想象一下:
- 在你的某个函数中存在大量的分支结构,导致你阅读起来非常复杂,甚至就连其嵌套结构都看得眼花缭乱;
- 你需要在上述假设中添加某些分支条件或者功能,但是由于太过复杂,你漏了一些条件;
- 在进行函数测试的时候,你不得不为每一个分支的可能设计单独的测试用例
- …很多种情况
所以你需要简化你的代码
2.1 原则
每个代码单元分支点的数量不超过4个、4个、4个。
2.2 情况一 链式条件处理
原代码:
public List<Color> getFlagColors(Nationlity nationality){
List<Color> result;
switch(nationality){
case DUTCH:
result = Arrays.asList(Color.RED,Color.WHITE,Color.BLUE);
break;
case GERMAN:
result = Arrays.asList(Color.BLACK,Color.RED,Color.YELLOW);
break;
case BELGIAN:
result = Arrays.asList(Color.BLACK,Color.YELLOW,Color.RED);
break;
case FRENCH:
result = Arrays.asList(Color.BLUE,Color.WHITE,Color.RED);
break;
case ITALIAN:
result = Arrays.asList(Color.GREEN,Color.WHITE,Color.RED);
break;
case UNCLASSIFIED:
default:
result = Arrays.asList(Color.GRAY);
break;
}
}
这个代码就是典型的链式条件,其各个分支是并列的关系,他有两种方法,第一种方法是使用MAP数据,将国家和国旗颜色当做数据存储起来,然后直接使用,就不用再做判断了;第二种方法就是将每个国家的国旗都作为一个类,然后其国旗颜色数据直接存储在类的成员中(变量或者方法都行),这样就可以通过这个类的实例来获取国旗的颜色,不用判断。这里的国家国旗的类都实现了同样的接口来方便调用,也就是利用了JAVA语言的多态,其他语言请参考使用。
方法1:
private static Map<Nationality, List<Color> FLAGS =
new HashMap<Nationality, List<color>>();
static {
FLAGS.put(DUTCH,Arrays.asList(Color.RED,Color.WHITE,Color.BLUE));
FLAGS.put(GERMAN,Arrays.asList(Color.BLACK,Color.RED,Color.YELLOW));
FLAGS.put(BELGIAN,Arrays.asList(Color.BLACK,Color.YELLOW,Color.RED));
FLAGS.put(FRENCH,Arrays.asList(Color.BLUE,Color.WHITE,Color.RED));
FLAGS.put(ITALIAN,Arrays.asList(Color.GREEN,Color.WHITE,Color.RED));
}
public List<Color> getFlagColors(Nationality nationality){
List<Color> colors = FLAGS.get(nationality);
return colors != null ? colors : Arrays.aslist(Color.GRAY);
}
方法2:
public interface Flag{
List<Color> getColors();
}
public class DutchFlag implements Flag{
public List<Color> getColors(){
return Arrays.asList(Color.RED,Color.WHITE,Color.BLUE)
}
}
public class ItalianFlag implements Flag{
public List<Color> getColors(){
return Arrays.asList(Color.GREEN,Color.WHITE,Color.RED)
}
}
//...省略N种国旗类定义
private static final Map<Nationality,Flag> FLAGS =
new HashMap<Nationality,Flag>();
static{
FLAGS.put(DUTCH,new DutchFlag());
FLAGS.put(GERMAN,new GermanFlag());
FLAGS.put(BELGIAN,new BelgianFlag());
FLAGS.put(FRENCH,new FrenchFlag());
FLAGS.put(ITALIAN,new ItalianFlag());
}
public List<Color> getFlagColors(Nationality nationality){
Flag flag = FLAGS.get(nationality);
flag = flag != flag : new DefaultFlag();
return flag.getColors();
}
当然了,上面的方式可能会引入更多的类、对象和代码,这就需要我们自己权衡在实际的代码中的改动了。
2.3 情况二 嵌套条件处理
将嵌套的条件语句提取到其他方法中即可。
原代码:
public static int calculateDepth(BinaryTreeNode<Integer> t,int n){
int depth = 0
if(t.getValue()==n){
return depth;
}else{
if(n<t.getValue()){
BinaryTreeNode<Integer> left = t.getLeft();
if(left == null){
throw new TreeException("Value out found in tree!");
}else{
return 1 + calculateDepth(left,n);
}
}else{
BinaryTreeNode<Integer> right = t.getRight();
if(right == null){
throw new TreeException("Value out found in tree!");
}else{
return 1+calculateDepth(right,n)
}
}
}
}
重构后的代码如下:
public static int calculateDepth(BinaryTreeNode<Integer> t,int n){
int depth = 0
if(t.getValue()==n){
return depth;
}else{
return traverseByValue(t,n)
}
}
private static int traverseByValue(BinaryTreeNode<Integer t,int n){
BinaryTreeNode<Integer> child = getChildNode(t,n)
if(childNode==null){
throw new TreeException("Value out found in tree!");
}else{
return 1+calculateDepth(childNode,n);
}
}
private static BinaryTreeNode<Integer> getChildNode(
BinaryTreeNode<Integer> t,int n){
if(n<t.getValue()){
return t.getLeft();
}else{
return t.getRight();
}
}
很明显,经过上面两个方法的处理之后,代码单元的复杂度确实有降低,代码更容易理解并且更易于测试了。
参考文献
[1] Visser J, Rigal S, Eck P V, et al. Building Maintainable Software, Java Edition: Ten Guidelines for Future-Proof Code[M]. O’Reilly Media, Inc. 2016.