Michael Feathers最近的博文在博客社区引发了一场异常激烈的论战。Feathers发表言论说一些面向对象编程语言的内嵌特性有助于测试的进行,并且使用面向对象编程语言编写的代码更容易恢复。

他举了这样一个例子,class X有一个叫作badMethod的方法,这个方法处理一些“痛苦”的工作,比如调用并更新产品数据库、或者处理一些甚至关系到底层硬件的事务:

  1. public class X { public void method() { ... badMethod(); ... } ...}

理想的设计是,系统可以允许独立测试一般的类和类组。但如果这个例子没有实现这样的设计,“badMethod是个非final,可覆写的方法”的事实就有利于为获得“测试足够的机动性”提供所需的灵活性,因为它允许“覆写功能并为我们创建一个楔子来让测试变得更简单”:

  1. public class TestableX extends X { void badMethod() { /* do nothing */ } }

Feathers称之为一个seam(接缝),“一个你无须修改便能使用一个功能替代另一个功能的地方”。他相信OO语言提供的缓绑定技术使得其本身比函数式语言的恢复更为友好。

一些评论者,包括Feathers本人在内,都强调了大多数语言都能提供seam的事实:预处理器、继承/多态性和委派、宏和函数指针、高阶函数、动态函数、一等函数、模块边界或monads。。。。。。其中一些人认为,真正关系到可测试性的是底层设计而非编程语言的选择。比如John,他断言,无论使用何种语言,“代码的结构需要首先考虑到简化单元测试。”另一位博客Andrew,强调说如果“代码的结构没有向所需的测试看齐的话”,那么实现将不得不向顺应测试的方向,做相应的修改。因此,他也评论说“关于‘seam’的想法确确实实是瞄准了为实现可测试性而进行恰当设计的底层问题”,也就是说,适当地布置seam。

针对这些争论,Feathers强调说,尽管大部分语言都拥有seam,但关键在于哪种语言用起来更为顺手,尤其是在代码未能以方便测试的方式而设计的情况下:

我同意“针对测试来设计”是真正的重点所在,但我也知道无论我们做什么,总会有一些系统没有以这样的方式来设计。也正是因为这个原因,我非常在乎可恢复性。

[…]

我知道设计seams是可能的,但那不是问题所在。真正的问题在于在它们没有被加入进设计的情况下,而适当布置它们到底有多简单。

[…]

当然,seams也不总是与你所想要实现的测试粒度相一致,毋庸置疑的是,在对seam具有良好支持的语言中,要实现与测试粒度相一致会简单得多,因为seam已经存在那里,也因为它创建新的seam更为简单。

根据Feathers所述,尽管在函数式语言中可以采用其他模型来达到同样的目的,但“这是沉重的”,但Haskell例外。在Haskell中,“大部分你想在测试中避免的代码,都可以采用monad来实现”。

尽管Feathers着重指出,他知道人们会辩论说“纯函数式可以满足任何单元测试的需求”,但仍然有许多评论者强力辩论说他没有考虑到函数式语言的细节,以及函数式语言所能提供的机会。Erikd表达了这样一种感觉,他觉得Feathers是在将Java构造器和惯用方法运用到函数式代码中去。

首先,他看上去是在使用Ocaml文法编写Java代码,然后又抱怨说Ocaml不够像Java。他的结论一点都不惊人。Ocaml不是为了编写类似Java这样面向对象的代码而设计的,就是这么简单。

其次,他声称使用函数式语言比Java困难。虽然使用Ocaml文法编写Java代码可能确实很难,但是编写一般的Ocaml代码或函数式代码就不会那么困难了。

很多函数式语言的拥护者强调说,在函数式编程中,是没有副作用的,并且据Greg M.所说,这点可以预防写出需要重构的代码,而且可以将测试变得更为简单:

函数式语言可以让你将代码结构在顶层就将所有讨厌的事务分离开来,并且保持代码的纯逻辑。

[…]

当你的单元拥有完全独立的保障时,单元测试可以变得如此简单!或者说,最差也能保证清楚和明显的依赖性。

Robert Goldman也发表辩论说“常规的面向对象编程语言中过度采用的状态对测试来说很不利”,因为人们需要“创建巨大的互相关联的对象实体,才能为测试提供平台。而且,检验预期的副作用的过程可能会导致额外的复杂性”。相反,“在类似于Haskell这样的纯函数式框架中,所有这些问题都被封装在Monad中”。正如Greg Monads提出的那样,它可以允许编写“一段(凭空)创建IO命令流的代码,以及另一段代码来利用这个IO流,并且决定如何执行这些命令”。

与Greg来自同一个阵营的Ericd坚持认为,在函数式编程中没有内部状态,所以也就没有状态变化的处理。如果要测试“一个没有状态转变的模型或系统”,人们根本不需要Feathers谈到的那种测试:

剩下所需的唯一测试是收集一组输入来测试所有边界条件,将这些输入传递给待测函数,然后验证其输出就可。

[...]

如果组件可以被分离测试(也就是纯函数)并且测试结果表明函数是正确的话,那么这些纯函数的组合理所当然也是正确的。

Feathers对此回答说,他“非常理解纯函数式,并且也知道拥有好的设计的代码自然不该有这些问题”。他强调,并不是所有的代码都有好的设计,而且,“Haskell的确是迫使你将副作用隔离开来的函数式编程语言的一种,然而其他一些语言,比如OCaml或Scala,“它们看起来无法避免人们将代码搞得乱七八糟”。

