BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

反对for行动

| 作者 赵劼 关注 5 他的粉丝 发布于 2009年7月14日. 估计阅读时间: 5 分钟 | CNUTCon 了解国内外一线大厂50+智能运维最新实践案例。

Francesco Cirillo于不久前发起了“反对if行动”,受此影响,Matthew Podwysocki也用这种方式提出了自己的声明,即“反对for行动”。

Matthew Poswysocki生活在华盛顿特区,作为微软的高级咨询师,维护或参与了诸多社区活动(如DC ALT.NET讨论组),并致力于推广各种敏捷实践。这次他提出,在代码中应该尽量使用和构建可以进行组合的函数,而不是显式的循环语句(包括for、foreach和while)。

Matthew认为,通过循环来实现的功能往往可以分为以下三种情况:

  1. 查询(映射、过滤等等)
  2. 聚合(求和、计数等等)
  3. 进行一些有副作用(Side Effect)的操作(读取文件、发送消息等等)

Matthew看来,使用for循环来处理“查询”和“聚合”时,最大的问题在于将关注点放在了如何做(How)而不是做什么(What)。他举了一个例子,“找出100以内所有质数”,并给出了一个实现:

var numbers = Enumerable.Range(1, 100);
var output = new List<int>();

foreach(var number in numbers)
    if(IsPrime(number)) output.Add(number);

Matthew认为:

这里的问题在于我们很难将现有逻辑与另一个操作进行组合,因为这里的实现涉及了“怎么做”。我们应该使用.NET 2.0以上版本中的泛型及延迟加载的特性进行函数式的构造。这样可以带来“声明式(declarative)”的感觉,而关注点就只有“做什么”了:

var primes = Enumerable.Range(1, 100)
    .Where(x => IsPrime(x));

Matthew提出,应该尽量避免在一个循环中进行多种操作,这样会为代码的可读性和维护性带来负面影响。而在Martin Fowler的Refactoring站点中,拆分循环内部逻辑的重构方式被命名为“Split Loop”。Matthew认为较好的方式是将逻辑进行组合,例如求出“100以内质数的数量”便可以这样实现:

var primesCount = Enumerable.Range(1, 100)
    .Where(x => IsPrime(x))
    .Count();

对于产生副作用的情况,Matthew引用了Eric Lippert对于“为什么IEnumerable<T>没有ForEach扩展方法”的回应。Eric认为,引入IEnumerable<T>的ForEach扩展方法事实上带来了副作用,而违背了IEnumerable<T>的设计初衷。此外,ForEach还会形成闭包,可能会造成一些难以发现的引用问题。

Matthew并没有赞同这种说法,不过它对这个看法表示理解。他认为,如果是使用C#进行编程,使用foreach来遍历一个IEnumerable没有太大问题。不过在F#中,最好还是使用iter或iteri方法进行遍历。关于这点,他使用F#交互命令行进行了演示:

> let flip f y x = f x y
- [1..10]
-   |> List.map((*) 2)
-   |> List.filter(flip (%) 3 >> (=) 0)
-   |> List.iter(printfn "%d");;
6
12
18
> [1..10]
-   |> List.map((*) 2)
-   |> List.filter(flip (%) 3 >> (=) 0)
-   |> List.iteri(printfn "%d\t%d");;
0       6
1       12
2       18

社区中有人认为,这个行动的目的是希望在面向对象编程环境中融入部分函数式编程的理念。C# 3.0引入了Lambda表达式,将高阶函数在.NET中的应用切实地推广开来。同时,其他平台也在进行着类似的改变。例如最近颇受好评的Scala语言也引入了函数式编程特性。

你对for的使用有何看法,并且对函数式编程的看法如何?

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

不太同意 by Cui Anders

在看到map、iter这样的操作时,头脑中应当还是有个foreach的影子吧

拜读了 by fu chao

可能我在编程中考虑的更多的是优化性能,完全没注意for循环还有副作用,才疏学浅了!

Re: 不太同意 by Jeffrey Zhao

有foreach的影子没有关系,关键还是怎么用。
我对这篇文章的看法很是赞同,使用函数式的方式将小方法组合起来。
而且,充分利用System.Linq.Enumerable里的扩展方法,开发效率提高的不是一点两点……

真是无语啊 by 陈 实

起初他们追杀GOTO,我不是GOTO,我不说话;接着,他们追杀IF,我不是IF,我不说话;后来,他们追杀FOR,我不是FOR,我继续不说话;此后,他们追杀instanceof,我不是instanceof,我还是不说话;最后他们奔typeof而来,再也没有人站起来为typeof说话了。

Re: 真是无语啊 by im Kevin

不得不说,函数式编程在很多情况下写起来的代码确实简洁优雅了不少,但是仅仅因为这个就一概否决了if和for是非常nc的做法,这些人根本不是极限编程(XP)者,而是极端编程主义者。

