首页 > 其他分享 >重构——搬移语句到调用者(Move Statements to Callers),其反向重构:搬移语句到函数(213)

重构——搬移语句到调用者(Move Statements to Callers),其反向重构:搬移语句到函数(213)

时间:2023-04-12 20:15:23浏览次数:44  
标签:语句 重构 photo emitPhotoData outStream write person location 搬移


8.4 搬移语句到调用者(Move Statements to Callers)

反向重构:搬移语句到函数(213)

emitPhotoData(outStream, person.photo);

function emitPhotoData(outStream, photo) {
  outStream.write(`<p>title: ${photo.title}</p>\n`);
  outStream.write(`<p>location: ${photo.location}</p>\n`);
}

emitPhotoData(outStream, person.photo);
outStream.write(`<p>location: ${person.photo.location}</p>\n`);

function emitPhotoData(outStream, photo) {
  outStream.write(`<p>title: ${photo.title}</p>\n`);
}

动机

作为程序员,我们的职责就是设计出结构一致、抽象合宜的程序,而程序抽象能力的源泉正是来自函数。与其他抽象机制的设计一样,我们并非总能平衡好抽象的边界。随着系统能力发生演进(通常只要是有用的系统,功能都会演进),原先设定的抽象边界总会悄无声息地发生偏移。对于函数来说,这样的边界偏移意味着曾经视为一个整体、一个单元的行为,如今可能已经分化出两个甚至是多个不同的关注点。

函数边界发生偏移的一个征兆是,以往在多个地方共用的行为,如今需要在某些调用点面前表现出不同的行为。于是,我们得把表现不同的行为从函数里挪出,并搬移到其调用处。这种情况下,我会使用移动语句(223)手法,先将表现不同的行为调整到函数的开头或结尾,再使用本手法将语句搬移到其调用点。只要差异代码被搬移到调用点,我就可以根据需要对其进行修改。

这个重构手法比较适合处理边界仅有些许偏移的场景,但有时调用点和调用者之间的边界已经相去甚远,此时便只能重新进行设计了。若果真如此,最好的办法是先用内联函数(115)合并双方的内容,调整语句的顺序,再提炼出新的函数来,以形成更合适的边界。

做法

最简单的情况下,原函数非常简单,其调用者也只有寥寥一两个,此时只需把要搬移的代码从函数里剪切出来并粘贴回调用端去即可,必要的时候做些调整。运行测试。如果测试通过,那就大功告成,本手法可以到此为止。

若调用点不止一两个,则需要先用提炼函数(106)将你不想搬移的代码提炼成一个新函数,函数名可以临时起一个,只要后续容易搜索即可。

如果原函数是一个超类方法,并且有子类进行了覆写,那么还需要对所有子类的覆写方法进行同样的提炼操作,保证继承体系上每个类都有一份与超类相同的提炼函数。接着将子类的提炼函数删除,让它们引用超类提炼出来的函数。

对原函数应用内联函数(115)。

对提炼出来的函数应用改变函数声明(124),令其与原函数使用同一个名字。

如果你能想到更好的名字,那就用更好的那个。

范例

下面这个例子比较简单:emitPhotoData 是一个函数,在两处地方被调用。

  function renderPerson(outStream, person) {
 outStream.write(`<p>${person.name}</p>\n`);
 renderPhoto(outStream, person.photo);
 emitPhotoData(outStream, person.photo);
}

function listRecentPhotos(outStream, photos) {
 photos
  .filter(p => p.date > recentDateCutoff())
  .forEach(p => {
   outStream.write("<div>\n");
   emitPhotoData(outStream, p);
   outStream.write("</div>\n");
  });
}

function emitPhotoData(outStream, photo) {
 outStream.write(`<p>title: ${photo.title}</p>\n`);
 outStream.write(`<p>date: ${photo.date.toDateString()}</p>\n`);
 outStream.write(`<p>location: ${photo.location}</p>\n`);
}

我需要修改软件,支持 listRecentPhotos 函数以不同方式渲染相片的 location 信息,而 renderPerson 的行为则保持不变。为了使这次修改更容易进行,我要应用本手法,将 emitPhotoData 函数最后的那行代码搬移到其调用端。

一般来说,像这样简单的场景,我都会直接将 emitPhotoData 的最后一行剪切并粘贴到两个调用它的函数后面。但为了演示这项重构手法如何在更复杂的场景下运作,这里我还是遵从更详细也更安全的步骤。

