26
votes

I'm familiar with refactoring fairly large code bases in C# and Java but Clojure is something of a different beast, especially since it:

  • Has a mix of macros and functions in typical code (i.e. you might want to refactor from a macro to a function or vice-versa?)
  • Uses dynamic typing in most circumstances (so you don't get compile time checks on the correctness of your refactored code)
  • Is functional rather than object-oriented in style
  • Has less support for refactoring in current IDEs
  • Is less tolerant of cyclic dependencies in code bases (making it harder to move blocks of code / definitions around!)

Given the above, what is the best way to approach code refactoring in Clojure?

3
You do like asking the hard questions ;) perhaps Clojure is too young for good answers to this to have developed? is three years enough? - Arthur Ulfeldt
How is this achieved in similar, more established languages such as Common Lisp or Scheme? - ponzao

3 Answers

8
votes

In "Working effectively with legacy code" Michael Feathers suggests adding unit tests to create artificial "inflection points" in the code that you can re-factor around.

a super brief and wholly incomplete overview on his approach to adding order to unstructured code:

  • devide the code into "Legacy" (without tests) and the rest.
  • create a test
  • recur on both halves.

The recursive approach seemed to fit well with the mental processes I use in thinking about Clojure so I have come to associate them. even new languages can have legacy code right?

This is what I got from my reading of that one book while thinking about clojure. So I hope it is useful as a general guideline. perhaps your codebase already has good tests, in which case you're already beyond this phase.

7
votes

I'm not an expert. But anyway:

  • Stay away from God functions. If you have a big function, break it down to smaller functions and each of these functions is doing one thing, and it is doing it well.
  • If you find usage of Java arrays (and it is not necessary to use them), convert them to Clojure sequences.
  • Embrace defrecord and defprotocol.
  • Stay away from macros unless you really can't proceed without writing a macro.
  • When it is possible, favor lazy sequences over recursion.
  • When creating a service, put the contract in its own namespace and the implementation in its own namespace.
  • Achieve dependency injection as passing functions as parameters to another functions.
  • Use desctructuring for a function's arglist when it is possible. It will lead to an easier to understand a function's implementation.
  • Consider using Prismatic Schema project.

Also, have a look at CursiveClojure. I think it is really promising.

I'm not the creator of CursiveClojure.

1
votes

I'm not familiar with refactoring fairly large code bases in C# or Java, but here goes.

Clojure:

  • Has a mix of macros and functions: I could be wrong, but I think you'll find that refactoring seldom moves the interface between macros and functions.

  • Uses dynamic typing (so you don't get compile time checks on refactored code): ... nor on any other code: you need more tests in either case.

  • Is functional rather than object-oriented in style Refactorings are stated in OO terms, but often survive simple transcription: method to function, class to function or closure or map.

  • Has less support for refactoring in current IDEs True: more busywork.

  • Is less tolerant of cyclic dependencies in code bases Two cases: mutual recursion in a namespace/file should be easier to cope with than it is; but cyclic dependencies between namespaces/packages cause confusion and ambiguity. I think Java only allowed them because C++ did, and C# followed suit.

I have found it useful to look through Martin Fowler's catalogue of refactorings. Most survive translation from OO to functional lingo. Some (such as Change Value to Reference) disappear.