188足球比分直播重构javascript视频存储

计算和格式化视频商店账单的简单示例打开了188足球比分直播重构手册1999。如果在现代的javascript中完成,重构有几个方向。188足球比分直播我在这里探讨了四个问题:对顶级函数的重构,188足球比分直播到带有调度程序的嵌套函数,使用类,以及使用中间数据结构的转换。

2016年5月18日

发现 类似物品在标签上: 188足球比分直播

许多年前,当我写作的时候重构手册188足球比分直播,我以一个(非常)简单的例子打开了这本书,重构了一些代码,这些代码计算了一个客户租用一些视频的费用(在188足球比分直播那些日子里,我们必须去一家商店才能做到这一点)。我最近在考虑这个重构的例子,188足球比分直播尤其是如果它是用现代的javascript编写的,它看起来会怎样。

任何重构188足球比分直播都是关于在特定方向上改进代码,适合开发团队的编码风格。在这本书中,这个例子是用Java编写的,而Java(尤其是Java)提出了某种风格的编码,面向对象的样式。使用javascript,然而,关于选择哪种风格还有很多选择。虽然你可以做一个类似Java的OO风格,尤其是ES6(EcmaScript 2015)并非所有的javascript专家都喜欢这种风格,许多人确实认为使用类是一件坏事。


此初始视频存储代码

为了进一步探索,我需要介绍一些代码。在本例中,是我在世纪之交写回的原始示例的javascript版本。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)let movie=movies[r.movieid];让这个数量=0;//确定每个电影开关的数量(movie.code)case“regular”:thisamount=2;如果(r.days>2)此金额+=(r.days-2)*1.5;}休息;案例“新”:该金额=R.days*3;断裂;案例“儿童”:此金额= 1.5;如果(r.days>3)此金额+=(r.days-3)*1.5;}休息;}//添加Frequent Renter Points FrequentRenterpoints++;//如果(movie.code==“新”&&r.days>2)frequentrenterpoints++,则为两天的新版本租赁增加奖金;//打印此出租结果的数字+=`\t$movie.title \t$thisamount \n`;总金额+=该金额;}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;}

我在用ES6。代码在两个数据结构上运行,这两个都只是JSON记录的列表。客户记录如下

“name”:“马丁”,“rentals”:[“movieid”:“f001”,“天”:3 },“movieid”:“f002”,“天”:1 },}

电影的结构是这样的

“F001”:“title”:“运行”,“code”:“常规”,“f002”:“title”:“trois coulers:bleu”,“code”:“常规”,//ETC }

在原著中,电影只是作为Java对象结构中的对象存在。对于本文,我更喜欢将JSON结构作为参数传递。我假设使用某种全局查找,比如A知识库,不适用于此应用程序。

语句方法打印出出租语句的简单文本输出

Martin Ran 3.5 Trois Couleurs的租赁记录:Bleu 2欠下的金额为5.5您赢得了2个频繁租赁积分

这个产量很粗糙,甚至按照示例代码的标准。我能不能不费吹灰之力把这些数字格式化?记得,然而,这本书是用Java 1.1编写的,在将string.format添加到语言之前。这可以部分地原谅我的懒惰。

语句函数是气味的一个例子长方法.它的大小足以让我怀疑。但是,仅仅因为代码有异味,本身就不足以对其进行重构。因为很难理解,所以分解不当的代码是一个问题。很难理解的代码很难修改,是添加新功能还是调试。所以如果你不需要阅读和理解一些代码,那么它的糟糕结构不会伤害你,你可以很高兴地让它单独呆一会儿。为了激发我们对这个代码片段的兴趣,我们需要一个改变的理由。我们的理由,正如我在书中所说,是编写语句方法的HTML版本,打印出来的东西。

租赁记录马丁

三点五
特罗伊斯·库勒:布鲁

欠款金额五点五

你挣的常客租车点

如我之前所说,在本文中,我将探索许多方法,通过这些方法我可以重构此代码,使添加额外的输出呈现变得更容易。所有这些方法都有相同的开始:将单个方法分解为一组函数,以捕获逻辑的不同部分。一旦我做了这个分解,我将探讨四种不同的方式来安排这些函数以支持可选的渲染。


分解成几个函数

每当我在这样一个过长的函数中工作时,我的第一个想法是使用查找逻辑代码块并使用提取方法.〔1〕第一个吸引我注意的是switch语句。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)let movie=movies[r.movieid];让这个数量=0;//确定每个电影开关的数量(movie.code)case“regular”:thisamount=2;如果(r.days>2)此金额+=(r.days-2)*1.5;}休息;案例“新”:该金额=R.days*3;断裂;案例“儿童”:此金额= 1.5;如果(r.days>3)此金额+=(r.days-3)*1.5;}休息;}//添加Frequent Renter Points FrequentRenterpoints++;//如果(movie.code==“新”&&r.days>2)frequentrenterpoints++,则为两天的新版本租赁增加奖金;//打印此出租结果的数字+=`\t$movie.title \t$thisamount \n`;总金额+=该金额;}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;}

