188足球比分直播使用循环和收集管道重构

循环是处理集合的经典方式,但是随着在编程语言中更多地采用一流的函数,收集管道是一个吸引人的选择。In this article I look at 188足球比分直播refactoring loops to collection pipelines with a series of small examples.

2015年7月14日

翻译: 中国人
发现 类似物品通过查看这些标签: 对象协作设计bet188足球· 188足球比分直播

编程中的一个常见任务是处理对象列表。大多数程序员都是通过循环来实现这一点的,因为它是我们第一个程序学习的基本控制结构之一。But loops aren't the only way to represent list processing,近年来,越来越多的人使用另一种方法,我称之为收集管道.这种风格通常被认为是函数式编程的一部分,但我在斯莫尔脱口秀中大量使用了它。由于OO语言支持lambda和库,使第一类函数更容易编程,然后收集管道成为一个吸引人的选择。


188足球比分直播重构一个简单的循环管道

我将从一个简单的循环示例开始,展示将循环重构为集合管道的基本方法。

假设我们有一个作者列表,每个都有以下数据结构。

类作者…

公共字符串名称get;集合;}公共字符串twitterhandle_get;set;公共字符串公司get;集合;}

此示例使用C#

这是回路。

类作者…

静态公共IEnumerable
      
       twitterhandles(IEnumerable
       
        authors,string company) {    var result = new List
        
         ();foreach(author a in authors)if(a.company==company)var handle=a.twitterhandle;如果(把手)!=空)result.add(handle);}}返回结果;}
        
       
      

我将循环重构为集合管道的第一步是应188足球比分直播用 提取变量 on the loop collection.〔1〕

类作者…

静态公共IEnumerable
      
       twitterhandles(IEnumerable
       
        authors,string company) {    var result = new List
        
         ();
         var loopstart=作者;ForEach(作者A in
         环启动)如果(a.company==company)var handle=a.twitterhandle;如果(把手)!=空)result.add(handle);}}返回结果;}
        
       
      

这个变量为管道操作提供了一个起点。我现在没有好的名声,所以我现在就用一个有意义的,希望稍后重命名。

然后我开始研究循环中的一些行为。我首先看到的是条件检查,我可以用过滤器操作.

类作者…

静态公共IEnumerable
      
       twitterhandles(IEnumerable
       
        authors,string company) {    var result = new List
        
         ();var loopstart=作者
         .其中(a=>a.公司==公司);foreach(在loopstart中编写A){
         如果(A.公司=公司){var句柄=a.twitterhandle;如果(把手)!=空)result.add(handle);
         }}返回结果;}
        
       
      

我看到循环的下一部分在twitter句柄上运行,而不是作者,所以我可以用A地图操作〔2〕.

类作者…

静态公共IEnumerable
      
       twitterhandles(IEnumerable
       
        authors,string company) {    var result = new List
        
         ();var loopstart=authors.where(a=>a.company==company)
         .选择(a=>a.twitterhandle);前额
         字符串句柄在环开始){
         var句柄=a.twitterhandle;如果(把手)!=空)result.add(handle);}返回结果;}
        
       
      

接下来在循环中作为另一个条件,我可以再次转到过滤操作。

类作者…

静态公共IEnumerable
      
       twitterhandles(IEnumerable
       
        authors,string company) {    var result = new List
        
         ();var loopstart=authors.where(a=>a.company==company).select(a=>a.twitterhandle)
         .哪里(h=>h!= NULL);foreach(loopstart中的字符串句柄){
         如果(把手)!=空)结果.添加(句柄);}返回结果;}
        
       
      

现在循环所做的就是将其循环集合中的所有内容添加到结果集合中,所以我可以移除循环并返回管道结果。

类作者…

静态公共IEnumerable
      
       twitterhandles(IEnumerable
       
        authors,字符串公司){
        var result = new List
         
          ();
         
    
        return作者。其中(a=>a.company==company)。选择(a=>a.twitterhandle)。其中(h=>h!= NULL);
        foreach(loopstart中的字符串句柄){
      
        结果.添加(句柄);
    
        }
    
        return结果;}
       
      

这是代码的最终状态

类作者…

静态公共IEnumerable
      
       twitterhandles(IEnumerable
       
        authors,string company)返回作者。其中(a=>a.company==company)。选择(a=>a.twitterhandle)。其中(h=>h!= NULL);}
       
      

我喜欢收集管道的一点是,当列表的元素通过管道时,我可以看到逻辑流。For me it reads very closely to how I'd define the outcome of the loop "take the authors,选择那些有公司的人,并让他们的twitter句柄删除任何空句柄”。

此外,这种类型的代码即使在不同的语言中也很常见,因为它们的语法和管道运算符的名称不同。〔3〕

Java

公开名单
      
       twitterhandles(列表
       
        authors,string company)返回authors.stream().filter(a->a.getCompany().equals(company)).map(a->a.getWitterhandle()).filter(h->null!=h).collect(tolist());}
       
      

红宝石

def twitter_handles authors,公司作者。选择a company==a.company.map a a.twitter.reject h h.nil?结束

虽然这与其他示例相匹配,我将取代决赛拒绝具有契约

克洛杰尔

(defn twitter handles[authors company](->>authors(filter(=company(:company%))(map:twitter handle)(remove nil?))

弗斯

let twitterHandles (authors : seq
      
       ,company:string)=authors>seq.filter(fun a->a.company=company)>seq.map(fun a->a.twitterhandle)>seq.choose(fun h->h)
      

再一次,if I wasn't concerned about matching the structure of the other examples I would combine the map and choose into a single step

我发现,一旦我习惯了用管道来思考,即使是用一种不熟悉的语言也能很快地应用它们。因为基本方法是相同的,所以即使是不熟悉的语法和函数名也相对容易翻译。

188足球比分直播在管道中重构,理解

一旦你有了一些表现为管道的行为,您可以通过重新排序管道中的步骤来进行潜在的重构188足球比分直播。一个这样的动作是,如果你有一张地图,后面跟着一个过滤器,您通常可以像这样在映射之前移动过滤器。

类作者…

静态公共IEnumerable
       
        twitterhandles(IEnumerable
        
         authors,string company)返回authors.where(a=>a.company==company).where(a=>
         A.Twitter句柄!=空)。选择(a=>a.twitterhandle);}
        
       

When you have two adjacent filters,你可以用连词把它们结合起来。[4]

类作者…

静态公共IEnumerable
       
        twitterhandles(IEnumerable
        
         authors,string company)返回authors.where(a=>a.company==company
         &&A.推特手柄!=空).选择(a=>a.twitterhandle);}
        
       

一旦我有了这样一个简单的过滤和映射形式的C收集管道,我可以用LINQ表达式替换它

类作者…

静态公共IEnumerable
       
        twitterhandles(IEnumerable
        
         authors,string company)从a in authors返回,其中a.company==company&&a.twitterhandle!=空,选择a.twitterhandle;}
        
       

我认为LINQ表达式是列表理解,and similarly you can do something like this with any language that supports list comprehensions.你是否更喜欢列表理解形式是一个问题,或者管道形式(我更喜欢管道)。一般来说,管道更强大,因为你不能把所有的管道重构成理解。


