Working with Legacy Haskell
A sort of book review: Working Effectively with Legacy Code from a Haskeller’s perspective.
In 2010 I picked up Michael Feathers’ Working Effectively with Legacy Code1 from the company book shelf because I felt the title described something I had been having trouble with. Most of my software experience at that time was from class projects, and I found that what I had learned so far in school wasn’t sufficient for corporate work. I was reminded of it again while thinking about some commentary from grumpy Haskell advocate Deech:
I've found very few tech tutorials which teach you to get out from under a mess. It's all golden path scenarios, maybe that's why there's such a massive gap between learning and practice.
Too much writing focuses on school-style stuff but not corporate wisdom, and so I got to wondering how much of Feathers I could translate into Haskell. What follows is a sort of book review: Working Effectively with Legacy Code from a Haskeller’s perspective. This article covers my thoughts on chapters one through four.
What is legacy code?
Our legacy is what we pass down for others to inherit. A legacy can be valuable, but in any inheritance one is likely to find something disturbing.
My favorite quote from this book comes from the preface:
To me, legacy code is simply code without tests.
I think this definition is spot on. When we’re using “legacy” in the pejorative sense, it’s code that we’re stuck with because it’s hard to change. When we say code is hard to change, we mean it’s hard to be confident that the changes will work and not break something. The book, then, is about how to regain lost confidence.
Specifically, by tests Feathers is talking about unit tests: checks that are very fast and have no dependencies on a database, network, file system, or any other kind of environment that isn’t fast and easy to set up. These tests are what really matters because they allow a tight feedback loop that lets us continually work with confidence.2
Main bullet point of the book: To work with legacy code, first we have to make it not legacy code anymore, and we do that by introducing testing. To introduce testing, careful refactors are often needed first. So we first do conservative refactoring code to make it testable, then write tests, and finally then we can begin the work we set out to do.
Working with Feedback
Feathers gives examples using Java and C++ with UML diagrams — all things I recall from school but haven’t worked with in a long while — and they are at times difficult for me to follow. But the gist of the example in chapter two is that shows two types of refactor to break problematic dependencies:
Changing a function’s domain to something more specific so a test doesn’t have to instantiate values that are not really needed.
Changing a function’s domain to something less specific by, in a language like Java, changing a parameter’s type from a concrete class to an interface.
These are really two ways of accomplishing the same thing, which I will illustrate in Haskell.
Keep reading with a 7-day free trial
Subscribe to Type Classes to keep reading this post and get 7 days of free access to the full post archives.