我的IDE(Intellij)为我提供了自动进行重构的功能,188足球比分直播但是它做的不对,它的JavaScript能力不如Java重构那样坚固或成熟。188足球比分直播所以我用手工的方式来做,其中包括查看候选提取所使用的数据。这里有三位数据:

  • 这个数额是由提取的代码计算的值。我可以在函数中初始化它并在结束时返回它
  • R是否在循环中检查租金,我可以把它作为参数传入。
  • 电影是出租的电影吗?这是早些时候的温度。像这样的临时变量通常会妨碍重构过程代码,188足球比分直播所以我更喜欢第一次使用以查询取代临时变量把它们转换成一个我可以在任何提取的代码中调用的函数。

一旦我完成了以查询取代临时变量代码看起来像这样。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;用于(让R代表客户。租赁){让这个数量=0;//确定每个电影开关的数量(电影观众(r).code)case“regular”:thisamount=2;如果(r.days>2)此金额+=(r.days-2)*1.5;}休息;案例“新”:该金额=R.days*3;断裂;案例“儿童”:此金额= 1.5;如果(r.days>3)此金额+=(r.days-3)*1.5;}休息;}//添加Frequent Renter Points FrequentRenterpoints++;//如果(电影观众(r).code==“新”&&r.days>2)FrequentRenterpoints++;//打印此出租结果的数字+=`\t${电影观众(r).title \t$此金额\n`;总金额+=该金额;}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;功能电影观众(出租)归还电影[出租.movieid];}

现在我提取switch语句。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;对于(客户租金的R)固定此金额=数额(r);//添加Frequent Renter Points FrequentRenterpoints++;//如果(movietor(r).code==“新”&&r.days>2)frequentrenterpoints++,则为两天的新版本租赁添加奖金;//打印此出租结果的数字+=`\t$movieor(r).title \t$thisamount \n`;总金额+=该金额;}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;功能movietor(出租)归还电影[出租.movieid];功能数额(r)让此金额=0;//确定每个电影开关的数量(movieor(r).code)case“regular”:thisamount=2;如果(r.days>2)此金额+=(r.days-2)*1.5;}休息;案例“新”:该金额=R.days*3;断裂;案例“儿童”:此金额= 1.5;如果(r.days>3)此金额+=(r.days-3)*1.5;}休息;}返还该金额;}

我现在把注意力集中在计算常客积分上。我可以做类似的代码提取

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)const thisamount=amountfor(r);频率解释点(r);//打印此出租结果的数字+=`\t$movieor(r).title \t$thisamount \n`;总金额+=该金额;}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;

功能频率解释点(R)//添加Frequent Renter Points FrequentRenterpoints++;//如果(movietor(r).code==“新”&&r.days>2)frequentrenterpoints++,则为为期两天的新版本租赁添加奖金;}

虽然我提取了函数,我不喜欢它更新父范围变量的工作方式。这样的副作用使代码难以解释,所以我改变它,使它的身体没有副作用。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)const thisamount=amountfor(r);FrequentRenterpoints+=用于(r)的FrequentRenterpoints;//打印此出租结果的数字+=`\t$movieor(r).title \t$thisamount \n`;总金额+=该金额;}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;

函数frequentrenterpoints for(r)let结果=1;if(movietor(r).code==“新建”&&r.days>2)结果++;返回结果;}

我有机会稍微清理一下这两个提取的函数,当我理解他们的时候。

函数amountfor(rent)让result=0;switch(movietor(rent).code)case“regular”:result=2;if(rent.days>2)result+=(rent.days-2)*1.5;}返回结果;案例“新”:结果=租金.days*3;返回结果;案例“儿童”:结果=1.5;if(rent.days>3)result+=(rent.days-3)*1.5;}返回结果;}返回结果;}

函数frequentrenterpoints for(rent)return(movieor(rent).code==“新”&&rent.days>2)?2:1;}

我可以用这些函数做更多的工作,尤其地数额,这就是我在书中所做的。但对于这篇文章,我将不再进一步研究这些功能的主体。

这样做了,我回到函数体。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)const thisamount=amountfor(r);FrequentRenterpoints+=用于(r)的FrequentRenterpoints;//打印此出租结果的数字+=`\t$movieor(r).title \t$thisamount \n`;总金额+=该金额;}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;

我喜欢使用的一般策略是去掉可变变量。这里有三个,一个是收集最后的字符串,另外两个计算该字符串中使用的值。我对第一个没意见,但想根除另外两个。要开始这样做,我需要拆分循环。首先,我简化了循环并内联常量。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;对于(让r of customer.rentals)frequentrenterpoints+=frequentrenterpoints for(r);result+=`\t$movieor(r).title \t$amountfor(r)\n`;totalamount+=金额(r);}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;

然后我把这个循环分成三部分。

功能说明(客户,电影)让totalAmount=0;设frequentrenterpoints=0;let result=`customer.name的租赁记录\n`;对于(让r of customer.rentals)frequentrenterpoints+=frequentrenterpoints for(r);}for(let r of customer.rentals)result+=`\t$movieor(r).title \t$amountfor(r)\n`;}for(让r of customer.rentals)totalamount+=amountfor(r);}//添加页脚行result+=`所欠金额为$totalAmount \n`;result+=`您赢得了FrequentRenterpoints FrequentRenter Points\n`;返回结果;

