May 28th, 2013
(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: email@example.com
This conversation is good. Some highlights:
There are two phases which seem, in java land at least, to get conflated.
1. Poking an API to learn it’s capabilities and behaviour.
2. Writing tests to prove the correctness of your use of the API.
These are quite different phases, each important in their own way
1. is the ‘kicking the tyres’, open the bonnet, boot etc when you’re looking at car to see if it’s what you want to buy.
2. is the MOT, looking at service history, getting an expert to look it over, test drive etc.
In java, there isn’t an effective way to do ’1′, so you end up writing tests to do this, because it’s handy, in an IDE, to run code snippets that way. Often these ‘type 1′ tests hang around longer than they should. I find in java that TDD shapes the design of the application in a positive way. For me one of the primary benefits of TDD is that it forces testability. I like to think more of ‘Test Driven Design’, than development – I design it such that it can be tested.
In clojure, because ‘noodling in the REPL’ is so easy and so much fun, we tend to build programs by creating small functions and composing them together from the bottom up. I find that working in this way, testability comes about almost for free. You get these small bits of functionality that are so easy to reason about that they don’t necessarily need extensive tests.
There are some hygiene aspects to working in the REPL. Everyone has been bitten by the function rename/code calls old function type stuff. The tooling around this is improving, but some discipline is still required. Committing to git *all the time* is the way I handle that. I tend to write code in a .clj buffer and evaluate in the REPL. If a function works as expected, I commit it. Often I write tests at this point and commit them too. When everything is working I then re-write the history to a clean set of ‘feature’ commits to be pushed upstream.
It’s important to distinguish between two meanings of “REPL” — one is a window that you type forms into for immediate evaluation; the other is the process that sits behind it and which you can interact with from not only REPL windows but also from editor windows, debugger windows, the program’s user interface, etc.
It’s important to distinguish between REPL-based development and REPL-driven development:
REPL-based development doesn’t impose an order on what you do. It can be used with TDD or without TDD. It can be used with top-down, bottom-up, outside-in and inside-out approaches, and mixtures of them.
REPL-driven development seems to be about “noodling in the REPL window” and later moving things across to editor buffers (and so source files) as and when you are happy with things. I think it’s fair to say that this is REPL-based development using a series of mini-spikes. I think people are using this with a bottom-up approach, but I suspect it can be used with other approaches too.
(But this is something I don’t typically do and I may be misunderstanding what people mean by REPL-driven development.)
(I prefer to get my thoughts straight into source files, and play with them there. If I don’t like what I have I can delete it.)
I typically run the code I am working on in a few different ways:
Before committing I’ll run all my tests in a fresh process. (That doesn’t mean I have to re-start other processes that are running my program, but it may be a good time to do that.)
Using REPL-based development. When I add or change definitions I’ll send each one to the REPL process. Those definitions might be data, functions, tests — anything. I’ll also play with the evolving program through its user interface.
FWIW, changing macros can be painful because all callers will need to be recompiled if they need the new definition, so if you’ve changed what the macro does rather than simply how it does it it can be a pain — maybe worth restarting the REPL in such cases. Ah! maybe https://github.com/clojure/tools.namespace helps here? — Not sure; I need to try it.
Recently I’ve been using Midje’s autotest feature via Leiningen. This creates a process that runs all your tests, and watches for changes to source files re-running tests as necessary. I have this running in a small always-visible window in the corner of my screen and I check that things stay green when I save my files. This is very nice, and I’m finding myself bothering less with keeping my own REPL process up-to-date with all my changes. If my REPL process gets out of date, the refresh function in https://github.com/clojure/tools.namespace is useful. I’ve found one problem in Midje autotest with records and protocols that makes me a little wary that when I start to use dark corners of Clojure things may not go well.