重构的第一步是先用提炼函数(106),将那些最终希望留在 emitPhotoData 函数里的语句先提炼出去。

  function renderPerson(outStream, person) {
 outStream.write(`<p>${person.name}</p>\n`);
 renderPhoto(outStream, person.photo);
 emitPhotoData(outStream, person.photo);
}

function listRecentPhotos(outStream, photos) {
 photos
  .filter(p => p.date > recentDateCutoff())
  .forEach(p => {
   outStream.write("<div>\n");
   emitPhotoData(outStream, p);
   outStream.write("</div>\n");
  });
}

function  emitPhotoData(outStream, photo) {
 zztmp(outStream,  photo);
 outStream.write(`<p>location: ${photo.location}</p>\n`);
}

function zztmp(outStream, photo) {
 outStream.write(`<p>title: ${photo.title}</p>\n`);
 outStream.write(`<p>date: ${photo.date.toDateString()}</p>\n`);
}

新提炼出来的函数一般只会短暂存在,因此我在命名上不需要太认真,不过,取个容易搜索的名字会很有帮助。提炼完成后运行一下测试,确保提炼出来的新函数能正常工作。

接下来,我要对 emitPhotoData 的调用点逐一应用内联函数(115)。先从 renderPerson 函数开始。

  function renderPerson(outStream, person) {
 outStream.write(`<p>${person.name}</p>\n`);
 renderPhoto(outStream, person.photo);
 zztmp(outStream,  person.photo);
 outStream.write(`<p>location: ${person.photo.location}</p>\n`);
}
function listRecentPhotos(outStream, photos) {
 photos
  .filter(p => p.date > recentDateCutoff())
  .forEach(p => {
   outStream.write("<div>\n");
   emitPhotoData(outStream, p);
   outStream.write("</div>\n");
  });
}

function emitPhotoData(outStream, photo) {
 zztmp(outStream, photo);
 outStream.write(`<p>location: ${photo.location}</p>\n`);
}

function zztmp(outStream, photo) {
 outStream.write(`<p>title: ${photo.title}</p>\n`);
 outStream.write(`<p>date: ${photo.date.toDateString()}</p>\n`);
}

然后再次运行测试,确保这次函数内联能正常工作。测试通过后,再前往下一个调用点。

  function renderPerson(outStream, person) {
 outStream.write(`<p>${person.name}</p>\n`);
 renderPhoto(outStream, person.photo);
 zztmp(outStream,  person.photo);
 outStream.write(`<p>location: ${person.photo.location}</p>\n`);
}

function listRecentPhotos(outStream, photos) {
 photos
  .filter(p => p.date > recentDateCutoff())
  .forEach(p => {
   outStream.write("<div>\n");
   zztmp(outStream, p);
   outStream.write(`<p>location: ${p.location}</p>\n`);
   outStream.write("</div>\n");
  });
}

function emitPhotoData(outStream, photo) {
 zztmp(outStream, photo);
 outStream.write(`<p>location: ${photo.location}</p>\n`);
}

function zztmp(outStream, photo) {
 outStream.write(`<p>title: ${photo.title}</p>\n`);
 outStream.write(`<p>date: ${photo.date.toDateString()}</p>\n`);
}

至此,我就可以移除外面的 emitPhotoData 函数,完成内联函数(115)手法。

function renderPerson(outStream, person) {
 outStream.write(`<p>${person.name}</p>\n`);
 renderPhoto(outStream, person.photo);
 zztmp(outStream,  person.photo);
 outStream.write(`<p>location: ${person.photo.location}</p>\n`);
}

function listRecentPhotos(outStream, photos) {
 photos
  .filter(p => p.date > recentDateCutoff())
  .forEach(p => {
   outStream.write("<div>\n");
   zztmp(outStream, p);
   outStream.write(`<p>location: ${p.location}</p>\n`);
   outStream.write("</div>\n");
  });
}

function emitPhotoData(outStream, photo) {
 zztmp(outStream, photo);
 outStream.write(`<p>location: ${photo.location}</p>\n`);
}

function zztmp(outStream, photo) {
 outStream.write(`<p>title: ${photo.title}</p>\n`);
 outStream.write(`<p>date: ${photo.date.toDateString()}</p>\n`);
}

最后,我将 zztmp 改名为原函数的名字 emitPhotoData,完成本次重构。

function renderPerson(outStream, person) {
 outStream.write(`<p>${person.name}</p>\n`);
 renderPhoto(outStream, person.photo);
 emitPhotoData(outStream, person.photo);
 outStream.write(`<p>location: ${person.photo.location}</p>\n`);
}