一些程序员担心这样的重构会带来性能影响,188足球比分直播在这种情况下,看看老而中肯的文章论软件性能

这个拆分允许我提取函数进行计算。

功能说明(客户,movies)let result=`customer.name$的租赁记录\n`;for(let r of customer.rentals)result+=`\t$movieor(r).title \t$amountfor(r)\n`;}result+=`欠款金额为${总量()}result+=`您已赢得${总频率解释点()常客积分\n`;返回结果;功能总量()让结果为0;for(let r of customer.rentals)result+=amountfor(r);}返回结果;}函数总频率解释点()让结果为0;for(let r of customer.rentals)result+=frequentrenterpoints for(r);}返回结果;}

成为粉丝收集管道,我还将调整循环以使用它们。

函数totalFrequentRenterpoints()返回customer.rentals.map((r)=>FrequentRenterpoints for(r))。减少((a,b)=a+b,0);}函数totalAmount()返回customer.rentals.reduce((total,r)=>总计+金额(r)0);}

我不确定我最喜欢这两种管道样式中的哪一种。


检查组合函数

现在让我们看看我们在哪里。这是所有代码。

函数语句(客户,movies)let result=`customer.name$的租赁记录\n`;for(let r of customer.rentals)result+=`\t$movieor(r).title \t$amountfor(r)\n`;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;函数totalfrequentrenterpoints()返回customer.rentals.map((r)=>frequentrenterpoints for(r))。减少((a,b)=a+b,0);}函数totalAmount()返回customer.rentals.reduce((总计,r)=>总计+金额(r)0);}函数移动器(出租)归还电影[出租.movieid];}函数值(出租)让结果=0;switch(movietor(rent).code)case“regular”:result=2;if(rent.days>2)result+=(rent.days-2)*1.5;}返回结果;案例“新”:结果=租金.days*3;返回结果;案例“儿童”:结果=1.5;if(rent.days>3)result+=(rent.days-3)*1.5;}返回结果;}返回结果;}函数FrequentRenterpoints for(租赁)退货(movieor(rent).code==“new”&&rent.days>2)?2:1;}

我现在有了一个很好的组合函数。函数的核心代码是7行,所有这些都与格式化输出字符串有关。所有计算代码都将移动到自己的嵌套函数集,每一个都很小,名字很清楚,以表明它的目的。

但我仍然不太适合编写HTML发送函数。分解后的函数都嵌套在整个语句函数中,这使得提取函数更容易,因为它们可以引用函数范围内的名称,包括彼此(如数额打电话电影观众)以及提供的参数顾客电影.但我不能写一个简单的HTML语句引用这些函数的函数。为了能够使用相同的计算支持一些不同的输出,我需要做一些进一步的重构。188足球比分直播现在我到达了一个临界点,在这个临界点上,我有几个重构选项要做,这取决于我喜欢如何分解代码。188足球比分直播接下来,我将逐一介绍这些方法,解释每一个如何工作,一旦我把这四个都做完,再比较一下。


使用参数确定输出

我可以采用的一种方法是将输出格式指定为语句函数的参数。我将使用188足球比分直播添加参数,正在提取现有的文本格式代码,并在开始时编写一些代码,当参数指示时发送到提取的函数。

功能说明(客户,电影,格式=“文本”)switch(format)case“text”:返回textstation();}引发新错误(`未知语句格式$格式`);

函数textStation()。{let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)result+=`\t$movieor(r).title \t$amountfor(r)\n`;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;}

然后我可以编写HTML生成函数并向调度器添加一个子句。

功能说明(客户,电影,format='text')switch(format)case“text”:返回textstation();case“html”:返回htmlstatement();}引发新错误(`Unknown statement format$format `);函数htmlstatement()let result=`

租赁记录$客户名称

\n;结果+= \n;for(让r of customer.rentals)result+=` \n;}结果+=”
$movieor(r).title $ {金额(r)}
\n;结果+=

欠款金额$totalAmount()

\n;结果+=

你挣的$totalFrequentRenterpoints()常客租车点

\n;返回结果;}

我可能会想到为调度器逻辑使用数据结构。

功能说明(客户,电影,format='text')const dispatchtable=“text”:textStation,“html”:htmlstatement;if(undefined==dispatchtable[format])引发新错误(`unknown statement format$format `);返回DispatchTable[格式].Call();

使用顶级功能

编写顶级HTML语句函数的问题是计算函数嵌套在文本语句函数中。因此,一个显而易见的方法是将它们移动到顶部上下文。

要做到这一点,我从寻找不涉及任何其他功能开始,在这种情况下电影观众

每当我移动函数时,我喜欢先将函数复制到新的上下文中,适应这种环境,然后用对移动函数的调用替换原始函数体。

函数topmovietor(出租,电影)
归还电影[租借.movieid];
}
功能说明(客户,电影)//[剪贴]

功能移动器(租赁)返回Topmovietor(出租,电影);}

函数frequentrenterpoints for(rent)return(movieor(rent).code==“新”&&rent.days>2)?2:1;}

我现在可以编译和测试,这将告诉我上下文的变化是否造成了任何麻烦。完成后,我可以内联转发函数。

功能电影观众(出租,电影)归还电影[租借电影ID];
功能说明(客户,电影)//[剪贴]

函数FrequentRenterpoints for(Rental)Return(Movieor(Rental,电影).code==“新建”&&rent.days>2)?2:1;}

里面也有类似的变化数额

以及内联,我还重命名了顶级函数以匹配旧名称,所以唯一的区别是电影参数。

然后我对所有嵌套函数都这样做

功能陈述(客户,movies)let result=`customer.name$的租赁记录\n`;for(let r of customer.rentals)result+=`\t$movieor(r,电影).title \t$amountfor(r,电影)}result+=`欠款金额为总金额(客户,电影)result+=`您已赢得$totalfrequentrenterpoints(客户,电影)经常租车点\n`;返回结果;函数总频率解释点(客户,电影)返回customer.rentals.map((r)=>frequentrenterpoints for(r,电影)。减少(a,b)=a+b,0);功能总量(客户,电影)归还客户。租金。减少(总计,r)=>总计+金额(r,电影)0);}函数电影观众(出租,电影)归还电影[租借电影ID];功能数额(出租,电影)让结果=0;开关(movietor(出租,movies).code)case“regular”:结果=2;if(rent.days>2)result+=(rent.days-2)*1.5;}返回结果;案例“新”:结果=租金.days*3;返回结果;案例“儿童”:结果=1.5;if(rent.days>3)result+=(rent.days-3)*1.5;}返回结果;}返回结果;函数频率解释点(出租,电影)归还(电影出租,movies).code==“新建”&&rent.days>2)?2:1;

现在我可以轻松地编写HTML语句函数了

功能HTMLStatement(客户,电影)让结果=`