嵌套循环-书籍读者

对于第二个例子,我将重构一个简单的,双嵌套循环。我假设我有一个允许读者阅读书籍的在线系统。我有一个数据服务,可以告诉我每个读者在某一天读过哪些书。此服务返回一个哈希,其键是读卡器的标识符,值是书籍的标识符集合。

接口数据服务…

地图
      
       >getbooksreadon(日期日期);
      

对于这个例子,我将切换到Java,因为我厌倦了用大写的第一个字母写的方法名。

这是循环

公共集
      
       getreadersofbooks(集合
       
        读者,收藏
        
         books,日期日期)设置
         
          结果=新哈希集<>();地图
          
           >data=dataservice.getbooksreadon(日期);对于(MAP)条目
           
            > e : data.entrySet()) { for (String bookId : books) { if (e.getValue().contains(bookId) && readers.contains(e.getKey())) { result.add(e.getKey());}}}返回结果;
           
          
         
        
       
      

我会用我平常的第一步,哪个适用 提取变量 到循环集合

公共集
      
       getreadersofbooks(集合
       
        读者,收藏
        
         books,日期日期)设置
         
          结果=新哈希集<>();地图
          
           >data=dataservice.getbooksreadon(日期);
           最终集
            
             > >
             条目=data.entryset();
            对于(MAP)条目
           
            >:
            条目)for(string bookid:books)if(e.getValue()。contains(bookid)&&readers.contains(e.getkey())result.add(e.getkey());}}}返回结果;
           
          
         
        
       
      

这样的动作让我很高兴IntelliJ's自动重构可以避免我键入188足球比分直播那个粗糙的类型表达式。

一旦将初始集合放入变量中,我可以研究循环行为的元素。所有的工作都在条件中进行,所以我将从条件中的第二个子句开始,并将其逻辑移动到滤波器.

公共集
      
       getreadersofbooks(集合
       
        读者,收藏
        
         books,日期日期)设置
         
          结果=新哈希集<>();地图
          
           >data=dataservice.getbooksreadon(日期);最终集
           
            >>entries=data.entryset()。
            流() 
            .filter(e->readers.contains(e.getkey()) 
            .collect(collectors.toset());对于(MAP)条目
            
             >e:entries)for(string bookid:books)if(e.getValue()。contains(bookid)
             &&readers.contains(e.getkey()))result.add(e.getkey());}}}返回结果;
            
           
          
         
        
       
      

使用Java的流库,管道必须以终端(如收集器)结束。

另一个子句更难移动,因为它引用了内部循环变量。这个子句测试map条目的值是否包含任何在方法参数的book s列表中的book。我可以通过使用一个集合交集来实现这一点。Java核心类不包括集合交集方法。我可以通过使用一个常见的面向集合的加载项到Java,例如番石榴阿帕奇公地.因为这是一个简单的教学示例,所以我将编写一个粗略的实现。

类UTILS…

公共静态
      
       集合
       
        交叉点(集合
        
         A收藏
         
          b){集
          
           结果=新哈希集
           
            (a);结果:保留醛(b);返回结果;}
           
          
         
        
       
      

This works here,但对于任何实质性的项目,我会用一个公共图书馆。

现在我可以将子句从循环移到管道中。

公共集
      
       getreadersofbooks(集合
       
        读者,收藏
        
         books,日期日期)设置
         
          结果=新哈希集<>();地图
          
           >data=dataservice.getbooksreadon(日期);最终集
           
            >>entries=data.entryset().stream().filter(e->readers.contains(e.getkey()))
            过滤器(E->!实用程序交集(e.getValue(),books).isEmpty()).collect(collectors.toset());对于(MAP)条目
            
             >E:条目)用于(字符串bookid:books){
             if(e.getValue().contains(bookID))。{result.add(e.getkey());
             }}}返回结果;
            
           
          
         
        
       
      

现在所有的循环都是返回映射入口的键,所以我可以通过添加地图操作通向管道

公共集
      
       getreadersofbooks(集合
       
        读者,收藏
        
         books,日期日期)设置
         
          结果=新哈希集<>();地图
          
           >data=dataservice.getbooksreadon(日期);
           结果=data.entryset().stream().filter(e->readers.contains(e.getkey()).filter(e->!实用程序交集(e.getValue(),books).isEmpty())
           .map(e->e.getkey()).collect(collectors.toset());
           对于(MAP)条目
            
             >E:条目){
            
    
           for(字符串bookid:books){
      
           result.add(e.getkey());
    
           }
  
           }返回结果;}
          
         
        
       
      

然后我可以使用将临时变量内联结果.

公共集
      
       getreadersofbooks(集合
       
        读者,收藏
        
         books,Date date) {
         集合
          
           结果=新哈希集<>();
          地图
         
          >data=dataservice.getbooksreadon(日期);
          returndata.entryset().stream().filter(e->readers.contains(e.getkey()).filter(e->!实用程序交集(e.getValue(),books).isEmpty()).map(e->e.getkey()).collect(collectors.toset());
          return结果;}
         
        
       
      

看着交叉路口的使用,我觉得很复杂,当我读到它时,我必须弄清楚它的作用——这意味着我应该提取它。〔5〕

类UTILS…

公共静态
      
       Boolean HasIntersection(集合
       
        A收藏
        
         b)返回!交集(a,b).isEmpty();}
        
       
      

类服务…

公共集
      
       getreadersofbooks(集合
       
        读者,收藏
        
         books,Date date) {    Map
         
          >data=dataservice.getbooksreadon(日期);返回data.entryset().stream().filter(e->readers.contains(e.getkey()).filter(e->utils)。
          hasIntersection(e.getValue(),书籍).map(e->e.getkey()).collect(collectors.toset());}
         
        
       
      

当您需要这样做时,面向对象方法处于劣势。在对象上在静态实用方法和常规方法之间切换比较困难。对于某些语言,我有一种方法让它像流类中的方法一样被读取,但是我在Java中没有这样的选择。

尽管有这个问题,我仍然觉得管道版本更容易理解。我可以将过滤器组合成一个单独的连接,but I usually find it easier to understand each filter as a single element.


设备供应

The next bit example uses some simple criteria to mark preferred equipment for particular regions.为了理解它的作用,我需要解释域模型。我们有一个在不同地区提供设备的组织。当你要求一些设备时,你也许能得到你想要的东西,但通常你会得到一个替代品,这几乎可以满足你的需求,但可能不是很好。举一个相当夸张的例子:你在波士顿,想要个吹雪机,但是如果店里没有,你可以得到一个雪铲代替。但是如果你在迈阿密,他们甚至不提供吹雪机,所以你只能得到雪铲。数据模型通过三个类来捕获这一点。

图1:提供的每个实例表示一个区域内提供了一种特定类型的设备,以支持对一种设备的需求。

所以我们可以看到这样的数据:

产品:【吹雪机】“雪铲”地区:【波士顿】“迈阿密”]产品:—地区:“波士顿”,支持:'吹雪机',提供:'Snow Blower'-地区:'Boston',支持:'吹雪机',supplied: 'snow-shovel'}  - {region: 'miami',支持:'吹雪机',提供:'雪铲'

我们将要看到的代码是一些将这些产品中的一些标记为首选的代码,meaning that this offering is the preferred offering for some equipment in a region.

类服务…

var checkedRegions=新哈希集
      
       ();foreach(在equipment.alloffering()中提供o1)region r=o1.region;if(checkedRegions.contains(r))继续;}提供posspref=空;foreach(var o2 in equipment.alloffering(r))if(o2.ispreference)pospref=o2;断裂;}else if(o2.ismatch posspref==null)posspref=o2;}}}possprefered=true;选中区域。添加(r);}
      

此示例位于C,因为我是个触发器。

我的怀疑是,对于这个循环所做的事情,有一些容易理解的逻辑,通过重188足球比分直播构它,我希望能够揭示出这一逻辑。

我的第一步是从外循环开始并应用 提取变量 to the initial loop variable.

类服务…

var loopstart=设备.alloffering();var checkedRegions=新哈希集
      
       ();ForEach(提供O1-In
       环启动)区域R=O1.区域;if(checkedRegions.contains(r))继续;}提供posspref=空;foreach(var o2 in equipment.alloffering(r))if(o2.ispreference)pospref=o2;断裂;}else if(o2.ismatch posspref==null)posspref=o2;}}}possprefered=true;选中区域。添加(r);}
      

然后我看循环的第一部分。它有一个控制变量,检查区域循环用来跟踪已经处理过的区域,以避免多次处理同一区域。那是一种气味,但它也表明循环变量o1只是通往该地区的踏脚石R.我强调这一点o1在我的编辑中。Once I know that,我知道我可以用地图.

类服务…

var loopstart=设备。分配().选择(o=>o.region);var checkedRegions=新哈希集
      
       ();前额
       区域R在环开始){
       区域R= O1.区域;if(checkedRegions.contains(r))继续;}提供posspref=空;foreach(var o2 in equipment.alloffering(r))if(o2.ispreference)pospref=o2;断裂;}else if(o2.ismatch posspref==null)posspref=o2;}}}possprefered=true;选中区域。添加(r);}
      

现在我可以谈谈检查区域控制变量。循环使用此方法避免多次处理一个区域。我还不清楚检查一个区域是否是等幂的,如果是这样的话,我可能会完全避免这种检查(并衡量一下是否对性能有明显的影响)。Since I'm not sure I decide to retain that logic,尤其是自避免重复对于管道来说是微不足道的.

类服务…

var loopstart=equipment.allofferings()。选择(o=>o.region)区别();
  var checkedRegions=新哈希集
       
        ();
       foreach(loopstart中的区域R){if(checkedRegions.contains(r))。{
      继续;
    }提供posspref=空;foreach(var o2 in equipment.alloffering(r))if(o2.ispreference)pospref=o2;断裂;}else if(o2.ismatch posspref==null)posspref=o2;}}}possprefered=true;选中区域。添加(r);}

下一步是确定波斯普雷夫变量。我认为这将更容易作为自己的方法处理,所以适用Extract Method

类服务…

var loopStart = equipment.AllOfferings()    .Select(o => o.Region)    .Distinct();foreach(loopstart中的区域r)var possprefer=可能的参考(设备,r);possprefered=true;}

静态提供可能的参考(设备设备,区域区域){提供posspref=空;foreach(设备中的var o2.alloffering(region))if(o2.ispreference)pospref=o2;断裂;}else if(o2.ismatch posspref==null)posspref=o2;}      }    }    return possPref;}

我将循环集合提取到变量中。

类服务…

静态提供可能的参考(设备设备,区域区域)提供posspref=空;var alloffering=设备。alloffering(区域);foreach(var o2 in艾伦斯)如果(o2.isprefered)posspref=o2;断裂;}else if(o2.ismatch posspref==null)posspref=o2;}      }    }    return possPref;}

因为循环现在在它自己的函数中,我可以用回程而不是休息。

类服务…

静态提供可能的参考(设备设备,区域区域)提供posspref=空;var alloffering=设备。alloffering(区域);foreach(var o2 in alloffering)if(o2.isprecired){返回O2;
        断裂;}else if(o2.ismatch posspref==null)posspref=o2;}      }    }    return possPref;}

第一个条件是寻找第一个产品,如果有的话,它传递一个条件。这是检测操作的任务(调用弗斯特在C语言中)〔6〕

类服务…

静态提供可能的参考(设备设备,区域区域)提供posspref=空;var alloffering=设备。alloffering(区域);possprefer=allofferings.firstordefault(o=>o.ispreferred);如果(NULL!=posspref)返回posspref;foreach(变量O2分配){如果(参考氧气){
        返回O2;
      }
        否则{如果(o2.ismatch posspref==null)posspref=o2;}}}返回posspref;}

最后一个条件相当复杂。它集波斯普雷夫对于名单上的第一个产品,但如果任何产品通过等匹配测试。但这个循环并没有被打破等匹配通过,所以以后等匹配产品将覆盖该匹配项。为了复制这种行为,我需要使用最后期限.〔7〕

类服务…

静态提供可能的参考(设备设备,区域区域)提供posspref=空;var alloffering=设备。alloffering(区域);possprefer=allofferings.firstordefault(o=>o.ispreferred);如果(NULL!=posspref)返回posspref;posspref=allofferings.lastordefault(o=>o.ismatch);如果(NULL!=posspref)返回posspref;foreach(变量o2分配)如果(O2匹配possPref == null) {          possPref = o2;}}返回posspref;}

The last remaining bit of the loop just returns the first item.

类服务…

静态提供可能的参考(设备设备,区域区域)提供posspref=空;var alloffering=设备。alloffering(区域);possprefer=allofferings.firstordefault(o=>o.ispreferred);如果(NULL!=posspref)返回posspref;posspref=allofferings.lastordefault(o=>o.ismatch);如果(NULL!=posspref)返回posspref;返回alloffering.first();
    foreach(变量O2分配){
      如果(posspref==null){
        POSPReFF=O2;
      }
    }
    return possPref;}

我的个人习惯是结果for the name of any variable used for returns in a function,所以我重命名它。

类服务…

静态提供可能的参考(设备设备,地区地区)产品结果= NULL;var alloffering=设备。alloffering(区域);结果= allOfferings.FirstOrDefault(o => o.isPreferred);如果(NULL!=结果)返回结果;结果=allofferings.lastordefault(o=>o.ismatch);如果(NULL!=结果)返回结果;返回alloffering.first();}

I'm reasonably happy now with可能的参考,我认为它以一种在领域内有意义的方式非常清楚地说明了逻辑。为了理解代码的意图,我不再需要弄清楚代码在做什么。

不过,既然我在C,我可以使用空合并运算符使其读取得更好(??)这允许我将几个表达式链接在一起,并返回第一个非空的表达式。

类服务…

静态提供可能的参考(设备设备,Region region) {    var allOfferings = equipment.AllOfferings(region);是否返回allofferings.firstordefault(o=>o.isPreference)??allofferings.lastordefault(o=>o.ismatch)??allofferings.first();}

在不太严格的类型语言中,将空值视为假值,you do the same thing with an "or" operator.另一种选择是组成一流的函数(但这是一个完全不同的主题)。

现在我回到外环,现在看起来像这样。

类服务…

var loopStart = equipment.AllOfferings()    .Select(o => o.Region)    .Distinct();foreach(loopstart中的区域R)var possprefer=可能参考(设备,r);possprefered=true;}

我可以用我的可能的参考在管道中。

类服务…

var loopstart=equipment.allofferings()。选择(o=>o.region).distinct()).Select(r => possiblePreference(equipment,R);foreach(在loopstart中提供o){var possprefer=可能性参考(产品,r);o.isprefered=真;}

注意将分号放在自己的行上的样式。我经常使用较长的管道来完成这项工作,因为这样更容易操作管道。

通过重命名初始循环变量,结果很清楚。

类服务…

var preferredOfferings=equipment.allofferings()。选择(o=>o.region).distinct()。选择(r=>possiblePreference(设备,R);foreach(在preferredofferings中提供o)o.ispreferred=true;}

我很乐意把它留在那里,但我也可以移动前额像这样进入管道的行为。

类服务…

equipment.alloffering()。选择(o=>o.region).distinct()。选择(r=>possible-reference(设备,r)).toList().foreach(o=>o.isPreference=true);

这是一个更有争议的步骤。许多人不喜欢在管道中使用带有副作用的函数。所以我要用中间体托利斯特自从前额在上不可用可枚举的.以及副作用问题,使用托利斯特同时提醒我们,每当我们使用副作用时,we'll also lose any laziness in the evaluation of the pipe (which is not an issue here since the whole point of the pipe is to select some objects for modification).

不管怎样,然而,我发现这比原来的循环清楚得多。前面的循环例子是相当清楚的,但这一次花了点心思想清楚它在做什么。当然是提取可能的参考is a big factor in making it clearer,你可以这样做保留一个循环,虽然我当然想避免逻辑上的浪费,以确保我避免重复的区域。


分组飞行记录

通过这个例子,我将看到一些总结航班延误信息的代码。代码以航班运行时间记录开始,最初来源于美国交通运输部交通统计局.经过初步的按摩,生成的数据如下所示

[“origin”:“bos”,“dest”:“lax”,“date”:“2015-01-12”,“number”:“25”,“carrier”:“aa”,“delay”:0.0,“cancelled”:false,“origin”:“bos”,“dest”:“lax”,“date”:“2015-01-13”,“number”:“25”,“carrier”:“aa”,“delay”:0.0,“cancelled”:false,…

这是处理它的循环

导出函数airportdata()const data=flightdata();const count = {};const cancellations=const totaldelay=for(让数据行)const airport=row.dest;如果(count[airport]==undefined)count[airport]=0;取消[机场]=0;总延误[机场]=0;}    count[airport]++;if(row.cancelled)取消预订[机场]+;}else totaldelay[机场]+=row.delay;}  }  const result = {};对于(让我计数)结果[I]=result[i].meanDelay = totalDelay[i] / (count[i] - cancellations[i]);结果[i].取消率=取消[i]/计数[i];}返回结果;

此示例使用javascript(节点上的es6),因为现在一切都必须用javascript编写。

循环按目的地机场汇总飞行数据()计算取消率和平均延迟。这里的中心活动是按目的地分组飞行数据,哪个非常适合分组运算符在管道中。所以我的第一步是创建一个捕获这个分组的变量。

import _ from 'underscore';
导出函数airportdata()const data=flightdata();const working=分组(数据,r=>r.dST);const count = {};const cancellations=const totaldelay=for(让数据行)const airport=row.dest;如果(count[airport]==undefined)count[airport]=0;取消[机场]=0;总延误[机场]=0;}    count[airport]++;if(row.cancelled)取消预订[机场]+;}else totaldelay[机场]+=row.delay;}  }  const result = {};对于(让我计数)结果[I]=result[i].meanDelay = totalDelay[i] / (count[i] - cancellations[i]);结果[i].取消率=取消[i]/计数[i];}返回结果;

关于这一步的一些事情。首先,我还想不出一个好名字,所以我就叫它工作.其次,尽管javascript在Array,它缺少分组运算符。我可以自己写,but instead I'll make use of the下划线库,它一直是JavaScript领域的一个有用工具。

COUNT变量捕获每个目的地机场的飞行记录数。我可以很容易地用地图操作.

导出函数airportdata()const data=flightdata();常量工作=链(数据).groupBy(r => r.dest).mapObject((值,key)=>返回计数:val.length).value();const count = {};const cancellations=const totaldelay=for(让数据行)const airport=row.dest;如果(count[airport]==undefined)count[airport]=0;取消[机场]=0;总延误[机场]=0;}    count[airport]++;if(row.cancelled)取消预订[机场]+;}else totaldelay[机场]+=row.delay;}  }  const result = {};对于(让我计数)结果[I]=结果[i].平均延迟=总延迟[i]/(工作[I].计数-取消[一];结果[i].取消率=取消[i]/工作[I].计数;}返回结果;

要在下划线中执行这样的多步骤管道,我必须用function.This ensures that each step in the pipeline is wrapped inside underscore,所以我可以使用方法链建造管道。缺点是我必须使用价值最后从中取出底层数组。

地图操作不是标准地图,因为它对一个javascript对象的内容进行操作,基本上是散列图,因此,映射函数作用于键/值对。在下划线中,我使用MapObjectfunction.

通常当我把行为转移到管道中时,我喜欢完全删除控制变量,但它也在跟踪所需的钥匙,所以我会留一段时间,直到我处理完其他的计算。

接下来我将处理取消变量,which this time I can remove.

导出函数airportdata()const data=flightdata();const working=.chain(data).groupby(r=>r.dest).mapobject((val,key)=>返回计数:val.length,cancellations: val.filter(r => r.cancelled).length}}).value();const count = {};const cancellations=const totaldelay=const cancellations=for(让数据行)const airport=row.dest;如果(count[airport]==undefined)count[airport]=0;取消[机场]=0;总延误[机场]=0;}    count[airport]++;if(行已取消){取消[机场]++;}else totaldelay[机场]+=row.delay;}  }  const result = {};对于(让我计数)结果[I]=结果[i].平均延迟=总延迟[i]/(工作[i].计数-正在工作[I].取消);结果[I].取消率=正在工作[I].取消/工作[I].计数;}返回结果;

映射函数现在变得相当长,所以我想是时候使用了Extract Method关于它。

导出函数airportdata()const data=flightdata();const summary=函数(行)返回计数:rows.length,取消:rows.filter(r=>r.cancelled).lengthconst working=.chain(data).groupby(r=>r.dest).mapobject((val,key) =>总结(VAL).ValueMe();const count = {};const totalDelay = {}  for (let row of data) {    const airport = row.dest;如果(count[airport]==undefined)count[airport]=0;总延误[机场]=0;}    count[airport]++;if(row.cancelled)else totaldelay[机场]+=row.delay;}  }  const result = {};对于(让我计数)结果[I]=结果[i].平均延迟=总延迟[i]/(工作[i].计数-工作[i].取消);结果[i].取消率=工作[i].取消/工作[i].计数;}返回结果;

