Clean Clojure: Meaningful Names

I came to 8th Light for a chance to write Clojure, and in the last few weeks, the lofty dream of slinging s-expressions during my day job has finally come true. (Apprentices are compensated at a flat rate of $0.002 per parenthesis). After spending three months learning rules and practices for writing clean object-oriented code, I’m now mapping them to functional programming. Over the next few posts, I’ll try translating some of the guidelines for clean code to Clojure. Like any style guide, there will be room for opinion, so don’t hesitate to leave comments or offer suggestions.

Clean Code is worth the cover price for Chapter 2 alone. Its advice is simple: use meaningful, clear names that reveal intent. This rule probably seems obvious, but the value is in its side effects. Taking the time to scrutinize every name requires the sort of mindfulness and thought that produces clean code. In addition to Uncle Bob’s general guidelines for good names, here are a few Clojure-specific rules on naming.

Verbs rule

Clojure’s categorical imperative: act in the Kingdom of Verbs. Functions do things, and their names should describe the things they do. This is usually an easy rule to follow, but functions that build or return data structures can be tricky. Make-user-map is better than user-data. Render-footer is better than footer alone.

But nouns are useful

Verbs are great, but they’re even greater when they have objects. A name like remove-temporary-files is much clearer than clean-up.

Nouns are also useful inside functions. I find my tolerance for repetition far lower in Clojure than in other languages: if I use an expression more than once, I’ll almost always put it in a let binding and give it a name. Inside functions that compose multiple transformations on some data structure, extracting intermediate steps into values in a let binding can be very helpful.

1
2
3
4
5
(defn make-french-wombat-pairs [crazy-nested-map]
  (let [interesting-pairs  (extract-interesting-pairs crazy-nested-map)
        pairs-with-wombats (map add-wombat interesting-pairs)
        in-french          (map to-french pairs-with-wombats)]
    in-french))

Good nouns are also helpful when destructuring values, which is awesomely useful but sometimes hard to read. Prefer putting them in let bindings to cramming them in the argument list, except for very simple cases.

1
2
3
4
5
6
7
8
9
(defn print-summary [statistics]
  (let [{raw-data :data {:keys [mean variance r-squared]} :summary} statistics]
    (println "Mean: " mean)
    (println "Variance: " variance
    (println "R-Squared: " r-squared)
    (println "Sample size: " (count raw-data)))))

(defn arctic-circle? [[latitude _]]
  (> latitude 65))

And okay fine, also adjectives

The one first-class exception to verbs everywhere is adjectives for predicates (functions that return true or false, like odd? and seq?). These should always end in question marks and always return either true or false.

1
2
(defn all-wombats? [coll]
  (every? (wombat? coll)))

Use the best name…

Clojure has a large set of core functions, and sometimes the clearest name for a function will collide with one of them. Use it anyways! This is why namespaces are useful. Similarly, don’t worry if the best name is a long one–it’s easy to rebind it to a new name when required.

…But don’t mislead

That said, make sure it really is the best name. Long names often indicate functions that can be split: invert-and-multiply and find-and-replace should probably be split in two. (Hint: and is a great clue). If a function’s name collides with a core function or incorporates a common name, it should act the same way: if table-map doesn’t apply a function to every cell in a table, it has the wrong name.

Use idiomatic vocabulary

The Clojure style guide, Clojuredocs examples and Clojure’s own library coding standards are good resources for picking up common Clojure idioms and vocabulary. Here are a few naming conventions.

In macros, expr is usually used for a single expression and body for a longer form.

1
2
(defmacro when [expr & body]
  `(if ~expr (do ~@body)))

“Collection” is often shortened to coll:

1
2
(defn remove-wombats [coll]
  (filter wombat? coll))

Bundling up extra arguments is almost always done with & more.

1
2
3
(defn shout-all-args [& more]
  (doseq [arg more]
    (println (str (upcase arg) "!"))))

Like in middle school math, n is usually an integer, x and y are default numerical inputs, and f, g, and h are often functions.

1
2
3
4
(defn local-max? [f [x y]]
  (let [f'  (derivative f)
        f'' (derivative (derivative f))]
    (and (< 0 (f'' x)) (= 0 (f' x))))))

Dynamic vars wear *earmuffs*. Try not to use them.

Simple format transformations often use an arrow, e.g.: cljs->clj, html->hiccup, hex->bytes.

Make side effects explicit

Clojure does a great job separating value, state, and identity. Clojure programmers should, too. If a function changes state or has side effects, its name should reflect it. Functions that mutate state like swap! and reset! end with a bang. Side effects hiding elsewhere should also be explicit: if format-page saves a file to disk, it should be format-and-save-page (or even better, two separate functions).

UPDATE: See also the Clojure Style Guide, a concise, comprehensive community-driven document with many more guidelines on clean Clojure.

Comments