百科问答小站 logo
百科问答小站 font logo



这个例子中的if else也要重构掉吗? 第1页

  

user avatar   caoglish 网友的相关建议: 
      

问题太关注于具体案例了,我先抽象成为一个问题:把if-else的代码风格改成表格驱动法的意义在哪里?

表格驱动的意义在于:逻辑和数据分离。


这一点

Ivony

已经提过了,但是现实意义我想这里补充一下。

在程序中,添加数据和逻辑的方式是不一样的,成本也是不一样的。简单的说,数据的添加是非常简单低成本和低风险的;而逻辑的添加是复杂高成本高风险的。


用PHP举个例子吧,比如说,国家简写转换,给一个国家全名,转换成国家简写,用if-else法就写成:

       <?php function contry_initial($country){     if ($country==="China" ){        return "CHN";     }else if($country==="America"){        return "USA";     }else if($country==="Japan"){       return "JPN";     }else{        return "OTHER";     } }      

如果我要增加一个国家,那么我要多加一个else if语句,那么我就是增加了一条逻辑


如果改成表驱动法就是:

       <?php function contry_initial($country){   $countryList=[       "China"=> "CHN",       "America"=> "USA",       "Japan"=> "JPN",     ];      if(in_array($country, array_keys($countryList))) {         return $countryList[$country];     }     return "Other";  }      

如果我增加一个国家,我需要在数组里面加个数据


那么接下来,我就可以剥离这个数据与逻辑的关系了。

       <?php function contry_initial($country, array $countryList){     if(in_array($country, array_keys($countryList))) {         return $countryList[$country];     }     return "Other"; }      

重构到此为止,这样的好处在哪里?

1) 代码本身的优势

  • 逻辑和数据区分一目了然
  • 关系表可以更换,比如国家表格可以是多语言的,中文版表格,英文版表格,日语版表格,以及单元测试中,可以注入测试表格。
  • 在单元测试中,逻辑必须测试,而数据无需测试。

可以想象如果没有表格法,弄个多语言,要写多少语句。

2)数据来源的灵活性

接下来我们再来看看这个国家表格数据来源,如果是数据表格:

  • 来自代码
  • 来自配置
  • 来自INI
  • 来自数据库
  • 来自WEB API

等等,只要数据能转化成数组即可。

而逻辑,必须写死在代码中,无法灵活地重新定义。

3)数据输入修改的成本与风险

我们想想,聘用一个不懂编程,但培训一下就会用后台的客服便宜,还是会一个懂系统开发人员便宜?

如果这个是数据,是来自于数据库的,那么基本上公司的任何有权限的人在后台把这个映射表填一下,就能正常工作了。这个耗费与风险几乎可以忽略不计。 如果数据来自第三方API,如果第三方添加修改了数据,你也是基本放心的。

但是如果这个是逻辑本身,那么只能是这个系统开发人员进行修改,构建,然后经过一系列的测试,进行专业部署流程,使得这个功能在产品上运行,是个耗费与风险是不言而喻的。另外考虑到多人开发,开发风格不统一的话,那么开发成本和代码审查就不可避免了

4) 数据格式的强制性和代码风格的随意性

在现实工程中,多人开发一个功能很常见。这里就有一个多人代码风格的问题了。你如何确保别人的代码中的逻辑一定对呢?

对于数据来说,一但数据格式被代码确定后,数据格式就是强制性的了。

比如这个例子,无论是谁,加几个美国的数据也只能这样加:

       <?php  $countryList=[       "China"=> "CHN",       "America"=> "USA",       "Japan"=> "JPN",       "US"=> "USA",       "United States of America"=> "USA",       "美国"=> "USA",     ];      

就算原始数据结构的花样丰富,最终数据必须格式化成如此。

然而如果是逻辑的话,开发人员一多,逻辑方法就可能发生变化。你可能指望对方这样写代码:

       <?php     if ($country==="China" ){        return "CHN";     }else if($country==="America"){        return "USA";     }else if($country==="Japan"){       return "JPN";     }else if($country==="US"){        return "USA";     }else if($country==="United States of America"){       return "USA";     }else if($country==="美国"){        return "USA";     }else{        return "OTHER";     }      

然而,对方可能会这样写:

       <?php  if ($country === "China") {      return "CHN";  } else if (in_array($country, ["America", "US", "United States of America", "美国"])) {      return "USA";  } else if ($country === "Japan") {      return "JPN";  } else {      return "" }      

后来多了一个日本的需求,又交给另外不同的人去写,说不定最后如此:

       <?php  if ($country === "China") {      return "CHN";  } else if (in_array($country, ["America", "US", "United States of America", "美国"])) {      return "USA";  } else if ($country === "Japan"||$country === "日本") {      return "JPN";  } else {      return "" }      

这样写,都没有错,然而风格却大相径庭。就是在多人合作编程过程中,无法控制所有人的风格,如果需要统一风格,必须依靠代码审查和大量修改,这需要大量资源和成本。

另外,就是因为如此,所有和if else相关的逻辑在单元测试中必须进行一次测试,才能确保逻辑代码的正确性,保证逻辑区域会正确运行;而如果是数据,由于数据格式的可控性,无需对数据进行测试。

由此可见:

  • 在多人开发的项目中,逻辑无序而不可控;数据格式有序而极易控制
  • 在单元测试中,逻辑区块必须进行测试,否则无法确保其正确性;而数据本身无需测试
  • 逻辑代码越少,逻辑复杂度就越少;逻辑代码越多,逻辑复杂度就越高