将函数分配给整个函数中的变量是JavaScript嵌套函数定义的方法,以将其范围限制在航空数据function.我可以想象这个函数更广泛地有用,但这是稍后要考虑的重构。188足球比分直播

现在来处理总延迟计算。

导出函数airportdata()const data=flightdata();const summary=函数(行)返回计数:rows.length,取消:rows.filter(r=>r.cancelled).length,totaldelay:行。筛选器(r=>!r.取消)。减少((acc,each)=>acc+each.延迟,0)}}const working=.chain(data).groupby(r=>r.dest).mapobject((val,key)=>汇总(val)).value();const count = {};const totalDelay = {}for(让数据行)const airport=row.dest;如果(count[airport]==undefined)count[airport]=0;总延误[机场]=0;}    count[airport]++;if(行已取消){
    }
    否则{
      totalDelay[机场]+=行延迟;
    }}常量结果=对于(让我计数)结果[I]=结果[I].平均延迟=工作[I].总延迟/(工作[I].计数-工作[I].取消);结果[i].取消率=工作[i].取消/工作[i].计数;}返回结果;

lambda中的总延迟表达式反映了原始公式,使用A减少操作求和。我经常发现先用地图读起来更好。

导出函数airportdata()const data=flightdata();const summary=函数(行)返回计数:rows.length,取消:rows.filter(r=>r.cancelled).length,totalDelay:行。筛选(r=>!取消。map(r=>r.delay).reduce((a,b)=>a+b)}}const working=.chain(data).groupby(r=>r.dest).mapobject((val,key)=>汇总(val)).value();const count = {};for(让数据行)const airport=row.dest;如果(count[airport]==undefined)count[airport]=0;}    count[airport]++;}常量结果=对于(让我计数)结果[I]=结果[I].平均延迟=工作[I].总延迟/(工作[I].计数-工作[I].取消);结果[i].取消率=工作[i].取消/工作[i].计数;}返回结果;

重新配方没什么大不了的,但我越来越喜欢它。lambda也有点长,但我觉得还不太需要提取。

我还利用这个机会替换lambda来调用summarize只命名函数。

导出函数airportdata()const data=flightdata();const summary=函数(行)返回计数:rows.length,取消:rows.filter(r=>r.cancelled).length,totaldelay:行。筛选器(r=>!r.cancelled).map(r=>r.delay).reduce((a,b)=>a+b)const working=.chain(data).groupby(r=>r.dest).mapobject(summarize.ValueMe();const count = {};for(让数据行)const airport=row.dest;如果(count[airport]==undefined)count[airport]=0;}    count[airport]++;}常量结果=对于(让我计数)结果[I]=结果[I].平均延迟=工作[I].总延迟/(工作[I].计数-工作[I].取消);结果[i].取消率=工作[i].取消/工作[i].计数;}返回结果;

现在所有相关数据都被删除了,我准备搬走了计数.

导出函数airportdata()const data=flightdata();const summary=函数(行)返回计数:rows.length,取消:rows.filter(r=>r.cancelled).length,totalDelay:行。筛选(r=>!r.cancelled).map(r=>r.delay).reduce((a,b)=>a+b)const working=.chain(data).groupby(r=>r.dest).mapobject(summary.value();const count = {};
  for (let row of data) {

    const airport=row.dest;
    if(count[机场]==未定义){
      计数[机场]=0;
    }
    计数[机场]++;
  }常量结果=为了(让我进来)工作)结果[I]=结果[I].平均延迟=工作[I].总延迟/(工作[I].计数-工作[I].取消);结果[i].取消率=工作[i].取消/工作[i].计数;}返回结果;

现在我把注意力转向第二个循环,它本质上是做一个地图计算它的两个值。

导出函数airportdata()const data=flightdata();const summary=函数(行)返回计数:rows.length,取消:rows.filter(r=>r.cancelled).length,totaldelay:行。筛选器(r=>!r.取消).map(r=>r.延迟).reduce((a,b)=>a+b)const formresult=函数(行){返回表示延迟:row.totaldelay/(row.count-row.cancellations),取消率:row.cancellations/row.countlet working=.chain(data).groupby(r=>r.dest).mapobject(summary).mapObject(formResult)值();返工;let result = {};
  为了(让我工作){
    结果[i]={};
    结果[I].平均延迟=工作[I].总延迟/(工作[I].计数-工作[I].取消);
    结果[i].取消率=工作[i].取消/工作[i].计数;
  }
  返回结果;}

所有这些都完成了,我可以用将临时变量内联工作做更多的改名和整理。

导出函数airportdata()const data=flightdata();const summary=函数(航班)返回numFlights:flights.length,num取消:航班。筛选(f=>f.cancelled)。长度,总延迟:航班。过滤(F=>!f.cancelled).map(f=>f.delay).reduce((a,b)=>a+b)const formresult=函数(airport)返回meandelay:airport.totaldelay/(airport.numflights-airport.numcancellations),取消率:airport.numconcellations/airport.numFlights return UuChain(data).groupby(r=>r.dest).mapObject(summary).mapObject(formResult).value();

通常情况下,最后一个函数的更大可读性的一点好处来自于提取函数。但我发现分组运算符在阐明函数的用途和帮助设置提取方面做了很多工作。

如果数据来自关系数据库,并且我遇到性能问题,那么重构还有另一个潜在的好处。188足球比分直播通过将188足球比分直播循环重构为集合管道,我将以更类似于SQL的形式表示转换。在这个任务中,我可能从数据库中提取了大量数据,但是重构的代码使得考虑将分组和第一级汇总逻辑移入SQL更加容易,这将减少我需要通过网络传输的数据量。通常我更喜欢在应用程序代码中保留逻辑而不是SQL,因此,我将把这种移动视为一种性能优化,并且只有当我能够度量一个显著的性能改进时才这样做。但这强化了这一点much easier to do optimizations when you have clear code to work with,这就是为什么我知道的所有性能向导首先强调清晰度的重要性,作为性能代码的基础。


标识符

对于下一个示例,我将查看一些代码,这些代码检查一个人是否具有一组必需的标识符。系统通常依靠某种希望唯一的ID来识别人,such as a customer id.在许多领域,你必须处理许多不同的识别方案,一个人应该有多个方案的标识符。因此,一个镇政府可能会期望一个人拥有一个镇ID,状态ID,还有国民身份证。

图2:具有不同方案标识符的人的数据模型

这种情况下的数据结构非常简单。Person类具有标识符对象的集合。Identifiers have a field for the scheme and some value.但是通常还有一些不能单独由数据模型强制执行的约束,这样的约束由这样的验证函数检查。

类人…