Re: 真是无语啊 by Jeffrey Zhao

我觉得我们不必极端地去理解他们的意思。
其实他们并不是说不应该使用for/if,而是不应该使用for/if“去做某些事情”。
或者说,“某些情况下”不应该使用for/if,有更好的做法。

Re: 真是无语啊 by 陈 实

没有错,两位的观点正是我想要说的,就算是被追杀了无数年GOTO也还是活在应该它发挥作用的地方嘛,所以追求更好是没有错的,但不能极端。

Re: 真是无语啊 by 陈 实

另,我之所以那么说是对连续两个这样的文章很烦,你提出不用IF与FOR来解决问题的思路这很好,但这个两个标题与内容也不知道是翻译的问题还是原文如此,真的很标题党的感觉。

切莫忘本——控制流是老祖 by 黄 海平

杀了goto杀if,杀了if杀for,杀了for下一个呢switch还是什么————都杀了还剩什么return吗?这不是常函数了吗?

语法糖而已 by soft SG

仅仅是改了一下写法而已哟!

Enumerable.Range(1, 100)
.Where ...
查看编辑器的实现,内部仍然是一个循环,C#语言给了一点语法糖,就至于如此说“反对FOR”...难道老外也是标题党?

此文绝非偏激之言或标题党 by 李 新

从文中我看懂了一点——for循环最大的问题在于将关注点放在了如何做(How)而不是做什么(What)。
使用for时,我们从代码中第一眼看到的、体会到的只有“转圈”——在第XX行有个循环,要围着范围YY绕圈圈,然后才会去理解转圈到底是为了什么。
解放FOR式编程是对的,并不极端。只是看个人是否接受罢了。就像当初从结构化编程到面向对象。
希望大家能勇于改变自己

垃圾越少越健康 by jianfeng shen

杀得好啊!垃圾东西越少越健康!

Re: 语法糖而已 by Jeffrey Zhao

哪个语言说到底不都是语法糖?那个语言最终不是变成机器码执行的呢?

我们为什么要有for/while/类型系统/变量?最终执行时哪会有这些东西啊。

关键就是,不同做法,使用的思路是不一样的。

否则,就算Internal DSL,查看源代码,都是普通的语言特性而已嘛。

Re: 切莫忘本——控制流是老祖 by yx z

haha,switch早就有人想杀了,
www.codeproject.com/KB/tips/DictorionaryEnumDel...

Re: 切莫忘本——控制流是老祖 by yx z

info q的服务器时间,是不是和正确时间晚12小时

Re: 切莫忘本——控制流是老祖 by Wei Fisher

还是这条回复最实在

Re: 切莫忘本——控制流是老祖 by Gene Ryan

同意这个回复!

这个行动有意义吗? by Gene Ryan

绝大多数语言都有FOR,你把for杀了,别人都看不懂了,你开心了?

青春 by xing xing

活力无限啊

Re: 此文绝非偏激之言或标题党 by 蔡 经伟

同意LZ的看法。
在下以为所谓“反对for”的本质是对于编程思想的一种改良,而不是革命。
传统的控制流方式,和新颖的函数式编程,或者lambda语法都是仍然有存在的意义,可以也应该共存。问题在于我们可以更好的理解和运用多样化的programming paradigm。

Re: 不太同意 by 李 夏光

在看到map、iter这样的操作时,头脑中应当还是有个foreach的影子吧


SICP 里面有这么一句:“...不仅因为它代表了一种公共模式,而且因为它建立起了一种处理表的高层抽象。...原来的定义...将人的注意力吸引到对于表中逐个元素的处理上。通过map定义...抑制了这种细节层面上的情况,强调的是从 元素表 到 结果表 的一个缩放变换。这两种定义形式之间的差异,并不在于计算机会执行不同的计算过程(其实不会),而在于我们同一个过程的不同思考方式”

我觉得打个比方说,就好像在考虑 矩阵运算 时,最小单位就是矩阵,没必要非要考虑矩阵里面的每一个元素的计算。应该是同一个道理。

Re: 切莫忘本——控制流是老祖 by 李 夏光

杀了goto杀if,杀了if杀for,杀了for下一个呢switch还是什么————都杀了还剩什么return吗?这不是常函数了吗?


switch不就是if么,逻辑有点乱哦,呵呵;return又算什么。。。
控制流不一定非要包含for 包含迭代,迭代原本不是必需的,完全可以通过递归实现,考虑性能的话,尾递归优化之后性能一样不差,有些算法更是便于用递归表达而用迭代则相当费事。——当然,这些讨论与本文没什么关系了,本文讨论的更大程度上应该是设计层面的问题。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

22 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT