重构手把手教你重构一段烂代码之见名知意
话不多说,直接上这次要重构的目标:public class Application { private List theList; ... // 本次需要重点关注的代码 public List getThem() { List list1 = new ArrayList<>(); for (int[] x : theList) { if (x[0] == 4) { list1.add(x); } } return list1; } }代码的坏味道
这是一段很短且真实逻辑并不复杂的代码,但是从这段小小的代码里面,我们却嗅到了不少坏味道:不明所以的List到底是个啥?出现了魔数(magic number)4。x[0]中这个下标0,代表什么意思?theList又是什么鬼?
总结来说:代码缺乏好的命名,无法让阅读者根据名字(函数名、变量名)来了解函数总体的逻辑。第一步重构
如果把代码改成下面这个样子,或许会好一点:public class Application { private static final int FLAGGED = 4; private static final int STATUS_VALUE_INDEX = 0; private List gameBoard; ... public List getFlaggedCells() { List flaggedCells = new ArrayList<>(); for (int[] x : gameBoard) { if (x[STATUS_VALUE_INDEX] == FLAGGED) { flaggedCells.add(x); } } return flaggedCells; } }
在这一步的重构中,我们仅仅是修改了函数名、变量名,定义出了常量。其中:theList变成了gameBoard。getThem变成了getFlaggedCells。魔数4被抽取为常量FLAGGED。魔术0被抽取为STATUS_VALUE_INDEX。list1变成了flaggedCells。
相信你看到这段代码,对它的作用已经能猜个七七八八了:
这大概是一个棋盘类游戏,棋盘由一个个格子构成,每个格子用一个int数组来表示。之所以用数组,是因为格子所包含的信息很多,比如颜色、值等等。其中,0这个下标代表格子的状态信息。如果被标记(flagged)过,数值为4。第二步重构
接下来,用面向对象的思想,再把一些信息"封装"一下。public class Application { private List gameBoard; public List getFlaggedCells() { List flaggedCells = new ArrayList<>(); for (Cell cell : gameBoard) { if (cell.isFlagged()) { flaggedCells.add(cell); } } return flaggedCells; } private static class Cell { private static final int FLAGGED = 4; private static final int STATUS_VALUE_INDEX = 0; private int[] info; ... private boolean isFlagged() { return info[STATUS_VALUE_INDEX] == FLAGGED; } } }
在这一步,通过定义Cell这个类(实际上不需要是内部类),部分实现细节屏蔽掉了。
上层应用不需要关注Cell内部是如何存储各种信息的,当前依然保留了int[]作为底层实现,但是数组是一种比较裸的用法,后续完全可以替换为更好的形式。
Cell提供了isFlagged方法,让上层应用可以只关心是否标记这样的业务逻辑,而不需要关注4和0这样的实现细节。
实际上有了这些之后,可通过Lambda表达式把getFlaggedCells化简到一行,你知道怎么做吗?总结
Phil Karlton说:
There are only two hard things in Computer Science: cache invalidation and naming things
大家平时都在讲工匠精神,有时候,匠心的体现并不需要多么花里胡哨的炫技,只需要好好的给代码取个见名知意的名字,或许就已经成功了一半。
说明:因为之前写的各种文章阅读量实在过于惨淡,所以这是一个新开的、比较偏实操一些的系列。其中代码的例子来自于《Clean Code》,文字部分为原创内容。 | | |