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. freemarker入门教程

    FreeMarker的模板文件并不比HTML页面复杂多少,FreeMarker模板文件主要由如下4个部分组成: 1,文本:直接输出的部分 2,注释:<#-- ... -->格式部分,不会输 ...

  2. Vs 2008 对 OpenMP 的 支持 以及 OpenMP的环境变量及库函数

    Visual C++® 2008对OpenMP的支持 VC++2008根据项目属性配置的指示进行 /openmp编译器切换,当配置了OpenMP支持后,编译器会提供_OPENMP定义,可以使用#ifd ...

  3. hibernate4.3.8与spring mvc结合遇到的问题

    2703 [2015-01-21 16:47:42 ] - [ip=, ref=, ua=, sid=] WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Er ...

  4. C++重载解析

    重载解析(overloading resolution)的规则决定了编译器为一个函数调用选用哪个函数定义.一般过程如下: 将名称相同的函数/模板函数找到并创建候选列表 从中挑选参数数目正确,符合完全匹 ...

  5. 一道在知乎很火的 Java 题——如何输出 ab【转】

    这是一个源自知乎的话题,原贴链接:一道百度的面试题,有大神会嘛? 虽然我不是大神,但我也点进去看了一下,思考了一会之后有了一些思路,然后去看其它人的答案的时候果然全都已经被各路大神们先想到并贴出来了, ...

  6. .net框架介绍

    .net 一般指 .net Framework框架,是一种平台,一种技术 C# 是一种编程语言,可以开发基于.net平台的应用 Java 既是一种技术,也是一种语言 .net可以开发桌面应用程序    ...

  7. ASP.NET-FineUI开发实践-16(二)

    实现那还差点,在事件参数里我传了一个boolall选中状态参数,这个参数由前台给的,RowSelect 传的是index 行号,就是改这,通过$符号来分开的, if (commandArgs.Leng ...

  8. eclipse[日文版] 的SVN 上传步骤

    可能有些朋友在日企上班,肯定要用到SVN,可是一般就下载和更新,没有用到上传 这里来介绍下上传 1.项目右键 2.点击Share Project 3.点击SVN下一步 4.选择你的上传的服务器地址 5 ...

  9. 佛祖保佑 永无BUG(网转 by atkfc)

        //                   _ooOoo_    //                  o8888888o    //                  88" . ...

  10. 利用iframe实现提交表单是页面部分刷新

    直接上代码: <%@ page language="java" import="java.util.*" pageEncoding="utf-8 ...