BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Debate about Testing and Recoverability: Object Oriented vs. Functional Programming Languages

Debate about Testing and Recoverability: Object Oriented vs. Functional Programming Languages

This item in japanese

Michael Feathers latest post provoked a very passionate debate in the blog community. Feathers argues that some features built in object oriented programming languages facilitate testing and make code written in them more easily recoverable.  

He gives an example, where class X has a method named badMethod that does something painful during testing like making a call to the production database and updating it or even touching some low level hardware:  

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

Ideally, the system should be designed in a way to allow classes and clusters of classes to be tested independently. However, if this is not the case, the fact that “badMethod is a non-final, override-able method” is instrumental to provide flexibility needed to get “enough maneuverability to test” because it allows to “override functionality and build ourselves a wedge that makes testing easy”:

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

This is what Feathers calls a seam, “a place where you can substitute one piece of functionality for another without editing”. He believes that this late-binding techniques provided by OO languages makes them more recovery friendly than functional languages.

Several commentators and Feathers himself highlighted the fact that most languages provide seams: preprocessors, inheritance/polymorphism and delegation, macros and function pointers, high-order functions, dynamic methods, first class functions, module boundaries or monads… And according to some observers, what really matters for testability is the underlying design rather than the choice of language. John, for instance, asserts that whatever language is used, “the code needs to be structured to facilitate unit testing in the first place.” Another blogger, Andrew, highlights that if “code isn't factored into methods that align with the needs of your tests”, the implementation will need to be changed to accommodate the test. Hence, he argues as well that “thoughts about "seams" are really just getting at the underlying issue of design for testability”, i.e. the proper placement of seems.

In response to these arguments, Feathers emphasizes that even though seams exist in most languages, it is the ease with which they can be used that matters, especially in cases when code wasn’t designed in a way to facilitate testing: 

I agree that "design for testability" is the real issue, but I also know (sadly) that no matter what we do, there will be systems where it doesn't happen. For that reason, recoverability is an issue that I care about a great deal. 

[…] 

I know that it is possible to design seams, but that isn't the question. The question is how easy it is to get them in place when they weren't designed in. 

[…] 

It's true that seams don't always align with the grain of the testing that you want to do, it definitely is easier in languages with good seam support both because of the seams that are there accidentally and because it is easier to open up new ones.

According to Feathers, even though there are alternative modules to link against in functional languages, “it’s clunky”, with exception of Haskel where “most of the code that you'd ever want to avoid in a test can be sequestered in a monad”

In spite of the fact that Feathers stresses that he is aware that one could argue that “functional purity obviates any need to unit test”, many commentators fiercely argued that he does not take into consideration the specificity of functional languages and opportunities that they offer. Erikd expressed a feeling that Feathers applies Java constructs and idioms to functional code:

Firstly he seems to be writing Java code using Ocaml's syntax and then complains that Ocaml is not enough like Java. His conclusion is hardly surprising. Ocaml is simply not designed for writing Java-like object oriented code.  

The second problem is his claim that testing in functional languages is more difficult than with Java. While this may be true when writing Java code with Ocaml's syntax, it is not true for the more general case of writing idiomatic Ocaml or functional code.

Many proponents of functional languages stress that in functional programming side-effects are isolated, and this, according to Greg M., prevents from writing code that would require refactoring and makes testing easy:

Functional languages make it easy to structure your code with all that nasty stuff seperated off at the top-level and keep your core logic pure.  

[…] 

Unit testing is so much easier when your units are guaranteed truly independent! Or at worst the dependencies are clear and explicit.

Robert Goldman also argues that “the heavy use of state in conventional object-oriented programming is detrimental to testing”, because one needs to “build up large bodies of interconnected objects in order to set the stage for the tests, and there may be additional complications in checking that the expected side-effects have occurred”, whereas “in a purely functional framework such as Haskell, any of these problems would have to be encapsulated in a Monad”. As highlighted by Greg Monads allow writing “one piece of code that creates (on-the-fly) a list/stream of IO commands, and another piece of code consumes the stream and decides how to execute those commands.” 

In the same vein, Ericd insists on the fact that in functional programming there is no internal state, thus no state transition handle. To test “a module or system without mutable state” one simply will not need the kind of testing Feathers is talking about:  

The only testing left is to collect a bunch of inputs that test for all the boundary conditions, pass each through the function under test and validate the output.  

[...]


If the components (ie pure functions) can be tested separately and shown to be correct, the composition of these pure functions is also correct

Feathers responds that he is “very aware of functional purity and the fact that code that's designed well shouldn't have these problems”. He emphasizes, however, that not all code is well designed and that “Haskell is the one FP language out that really forces you to separate side-effects”, whereas in others, e.g. OCaml or Scala, “there doesn't seem to be anything that keeps people from messing up” 

Nevertheless, many of Feathers detractors believe that the only way to mess up with functional code is to use non functional idioms while coding in functional languages. Goldman asserts, for instance, that programs with side-effects are “admitted by non-functional parts of hybrid languages like ML, Ocaml, and Common Lisp” and avoided otherwise. And Greg goes along the same lines arguing that unless one fights against the functional language by writing code in a non-functional idiom, there is no way to “get the IoC and seperation-of-concern issues you [get] with imperative OO code.” This is why Erikd insists on the fact that writing quality code in functional languages requires from people coming from OO backgrounds to discard “old habits and ways of thinking” and to ignore “object oriented and imperative programming features for as long as possible.”

Rate this Article

Adoption
Style

BT