The Elements of Style

Jun 02, 2016

An accumulation of Clojure coding advice

With Clojure adoption at SMX growing under Colin’s happy evangelism, now seems the time to distil a uniform guide for the team. Thumbing through Strunk & White on holiday inspired me to aggregate some advice that informs my style, here’s my contribution to the conversation:

Elementary

Bozhidar Batsov’s Community Coding Guide is an an evolving consensus on style that aims to promote some basic uniformity between projects. A collaborative, indispensible reference for basic Clojure idioms.

Peer into the pull requests and learn why to avoid single segment namespaces. Poke around the open issues to find a discussion on the merits of the different ways to mark a function as private.

Namespaces

Follow Prismatic’s guide to namespace organisation, grouping functions within pragma blocks and using a public pragma block rather than defn- or ^:private to denote accessibilily.

Require namespaces with an alias that adds context, consider that context when naming a namespace and deciding the functions that it will contain. Only abbreviate namespace aliases where they are overly long or very common, use dotted namespaces freely:

 (:require [clojure.core.async :as async]
           [smx.eventstore.cassandra.time-series :as time.series]
           [clj-time.core :as time]
           [clj-time.coerce :as time.coerce]
           [schema.core :as s]
           [com.stuartsierra.component :as cp])

Rarely :use a namespace or :refer to symbols, the exception being within tests.

Personally I order required namespaces:

  • clojure core
  • clojure other
  • contained in this project
  • contained in supporting SMX projects
  • third party

Within those groupings I order by length, I think we can all agree that’s probably a bit much.

Functions

Stuart Sierra’s guide to naming functions is full of sensible advice, not least the implicit suggestion that you separate the pure from the side-effecting. The direction not to repeat the namespace name within a function name is clearly correct if you’re aliasing that namespace.

Stuart’s do’s and don’ts series is worth reading. The article on record constructors mirrors our existing approach, take his advice against functions with optional arguments using varargs.

Prefer a multi-arity function with an options map instead of keyword style optional varags, e.g:

(defn execute
  "Execute a query with (optional) connection modifiers"
  ([connection query]
   (execute connection executable nil))
  ([connection query {:keys [opt-one opt-two]}]
   (log/trace "executing" query opt-one opt-two)

;; preferable to

(defn execute
  "Execute a query with (optional) connection modifiers"
 ([connection query & {:keys [opt-one opt-two]}]
  (log/trace "executing" query opt-one opt-two)

The exception being when one or more of the optional arguments are required, e.g spec/fdef:

(defmacro fdef
  "Takes a symbol naming a function, and one or more of the following.. <snip>"
  [fn-sym & {:keys [args ret fn] :as m}]

Destructure within function params only if you want to communicate to any caller of a function the content of the parameter, prefer to destructure within a let immediately within the function body.

If you find a pure function named this-and-that refactor this and that and compose elsewhere.

Have each function do as little as possible.

Contracts

Move from schema to clojure.spec as soon as feasible. Schema has been widely applied throughout our projects, but the generative testing of clojure.spec and integration as a part of the language are too advantageous to pass over.

Protocols

Use them judiciously for polymorphism and open extension, not reflexivly for defining boundaries to a system like you might a Java interface. Prefer namespaces with pragma blocks.

Remember, protocols describe an interface but offer no guarantee of full implementation:

(defprotocol TwoFunctions
  (one [this] "one")
  (two [this] "two"))
=> TwoFunctions
(defrecord NaughtyConcreteImpl []
  TwoFunctions
  (one [_] (prn "hello")))
=> user.NaughtyConcreteImpl
(one (->NaughtyConcreteImpl))
"hello"
=> nil
(two (->NaughtyConcreteImpl))
AbstractMethodError Method user/NaughtyConcreteImpl.two()Ljava/lang/Object; is abstract  user.NaughtyConcreteImpl (form-init9019193871841315968.clj:-1)

Verbosity

Brevity is not abbreviation.

My enthusiasm for the brevity of Clojure after the ceremony of Java caused me to submit more than one code review that looked like an entry into an obfuscated code contest.

“Optimizing for readability usually means being more verbose. Don’t abbreviate unless you have to.” - Stuart Sierra

At a glance, what does this function do?

(defn segment
  [b s]
  (map (fn [[p l]] (.. b (slice) (position p) (limit l)))
       (partition 2 1 [(.limit b)] (range (.position b) (.limit b) s))))

As opposed to the more verbose:

(defn boundaries
  "Calculate the start and finish of each slice within a range"
  [position limit slice-size]
  (partition 2 1 [limit] (range position limit size)))

;;;;;;;;;;;;;
;;; Public

(s/defn segment :- [ByteBuffer]
  "Return slices of a bytebuffer with size determined by slice-size"
  [buffer :- ByteBuffer
   slice-size :- s/Int]
  (map (fn [[start finish]]
         (.. buffer
             (slice)
             (position start)
             (limit finish)))
       (boundaries (.position buffer) (.limit buffer) slice-size)))

Use a few more characters, future self exhorts you.

Brevity

Optimizing for readability doesn’t mean using more s-expr than are necessary.

For example, often it is possible to contract repeated lets and conditionals:

(if-let [error (invalid? input)]
  error
  (if-let [result (process input)]
    result
    :none)

;; prefer

(or (invalid? input)
    (process input)
    :none)
(when (seq active)
  (when-let [[idx ch] (async/alts! (vec active)))]
    ...

;; consider, where appropriate

(when-let [[idx ch] (when (seq active) (async/alts! (vec active)))]
  ...

Clojure has a range of threading macros, a couple of which have useful conditional semantics.

The some-> macro is ideal for progressively evaluating forms in a nil-safe manner:

(when-let [val (:key data)]
  (when-let [val (normalize val)]
    (str/lower-case val)))

;; prefer the nil-safe some-> threading macro

(some-> (:key data) normalize str/lower-case)

The cond-> macro is well suited for conditionally associating fields:

(defn a-fn
  [data some-flag?]
  (let [result (assoc data :a "x" :b "y")]
    (if some-flag?
      (assoc result :c "z")
      result)))

;; rather than a let and if-statement, prefer cond->

(defn a-fn
  [data some-flag?]
  (cond-> (assoc data :a "x" :b "y")
          some-flag? (assoc :c "z")))

Wondering where opportunities to crunch s-exprs lie? MR_ZORRR makes the fine suggestion to identify some automatically with the static analysis tool jonase/kibit.

Component

Use it to manage dependencies and lifecycle only. Avoid the temptation to add protocols to every component in an attempt to recreate an OOP-style graph of objects blending state and behaviour.

Mimic

Be aware of and adopt the style in place when commencing work on an existing project.

Example

Here’s an example of a Clojure namespace that adheres to some of the style suggestions above.