How to ns
Stuart’s Opinionated Style Guide for Clojure Namespace Declarations
Summary
Make it look like this:
(ns com.example.my-application.server "Example application HTTP server and routing." (:refer-clojure :exclude [send]) (:require [clojure.core.async :as async :refer [<! <!! >! >!!]] [com.example.my-application.base] [com.example.my-application.server.sse :as server.sse] [io.pedestal.http :as http] [io.pedestal.http.sse :as http.sse] [ring.util.response :as response]) (:import (java.nio.file Files LinkOption) (org.apache.commons.io FileUtils)))
The Rules
Roughly in order of importance:
Main clauses
Put clauses in this order, omitting those that are not needed:
:refer-clojure
:require
:import
Use each clause at most once.
Do not :use
.
Do not :refer :all
.
Do not :rename
.
Use :require :refer
only for very frequently-used symbols.
Keywords or symbols?
Use keywords, not symbols, for :refer-clojure
, :require
, and :import
.
;; Good (ns com.example.my-application (:require [clojure.string :as string]) (:import (java.util Date)))
;; Bad (ns com.example.my-application (require [clojure.string :as string]) (import (java.util Date)))
Symbols happen to work in most versions of Clojure, but were never correct syntax.
Reference: ns macro docstring.
Vectors or lists?
Use lists (parentheses) for the top-level clauses such as (:require
...)
. Because the first element, the :require
or :import
keyword,
is special.
;; Good (ns com.example.application (:require [clojure.string :as string]) (:import (java.util Date)))
;; Bad (ns com.example.application [:require [clojure.string :as string]] [:import (java.util Date)])
Use vectors (square brackets) inside :require
. Because otherwise
they would be interpreted as prefix lists.
;; Good (:require [clojure.string :as string])
;; Bad (:require (clojure.string :as string))
Use lists (parentheses) inside :import
. Because the first symbol,
the package name, is special.
;; Good (:import (java.util Date UUID) (java.io File))
;; Bad (:import [java.util Date UUID] [java.io File])
This (lists in :import
) turns out to be the most contentious point.
A number of people disagree with this recommendation, on the grounds
that the package-class list does not represent an invocation, as lists
usually do in Clojure. I stand by it, though, for the reason above and
because both ns and import have historically been documented this way.
Put every :require
’d namespace in a vector (square brackets)
even if it doesn't have an :as
or :refer
. This makes visual
scanning and sorting easier.
;; Good (:require [com.example.client :as client] [com.example.server])
;; Bad (:require [com.example.client :as client] com.example.server)
References: ns macro docstring, require docstring, import docstring.
Prefix lists
Never use prefix lists in :require
. Write out the full name of
each namespace. Because namespaces have no hierarchical relationship,
and it makes searching easier.
;; Good (:require [com.example.client] [com.example.routes] [com.example.server])
;; Bad (:require (com.example client server routes))
Always use prefix lists in :import
, even if you're only importing
one class from a package. Because packages and classes do have a
hierarchical relationship: packages contain classes.
;; Good (:import (java.io File) (java.util Date UUID))
;; Bad (:import java.io.File (java.util Date UUID))
Aliases
Use full words as namespace aliases, except for very frequently-used
namespaces with established conventions such as
[clojure.spec :as s]
.
Reference: Clojure Do’s: Namespace Aliases
Vectors or lists? part 2
Put :refer
’d or :exclude
’d symbols in a vector
(square brackets).
;; Good (:require [clojure.string :refer [blank? capitalize ends-with?]])
;; Bad (:require [clojure.string :refer (blank? capitalize ends-with?)])
The docstrings of ns
and refer
disagree here: refer says
“list-of-symbols,” but ns shows an example with vectors. Vectors make
more sense because the first symbol is not special or different from
the others.
Sorting
Sorting makes scanning easier and reduces the likelihood of accidentally repeating symbols.
Sort :require
’d namespaces lexicographically.
;; Good (:require [clojure.java.io :as io] [cognitect.transit :as transit] [com.example.my-application.server.api :as api] [io.pedestal.http :as http])
;; Bad (:require [clojure.java.io :as io] [io.pedestal.http :as http] [cognitect.transit :as transit] [com.example.my-application.server.api :as api])
Sort :import
’ed packages lexicographically.
;; Good (:import (com.example.java ExampleMain) (java.nio.file Files) (java.nio.file.attribute FileAttribute) (org.apache.commons.io FileUtils))
;; Bad (:import (org.apache.commons.io FileUtils) (java.nio.file Files) (java.nio.file.attribute FileAttribute) (com.example.java ExampleMain))
Within a :refer
or :exclude
vector, sort symbols lexicographically.
;; Good (:require [clojure.string :refer [blank? capitalize ends-with?]])
;; Bad (:require [clojure.string :refer [capitalize ends-with? blank?]])
Within an :import
prefix list, sort class names lexicographically.
;; Good (java.nio.file.attribute FileAttribute FileAttributeView UserPrincipal)
;; Bad (java.nio.file.attribute FileAttributeView UserPrincipal FileAttribute)
Docstring
Include a docstring. Describe what the purpose of this namespace is and how it fits into the rest of the codebase.
Prefer docstrings over large block comments where possible.
;; Good (ns com.example.my-application-routes "HTTP server routes for my application. Routes generally follow the pattern /api/:type/:id where :type is a type of object and :id is an integer ID.")
;; Bad (ns com.example.my-application-routes) ;; HTTP server routes for my application. ;; Routes generally follow the pattern /api/:type/:id ;; where :type is a type of object and :id is an integer ID.
Line breaks
Start the contents of :require
or :import
on a new line after the
keyword.
;; Good (:require [clojure.string :as string] [io.pedestal.http :as http])
;; Not so good (:require [clojure.string :as string] [io.pedestal.http :as http])
This recommendation is slightly different from common practice, but it
makes it easier to sort names with editor commands (sort-lines
in
Emacs).
Put each :require
’d namespace or :import
’ed package
on its own line.
;; Good (:require [clojure.string :as string] [io.pedestal.http :as http])
;; Bad (:require [clojure.string :as string] [io.pedestal.http :as http])