def check_valid_ids required_方案,注:nil note=notification.new note.add_error“has no ids”if@ids.size<1 used=[]found_required=[]dups=[]for id in@ids next if id.void?如果使用,包括?(id.scheme)dups<<id.scheme else for req in required_schemes if id.scheme==req found_required<<req required_schemes.delete req next end used<<id.scheme end if dups.size>0 note.add_error“Duplicate schemes:”+dups.join(“,“)如果所需的_schemes.size>0,则结束,如果所需的_schemes中的req缺少_name+=(缺少_name.size>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

这个例子在Ruby中,because I like programming in Ruby

有几个其他的对象支持这个循环。标识符类知道其方案,价值,以及它是否是空的——这意味着它已被逻辑删除(但仍保留在数据库中)。还有一个通知跟踪任何错误。

类标识符attr_reader:方案,:值定义无效?…类通知定义添加错误E…

在这个程序中,对我来说最大的气味是循环同时做两件事。它都在查找重复的标识符(收集在杜普斯) and finding required schemes that are missing (in所需的方案)程序员很常见,面对同一个对象集合的两件事,决定在同一个循环中做这两件事。One reason is the code required to set up the loop,写两次似乎很遗憾。现代的循环结构和管道消除了这一负担。更有害的原因是对性能的担忧。当然,许多性能热点都涉及循环,在有些情况下,回路可以融合以改善问题。但这些只是我们写的所有循环中的一小部分,所以我们应该遵循通常的编程原则。注重表现的清晰,除非你有一个测量仪,严重的性能问题。如果你有这样的问题,然后修复它优先于清晰,但这种情况很少见。

注重表现的清晰,除非你有一个测量仪,严重的性能问题。

所以面对一个循环做两件事,我毫不犹豫地复制循环以提高清晰度。性能分析很少会导致我反向重构。188足球比分直播

所以我的第一步是使用重构,我称之为拆分循环。188足球比分直播当我这样做的时候,我首先得到循环和将循环连接到一个连贯的代码块中的代码,应用Extract Method对它。

类人…

def check_valid_ids required_方案,注:无注=notification.new note.add_error“has no ids”if@ids.size<1返回内部\检查\有效的\需要的\方案,注:注意:结束def内部检查有效的\u id必需的\u方案,注:零used=[]found_required=[]dups=[]for id in@ids next if id.void?如果使用,包括?(id.scheme)dups<<id.scheme else for req in required_schemes if id.scheme==req found_required<<req required_schemes.delete req next end used<<id.scheme end if dups.size>0 note.add_error“Duplicate schemes:”+dups.join(“,“)如果所需的_schemes.size>0,则结束,如果所需的_schemes中的req缺少_name+=(缺少_name.size>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

这个提取方法有两件事,所以现在我想复制它来形成两个独立方法的脚手架,每一个都只做一件事。如果我复制它并给每个人打电话,那么我的累积通知将得到两倍的错误,我可以通过从每个副本中删除不相关的更新来避免这种情况。

类人…

def check_valid_ids required_方案,注:零注=notification.new note.add_error“has no ids”if@ids.size<1检查“无需重复的ID”方案,注:注释
检查所有所需方案所需方案,注:注释结束def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注:零used=[]found_required=[]dups=[]for id in@ids next if id.void?如果使用,包括?(id.scheme)dups<<id.scheme else for req in required_schemes if id.scheme==req found_required<<req required_schemes.delete req next end used<<id.scheme end if dups.size>0 note.add_error“Duplicate schemes:”+dups.join(“,“)如果所需的_schemes.size>0,则结束,如果所需的_schemes中的req缺少_name+=(缺少_name.size>0)?““+请求到\u s:请求到\u s end注意:添加“丢失的方案:”+丢失的名称结束返回注释结束def检查所有所需方案所需方案,注:零used=[]found_required=[]dups=[]for id in@ids next if id.void?if used.include?(id.scheme)        dups << id.scheme      else        for req in required_schemes          if id.scheme == req            found_required << req            required_schemes.delete req            next          end        end      end      used << id.scheme    end        if dups.size > 0注意:添加“重复方案:”+dups.join(“,“”如果所需的请求中缺少名称+=(缺少名称。大小>0,则结束?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

删除双重更新很重要,这样,当我重构时,我的测试都会一直通过。188足球比分直播

结果很难看,但是现在我可以独立地研究每种方法,removing anything that doesn't involve the purpose of each method.

188足球比分直播重构无重复检查

我先从没有重复的案子开始,我可以在几个步骤中去掉大块代码,每次测试后,确保我不会出错。我首先删除使用的代码所需的方案最后

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)note: nil    used = []    found_required = []    dups = []    for id in @ids      next if id.void?如果使用,包括?(id.scheme)dups<<id.scheme else for req in required_schemes if id.scheme==req found_required<<req required_schemes.delete req next end used<<id.scheme end if dups.size>0 note.add_error“Duplicate schemes:”+dups.join(“,“”结束如果需要,请选择“方案大小>0”
      缺少_name=“”
      对于所需计划中的请求
        missing_names += (missing_names.size > 0)?““+请求到:请求到”
      结束
    结束return note  end

然后我取出条件的不需要的分支

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注:未使用=[]found_required = []dups = []    for id in @ids      next if id.void?if used.include?(id.scheme)        dups << id.scheme其他的
        对于所需计划中的请求
          如果id.scheme==req
            找到“Required<<Req
            所需的_schemes.delete req
            下一个
          结束
        结束end used<<id.scheme end if dups.size>0 note.add_error“重复方案:”+dups.join(“,“)结束返回注释结束

在这一点上我可以,或许应该,已经删除了现在不需要的所需的方案参数。I didn't,最后你会看到它得到了解决。

我照常做 提取变量

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注:未使用=[]重复=[]    输入=入侵检测系统身份证输入下一个如果id.void?如果使用,包括?(id.scheme)dups<<id.scheme end used<<id.scheme end if dups.size>0注:添加“重复方案:”+dups.join(“,“)结束返回注释结束

然后我可以添加滤波器并删除跳过空标识符的行。

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注意:nil used=[]dups=[]input=@ids。拒绝id id.void?}输入ID下一个如果id.void?如果使用,包括?(id.scheme)dups<<id.scheme end used<<id.scheme end if dups.size>0注:添加“重复方案:”+dups.join(“,“)结束返回注释结束

进一步看循环,我可以看到它使用的是方案而不是ID,so I can add the pipeline step to地图IDS到方案。

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注意:nil used=[]dups=[]input=@ids.reject id id.void?}映射{ id id }。方案}对于方案在输入中(如果使用)。是否包括?(方案)重复<<方案end      used <<方案end        if dups.size > 0      note.add_error "duplicate schemes: " + dups.join(",“)结束返回注释结束

在这一点上,我已经将循环体简化为简单的移除重复行为。有一种查找重复项的管道方法,这是为了group计划本身和滤波器those that appear more than once.

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注意:nil used=[]dups=[]input=@ids.reject id id.void?}.map id id.scheme.按S S分组。选择K,V V。大小>1。键计划的输入如果used.include ?(计划)DUPS <方案结束使用<<scheme end if dups.size>0 note.add_error“Duplicate schemes:”+dups.join(“,“)结束返回注释结束

现在管道的输出是重复的,so I can remove the input variable and assign the pipeline to the variable (and remove the now unneeded习惯于变量)。

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注:零使用=
    DUPS=

DUPS=@ IDS.拒绝ID ID.void?}.map id id.scheme.group_by s.选择k,v v.size>1.键计划的输入
      DUPS <方案
      使用<方案
    结束如果dups.size>0,请注意添加“重复方案:”+dups.join(“,“)结束返回注释结束