租赁记录$客户名称

\n;结果+= \n;for(让r of customer.rentals)result+=` \n;结果+=
$ {电影) $ {金额(r),电影)
\n;结果+=

欠款金额总金额(客户,电影)

\n;结果+=

你挣的$总频率的解释(客户,电影)常客租车点

\n;返回结果;}

声明部分应用的局部函数

当使用这样的全局函数时,参数列表可能会变长。所以有时候声明一个局部函数,用一些函数调用全局函数是很有用的,或全部,填写的参数。本地函数,这是全局函数的部分应用,以后可以使用。在JavaScript中有多种方法可以做到这一点。一种是将局部函数赋给变量。

功能HTMLStatement(客户,电影)const amount=()=>总金额(客户,电影);
康斯特频率解释点=()=>totalfrequentrenterpoints(客户,电影);
康斯特电影=(阿伦塔)=>电影人(阿伦塔,电影);
const rentalamount=(arental)=>金额for(arental,电影);让结果=

租赁记录$客户名称

\n;结果+= \n;for(让r of customer.rentals)result+=` \n;}结果+=”
${电影(R)标题} ${租金金额(r)}
\n;结果+=

欠款金额${金额()}

\n;结果+=

你挣的${频率解释点()}常客租车点

\n;返回结果;}

另一种方法是将它们声明为嵌套函数。

功能HTMLStatement(客户,电影)让结果=`

租赁记录$客户名称

\n;结果+= \n;for(让r of customer.rentals)result+=` \n;}结果+=”
$电影(r).title 美元租赁金额(R)
\n;结果+=

欠款金额${金额()}

\n;结果+=

你挣的$FrequentRenterpoints()常客租车点

\n;返回结果;函数amount()返回totalamount(客户,电影); 函数frequentrenterpoints();返回totalfrequentrenterpoints(客户,电影); 函数rentalMount(arental)返回(arental,电影); 函数影片(arental)返回movietor(arental,电影);}

另一种方法是绑定.我让你去查一下那张表——这不是我在这里要用的东西,因为我觉得这些表格更容易理解。


使用类

我对物体的方向很熟悉,所以考虑类和对象并不奇怪。ES6为经典OO引入了良好的语法。让我们看看如何将它应用于这个示例。

我的第一步是将数据包装到对象中,从客户开始。

客户,ES6…

导出默认类客户构造函数(数据)此数据=数据;}get name()返回此。_data.name;get rentals()返回此。_data.rentals;

语句.es6…

从“./customer.es6”导入客户;函数语句(客户ARG,电影)const customer=新客户(customerag);let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)result+=`\t$movieor(r).title \t$amountfor(r)\n`;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;

到目前为止,类只是对原始JavaScript对象的简单包装。下一步我会在租金上做一个类似的包装。

租金。

导出默认类出租构造函数(数据)此数据=数据;}get days()返回此。_data.days get movieid()返回此。_data.movieid

客户,ES6…

从“./rental.es6”导入租金导出默认类customer constructor(data)this data=data;}get name()返回此。_data.name;get rentals()返回此。_data.rentals.map(r=>新租金(r));}

