Showing posts with label tdd. Show all posts
Showing posts with label tdd. Show all posts

Saturday, 14 January 2017

Expressive types, not oppressive types

Uncle Bob wrote a recent post in which he warns programmers against the "dark path" some modern languages have taken - that is to "double down" on static typing. He cites Swift and Kotlin as examples, though his argument is meant to be interpreted more generally.
I share many points of view in common with Uncle Bob. I find the dynamically typed Clojure programming language beautiful and expressive - most of my personal projects are written in Clojure. I think that TDD (test-driven design) is a valuable and important discipline - I work for an agile consulting company where most of our projects include helping clients to get better at testing.

But I disagree strongly with the way Uncle Bob frames this discussion on static types.

Uncle Bob looks at advanced type systems and sees them as more oppressive rather than more expressive. Being able to describe whether or not a function can return null is an opportunity, not a constraint. Being able to use types to describe your code's intent is an opportunity, not a constraint. Being able to reason about the behaviour of a function based on its type signature is an opportunity, not a constraint.
The kicker is that this is almost exactly the fallacy about TDD that we have railed against for years. We call it "test-driven design" because we know that evolving code in response to examples is a great way to inform a design. Folks who have not learnt to listen to their unit tests see them as nagging constraints that prevents them from writing code in the way they'd like. A master of TDD uses tests as feedback for their design.

Anyone who sees unit test as mere "checks" that make changing code needlessly difficult isn't getting the most out of test-driven design. Anyone who sees static types as mere "checks" that make changing code needlessly difficult isn't getting the most out of type-driven design.
Based on his post Uncle Bob falls into the latter category. He sees types as ad hoc antidotes for specific mistakes rather than tools for thought - "Every time there’s a new kind of bug, we add a language feature to prevent that kind of bug."
If that's Uncle Bob's experience of Swift and Kotlin, he should try Elm. Or F#. Or Haskell. If his experience is anything like mine, he would find that more sophisticated types lead to less ad hockery, not more.

In a follow-up post, Uncle Bob is explicit about what he wants in a programming language - "There is a balance point after which every step down The Dark Path increases the cost over the benefit. I think Java and C# have done a reasonable job at hovering near the balance point."
I couldn't disagree more. Java and C# have two of the most onerous and least beneficial type systems. Their complexity and absence of type inference force excessive bookkeeping on the programmer. They lack of basic features like sum types, which denies the programmer an important expressive idiom.
Java and C# represent the nadir of the type system trade-off, not the zenith. Type systems are tools. Better tools help us write better code. We should welcome each and every advance in the tools we use to do our job, because frankly we could do a lot better than what we have now.

To argue that employing more expressive types is a "dark path" that leads developers away from personal responsibility isn't accurate or helpful.

Monday, 17 May 2010

Safe habits

When I was growing up, I found some of my parents' habits irritatingly conservative. Examples included:
  • Always lock doors from the outside using the key
  • Always turn on the cold tap before the hot tap
The idea was that following these habits guaranteed you could never burn yourself or lock yourself out. But I found these precautions frustrating because I knew that it was unlikely that I'd forget my keys or put my hand in the scalding water.

However, I was mistaking the most likely outcome for the entire distribution. In Life's Grandeur, Stephen Jay Gould calls this "reification" - fixating on the average case and ignoring variation and atypical outcomes.

On any given occasion, my cavalier attitude would probably suffice. But sooner or later, if I locked doors by setting the snib and pulling them shut, my keys would not be in my pocket.

My parents understood my fallibility better than I did. And they were in a better position to appreciate the myriad of door-locking scenarios that would confront me over my lifetime.

That's why I like to think that if my mother were a programmer (and she'd make a good one) she would advocate TDD, which goes as follows:
  1. Write a failing test
  2. Write the minimum of code to pass the test
  3. Refactor
If you make TDD a permanent habit then you are inevitably at most 5 or 10 minutes away from working, tested code. And you know that every bit of code you write has a corresponding test.

Be mindful of your development habits, and design them for the bad days, not the average days.