这给了我们一个好管道,但这其中有一个令人不安的因素。管道中的最后三个步骤是删除重复项,但知识在我的头脑里,代码中没有。我需要通过使用Extract Method.

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注意:nil dups=@ids.reject id id.void?}.map id id.scheme复制品如果dups.size>0,请注意添加“重复方案:”+dups.join(“,“)结束返回注释结束

类数组…

def自我复制。按s分组。选择k,v v.大小>1。键结束

在这里,我使用Ruby的能力向现有的类(称为monkey patching)添加一个方法。我也可以在最新的Ruby版本中使用Ruby的精致特性。但是很多OO语言不支持monkey补丁,在这种情况下,我必须使用本地定义的函数,沿着这条线

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注:nil schemes=@ids.reject id id.void?}.map id id.scheme如果重复(方案)。大小>0注意。添加“错误”重复方案:“+重复(方案)。联接(”,“)结束返回注释结束def duplicates anarray anarray.group_by s.select k,v v.size>1.keys end

Defining a method on person doesn't work so well for the pipeline as putting it on the array.但通常我们不能在数组上放置方法,因为我们的语言不涉及猴子修补,或者项目标准不容易,或者它是一个不够通用的方法,不能放在通用列表类上。在这种情况下,面向对象的方法会阻碍功能性方法,不会将方法绑定到对象,工作得更好。

每当我有这样的局部变量时,我总是考虑使用以查询取代临时变量把变量转换成一个方法——结果是这样的。

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注:无重复的身份方案.SIZE>0注意。添加\u错误“重复方案:”。+重复的身份方案.join(",“)结束返回注释结束DEF重复的身份方案@id.reject id id.void?}.map id id.scheme.重复结束

我根据我是否认为重复的身份方案行为可能对Person类中的其他方法有用。但尽管我更喜欢在创建查询方法时出错,对于这种情况,我更喜欢将它作为局部变量保存。

188足球比分直播重构对所有必需方案的检查

现在我已经把支票清理干净了,没有重复的支票,我可以检查一下我们是否有所有需要的计划。这是目前的方法。

类人…

def检查所有所需方案所需方案,note: nil    used = []    found_required = []    dups = []    for id in @ids      next if id.void?如果使用,包括?(id.scheme)dups<<id.scheme else for req in required_schemes if id.scheme==req found_required<<req required_schemes.delete req next end used<<id.scheme end if dups.size>0 end if required_schemes.size>0 missing_name=“”对于所需的REQ,缺少\u个名称+=(缺少\u个名称。大小>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

和前面的方法一样,我的第一步是删除与检查重复项有关的任何内容。

类人…

def检查所有所需方案所需方案,注:零使用=found_required = []DUPS=@ids next if id.void中的id?如果使用,是否包括?(ID.方案)
        DUPS<<ID.方案
      其他的如果id.scheme==req found_required<<req required_schemes.delete req next end结束
      使用<<id.scheme结束如果dups.size>0
    结束如果所需的_-schemes.size>0缺少_-names=“”对于所需的_-schemes缺少_-names+=(缺少_-names.size>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

为了达到这个目的,I start by looking at the必需品变量。像查重复案件一样,它主要关注我们拥有非空标识符的方案,所以我倾向于从将方案捕获到一个变量并使用这些变量而不是ID开始。

类人…

def检查所有所需方案所需方案,注:未找到_REQUIRED=[]方案=@ids.reject i i.void?}.map i i.方案

方案中的S
      下一个如果id.void?对于所需计划中的请求如果S==Req找到“必需”<<req required_schemes.delete req next end end if required_schemes.size>0 missing_names=“”for req in required_schemes missing_names+=(missing_names.size>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

目的必需品是要捕获在所需的方案从我们的ID中列出方案。That sounds like a set intersection to me,这是我希望在任何有自尊的收藏品上都能发挥的作用。所以我应该能够确定是否需要。

类人…

def检查所有所需方案所需方案,注:零    found_required = []方案=@ids.reject i i.void?}.map i i.方案方案中的S
      对于所需计划中的请求
        如果S==Req
          找到“Required<<Req
          所需的_schemes.delete req
          下一个
        结束
      结束
    结束
找到“必需”=方案和“必需”方案如果所需的_-schemes.size>0缺少_-names=“”对于所需的_-schemes缺少_-names+=(缺少_-names.size>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

可悲的是,这一举措没有通过测试。现在我更仔细地看代码并意识到必需品以后代码根本不使用,它是一个僵尸变量,可能曾经被用来做过什么,但是这种用法后来被放弃了,并且从未从代码中删除变量。所以我把刚才做的修改退出来,把它取下来。

类人…

def检查所有所需方案所需方案,注:零found_required = []方案=@ids.reject i i.void?}.map i i.scheme for s in scheme s for req in required_scheme s if s==req找到“Required<<Req必需的_schemes.delete req next end end if required_schemes.size>0 missing_names=“”for req in required_schemes missing_names+=(missing_names.size>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

现在我看到循环正在从参数中移除元素所需的方案.修改这样的参数对我来说是严格的不允许,除非它是一个收集参数(如注释)。所以我马上申请移除对参数的赋值

类人…

def检查所有所需方案所需方案,注:零缺少_schemes=必需的_schemes.dup方案=@ids.reject i i.void?}.map i i.scheme for s in scheme s for req in required_scheme s if s==req缺少_schemes.delete req下一个末端if missing_schemes.size > 0缺少_name=“”对于缺少的计划中的请求missing_names += (missing_names.size > 0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

这样做还显示了循环正在从它正在枚举的列表中删除项——这比修改参数更糟糕。

既然这一点得到了澄清,我可以看出一个固定的操作是合适的,但我需要做的是从所需列表中删除我们拥有的方案-使用设置差异操作。

类人…

def检查所有所需方案所需方案,注:零    缺少_schemes=必需的_schemes.dup方案=@ids.reject i i.void?}.map i i.方案丢失的方案=必需的方案-方案

    方案中的S
      对于所需计划中的请求
        如果S==Req
          缺少_schemes.delete req
          下一个
        结束
      结束
    结束如果缺少的_schemes.size>0缺少的_name=,对于缺少的_schemes中的req,缺少的_name+=(缺少的_name.size>0)?““+req.to_s:req.to_s end note.add_error”缺少的方案:“+缺少的_名称结束返回注释结束”

现在我看第二个循环,正在形成错误消息。这只是将方案转换为字符串并用逗号连接它们——这是字符串连接操作的工作。

类人…

def检查所有所需方案所需方案,note: nil    schemes = @ids      .reject{|i| i.void?}      .map {|i| i.scheme}    missing_schemes = required_schemes - schemes    if missing_schemes.size > 0Missing_Names=Missing_Schemes.Join(“,“”
      对于缺少的计划中的请求
        missing_names += (missing_names.size > 0)?““+请求到:请求到”
      结束note.add_error "missing schemes: " + missing_names    end    return note  end

巩固这两种方法

现在两种方法都被清理干净了,他们只做了一件事,而且清楚他们在做什么。它们都需要非空标识符的方案列表,所以我倾向于使用Extract Method

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注:零DUPS=@ IDS
      .拒绝ID ID.void?}
      .map id id.scheme
      复制品DUPS=身份识别方案.dups.size>0时重复。注意:添加“重复方案:”+dups.join(“,“)结束返回注释结束

def检查所有所需方案所需方案,注:零方案=@入侵检测系统
      .拒绝I I.无效?}
      .map {|i| i.scheme}缺少_方案=必需的_方案-身份识别方案如果缺少_schemes.size>0,则缺少_name=缺少_schemes.join(“,“)note.add_error”缺少方案:“+缺少名称结束返回note endDEF身份识别方案@id.reject i i.void?}.map i i.方案结束