现在我已经用简单的JSON对象包装了类,我的目标是移动法.就像把功能移到顶层一样,要使用的第一个函数是不调用任何其他函数的函数-电影观众.但是这个函数需要一个电影列表作为上下文,这需要提供给新创建的租赁对象。

语句.es6…

函数语句(customerag,movies)const customer=新客户(customerag,电影;let result=`customer.name的租赁记录\n`;for(let r of customer.rentals)result+=`\t$movieor(r).title \t$amountfor(r)\n`;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;

类客户…

建造师(数据,电影)this._data=数据;this.\u movies=电影}get rentals()返回此.u data.rentals.map(r=>新租赁(r,这部电影);

班级出租…

建造师(数据,电影)this._data=数据;this.\u movies=电影;}

一旦我有了支持数据,我可以移动这个功能。

语句.es6…

功能移动器(租赁)返回租赁电影;}

班级出租…

get movie()返回此。_movies[this.movieid];}

就像我之前做的那样,第一步是将核心行为置于新的环境中,把它融入到那个环境中,调整原函数调用新函数。一旦成功,内联原始函数调用相对容易。

语句.es6…

函数语句(customerag,movies)const customer=新客户(customerag,电影);let result=`customer.name的租赁记录\n`;for(让r of customer.rentals)result+=`\t${R.电影.title \t$amountfor(r)\n`;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;

函数amountfor(rent)让result=0;开关(开关)租赁电影.code)case“regular”:结果=2;if(rent.days>2)result+=(rent.days-2)*1.5;}返回结果;案例“新”:结果=租金.days*3;返回结果;案例“儿童”:结果=1.5;if(rent.days>3)result+=(rent.days-3)*1.5;}返回结果;}返回结果;}

函数frequentrenterpoints for(出租)返回(租赁电影.code==“新建”&&rent.days>2)?2:1;}

我也可以使用相同的基本序列将两个计算转移到租金中。

语句.es6…

函数语句(customerag,movies)const customer=新客户(customerag,电影);let result=`customer.name的租赁记录\n`;for(让r of customer.rentals)result+=`\t$r.movie.title \t${R.量}\n;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;

函数totalFrequentRenterpoints()返回customer.rentals.map((r)=>R.频率解释)减少((a,b)=a+b,0);}函数totalAmount()返回customer.rentals.reduce((total,r)= >总计+R.量,0);}

班级出租…

获取frequentrenterpoints()。{返回(.movie.code==“新建”&&this.days>2)?2:1;}获取金额()设结果=0;开关(开关).movie.code)case“regular”:结果=2;如果(.days>2)结果+=(.天-2)*1.5;}返回结果;案例“新”:结果=天×3;返回结果;案例“儿童”:结果=1.5;如果(.days>3)结果+=(.天-3)*1.5;}返回结果;}返回结果;}

然后我可以将这两个合计功能移到客户那里

语句.es6…

函数语句(customerag,movies)const customer=新客户(customerag,电影);let result=`customer.name的租赁记录\n`;for(让r of customer.rentals)result+=`\t$r.movie.title \t$r.amount \n`;}result+=`欠款金额为${顾客数量}\n;result+=`您已赢得${客户频率解释}常客租车点\n`;返回结果;}

类客户…

获取frequentrenterpoints()。{返回.rentals.map((r)=>r.frequentrenterpoints).reduce((a,b)=a+b,0);}获取金额()返回.租金.减少(总计,r)=>总计+R.金额,0);}

随着计算逻辑转移到出租和客户对象中,编写语句的HTML版本很简单。

语句.es6…

函数htmlstatement(customerag,movies)const customer=新客户(customerag,电影);让结果=

租赁记录$客户名称

