Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I effectively manage a Clojure code base?

Tags:

A coworker and I are Clojure newbies. We started a project a couple months back, but quickly found that we had a tough time dealing with our code base -- by 500 LOC we basically had no idea where to start with the debugging, when things went wrong (which was often). Instead of pairs, functions were getting lists, or numbers, or what-have-you.

Now we're starting a new but related project and migrating a lot of the old code over. But we're again hitting a wall.

We're wondering, how do we effectively manage a Clojure project, especially as we make changes to existing code?

What we've come up with:

  • liberal use of unit-tests
  • liberal use of pre-, post-conditions
  • informal type declarations in function comments
  • use defrecord/defstruct/defprotocol to implement a data model, which would really simplify testing

But post-, pre-conditions seem not to be used very often. Unit-testing + comments will only help so much. And it seems like Clojure programmers don't typically implement formal data models.

Do we just not get Clojure? How do Clojure programmers know that their code is robust and correct?

like image 499
Matt Fenwick Avatar asked Oct 18 '11 19:10

Matt Fenwick


People also ask

Why Clojure is the future?

1. Enterprises keep investing in Clojure. Clojure probably won't steal the spotlight in lists of popular languages in 2022 outside of the world of functional programming. But even though Clojure does not have the buzz of other languages, it is used by many market leaders and organisations.

Why should I use Clojure?

Clojure is designed to be a hosted language, sharing the JVM type system, GC, threads etc. All functions are compiled to JVM bytecode. Clojure is a great Java library consumer, offering the dot-target-member notation for calls to Java. Clojure supports the dynamic implementation of Java interfaces and classes.

Is Clojure a JVM language?

Clojure is the result of all this. It's a LISP functional programming language running on the JVM designed for concurrent programs.


2 Answers

I think this is actually an evolving area - Clojure hasn't really been around long enough for all of the best practices and associated tools for managing a large code base to be developed yet.

Some suggestions from my experience:

  • Structure your code in a "bottom up" way - in general, the way you want to structure you code will have the "utility" code at the top of the file (or imported from another namespace) and the "business logic" code that uses these utility functions towards the end of the file. If this seems difficult to do, then it's probably a hint that your code needs some refactoring.

  • Tests as examples - Test code in clojure works very well both to sanity check your code but also as documentation (e.g. "what kind of parameter is this function expecting?"). If you hit a bug, refer to your tests to check your assumptions and write a couple of new tests to flush out what is going wrong.

  • Keep functions simple and compose them - Kind of an extension of the "single responsibility principle" to functional programming. I consider more than 5-10 lines in a Clojure function as a major code smell (if this seems extreme, just remember that you can probably achieve as much in 5-10 lines of Clojure as you could with 50-100 lines of Java/C#)

  • Watch out for "imperative habits" - when I first started using Clojure, I wrote a lot of pseudo-imperative code in Clojure. An example would be emulating a for loop with "dotimes" and accumulating some result within an atom. This can be painful - it's not idiomatic, it's confusing and usually there is a much smarter, simpler and less error-prone functional way of doing it. This takes practice, but it is worth it in the long run...

  • Debug at the REPL - usually when I hit an issue, coding at the REPL is the easiest way to flush it out. Generally this means running some specific parts of the larger algorithm to check assumptions etc.

  • Refactor common utility functions out - you'll probably find a bunch of common or structure repeated in many functions. Well worth pulling this out into a function or macro that you can re-use in other places or projects - that way you can test it much more rigorously and have the benefits in multiple places. Bonus points if you can get it all the way upstream into Clojure itself! If you do this well enough, then your main code base will be extremely succinct and therefore easy to manage, containing nothing but the genuinely domain-specific code.

like image 167
mikera Avatar answered Jan 03 '23 02:01

mikera


simple composable abstractions

"It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures." - Alan J. Perlis

For me its all about composing simple functions. Try to break every function down into the smallest units you can and then have another function that composes them to do the work your need. You know you are in good shape is every function can be tested independently. If you go too heavy on the macroes then it can make this step harder because macroes compose differently.

D.R.Y, Seriously, just don't repeat yourself

starting with well decomposed functions in a a bunch of namespaces; every time I need one of the composable parts somewhere else I "hoist" that function up to a library included by both namespaces. This way your commonly used abstractions sort of evolve over the course of the project into "just enough framework". It is very difficult to do this unless you really have discrete composable abstractions.

like image 38
Arthur Ulfeldt Avatar answered Jan 03 '23 02:01

Arthur Ulfeldt