然后我想做一些小的清理。首先,我要通过检查集合的大小来测试它是否是空的。我总是更倾向于用一种更具揭示意图的空方法。

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)note: nil    dups = identity_schemes.duplicates除非dups.empty?注意:添加“重复方案:”+dups.join(“,“)END RETURN NOTE END DEF CHECK_ALL_REQUIRED_SCHEMES REQUIRED_SCHEMES,注:无丢失的“方案=必需的”方案-标识“方案除非缺少“方案”为空?Missing_Names=Missing_Schemes.Join(“,“)note.add_error”缺少方案:“+缺少名称结束返回note end

我没有这个重构的名字,188足球比分直播它应该是“用意向性揭示方法代替实施性揭示方法”。

The误名变量没有拉它的重量,所以我会用将临时变量内联关于它。

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)注意:nil dups=标识\schemes.duplicates除非dups.empty?注意:添加“重复方案:”+dups.join(“,“)END RETURN NOTE END DEF CHECK_ALL_REQUIRED_SCHEMES REQUIRED_SCHEMES,注:无丢失的方案=必需的方案-标识方案,除非丢失的方案为空?missing_names =missing_schemes.join(",“”注意:添加“丢失方案:”错误+missing_schemes.join(",“”结束返回注释结束

我还希望将这两种方法都转换为使用单行条件语法

类人…

def check_no_duplicate_ids required_schemes(定义检查没有重复的\u id需要的\u方案)note: nil    dups = identity_schemes.duplicates除非dups.empty?注意:添加“重复方案:”+dups.join(“,“”除非dups.empty?
    结束返回说明结束定义检查所有所需方案所需方案,注:无丢失的“方案=必需的”方案-标识“方案除非缺少“方案”为空?note.add_error“Missing Schemes:”+Missing_Schemes.Join(“,“”除非缺少“方案”为空?
    结束return note  end

同样,还没有定义重构,188足球比分直播这将是一个非常具体的红宝石。

这样,我觉得这些方法已经不值得了,所以我把它们排列起来,和提取的身份识别方案方法,回到呼叫方

类人…

def check_valid_ids required_方案,注意:nil note=notification.new note.add_error“has no ids”if@ids.size<1 identity_schemes=@ids.reject i i.void?}.map i i.scheme dups=identity schemes.duplicates注.添加错误(“重复方案:”+dups.join(“,“))除非dups.empty?Missing_Schemes=Required_Schemes-Identity_Schemes Note.Add_Error“Missing Schemes:”+Missing_Schemes.Join(“,“)除非缺少方案。空?return note  end

最后一种方法比我通常使用的方法要长一点,but I like its cohesiveness.如果它长大了,我想把它分开,也许用用方法对象替换方法即使如此,我发现它在传达验证正在检查的错误时更加清晰。


最后的想法

这组重构示例到此结束。188足球比分直播我希望它能让您很好地理解集合管道如何能够澄清操作集合的代码逻辑,以及如何将循环重构为集合管道通常非常简单。

As with any 188足球比分直播refactoring,有一个类似的反向重构,将一个收集管道转换成一个循环,188足球比分直播但我几乎从来没有这样做过。

如今,大多数现代语言都提供一流的功能和一个集合库,其中包括集合管道的必要操作。如果你不习惯收集管道,这是一个很好的练习,可以获取遇到的循环并像这样重构它们。如果你发现最后的管道比原来的回路还不清楚,完成重构后,您总是可以恢复重构。188足球比分直播即使恢复重构,188足球比分直播这个练习可以教会你很多关于这种技巧的知识。我已经使用这种编程模式很长时间了,并且发现它是帮助我阅读自己的代码的一种有价值的方法。因此,我认为值得努力探索,看看你的团队是否得出了类似的结论。


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

关于类似主题的文章…

…查看以下标签:

对象协作设计bet188足球 188足球比分直播

脚注

1:好,事实上,我的第一步是考虑申请Extract Method在循环中,as it's often easier to manipulate a loop if its isolated into its own function.

2:为了我,很奇怪看到映射算子称为“选择”。原因是C中的管道方法来自Linq,其主要目的是抽象数据库访问,因此,方法名被选择为与SQL类似。“select”是SQL中的投影运算符,which makes sense when you think of it as selecting columns,但是,如果您考虑使用函数映射,它是一个奇怪的名称。

三:当然,这并不是所有可以进行收集管道的语言的综合列表,所以我期待着我没有显示javascript的抱怨声,斯卡拉或者其他的++。我不想在这里列出很多语言,只是一个小的集合,它的变化足以传达这样一个概念:集合管道在一种陌生的语言中很容易遵循。

4:在某些情况下需要短路,虽然这里不是这样

5:我经常发现这个胸部是阴性的。这是因为否定()在表达式的开头,而谓词(栈空)就在最后。两者之间有任何实质性的表达,结果很难分析。(至少对我来说是这样。)

6:我还没有把这个放在操作员目录里。

7:如果我使用的语言没有检测最后一个传递谓词的项的操作,我可以先反转,然后检测第一个。


确认

Kit Eason帮助F示例变得更加惯用。LesRamer帮助我提高了我的C。Richard Warburton纠正了我Java中一些松散的措辞。DanielSandbecker在Ruby示例中发现了一个错误。Andrew KiellorBruno TrecentiDavid JohnstonDuncan Cragg卡雷尔·阿方索,Korny SietsmaMatteo Vaccari,Pete Hodgson斯利瓦斯塔瓦斯科特·罗宾逊,Steven LoweVijayAravmudhan在thoughtworks邮件列表中讨论了本文的草稿。

重要修改

2015年7月14日:添加标识符示例和最终想法

07 2015年7月:添加了分组飞行数据示例

2015年6月30日:添加了设备供应示例。

2015年6月25日:添加了嵌套循环安装

2015年6月23日:Published first installment