#代码不朽# 编写简单的代码单元——编写可维护软件的10大要则(二)

本文探讨了如何通过减少代码单元中的分支点数量来提高软件的可维护性,并提供了两种实用的方法来简化复杂的条件判断,包括使用Map数据结构和创建特定类实现多态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值