总结:

所有编程书籍上面都是从语言本身来解释重构的;我在这里是通过项目实践过程中现实意义来解释这个重构。重构if-else成表驱动的在一个项目现实意义总结一下就是:

  • 逻辑与数据分离
  • 逻辑修改成本巨大,数据修改成本极小
  • 逻辑修改风险巨大,数据修改风险极小
  • 数据来源灵活,数据改变灵活

--------------分割线-----

在回头用我上面的观点看看问题的代码:那朋友的重构也是有意义的,因为这个表格可以进一步分离出去的。就算一个评分算法的个数不变了,但评分中参照数据的在未来是要修改的,那么修改数值数据比修改数值逻辑要便宜的多。


user avatar   Ivony 网友的相关建议: 
      

首先回答问题:

这样做是有意义的。


但是提问者和举例子的人理解都有偏差。

首先是提问者说的改写后逻辑反而不清晰了,这里有两个原因,一是对函数式和声明式编程的不熟悉,下意识的希望控制所有的代码逻辑和细节,缺乏高层思维。其次就是这些例子脱离实际,重写的也不够彻底。


那么简单来说说这样的重构为何有意义?

首先某高票答案的说法基本完全无法认同,无论何种分支代码代码最终必然是条件跳转,分支只能从一种形式转换为另一种形式这种说法毫无意义,因为这里的重构完全与分支无关。


这里重构的意义在于把数据剥离出来!

虽然我觉得理解这个对于初学者而言有一定的难度,但是要理解这样的重构,还是必须从这个层面来讨论。

       function doSomething( a ) {   if ( a === 'x' )     doX();   else if ( a === 'y' )     doY();   else     doZ(); }      


重构之后:

       function doSomething(a) {     var lookup = {x: doX, y: doY}, def = doZ; //数据     (lookup[a] || def)();                     //实现 }      

这个重构的目的其实是在于,把数据从实现中剥离出来


为什么要这么做?

原因一:

相较于实现逻辑,数据变更的可能性更大,例如我需要增加一个a==='h'的时候也执行doX,那么直接修改数据就比修改if ... else要方便。在强类型设计语言中,强数据类型可以确保数据的完整性,修改数据比修改代码要安全得多。这也是为什么我们做一个程序有一堆配置的原因,因为相较于修改代码,配置的修改,要安全得多



原因二:

分析和阅读一段代码的时候,很多时候是有侧重面的,有时候侧重于数据,有时候侧重于逻辑。假设我们有这样一个需求,当某某值小于100时,就如何如何。那这个里面的100就是数据,当需求变更为某某值小于200时,才如何如何,那么我们关注的点在于这个数据的修改。而不是整个逻辑的修改,数据的剥离,有助于我们更快的发现修改点和修改代码

事实上通常用于消除魔幻数字的named data,本质上也是从实现中剥离数据的一种方式。

命名数据的例子:

       var maxAge = 100;  //...  if ( age > maxAge )   error( "invalid age." );      

消除魔幻数字的例子:

       var actiontoken_insert = 0;  //...  if ( action === actiontoken_insert )   insert( data );      

但是消除魔幻数字更彻底的方式是基于约定,将token与具体的实现逻辑基于约定进行自动的匹配,去除无意义的match代码,更彻底的消除魔幻数字:

       dataActions[action]( data );      



原因三:

数据和实现分别可以重用,而数据和实现的重用方式和逻辑却不尽相同。数据从实现逻辑中剥离,能够为接下来的消除重复代码做好准备工作。


以上是这样做为什么是有意义的。

然后说这个例子中的不妥之处。



首先第一个例子很明显这个a==='x',a==='y'中的x和y也是一种魔幻数字,只不过变成了字符串这种更高级的形式了。


所以我们需要去解决的是消灭掉这段逻辑,从数据源头把x和y这种魔幻数字给干掉,变成更有意义的表达形式,然后再通过约定来直接消灭掉中间的match的过程,省略掉mapping数据。


第二个例子中将数据提取出来后,可以明显的看出来这是一个连续的区间,既然是连续区间,那么写成很多个数组显然是错误的,应当进一步将数据合并成为一个数组,再将实现逻辑剥离为通用函数,改为函数调用:

       var ranges = [7, 9, 11, 30, 45, 60, 75, 90]  var currentRange = getRange( score, ranges );      



当然,任何事情都有一个度,数据的剥离自然是正确的,但也有其成本和收益,若收益低于成本,例如逻辑实在过于简单,或者说数据和逻辑混杂太深难以剥离,那么此时剥离数据并不总是对的。




  

相关话题

  成功重构了代码是种怎样的体验? 
  成功重构了代码是种怎样的体验? 
  成功重构了代码是种怎样的体验? 
  这个例子中的if else也要重构掉吗? 
  成功重构了代码是种怎样的体验? 
  成功重构了代码是种怎样的体验? 
  成功重构了代码是种怎样的体验? 
  成功重构了代码是种怎样的体验? 
  这个例子中的if else也要重构掉吗? 
  成功重构了代码是种怎样的体验? 

前一个讨论
在合理的前提下,你最希望《名侦探柯南》动画和漫画的大结局是什么?
下一个讨论
为什么物理公式一定要用人名命名?





© 2024-11-21 - tinynew.org. All Rights Reserved.
© 2024-11-21 - tinynew.org. 保留所有权利