无论如何,很多不同意Feathers看法的争论者认为,将函数式代码搞得乱七八糟的唯一方式就是在使用函数式语言时采用非函数式用法。Goldman断言,拥有副作用的程序“被公认为是像ML、Ocaml和Common Lisp这样的混合性语言的非函数式部分”,显然是要避免使用的。Greg同样支持这个观点,他表示,除非人们非要和函数式语言作对,以非函数式用法的方式来编写代码,那自然也就没办法“得到你本可以从权威的OO代码中‘得到’的IoC和分离关注点。”这也是为什么Erikd坚持认为,有OO技术背景的人想要使用函数式语言编写高质量代码的话,就必须抛弃“旧习和思考方式”,尽可能长时间地忘却“面向对象和专断的编程特性”。

测试和恢复性的争论:面向对象vs.函数式编程的更多相关文章

  1. Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程

    Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程 这里的函数式编程的设计以muduo为例进行对比说明: Reactor实现架构对比 面向对象的设计类图如下: 函数式编程以muduo为例 ...

  2. javascript消除字符串两边空格的两种方式,面向对象和函数式编程。python oop在调用时候的优点

    主要是javascript中消除字符串空格,比较两种方式的不同 //面向对象,消除字符串两边空格 String.prototype.trim = function() { return this.re ...

  3. Python学习一(面向对象和函数式编程)

    学习了一周的Python,虽然一本书还没看完但是也收获颇多,作为一个老码农竟然想起了曾经荒废好久的园子,写点东西当做是学习笔记吧 对Python的语法看的七七八八了,比较让我关注的还是他编程的思想,那 ...

  4. C#中面向对象编程中的函数式编程详解

    介绍 使用函数式编程来丰富面向对象编程的想法是陈旧的.将函数编程功能添加到面向对象的语言中会带来面向对象编程设计的好处. 一些旧的和不太老的语言,具有函数式编程和面向对象的编程: 例如,Smallta ...

  5. 2017.4.9 函数式编程FP

    函数编程(简称FP)不只代指Haskell Scala等之类的语言,还表示一种编程范式,和面向对象的编程方式一样,是编程思维,软件思考方式,也称面向函数编程. 编程的本质是组合,组合的本质是范畴Cat ...

  6. Java8内置的函数式编程接口应用场景和方式

    首先,我们先定义一个函数式编程接口 @FunctionalInterface public interface BooleanFunctionalInterface<T> { boolea ...

  7. 函数式编程(九)——map,filter,reduce

    编程方法论: 面向过程:按照一个固定的流程去模拟解决问题的流程 函数式:编程语言定义的函数 + 数学意义的函数 y = 2*x + 1 函数用编程语言实现 def fun(x): return 2*x ...

  8. Scala函数式编程——近半年的痛并快乐着

    从9月初啃完那本让人痛不欲生却又欲罢不能的<七周七并发模型>,我差不多销声匿迹了整整4个月.这几个月里,除了忙着讨食,便是继续啃另一本"锯著"--<Scala函数 ...

  9. Scala(二)——基础语法(与Java的区分)和函数式编程

    Scala快速入门(二) 一.键盘输入 关于基本类型的运算,以及复制运算,条件运算,运算符等知识,均和Java语言一样,这里不过多叙述. val name = StdIn.readLine() Std ...

随机推荐

  1. 什么是ARC

    arc就是自动引用计算.英文名Automatic Reference Counting.在一开始的IOS开发中,内存管理是需要手动的,对某个资源的引用,引用后就对其计算+1,当不再使用就-1,当计算为 ...

  2. JAVA大数类

    JAVA大数类api http://man.ddvip.com/program/java_api_zh/java/math/BigInteger.html#method_summary 不仅仅只能查J ...

  3. 使用XStream注解实现Java对象与XML互相转换的代码示例

    本文记录一下使用xstream这个api的注解特性对Java对象与XML字符串相互转换的一些代码示例.    我们很多人都处理过XML文件,也有很多非常成熟的第三方开源软件.如:jdom.dom4j等 ...

  4. nodejs这个过程POST求

    下面是一个web登陆模拟过程.当我们问一个链接,你得到一个表格,然后填写相应的表格值,然后提交登陆. var http = require('http'); var querystring = req ...

  5. Android中为图标加上数字--用于未读短信数提醒,待更新应用数提醒等

    本文属于原创,转载请著名出处:http://flysnow.iteye.com/blog/906770 写道 在我们开发一些如短消息.应用商店等应用时,会考虑在短消息的图标上加上未读短信的数量,在应用 ...

  6. POJ2250:Compromise(LCS)

    Description In a few months the European Currency Union will become a reality. However, to join the ...

  7. Python | 基础系列 · Python为什么没有switch/case语句?

    与我之前使用的所有语言都不同,Python没有switch/case语句.为了达到这种分支语句的效果,一般方法是使用字典映射: def numbers_to_strings(argument): sw ...

  8. CentOS 6.5断电后启动出现:unexpected inconsistency run fsck manully

    CentOS 6.5断电后启动出现:unexpected inconsistency run fsck manully 如下图: 解决方法: 1.输入root用户的密码回车: 2.执行以下命令,修复磁 ...

  9. TCO 2015 Round 1B DIV1 500 概率题

    [题意]现在有一些线索,每个线索被发现的概率p[i],如果线索i被知道,那么其他线索也可能会被知道,用vector<string> c给出,c[i][j]='Y'表示知道i这个线索,j这个 ...

  10. Topcoder SRM 656 (Div.1) 250 RandomPancakeStack - 概率+记忆化搜索

    最近连续三次TC爆零了,,,我的心好痛. 不知怎么想的,这题把题意理解成,第一次选择j,第二次选择i后,只能从1~i-1.i+1~j找,其实还可以从j+1~n中找,只要没有被选中过就行... [题意] ...