代码的坏味道(1)——过长方法

坏味道——过长方法(Long Method)

特征

一个方法含有太多行代码。一般来说,任何方法超过 10 行时,你就可以考虑是不是过长了。
函数中的代码行数原则上不要超过 100 行。

img
img

问题的原因

通常情况下,创建一个新方法的难度要大于添加功能到一个已存在的方法。大部分人都觉得:“我就添加这么两行代码,为此新建一个方法实在是小题大做了。”于是,张三加两行,李四加两行,王五加两行。。。方法日益庞大,最终烂的像一锅浆糊,再也没人能完全看懂了。于是大家就更不敢轻易动这个方法了,只能恶性循环的往其中添加代码。所以,如果你看到一个超过200行的方法,通常都是多个程序员东拼西凑出来的。

解决方法

一个很好的技巧是:寻找注释。添加注释,一般有这么几个原因:代码逻辑较为晦涩或复杂;这段代码功能相对独立;特殊处理。
如果代码前方有一行注释,就是在提醒你:可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。如果方法有一个描述恰当的名字,就不需要去看内部代码究竟是如何实现的。就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数中。

img
img
  • 为了给一个方法瘦身,可以使用 提炼方法(Extract Method)
  • 如果局部变量和参数干扰提炼方法,可以使用 以查询取代临时变量(Replace Temp with Query)引入参数对象(Introduce Parameter Object)保持对象完整(Preserve Whole Object)
  • 如果前面两条没有帮助,可以通过 以函数对象取代函数(Replace Method with Method Object) 尝试移动整个方法到一个独立的对象中。
  • 条件表达式和循环常常也是提炼的信号。对于条件表达式,可以使用 分解条件表达式(Decompose Conditional) 。至于循环,应该使用 提炼方法(Extract Method) 将循环和其内的代码提炼到独立函数中。

收益

  • 在所有类型的面向对象代码中,方法比较短小精悍的类往往生命周期较长。一个方法越长,就越不容易理解和维护。
  • 此外,过长方法中往往含有难以发现的重复代码。
img
img

性能

是否像许多人说的那样,增加方法的数量会影响性能?在几乎绝大多数情况下,这种影响是可以忽略不计,所以不用担心。
此外,现在有了清晰和易读的代码,在需要的时候,你将更容易找到真正有效的方法来重组代码和提高性能。

重构方法说明

提炼方法(Extract Method)

问题

你有一段代码可以组织在一起。

void printOwing() {
  printBanner();

  //print details
  System.out.println("name: " + name);
  System.out.println("amount: " + getOutstanding());
}

解决

移动这段代码到一个新的方法中,使用方法的调用来替代老代码。

void printOwing() {
  printBanner();
  printDetails(getOutstanding());
}

void printDetails(double outstanding) {
  System.out.println("name: " + name);
  System.out.println("amount: " + outstanding);
}

以查询取代临时变量(Replace Temp with Query)

问题

将表达式的结果放在局部变量中,然后在代码中使用。

double calculateTotal() {
  double basePrice = quantity * itemPrice;
  if (basePrice > 1000) {
    return basePrice * 0.95;
  }
  else {
    return basePrice * 0.98;
  }
}

解决

将整个表达式移动到一个独立的方法中并返回结果。使用查询方法来替代使用变量。如果需要,可以在其他方法中合并新方法。

double calculateTotal() {
  double basePrice = quantity * itemPrice;
  if (basePrice > 1000) {
    return basePrice * 0.95;
  }
  else {
    return basePrice * 0.98;
  }
}

引入参数对象(Introduce Parameter Object)

问题

你的方法中包含重复的参数组。
![img](https://raw.githubusercontent.com/atlantis1024/JavaParty/master/images/%E7%BC%96%E7%A8%8B/%E9%AB%98%E6%95%88%E7%BC%96%E7%A8%8B/%E9%87%8D%E6%9E%84/long-method/Introduce Parameter Object - Before.png)

解决

使用一个对象来代替这些参数。
![img](https://raw.githubusercontent.com/atlantis1024/JavaParty/master/images/%E7%BC%96%E7%A8%8B/%E9%AB%98%E6%95%88%E7%BC%96%E7%A8%8B/%E9%87%8D%E6%9E%84/long-method/Introduce Parameter Object - After.png)

保持对象完整(Preserve Whole Object)

问题

你获得一个对象的几个参数,然后将这些参数传入一个方法。

int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);

解决

尝试将整个对象传入方法。

boolean withinPlan = plan.withinRange(daysTempRange);

以函数对象取代函数(Replace Method with Method Object)

问题

你有一个过长方法,它的局部变量交织在一起,以致于你无法应用提炼方法(Extract Method) 。

class Order {
  //...
  public double price() {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    // long computation.
    //...
  }
}

解决

将方法移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将方法分割成这个类中的多个方法。

class Order {
  //...
  public double price() {
    return new PriceCalculator(this).compute();
  }
}

class PriceCalculator {
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;
  
  public PriceCalculator(Order order) {
    // copy relevant information from order object.
    //...
  }
  
  public double compute() {
    // long computation.
    //...
  }
}

分解条件表达式(Decompose Conditional)

问题

你有复杂的条件表达式。

if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
  charge = quantity * winterRate + winterServiceCharge;
}
else {
  charge = quantity * summerRate;
}

解决

根据条件分支将整个条件表达式分解成几个方法。

if (notSummer(date)) {
  charge = winterCharge(quantity);
}
else {
  charge = summerCharge(quantity);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336

推荐阅读更多精彩内容

  • 何时重构、何时停止重构学会判断一个类内有多少实例变量算是太大、一个函数内有多少行代码算太长 下面是对各种坏味道的简...
    每天学点编程阅读 1,474评论 0 4
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,672评论 0 9
  • 风吹过云寄来的贺卡,你笑靥如花。 此刻我扬起手中的细沙,倾怀将你收纳。 前朝荐归人,身后合野马。 俗名结实了袈裟,...
    爬山猫阅读 976评论 72 47
  • 命运之轮转动不歇,即使过往的生命都是看着河流流经,至少现在,我还可以选择,去舀一捧甘泉,与大家共饮。 写下这个题目...
    憩栈阅读 188评论 0 0