function listRecentPhotos(outStream, photos) {
 photos
  .filter(p => p.date > recentDateCutoff())
  .forEach(p => {
   outStream.write("<div>\n");
   emitPhotoData(outStream, p);
   outStream.write(`<p>location: ${p.location}</p>\n`);
   outStream.write("</div>\n");
  });
}

function emitPhotoData(outStream, photo) {
 outStream.write(`<p>title: ${photo.title}</p>\n`);
 outStream.write(`<p>date: ${photo.date.toDateString()}</p>\n`);
}

标签:语句,重构,photo,emitPhotoData,outStream,write,person,location,搬移
From: https://www.cnblogs.com/bonelee/p/17311059.html

相关文章

  • 重构之Divergent Change(发散式变化)&Shotgun Surgery (散弹式修改)
    重构之DivergentChange(发散式变化)&ShotgunSurgery(散弹式修改) 5.DivergentChange发散式变化描述:一个类被锚定了多个变化,当这些变化中的任意一个发生时,就必须对类进行修改。解释:一个类最好只因一种变化而被修改操作:你应该找出某特定原因而造成的所有变化,然后运用ExtractCl......
  • web前端tips:使用 forEach 循环中的 return 语句会发生什么?
    近日,笔者在认真搬砖的过程中,突然遇到一个问题,请看大屏幕(代码):data(){return{statusList:[{code:"1",name:"已保存"},{code:"2",name:"已提交"}]......
  • 条件覆盖,路径覆盖,语句覆盖,分支覆盖
    条件覆盖,路径覆盖,语句覆盖,分支覆盖 转自http://hi.baidu.com/%D2%D7%B1%D8%BA%C6/blog/item/f016729f4fbeaebbc9eaf4df.html语句覆盖是指选择足够的测试用例,使得运行这些测试用例时,被测程序的每一个语句至少执行一次,其覆盖标准无法发现判定中逻辑运算的错误;判定覆盖是指选择......
  • 网络框架重构之路plain2.0(c++23 without module) 综述
    最近互联网行业一片哀叹,这是受到三年影响的后遗症,许多的公司也未能挺过寒冬,一些外资也开始撤出市场,因此许多的IT从业人员加入失业的行列,而且由于公司较少导致许多人求职进度缓慢,很不幸本人也是其中之一。自从参加工作以来,一直都是忙忙碌碌,开始总认为工作只是为了更好的生活,但是一......
  • dowhile,while,for语句
    循环语句//使用三种语句求0~10的总和for循环语句varsum=0;for(vari=0;i<10;i++){sum+=i;}console.log(sum);while语句vari=0;varsum=0;while(i<10){sum+=i;i++;}console.log(sum);dowhile语句vari=0;varsum=0;do{......
  • if,switch语句
    if语句vara=1;varb=2;if(a>b){console.log(a);}else{console.log(b);}//if/elseifif(a>b){}elseif(){}elseif(){}//嵌套ifif(){if(){}}elseif(){}else{}switch语句varname="sss";switch(......
  • MATLAB代码:基于二阶锥规划的主动配电网动态重构研究
    MATLAB代码:基于二阶锥规划的主动配电网动态重构研究关键词:配电网重构二阶锥主动动态重构参考文档:《考虑动态网络重构的主动配电网优化运行策略》参考了重构部分公式《主动配电网最优潮流研究及其应用实例》参考了二阶锥松弛部分公式仿真平台:MATLABYALMIP+CPLEX优势:代码注......
  • MATLAB代码:基于SOE算法的多时段随机配电网重构方法
    MATLAB代码:基于SOE算法的多时段随机配电网重构方法关键词:配电网重构SOE算法多时段随机重构  仿真平台:MATLAB+CPLEXgurobi平台优势:代码具有一定的深度和创新性,注释清晰主要内容:代码主要做的是一个通过配电网重构获取最优网络拓扑的问题,从而有效降低网损,提高经济效益,同时......
  • plsql控制语句(循环)
    --4.利用三种循环和goto手动循环语句求1到100的偶数和1.loop循环:declare--声明一个变量v_n和一个v_n和的变量v_sum并赋值为0v_nnumber(10):=0;v_sumnumber(10):=0;beginloop......
  • Go笔记(二):条件控制语句
    Go语言中的if语句根据条件表达式的结果选择需要执行的业务流程。1、if控制语句1、if语法语法详情如下:if条件表达式{//条件表达式为true,执行的代码块}条件表达式必须返回布尔型的结果,与Java语法不同,在Go中,if的条件表达式不使用括号。2、if/else语法......