\n;结果+= \n;for(让r of customer.rentals)result+=` \n;}结果+=”
$r.电影.标题 ${r.金额}
\n;结果+=

欠款金额$客户金额

\n;结果+=

你挣的$客户.频率解释要点常客租车点

\n;返回结果;}

没有语法的类

ES2015中的类语法存在争议,有些人觉得它不需要(通常是关于Java开发人员的一个方面)。您可以采取完全相同的一系列重构步骤来得出这样的结果:188足球比分直播

函数语句(客户机ARG)movies)const customer=创建客户(customerag,电影);let result=`customer.name()的租赁记录\n`;for(let r of customer.rentals())result+=`\t$r.movie().title \t$r.amount()\n`;}result+=`欠款金额为$customer.amount()\n`;result+=`您赢得了$customer.frequent renter points()频繁租车积分\n`;返回结果;}函数createCustomer(数据,电影)返回名称:()=>data.name,租金:租金,金额:频率分频点:频率分频点;功能租赁()返回数据。租金。地图(r=>创建租金(r,电影);}功能频率解释点()return rentals().map((r)=>r.frequentrenterpoints()).reduce((a,b)=a+b,0);}函数量()返还租金()。减少((总计,r)=>合计+r.金额(),0);}函数CreateRental(数据,电影)返回天:()=>data.days,movieid:()=>数据.movieid,电影,电影,金额:频率分频点:频率分频点;功能电影()返回电影[data.movieid];}函数量();让结果为0;switch(movie().code)case“regular”:result=2;如果(data.days>2)result+=(data.days-2)*1.5;}返回结果;案例“新”:结果=data.days*3;返回结果;案例“儿童”:结果=1.5;如果(data.days>3)result+=(data.days-3)*1.5;}返回结果;}返回结果;}功能频率解释点()返回(movie().code==“新建”&&data.days>2)?2:1;}

这种方法使用作为对象的函数模式。构造函数函数(创造顾客创造物的)返回函数引用的javascript对象(哈希)。每个构造函数函数都包含一个包含对象数据的闭包。因为函数的返回对象在同一个函数上下文中,所以它们可以访问此数据。我认为这与使用类语法的模式完全相同,但实现方式不同。我更喜欢使用显式语法,因为它更显式-因此使我的思路更清晰。


数据转换

所有这些方法都涉及到语句打印函数调用其他函数来计算所需的数据。另一种方法是将这些数据传递给数据结构本身的语句打印函数。在这种方法中,计算函数用于转换客户数据结构,使其具有打印功能所需的所有数据。

在重构188足球比分直播方面,这是肯特·贝克去年夏天向我描述的尚未编写的分步重构的一个例子。通过这种重构,我将计188足球比分直播算分为两个阶段,使用中间数据结构进行通信。我从引入中间数据结构开始重构。188足球比分直播

功能说明(客户,电影)康斯特数据=CreateStatementData(客户,电影);let result=`租赁记录${数据姓名}\n;对于(r)数据.rentals)result+=`\t$movieor(r).title \t$amountfor(r)\n`;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;函数createStatementData(客户,movies)让result=object.assign(,客户);返回结果;}

对于本例,我将使用添加的元素丰富原始客户数据结构,因此,从调用对象分配.我还可以制作一个全新的数据结构,这种选择实际上取决于转换后的数据结构与原始数据结构的不同程度。

然后我对每一行的租金做同样的事情

函数语句…

函数createStatementData(客户,movies)让result=object.assign(,客户);result.rentals=customer.rentals.map(r=>createrentaldata(r));返回结果;函数CreateRentAlData(Rental)let result=object.assign(,租赁);返回结果;}}

注意我筑巢创建人数据里面创建语句数据因为任何来电者创建语句数据不需要知道内部是如何建立起来的。

然后我可以开始填充转换后的数据,从租来的电影的片名开始。

功能说明(客户,movies)const data=createStatementData(客户,电影);let result=`data.name的租赁记录\n`;for(让r of data.rentals)result+=`\t${R.标题}\ T$金额(R)\n`;}result+=`欠款金额为$totalAmount()\n`;result+=`您赢得了$totalFrequentRenterpoints()FrequentRenter Points\n`;返回结果;
//…函数createStatementData(客户,电影)/…

函数CreateRentAlData(Rental)let result=object.assign(,租赁);result.title=movietor(出租).title;返回结果;}

我接着计算这个数额,然后是总数。

功能说明(客户,movies)const data=createStatementData(客户,电影);let result=`data.name的租赁记录\n`;for(让r of data.rentals)result+=`\t$r.title \t${R.量}\n;}result+=`欠款金额为${data.totalamount总金额}\n;result+=`您已赢得${数据.总频率}常客租车点\n`;返回结果;函数createStatementData(客户,movies)让result=object.assign(,客户);result.rentals=customer.rentals.map(r=>createrentaldata(r));result.totalAmount=totalAmount();
result.totalFrequentRenterpoints=totalFrequentRenterpoints();返回结果;函数CreateRentAlData(Rental)let result=object.assign(,租赁);result.title=movietor(出租).title;result.amount=金额(租金);返回结果;}

现在我已经让所有的计算函数把它们的计算结果作为数据,我可以移动函数来将它们与语句呈现函数分开。首先,我把所有的计算函数都移到里面创建语句数据

功能说明(客户,movies)//body…函数createStatementData(customer,movies)//body…function createrentaldata(出租)…function totalfrequentrenterpoints()…function totalamount()…function movietor(出租)…function amountfor(出租)…function frequentrenterpoints for(出租)…

然后我移动创建语句数据在外面陈述.

功能说明(客户,movies)…函数createStatementData(客户,电影)函数CreateRentAlData(Rental)…函数TotalFrequentRenterpoints()…函数TotalAmount()…函数movietor(Rental)…函数Amountfor(Rental)…函数FrequentRenterpoints for(Rental)…

一旦我把这些功能分开,我可以编写HTML版本的语句来使用相同的数据结构。

功能HTMLStatement(客户,movies)const data=createStatementData(客户,电影);让结果=

租赁记录${DATA .Name }

