Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring techniques for Clojure

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?

like image 687
mikera Avatar asked Oct 03 '11 04:10

mikera


3 Answers

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.

like image 83
Arthur Ulfeldt Avatar answered Nov 15 '22 06:11

Arthur Ulfeldt


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.

like image 28
Chiron Avatar answered Nov 15 '22 08:11

Chiron


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.

like image 45
Thumbnail Avatar answered Nov 15 '22 06:11

Thumbnail