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

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

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

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

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. wxPython 自动提示文本框

    1.原版和例子都在这里 在浏览器的地址栏,或者在百度.google 输入文字的时候,输入框的下面会把有关的项目都提示出来. wxPython 没有提供类似的控件,google 了一下,发现了一个,很好 ...

  2. java关于ArrayList中toArray方法的使用

    先来看下面这段程序 Collection collect= new ArrayList();   collect.add("小黑");   collect.add("小白 ...

  3. 安全标识符SID技术介绍及查看技巧

    说到安全标识符SID就要先说说安全主体(Security Principals),安全主体是一个能够对它分配权限的对象,例如,用户.组和计算机: 对于每一个Windows 200x域中的安全主体都有一 ...

  4. MyBatis第一个项目示例

    1.创建一个Java project,JikeBook 2.添加项目所需的依赖包 如 mybatis-3.2.8.jar,是实现mybatis功能所必需的依赖包 mysql-connector-jav ...

  5. printf详解

    用了这么多年c了,今天想编个小程序练练手,忽然发现对于printf这个函数并不甚了解.上网查了查,下面是对printf()的详解: 函数原型: #include <stdio.h> int ...

  6. C#多线程的用法5-线程间的协作Monitor

    之前我们使用lock快捷方式,实现了多线程对同一资源的共享.在C#中lock实际上是Monitor操作的简化版本. 下面使用Monitor来完成之前的lock功能,你可以在此做一下对照: privat ...

  7. pymysql实现从a表过滤出有效信息添加至b表

    # Author: yeshengbao # -- coding: utf-8 -- # @Time : 2018/4/16 19:23 import pymysql # 创建连接 conn = py ...

  8. 局部内部类访问它所在方法的局部变量时,要求该局部变量必须声明为final的原因

    这是java的一条规则.那么为什么会有这条规则呢?要想弄懂这个问题,就需要弄懂局部内部类对象和局部变量的生命周期的谁更长的问题. 首先,看一段代码,以没有将变量声明为final的代码作为例子,代码如下 ...

  9. TPS、并发用户数、吞吐量关系

    TPS.并发用户数.吞吐量关系 摘要 主要描述了在性能测试中,关于TPS.并发用户数.吞吐量之间的关系和一些计算方法. loadrunner TPS 目录[-] 一.系统吞度量要素: 二.系统吞吐量评 ...

  10. 自学华为IoT物联网_06 智慧家庭物联网常见问题及解决方案

    点击返回自学华为IoT物流网 自学华为IoT物联网_06 智慧家庭物联网常见问题及解决方案 1. 家庭中遇到的问题 2.1 华为智慧家庭概念的发展历程 2.2 华为智慧家庭的解决方案架构 智慧家庭主要 ...