\n;结果+= \n;for(让r of data.rentals)结果+=` \n;结果+=
${Realth} ${r.金额}
\n;结果+=

欠款金额$data.totalamount_

\n;结果+=

你挣的$data.totalfrequentrenterpoints_常客租车点

\n;返回结果;}

我也可以移动创建语句数据到一个单独的模块来进一步澄清计算数据和呈现语句之间的界限。

ES6

从“./createstatementdata.es6”导入createstatementdata;功能HTMLStatement(客户,电影)…功能说明(客户,电影){ }

创建声明数据.es6

导出默认函数createStatementData(客户,电影)函数createrentaldata(出租)…函数totalFrequentRenterpoints()…函数totalAmount()…函数movietor(出租)…函数amountfor(出租)…函数frequentRenterpoints for(出租)…

比较方法

现在是时候退后一步看看我有什么了。我有一个初始的代码体,作为单个内联函数编写。我希望重构此代码,以便在不复制计算代码的情况下进行HTML呈现。我的第一步是将这段代码分解成几个函数,生活在最初的功能中。从那里,我探索了四条不同的道路:

顶级功能

将所有函数作为顶级函数写入

功能HTMLStatement(客户,电影)

函数文本语句(客户,电影)

功能总金额(客户,电影)

函数totalfrequentrenterpoints(客户,电影)

功能金额(租金,电影)

函数frequentrenterpoints for(出租,电影)

功能移动器(租赁,电影)

显示代码

参数调度

使用顶级函数的参数来说明要发出的输出格式

功能说明(客户,电影,格式)

函数htmlstatement())

函数textStation()。

函数totalAmount()

函数totalFrequentRenterpoints()

功能金额(租赁)

功能频率解释点(出租)

功能移动器(租赁)

显示代码

将计算逻辑移动到呈现函数使用的类中

函数文本语句(客户,电影)

功能HTMLStatement(客户,电影)

类客户

获取金额()

获取FrequentRenterpoints()

获取租金()

班级出租

获取金额()

获取FrequentRenterpoints()

获取电影()

显示代码

转型

将计算逻辑拆分为单独的嵌套函数,为呈现函数生成中间数据结构

功能说明(客户,电影)

功能HTMLStatement(客户,电影)

函数createStatementData(客户,电影)

函数createRentAlData())

函数totalAmount()

函数totalFrequentRenterpoints()

功能金额(租赁)

功能频率解释点(出租)

功能移动器(租赁)

显示代码

我将从顶层函数示例开始,因为比较的基线是概念上最简单的替代方法。〔2〕这很简单,因为它把工作分成一组纯函数,所有这些都可以从我的代码中的任何一点调用。这是一个简单易用且易于测试的方法——我可以通过测试用例或使用repl轻松地测试任何单个函数。

顶级函数的缺点是有很多重复的参数传递。每个功能都需要给定电影数据结构,客户级功能也需要给定客户结构。我不关心这里的重复输入,但我担心重复阅读。每次我读参数时,我都要弄清楚它们是什么,并检查参数是否在变化。对于所有这些功能,客户和电影数据是公共上下文——但是对于顶级函数来说,公共上下文并不是显式的。我在阅读程序时推断它,并在我的头脑中建立它的执行模型,但我希望事情尽可能的明确。

随着上下文的增长,这个因素变得更加重要。我这里只有两个数据项,但找到更多的并不少见。仅使用顶级函数,每次调用都会得到大量参数列表,每一个都增加了我阅读理解的负担。这可能导致将所有这些参数绑定到上下文参数中的陷阱,它包含许多函数的所有上下文,最终掩盖了这些功能的作用。我可以通过定义局部应用的函数来减轻这一切的痛苦,但这是一个额外的函数,声明要放入混合中——这必须与每一位客户机代码重复。

其他三个备选方案的优势在于它们各自明确了共同背景,在程序结构中捕获它。参数调度方法通过捕获顶级参数列表中的上下文来实现这一点,然后,它作为所有嵌套函数的公共上下文提供。这对于原始代码尤其有效,使从单个函数到嵌套函数188足球比分直播的重构比缺乏嵌套函数的语言更简单。

但是当我需要不同于上下文的整体行为时,参数调度方法开始动摇,例如HTML格式的响应。我需要编写某种分派器来决定要调用哪个函数。为渲染器指定格式也不错,但这种调度逻辑是一种独特的味道。不管我怎么写,它仍然在本质上复制语言调用命名函数的核心能力。我正朝着一条能让我很快陷入胡言乱语的道路前进:

函数ExecuteFunction(名称,args)const dispatchtable=/…

这种方法有其背景,当输出格式的选择作为数据出现在我的调用者身上时。在这种情况下,必须在该数据项上有一个调度机制。但是,如果调用方调用这样的语句函数…

const somevalue=对账单(客户,电影演员,“文本”;

…那么我就不应该在代码中编写分派逻辑了。

调用方法是这里的关键。使用文字值表示函数的选择是一种味道。而不是这个API,让调用者说出他们想要什么作为函数名的一部分,文本语句HTML语句.然后我可以使用语言的函数调度机制,避免自己把其他东西拼凑在一起。

所以在我的腰带下有这两种选择,我在哪里?我想要一些明确的共同的逻辑背景,但需要使用该逻辑调用不同的操作。当我感到这种需要时,我马上想到了使用面向对象——本质上,这是一个在公共上下文上独立调用的集合操作。〔3〕这将引导我了解示例的类版本,这使我能够捕捉到客户和租赁对象中的客户和电影的共同背景。我在实例化对象时设置了一次上下文,然后所有进一步的逻辑都可以使用这个公共上下文。

对象方法类似于顶级案例中部分应用的局部函数,但这里的公共上下文是由构造函数提供的。因此我只写局部函数,不是顶级的。调用方用构造函数指示上下文,然后直接调用本地函数。我可以将本地方法看作是概念顶级函数在对象实例的公共上下文上的部分应用。

使用类引入了另一个概念——将呈现逻辑与计算逻辑分离。原始单函数的一个缺点是它将两个函数混合在一起。分解成函数在某种程度上将它们分开,但它们仍然存在于同一个概念空间中。这有点不公平,我可以把计算函数放在一个文件中,把渲染函数放在另一个文件中,通过适当的导入语句链接它们。但我发现公共上下文为如何将逻辑分组到模块中提供了一个自然的提示。

我将对象描述为一组常见的部分应用程序,但有另一种方式来看待它们。对象通过输入数据结构进行实例化,但是,使用通过计算函数公开的计算数据来丰富这些数据。我通过制造这些吸气剂来强化这种思维方式,因此客户机将它们与原始数据完全相同-应用统一接入原则.我可以认为这是从构造函数参数到getter的虚拟数据结构的转换。转换示例是相同的想法,但通过创建一个新的数据结构来实现,该结构将初始数据与所有计算数据结合起来。就像对象封装了客户和租赁类中的计算逻辑一样,转换方法将该逻辑封装在创建语句数据创建人数据.这种转变基本的方法列表和散列数据结构是许多功能性思维的共同特征。它允许创建数据函数共享所需的上下文,以及以简单方式使用多个输出的呈现逻辑。

将类视为转换和转换方法本身之间的一个微小区别是转换计算发生时。这里的转换方法可以同时转换所有内容,而类对每个调用进行单独的转换。当计算恰好与另一个匹配时,我可以很容易地切换。在类的情况下,我可以通过在构造函数中进行计算,一次执行所有的计算。对于转换案例,我可以通过返回中间数据结构中的函数按需重新计算。这里的性能差异几乎总是微不足道的,如果这些函数中的任何一个代价高昂,我的最佳选择通常是使用方法/函数并在第一次调用后缓存结果。

所以有四种方法——我的偏好是什么?我不喜欢写调度程序逻辑,所以我不会使用参数调度方法。顶层函数是我要考虑的,但随着共享环境的扩大,我对它们的品味迅速下降。即使只有两个论点,我倾向于寻求其他的选择。在类和转换方法之间进行选择比较困难,这两种方法都提供了一种很好的方法,可以使公共上下文显式化并很好地分离关注点。我不喜欢打架,所以也许我只是让他们玩整洁的墨水和选择赢家。


进一步重构188足球比分直播

在这次探索中,我研究了四种排列计算和呈现函数的方法。软件是一种很有延展性的媒介,有更多的变化可以用它来完成,但这四个问题我认为是最有意思讨论的。

除了这些函数的排列之外,还有更多的重构。188足球比分直播在本书的例子中,我分解了数量频率解释点支持用新电影类型扩展模型的计算。我将对呈现代码进行更改,例如拉出一个常见的收割台模式,线,页脚。但是,我认为这四条路径对于本文来说已经足够了。

我的结论,如果我有一个,有不同的方法来合理地安排可观察到的相同的计算。不同的语言鼓励某些风格-原始的书籍重构是用Java完成的,188足球比分直播这大大鼓励了班级的风格。javascript轻松支持多种样式,这很好,因为它为程序员提供了选项,坏的,因为它为程序员提供了选项。(在JavaScript中编程的一个困难是几乎没有人对什么是好的样式达成共识。)理解这些不同的样式很有用,但更重要的是要认识到是什么把他们联系在一起。小功能,只要他们是有名的,可以同时和随着时间的推移结合和操纵以支持各种需求。公共上下文建议将逻辑分组在一起,虽然编程的大部分艺术都在决定如何将关注点分离到一组清晰的上下文中。


分享:
如果你觉得这篇文章有用,请分享。我感谢你的反馈和鼓励

脚注

1:重构目录188足球比分直播是在面向对象词汇流行时编写的。所以我用“方法”来指函数/子例程/过程或类似的。在JavaScript中,使用“函数”是明智的,但我使用的是书中重构的名称。188足球比分直播

2:参数调度可以更好地进行第一次重构,188足球比分直播因为它的结构更接近于原始的嵌套函数集。但在比较备选方案时,顶级功能案例是更好的起点。


确认

Vitor Gomes提醒我ES6中的默认参数值。

贝丝·安德烈斯·贝克,Bill Wake晁洋佳Greg DoenchHenrique SouzaJay Fields杨凯文,Marcos BrizenoPete Hodgson瑞安·鲍彻在邮件列表上讨论了这篇文章的草稿。

鲁本·巴特林接到通知说要改正一堆打字错误。

UdoBorkowski在示例中指出了一个bug。

重要修改

2016年5月18日:首次发表