🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
曾用名:内联函数(Inline Method) 反向重构:提炼函数(106) ![](https://box.kancloud.cn/6f6ee97f778614e56535b27ac15ec86c_482x313.jpeg) ``` function getRating(driver) {  return moreThanFiveLateDeliveries(driver) ? 2 : 1; } function moreThanFiveLateDeliveries(driver) {  return driver.numberOfLateDeliveries > 5; } ``` ![](https://box.kancloud.cn/a3bed334e2e1f6d1a46c5039deb25af9_91x152.jpeg) ``` function getRating(driver) {  return (driver.numberOfLateDeliveries > 5) ? 2 : 1; } ``` ### 动机 本书经常以简短的函数表现动作意图,这样会使代码更清晰易读。但有时候你会遇到某些函数,其内部代码和函数名称同样清晰易读。也可能你重构了该函数的内部实现,使其内容和其名称变得同样清晰。若果真如此,你就应该去掉这个函数,直接使用其中的代码。间接性可能带来帮助,但非必要的间接性总是让人不舒服。 另一种需要使用内联函数的情况是:我手上有一群组织不甚合理的函数。可以将它们都内联到一个大型函数中,再以我喜欢的方式重新提炼出小函数。 如果代码中有太多间接层,使得系统中的所有函数都似乎只是对另一个函数的简单委托,造成我在这些委托动作之间晕头转向,那么我通常都会使用内联函数。当然,间接层有其价值,但不是所有间接层都有价值。通过内联手法,我可以找出那些有用的间接层,同时将无用的间接层去除。 ### 做法 - 检查函数,确定它不具多态性。 > 如果该函数属于一个类,并且有子类继承了这个函数,那么就无法内联。 - 找出这个函数的所有调用点。 - 将这个函数的所有调用点都替换为函数本体。 - 每次替换之后,执行测试。 > 不必一次完成整个内联操作。如果某些调用点比较难以内联,可以等到时机成熟后再来处理。 - 删除该函数的定义。 被我这样一写,内联函数似乎很简单。但情况往往并非如此。对于递归调用、多返回点、内联至另一个对象中而该对象并无访问函数等复杂情况,我可以写上好几页。我之所以不写这些特殊情况,原因很简单:如果你遇到了这样的复杂情况,就不应该使用这个重构手法。 ### 范例 在最简单的情况下,这个重构简单得不值一提。一开始的代码是这样: ``` function rating(aDriver) {  return moreThanFiveLateDeliveries(aDriver) ? 2 : 1; } function moreThanFiveLateDeliveries(aDriver) {  return aDriver.numberOfLateDeliveries > 5; } ``` 我只要把被调用的函数的`return`语句复制出来,粘贴到调用处,取代原本的函数调用,就行了。 ``` function rating(aDriver) { return aDriver.numberOfLateDeliveries > 5 ? 2 : 1; } ``` 不过实际情况可能不会这么简单,需要我多做一点儿工作,帮助代码融入它的新家。例如,开始时的代码与前面稍有不同: ``` function rating(aDriver) {  return moreThanFiveLateDeliveries(aDriver) ? 2 : 1; } function moreThanFiveLateDeliveries(dvr) {  return dvr.numberOfLateDeliveries > 5; } ``` 几乎是一样的代码,但`moreThanFiveLateDeliveries`函数声明的形式参数名与调用处使用的变量名不同,所以我在内联时需要对代码做些微调。 ``` function rating(aDriver) { return aDriver.numberOfLateDeliveries > 5 ? 2 : 1; } ``` 情况还可能更复杂。例如,请看下列代码: ``` function reportLines(aCustomer) {  const lines = [];  gatherCustomerData(lines, aCustomer);  return lines; } function gatherCustomerData(out, aCustomer) {  out.push(["name", aCustomer.name]);  out.push(["location", aCustomer.location]); } ``` 我要把`gatherCustomerData`内联到`reportLines`中,这时简单的剪切和粘贴就不够了。这段代码还不算很麻烦,大多数时候我还是一步到位地完成了重构,只是需要做些调整。如果想更谨慎些,也可以每次搬移一行代码:可以首先对第一行代码使用搬移语句到调用者(217)——我还是用简单的“剪切-粘贴-调整”方式进行。 ``` function reportLines(aCustomer) {  const lines = [];  lines.push(["name", aCustomer.name]);  gatherCustomerData(lines, aCustomer);  return lines; } function gatherCustomerData(out, aCustomer) {  out.push(["name", aCustomer.name]);  out.push(["location", aCustomer.location]); } ``` 然后继续处理后面的代码行,直到完成整个重构。 ``` function reportLines(aCustomer) {  const lines = [];  lines.push(["name", aCustomer.name]);  lines.push(["location", aCustomer.location]);  return lines; } ``` 重点在于始终小步前进。大多数时候,由于我平时写的函数都很小,内联函数可以一步完成,顶多需要一点代码调整。但如果遇到了复杂的情况,我会每次内联一行代码。哪怕只是处理一行代码,也可能遇到麻烦,那么我就会使用更精细的重构手法搬移语句到调用者(217),将步子再拆细一点。有时我会自信满满地快速完成重构,然后测试却失败了,这时我会回退到上一个能通过测试的版本,带着一点儿懊恼,以更小的步伐再次重构。