Clojure and refactoring

March 2, 2014

Last night I tweeted

Seems to me that refactoring-tools are much less needed when programming #clojure

— Erik Assum (@slipset) March 1, 2014

to which I got a couple of responses.

First from @mikera which contained a link to a really nice post he’d written outlining some good points about Java vs Clojure:

@slipset I've found the opposite - lack of refactoring support is one of my (very few) gripes about #Clojure. see: http://t.co/oqRYCrdkU0

— Mike (@mikera) March 2, 2014

Then @odinodin tweeted

@slipset why isn’t there the same need for refactoring support? Just curious about what you’re thinking about

— Odin Hole Standal (@odinodin) March 2, 2014

So I thought I’d share some (deeper) thoughts on this.

Disclaimer

I’ve only coded Clojure for a bit over a month, only written close to 1000 lines of code, and I’m the only person working on this project.

What kind of refactorings and other tools do I use when working in Java?

Previously, I’ve had the opinion that working in a language which has less IDE support than Java would be a no-starter, and I still cannot imagine coding in Java without refactoring and Intellisense. Which I to some extent miss in Javascript as well.

As for the refactorings I use in Java, I guess extract method, various renames (method, class, packagae) are the ones I use most. I do not consider cleaning up imports a refactoring, that’s just cleanup.

Why don’t I miss this in Clojure

I’m not sure why I don’t miss these things in Clojure, but I do notice a couple of things:

Working in the repl

Since all my development happens in the REPL, I have a workflow which consists of

  1. Make stuff work
  2. Clean it up
  3. recur 0
  4. When happy, I move the code into the file it belongs

This leads to code that is much more worked through than the stuff you bang out in Java

Working with higher abstractions

Since Clojure lets you work with higher-order functions and has the great sequence abstraction, you never end up with code lik this:

foreach (Foo f: foos) {
    if (foo.getBaz() != null && foo.getOmg() != HAPPENING) {
        bars.add(foo);
    }
}
return bars;

Which I probably would refactor into something like:

foreach (Foo f: foos) {
   if (!foo.isBar()) continue;
   bars.add(foo);
}
return bars;

Which includes extract method.

In Clojure this is

(filter #(and (:baz %) (= (:omg %) :happening)) foos)

Pulling out the predicat here is just a C-M-k away (plus some more stuff ;) so the refactor is done so fast I don’t even have time to miss a refactoring tool.

Dense code

It has also dawned upon me that since Clojure is so dense, eg lots of stuff happening in few lines of code, I really need to be prudent when organizing my code. If I let a function become longer than 10 lines, I’m lost, so I constantly mold my code to keep it understandable.

Also, files with more than 150 lines of code makes my head explode, so I constantly move stuff into other packages.

But isn’t this what refactoring tools help you with?

No, not the way I look at refactoring. Because refactoring for me is taking something that is functionally finished, even deployed, and reorganizing it mechanically. The stuff I mention above is stuff I while developing, not after. Put in another way, working in the REPL, I constantly refactor stuff before it even has a chance to become a big hairy ball of mud.

All the helpers are written

Also I find that extracting stuff to smaller helper functions/classes like I do in Java is no longer needed since these methods seem to be already implemented in Clojure.

Classic Java

if (number % 2 == 0) {
     evens.add(number);
} else {
	odds.add(number);
}

clojure

(group-by odd? (range 10))

In Java, I’d probably extract isOdd(int i) whereas in Clojure, that’s already done.

Functional code leeds to less intertwinedness

It seems like when banging out some Java-code, even if you take care, way too many functions seem to know about far too much common stuff - normally you’d have three methods operating on the same hashmap or something like this.

In Clojure you simply don’t.

Summary

So I guess that the combination of the REPL, the sequence abstraction, dense code, and my stupidity leeds me to writing code that doesn’t need too much after-the-fact refactoring.