Is TDD Language Neutral? 119

Posted by Brett Schuchert Tue, 09 Feb 2010 15:38:00 GMT

On another blog, a commenter wrote (I’m leaving names out, though you can see the original blog if you look):
I don’t think that it makes sense to say “teach a TDD class in several languages simultaneously”. TDD is a language-independent technique.

What follows is a slightly changed version of my response. What I’m wondering is this: Do you think the practice of TDD is fundamentally impacted by your programming language?

Read on for my response. You might want to add a comment before reading my response to avoid “group think.”

Based on the original comment, I started with: It seems to me that you’ve not written professionally in many different languages. Or if you have, you’ve not effectively used those different programming languages.

Now let me be a bit more clear. Java and C++ are different languages, to be sure. However, they are both class-based, statically typed languages without lambdas (well C++ and Java will both be getting them, but they don’t have them now).

Contrast Java and C++ with Smaltalk and Self. Smalltalk is class based and supports inheritance but it is dynamically typed. Self is object-oriented, but it does not have classes. In fact, it technically does not have inheritance, but it fully supports OOP. How? Delegation. JavaScript is closer to Self than it is to Smalltalk. But JavaScript, Self and Smalltalk are all closer than C++, Java, C# and Objective-C.

How about the Open/Closed principle? How I interpret that design principle depends on the context (language) in which work. In raw Java, what I think needs to be closed is different than say raw Ruby. If I add Aspect Oriented Programming into the mix (yes, I’ve actually written and deployed systems that used Aspect J – in a limited fashion), then that opens things up a bit, but still not as much as in Ruby.

In Smalltalk, the closest analog to static methods are class methods. However, a class method in Smalltalk is actually an instance method on a class object, which is a subclass of the Class class. (Yes, that’s what I meant.)

Why is this important? When I’m working with legacy code in C++/Java/C#/VB.Net – languages where static methods are never dynamically bound, I avoid using static methods and, in fact, I redesign using what Michel Feathers calls “Introducing an Instance Delegator”.

In Smalltalk, this technique is irrelevant and unnecessary.

In languages like JavaScript, Self, Smaltalk, Ruby, etc., interfaces are not really necessary. Sure you can use them, but they are more for documentation.

Consider templates in C++ versus generics in Java. In Java, generics experience type erasure so you often need to use interfaces with generics – or at least tell the compiler a type it can assume will always be used with a generic. In C++, the most you really need to do is let the complier know that a template parameter is a typename as opposed to a primitive. When the code is compiled, any implied requirements of the type parameter will either be satisfied or not. (Woe be it to you if they are not!)

Why? In Java, generics are syntactic sugar. C++ generates a unique class for each template occurrence. C# is much closer to C++ than Java in this respect. Java got it wrong (in my opinion) – though what they did add did improve the language a bit.

How about moving away from OO languages? Scala is a mixed language, as is F#. Haskel is not. Lisp as well. How you break a problem into parts is (or at least should be) different in these languages. I don’t mean different in syntax, to be sure it will be. I mean how you break a problem into its parts is fundamentally different in pure functional languages than OO languages.

Or consider COBOL. I’ve programmed in it. I can thank COBOL for my touch-typing skills. COBOL is yet a different kind of language. Yes, syntactically, but that’s not the interesting difference. COBOL is a record-oriented processing language. It is well suited to batch processing. AWK is similar in this respect as is SNOBOL. (I’ve used AWK quite a bit, and I’ve used a variant of SNOBOL called SPITBOL as well.)

Let’s continue with a building analogy. If all I have to build is bamboo then I have available to me certain techniques for building a house. If I have steel, I have different alternatives. The design of my house will be directly impacted by the media (language) available.

Additionally, if I’m building on clay versus bedrock, I will also make fundamentally different design issues. When I lived in Texas I had a floating slab foundation, which were common for several reasons: cost, temperature zone, available building materials. Texas was the first place I lived that did not in general have basements. The other option, pier and beam, was no longer practiced in general due to cost.

I’m originally from the Mid-West (Iowa). In Iowa, buildings are built such that their foundation is below the frost line. The frost line was something like 30” (I’m remembering something from 20 – 30 years ago). So all the houses had basements. Or if not basements, half-basements.

The same kinds of things can be said about programming languages as well as their ecosystems as well as the political environment. I’ve already made the language point above. But what about the political environment?

I have worked at places where open source was forbidden. So this rules out Hibernate and Spring. Taking away these tools does not stop me from building a system, but it does have an impact on gross assignment of responsibility, which is an important component of software architecture.

Now, back down to a TDD class. When I teach TDD, I am not teaching just TDD. Furthermore, I am not just talking about TDD. I want students to apply the practice. So I start with a problem – a requirements statement. That does not initially vary with the language. In practice it eventually does for several reasons:

  • How far the class can get with the problem is impacted by by their skills as well as the programming language. C++ and vi are slower to work in that Java and Eclipse.
  • What you can reasonably do is impacted by both the language as its environment. For example, it is easy to look at a DLL and dynamically find all concrete implementations of an interface in any .Net language. It’s harder to do that in Java, though possible. In C++ it is platform specific.
  • There are more…

The students do need to learn the mechanics:

  • How to write a test using a particular unit testing framework
  • How to write production code
  • The shortcut keys of the environment
  • The refactoring support in the IDE

But what are the students testing? Units? Sure, let’s go there.

Language affects the units. You are testing units. Different kinds of units, different kinds of responsibilities. Different kinds of responsibilities to be verified, different unit tests.

Things are not so cleanly separated as you would like to imagine.

This very idea can be seen in natural languages. Different languages are better/worse at expressing things. The language you use directly affects how you interpret the world. This actually impacts the physical wiring in the brain. Really, it does.

So I do not agree.

And I challenge you to write the same problem in two fundamentally different languages (e.g. Java and Lisp) using TDD. Then have those two different solutions reviewed by experts in each of those languages.

I claim that if you’ve done an effective job of using those languages, your solutions will be different AND the unit tests you created to get to those solutions will be fundamentally different (not just mechanically different).

If, on the other hands, the unit tests are the same (other than the mechanics), then at least one group of people will say your solution is a bad use of their particular language. You’ll either be programming Java in Lisp or Lisp in Java.