[Refactoring]Long Method
Long Method(过长函数)
定义:函数太长,逻辑混乱,临时变量太多等。。
影响:可读性差,引起其它坏味道导致维护成本高。
目标:分解。增强可读性。
有个很有趣的例子:问:要把大象装冰箱里分几步?答:分三步。。
这个例子其实很有逻辑性性,想要完成一件事情,把这件事情分成一小步一小步去做。为什么呢?“不积跬步,难以至千里。”好像有点歪楼了。。
下面看来看看这个函数:
public bool GetMatchedPairResult(string boyNumber, string girlNumber) { //get boy number last four digit var boyLastDigtNumber = 0; if (!string.IsNullOrEmpty(boyNumber) && boyNumber.Length > 4) { boyLastDigtNumber = int.Parse(girlNumber.Substring(girlNumber.Length - 4, boyNumber.Length)); } //get Girl number last four digit var girlLastDigtNumber = 0; if (!string.IsNullOrEmpty(girlNumber) && girlNumber.Length > 4) { girlLastDigtNumber = int.Parse(girlNumber.Substring(girlNumber.Length - 4, girlNumber.Length)); } //Number is in range if ((boyLastDigtNumber > 1111 && boyLastDigtNumber < 5555) && (girlLastDigtNumber > 5555 && girlLastDigtNumber < 9999)) { //is lucky number. if (boyLastDigtNumber + girlLastDigtNumber == 8888 || girlLastDigtNumber - boyLastDigtNumber == 1314) { //if other return true; } } return false; }
Feature Envy里说的媒婆有一个函数去实现号码匹配。这个函数的功能是:
1、取男孩和女孩电话号码的后四位。
2、先通过第一个规则去判断后四位号码是否在指定范围内。
3、通过一些运算判断两个电话号码是否匹配。
虽然这个函数比较简单,但是里面的逻辑第一眼看上去并不是很清晰。神马字符串截取,神马if嵌套,神马8888。。需要一行一行结合注释去看。
我们的目标是不关心它内部是如何实现。直接喵一眼函数,就知道它里面都做了什么事情。这个函数里有很多临时变量,也有Duplicated Code.还有Magic Number。
分解!
<span> </span>private const int lastDigitCount = 4; private const int luckyNumber = 8888; private const int loveNumber = 521; private readonly NumberRange boyRange = new NumberRange(1111, 5555); private readonly NumberRange girlRange = new NumberRange(5555, 9999); public bool GetMatchedPairResult(string boyNumber, string girlNumber) { var boyLastDigtNumber = GetLastDigtNumber(boyNumber, lastDigitCount); var girlLastDigtNumber = GetLastDigtNumber(girlNumber, lastDigitCount); return IsInTheRange(girlRange, girlLastDigtNumber, boyRange, boyLastDigtNumber) && IsLuckyNumber(girlLastDigtNumber, boyLastDigtNumber); } private static bool IsInTheRange(NumberRange girlRange, int girlLastDigtNumber, NumberRange boyRange, int boyLastDigtNumber) { return boyRange.Include(boyLastDigtNumber) && girlRange.Include(girlLastDigtNumber); } private static bool IsLuckyNumber(int girlLastDigtNumber, int boyLastDigtNumber) { return boyLastDigtNumber + girlLastDigtNumber == luckyNumber || girlLastDigtNumber - boyLastDigtNumber == loveNumber; } private static int GetLastDigtNumber(string Number, int digit) { var boyLastDigtNumber = 0; if (!string.IsNullOrEmpty(Number) && Number.Length > digit) { boyLastDigtNumber = int.Parse(Number.Substring(Number.Length - digit, Number.Length)); } return boyLastDigtNumber; }
为了干掉 ↓
boyLastDigtNumber > 1111 && boyLastDigtNumber < 5555
提出一个Range:
public class NumberRange { private readonly int _minValue; private readonly int _maxValue; public NumberRange(int minValue, int maxValue) { _minValue = minValue; _maxValue = maxValue; } public bool Include(int number) { return number > _minValue && number < _maxValue; } }
这样我第一眼看到GetMatchedPairResult函数内部时,
1、分别获取号码后四位
2、判断号码是否在范围内(IsInTheRange)
3、判断号码是否是Lucky(IsLuckyNumber)
为什么把这个大函数分解为4个小函数?
大函数,它让别人读起来很困难,很难让人理解维护此函数的成本自然就提高了。小函数,可以让我们一眼看出它的招数或者单看名字就应该猜到它是如何实现。这样的函数才算完美。对于我们上面的Long Method,先分析函数的作用,然后把函数内部做的每一个步骤分离开来。分别抽出小函数。提高一个小函数的解释能力,同时这个小函数也可以被其他函数重用。个人比较喜欢7行以内的函数,虽然7是我的Lucky Number,这样让自己读起来感觉很爽。如果能在加上清晰的函数命名,爽歪歪了。
至于一个函数的抽象粒度以及抽象层次要根据场景。(这句话很虚,没有具体的好场景,今后单独整这个场景)