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

5 篇文章 0 订阅
4 篇文章 0 订阅

1 前言

在实际的学习和工作(我没工作过)中,总是避免不了把自己的代码给别人阅读以及回顾自己以前的代码,或者是对现有项目进行升级。这时候就需要有一个良好的规范来约束自己的代码,以得到更好的可维护性。本文示例使用的是JAVA语言,但是请注意,这里讲的是原则问题,理论上大部分语言都可以通用。

本文内容学习自:《Building Maintainable Software, Java Edition》详情见末尾参考文献

2 短小的代码单元

代码单元:最小的可以独立执行的一段代码,在C语言中成为函数、JAVA中称为方法、VB中称为子程序等等,几乎每种语言都有其代码单元,只是称呼不同。

假如:

  • 你在调试程序的时候,碰到一个几十行甚至上百行代码的函数,请问你头大不?(嗯,在你刚刚写完一个复杂函数的时候,你可能对其了如指掌,请你一个月后再来看,你的头照样会大)
  • 你在修改代码的时候,想要使用某个小功能(这个已经在你另一个超复杂函数中实现了),然后不得不在你的新函数中重新复制了以前的代码并做小小的修改,就可以用了(然后你的代码中重复代码很多,这和后面会说的不写重复代码 类似)。
  • …很多种情况。

好吧,你需要简化你的代码单元,书中推荐的代码行数是15行、15行、15行(重要的事情说三遍)。

方法一:提取方法

这里借用原书中使用的代码,是Apache 2.0 协议下的开源框架:JPacman

public void start(){
    if(inProgress){
        return;
    }
    inProgress = true;
    //如果玩家死亡,更新观察者
    if(!isAnyPlayerAlive()){
        for(LevelObserver o : observers){
            o.levelLost();
        }
    }
    //如果豆被吃光,更新观察者
    if(remainingPellets()==0){
        for(LevelObserver o : observers){
            o.levelWon();
        }
    }
}

这个代码单元一共有16行(注释不算),虽然很简单,这里只是演示方法。提取方法就是简单地从原有代码中,提取出一部分代码组成新的函数。当然这个提取要提取合理逻辑的。比如下面的新代码:

public void start(){
    if(inProgress){
        return;
    }
    inProgress = true;
    updateObservers();
}

private void updateObservers(){
    //如果玩家死亡,更新观察者
    if(!isAnyPlayerAlive()){
        for(LevelObserver o : observers){
            o.levelLost();
        }
    }
    //如果豆被吃光,更新观察者
    if(remainingPellets()==0){
        for(LevelObserver o : observers){
            o.levelWon();
        }
    }
}

按照这样写,不仅逻辑更加清晰,还能够在其他代码中方便地调用更新观察者的功能。当然,还有更极端的策略,请看下面:

public void start(){
    if(inProgress){
        return;
    }
    inProgress = true;
    updateObservers();
}

private void updateObservers(){
    updateObserversPlayerDied();
    updateObserversPelletsEaten();
}

private void updateObserversPlayerDied(){
    if(!isAnyPlayerAlive()){
        for(LevelObserver o : observers){
            o.levelLost();
        }
    }
}

private void updateObserversPelletsEaten(){
    if(remainingPellets()==0){
        for(LevelObserver o : observers){
            o.levelWon();
        }
    }
}

这就是传说中使用函数名代替注释的操作,当然,这样的提取不太建议,因为更新观察者的函数已经很简单了,如此抽取可能会让总代码量增多(函数定义的那两行),还会增大函数调用的开销,所以请适可而止

方法二:将方法替换为对象

使用对象的方法是为了解决参数过多的问题(什么鬼,怎么就参数过多了?)。听我慢慢说,如果在一个方法中使用了多个局部变量,那么如果要按照前面的方法进行提取,新提取出来的函数就只能通过参数的方式来传递局部变量,并且对于其多个返回值在有些情况下实现起来也很麻烦。所以使用对象的方法,把原函数中的局部变量改为类的私有变量,这样,该类的所有成员函数都可以访问其私有变量,实现和局部变量相同的效果。

请看下列:

public Board createBoard(Square[][] grid){
    assert grid != null;
    Board board = new Board(grid);
    int width = board.getWidth();
    int height = board.getHeight();
    for(int x = 0; x < width; x++){
        for(int y = 0; y < height; h++){
            Square square = grid[x][y];
            for(Direction dir : Direction.values()){
                int dirX = (width + x + dir.getDeltaX()) % width;
                int dirY = (height + y + dir.getDeltaY()) % height;
                Square neighbour = grid[dirX][dirY];
                square.link(neighbour, dir);
            }
        }
    }
}

可以修改成下面这样,比提取方法减少了参数的传递:

class BoardCreator{
    private Square[][] grid;
    private Board board;
    private int width;
    private int height;
    BoardCoreator(Square[][] grid){
        assert grid != null;
        this.grid = grid;
        this.board = new Board(grid);
        this.width = board.getWidth();
        this.height = board.getHeight();
    }
    Board create(){
        for(int x = 0; x < width; x++){
            for(int y = 0; y < height; h++){
                Square square = grid[x][y];
                for(Direction dir : Direction.values()){
                    setLink(square, dir, x, y);
                }
            }
        }
        return this.board;
    }
    private void setLink(Square square, Direction dir, int x, int y){
        int dirX = (width + x + dir.getDeltaX()) % width;
        int dirY = (height + y + dir.getDeltaY()) % height;
        Square neighbour = grid[dirX][dirY];
        square.link(neighbour, dir);
    }

}

当然,如果改成了这样,那么原函数调用的地方也要修改一下辣。

public Board createBoard(Square[][] grid){
    return new BoardCreator(grid).create();
}

参考文献

[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.

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值