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.