I modelli da prendere in esempio sono importanti.
Tip
|
Puoi trovare una bellissima versione di questa guida con una navigazione decisamente migliorata (in inglese) a https://guide.clojure.style. |
Questa guida allo stile fornisce le best practice ai programmatori Clojure in modo che possano scrivere codice facilmente mantenibile da altri programmatori Clojure. Una guida allo stile che riflette l’utilizzo nel mondo reale viene utilizzata, e una guida allo stile che viene rifiutata dalle persone che dovrebbe aiutare rischia di non essere affatto utilizzata — non importa quanto sia fatta bene.
Questa guida è separata in diverse sezioni di regole correlate. Abbiamo provato ad includere il ragionamento dietro alle regole (se è stato omesso, abbiamo presento che fosse piuttosto ovvio).
Non abbiamo scovato tutte le regole dal nulla; sono basate soprattutto sull’esperienza degli editori della guida, sui feedback e sui consigli dei numerosi membri della community di Clojure, e altre fonti molto apprezzate per la programmazione Clojure come "Clojure Programming" e "The Joy of Clojure".
Nulla di ciò che viene scritto qui è inciso sulla pietra. Questa guida di stile si evolve con il passare del tempo, man mano che vengono identificate nuove convenzioni e quelle vecchie vengono rese obsolete dai cambiamenti nel liguaggio Clojure stesso.
Note
|
Gli sviluppatori Clojure mentengono anche una lista di linee guida per la programmazione con librerie.[1] Esse sono una delle fonti di ispirazione per il documento che stai leggendo. |
Puoi generare una copia PDF di questa guida usando AsciiDoctor PDF, e una copia HTML con AsciiDoctor utilizzando i seguenti comandi:
# Genera README.pdf
asciidoctor-pdf -a allow-uri-read README.adoc
# Genera README.html
asciidoctor
Tip
|
Installa 'rouge' con gem per ottenere la colorazione del codice nel documento generato. gem install rouge |
I programmi devono essere scritti per le persone da leggere, e solo incidentalmente per le macchine da eseguire.
Structure and Interpretation of Computer Programs
È risaputo che il codice viene letto molto più spesso di quanto viene scritto. Le linee guida fornite sono pensate per migliorare la leggibilità del codice e renderlo consistente (coerente) in tutto il largo spettro del codice Clojure. Sono anche pensate per riflettere l’utilizzo di Clojure nel mondo reale invece di un suo ideale. Quando abbiamo dovuto scegliere tra una pratica ormai stabilita e un alternativa soggetivamente migliore abbiamo optato per raccomandare la pratica stabilita.[2]
Ci sono alcune aree in cui non esiste un consenso generale chiaro nella community di Clojure riguardante un particolare stile (come intentazione fissa vs intentazione semantica, commenti uniformi, commenti semantici, etc). In questi scenari tutti gli stili popolari sono riconosciuti e viene lasciato a te sceglierne uno e applicarlo coerentemente.
Fortunatamente Clojure è un Lisp, e i Lisp sono fondamentalmente semplici. Anche se questa guida è stata creata qualche anno dopo Clojure (la prima versione è stata pubblicata all’inizio del 2013), si vedeva che il codice Clojure in natura era abbastanza uniforme. Attribuiamo questo sia alla semplicità di cui abbiamo già parlato sia al fatto che dal primo giorno i Clojuristi adottarono molte delle convenzioni stilistiche di altri dialetti Lisp stabiliti (ad esempio Common Lisp e Scheme). Questo rese il lavoro sulla guida è abbastanza semplice e diretto, soprattutto rispetto al massiccio sforzo e frustrazione che era la Community Ruby Style Guide.[3]
Clojure è notoriamente ottimizzato per semplicità e chiarezza. Ci piace credere che questa guida ti aiuterà a ottimizzare al massimo semplicità e chiarezza.
Una sciocca coerenza è l’hobgoblin delle piccole menti, adorato da piccoli politici, filosofi e teologi.
Una guida di stile riguarda la coerenza.[4] La coerenza con questa guida di stile è importante. La coerenza all’interno di un progetto è più importante. La coerenza all’interno di una classe o di un metodo è la cosa più importante.
Tuttavia, sappi quando essere incoerente: a volte i consigli della guida di stile non sono applicabili. In caso di dubbio, usa il tuo miglior giudizio. Guarda altri esempi e decidi cosa sembra migliore. E non esitate a chiedere!
In particolare: non rompere la retrocompatibilità solo per rispettare questa guida!
Alcuni altri buoni motivi per ignorare una particolare linea guida:
-
L’applicazione della linea guida renderebbe il codice meno leggibile, anche per chi è abituato a leggere il codice che segue questa guida di stile.
-
Per essere coerenti con il codice circostante che non la rispetta (forse per ragioni storiche) - sebbene questa sia anche un’opportunità per ripulire il casino di qualcun altro (in vero stile XP).
-
Perché il codice in questione è stato scritto prima dell’introduzione della linea guida e non c’è nessun altro motivo per modificare quel codice.
-
Quando il codice deve rimanere compatibile con le versioni precedenti di Clojure che non supportano la funzionalità consigliata dalla guida di stile.
Sono disponibili traduzioni della guida originale nelle seguenti lingue:
-
Portoghese (Lavori in corso)
Note
|
Queste traduzioni (compresa questa in Italiano) non sono mantenute dal team di editori della versione originale Inglese, quindi la qualità e livello di completezza può variare. Le versioni tradotte della guida spesso ritardano con le aggiunte rispetto all’originale. |
Quasi tutti sono convinti che ogni stile tranne il proprio sia brutto e illeggibile. Lascia fuori il "tranne il proprio" e hanno probabilmente ragione…
Ove possibile, evita di creare righe più lunghe di 80 caratteri.
Molte persone in questi giorni ritengono che una lunghezza massima della riga di 80 caratteri sia solo un residuo del passato e ha poco senso oggi. Dopo tutto - i display moderni possono facilmente contenere oltre 200 caratteri su una singola riga. Eppure ci sono alcuni importanti vantaggi che si ottengono dall’attenersi a righe di codice più brevi.
Innanzitutto, numerosi studi hanno dimostrato che gli esseri umani leggono molto più velocemente verticalmente e linee di testo molto lunghe ostacolano il processo di lettura. Come mostrato in precedenza, uno dei principi di questa guida di stile è ottimizzare il codice che scriviamo per il consumo umano.
Inoltre, la limitazione della larghezza della finestra dell’editor richiesta rende possibile avere diversi file aperti fianco a fianco e funziona bene quando si utilizzano strumenti per la revisione del codice che presentano le due versioni in colonne adiacenti.
Il wrapping predefinito nella maggior parte degli strumenti interrompe la struttura visiva del codice, rendendo più difficile la comprensione. I limiti sono scelti per evitare il wrapping negli editor con la larghezza della finestra impostata su 80, anche se lo strumento posiziona un glifo marcatore nella colonna finale durante il wrapping delle righe. Alcuni strumenti basati sul Web potrebbero non offrire un wrapping dinamico della linea.
Alcuni team preferiscono fortemente una lunghezza della linea più lunga. Per il codice gestito esclusivamente o principalmente da un team che può raggiungere un accordo su questo problema, va bene aumentare il limite di lunghezza della riga fino a 100 caratteri o fino a 120 caratteri. Per favore, frena l’impulso di andare oltre i 120 caratteri.
Usa gli spazi per l’indentazione. Non i tabs.
Usa 2 spazi per indentare i corpi dei form che hanno parametri. Questo include tutte le varianti di def
, form speciali e macro che introducono associazioni locali (ad esempio loop
, let
, when-let
) e tante macro come when
, cond
, as->
, cond->
, case
,
with-*
, ecc.
;; bene
(when qualcosa
(qualcos-altro))
(with-out-str
(println "Ciao, ")
(println "mondo!"))
;; male - quattro spazi
(when qualcosa
(qualcos-altro))
;; male - uno spazio
(with-out-str
(println "Ciao, ")
(println "mondo!"))
Allinea verticalmente gli argomenti delle funzioni (macro) su più linee.
;; bene
(filter even?
(range 1 10))
;; male - argomento allineato con il nome della funzione (uno spazio di indentazione)
(filter even?
(range 1 10))
;; male - due spazi di indentazione
(filter even?
(range 1 10))
Il ragionamento dietro a questa linea guida è piuttosto semplice - gli argomenti sono più facili da processare per il cervello umano se si distinguono e sono vicini.
Note
|
Generalmente, dovresti utilizzare la formattazione mostrata nella scorsa linea guida, a meno che tu sia limitato dallo spazio orizzontale disponibile. |
Utilizzare un solo spazio di indentazione per gli argomenti della funzione (macro) dove non ci sono argomenti sulla stessa riga del nome della funzione.
;; bene
(filter
even?
(range 1 10))
(or
ala
bala
portokala)
;; male - due spazi di indentazione
(filter
even?
(range 1 10))
(or
ala
bala
portokala)
Questo può sembrare una strana regola speciale per le persone senza un background di Lisp, ma il ragionamento alla base è abbastanza semplice. Le chiamate di funzione sono nient’altro che normali liste letterali e normalmente quelli sono allineati allo stesso modo di altri tipi di raccolte letterali quando si estendono su più righe:
;; liste letterali
(1
2
3)
;; vettori letterali
[1
2
3]
;; set (insiemi) letterali
#{1
2
3}
Certo, le liste letterali non sono molto comuni in Clojure, ecco perché è comprensibile che per molte persone le liste letterali non sono altro che una sintassi di invocazione.
Come vantaggio collaterale, anche in questo scenario gli argomenti delle funzioni sono ancora allineati, solo che essi sono accidentalmente allineati anche con il nome della funzione.
Le linee guida per indentare in modo diverso le macro con il form del corpo da tutte le altre chiamate di macro e funzioni sono note collettivamente come "rientro semantico". In poche parole, questo significa che il codice è indentato in modo diverso, in modo che l’indentazione dia al lettore del codice alcuni cenni sul suo significato.
Lo svantaggio di questo approccio è che richiede che i formattatori di codice Clojure siano più intelligenti. Devono cosiì elaborare le liste di argomenti delle "macro" e fare affidamento sul fatto che le persone denominino i propri parametri in modo coerente o elaborino altri metadati di indentazione.
Alcune persone nella comunità di Clojure sostengono che non ne valga la pena e che tutto dovrebbe semplicemente essere indentato nello stesso modo. Ecco alcuni esempi:
;;; Indentazione fissa
;;
;; macro
(when qualcosa
(qualcos-altro))
(with-out-str
(println "Ciao, ")
(println "mondo!"))
;; chiamata di funzione su due linee
(filter even?
(range 1 10))
;; chiamata di funzione su tre linee
(filter
even?
(range 1 10))
Questo suggerimento ha sicuramente guadagnato terreno nella comunità, ma va anche contro gran parte della tradizione Lisp e uno degli obiettivi primari di questa guida di stile -vale a dire ottimizzare il codice per il consumo umano.
C’è un’eccezione alla regola di indentazione fissa: gli elenchi di dati (quelli che non sono un’invocazione di funzione):
;;; Indentazione fissa
;;
;; liste letterali
;; facciamo ancora
(1
2
3
4
5
6)
;; e anche
(1 2 3
4 5 6)
;; invece di
(1 2 3
4 5 6)
;; oppure
(1
2
3
4
5
6)
Ciò garantisce che gli elenchi siano coerenti con il modo in cui gli altri tipi di raccolta sono normalmente indentati.
Allinea verticalmente le associazioni 'let' (e simili a 'let').
;; bene
(let [cosa1 "delle cose"
cosa2 "altre cose"]
(foo cosa1 cosa2))
;; male
(let [cosa1 "delle cose"
cosa2 "altre cose"]
(foo cosa1 cosa2))
Allinea verticalmente le chiavi delle mappe.
;; bene
{:cosa1 cosa1
:cosa2 cosa2}
;; male
{:cosa1 cosa1
:cosa2 cosa2}
;; male
{:cosa1 cosa1
:cosa2 cosa2}
Usa il fine linea in stile Unix.[5]
Tip
|
Se stai usando Git potresti voler aggiungere la seguente impostazione di configurazione per proteggere il progetto dai fine linea di Windows che si insinuano al suo interno: $ git config --global core.autocrlf true |
Termina ogni file andando a capo.
Tip
|
Questo dovrebbe essere fatto per configurazione dell’editor, non manualmente. |
Se del testo precede una parentesi di apertura ((
, {
e
[
) o segue una parentesi di chiusura ()
, }
e ]
), separa il
testo da quella parentesi con uno spazio. Al contrario, non lasciare spazio dopo una parentesi di apertura e prima del testo seguente o dopo il testo precedente e prima di una parentesi di chiusura.
;; bene
(foo (bar baz) quux)
;; male
(foo(bar baz)quux)
(foo ( bar baz ) quux)
Lo zucchero sintattico provoca il cancro al punto e virgola.
Non utilizzare le virgole tra gli elementi dei valori letterali di raccolta sequenziali.
;; bene
[1 2 3]
(1 2 3)
;; male
[1, 2, 3]
(1, 2, 3)
Prendi in considerazione il miglioramento della leggibilità dei valori letterali della mappa tramite un uso giudizioso di virgole e interruzioni di riga.
;; bene
{:name "Bruce Wayne" :alter-ego "Batman"}
;; buono e probabilmente un po 'più leggibile
{:name "Bruce Wayne"
:alter-ego "Batman"}
;; buono e probabilmente un po 'più compatto
{:name "Bruce Wayne", :alter-ego "Batman"}
Posiziona tutte le parentesi finali su un’unica riga anziché su righe distinte.
;; bene; linea singola
(when something
(something-else))
;; male; linee separate
(when something
(something-else)
)
Inserisci una singola linea vuota in mezzo ai form del top-level.
;; bene
(def x ...)
(defn foo ...)
;; male
(def x ...)
(defn foo ...)
;; male
(def x ...)
(defn foo ...)
Un eccezione alla regola è il raggruppamento di definizioni (def
).
;; bene
(def min-rows 10)
(def max-rows 20)
(def min-cols 15)
(def max-cols 30)
Non inserire righe vuote nel mezzo di una funzione o
macro. È possibile fare un’eccezione per indicare il raggruppamento di
costrutti a coppie come si trovano ad es. let
e cond
, nel caso non ci stiano sulla stessa linea.
;; bene
(defn fibo-iter
([n] (fibo-iter 0 1 n))
([curr nxt n]
(cond
(zero? n) curr
:else (recur nxt (+' curr nxt) (dec n)))))
;; accettabile - la linea vuota delimita la coppia del costrutto cond
(defn fibo-iter
([n] (fibo-iter 0 1 n))
([curr nxt n]
(cond
(zero? n)
curr
:else
(recur nxt (+' curr nxt) (dec n)))))
;; male
(defn fibo-iter
([n] (fibo-iter 0 1 n))
([curr nxt n]
(cond
(zero? n) curr
:else (recur nxt (+' curr nxt) (dec n)))))
Occasionalmente, potrebbe sembrare una buona idea aggiungere una riga vuota qua e là in una definizione di funzione più lunga, ma se arrivi a questo punto dovresti anche considerare se questa lunga funzione non stia facendo troppo e potrebbe potenzialmente essere suddivisa in più funzioni.
Evita di lasciare spazi a fine linea.
Usa un file per namespace e un namespace per file.
;; bene
(ns foo.bar)
;; male
(ns foo.bar)
(ns baz.qux)
;; male
(in-ns quux.quuz)
(in-ns quuz.corge)
;; male
(ns foo.bar) o (in-ns foo.bar) in più file
Evita i namespace con un singolo segmento.
;; bene
(ns esempio.ns)
;; male
(ns esempio)
Gli spazi dei nomi esistono per eliminare ambiguità tra i nomi. Utilizzare uno spazio dei nomi formato da un singolo segmento ti mette in conflitto diretto con tutti gli altri che usano spazi dei nomi con un singolo segmento, rendendo così più probabile il conflitto con un’altra base di codice.
In pratica ciò significa che le biblioteche non dovrebbero mai utilizzare spazi dei nomi a segmento singolo per evitare conflitti di namespace con altre librerie. All’interno della tua app privata, ovviamente, puoi fare quello che vuoi.
Tip
|
è prassi comune utilizzare la convenzione dominio.nome-libreria
o nome-libreria.core per le librerie con un singolo spazio dei nomi al loro interno.
Continua a leggere per una maggiore copertura dell’argomento della denominazione dello spazio dei nomi.
|
Ci sono altre ragioni per le quali potresti voler evitare spazi dei nomi a segmento singolo, quindi dovresti pensarci bene prima di usarli nel tuo codice.
Evita di usare spazi dei nomi esageratamente lunghi (per esempio, più di 5 segmenti).
Inizia ogni spazio dei nomi con un form ns
completo, composto da
refer
, require
e import
, convenzionalmente in questo ordine.
(ns examples.ns
(:refer-clojure :exclude [next replace remove])
(:require [clojure.string :as s :refer [blank?]])
(:import java.util.Date))
Quando ci sono più dipendenze, potresti voler assegnare a ciascuna la propria riga. Ciò facilita l’ordinamento, la leggibilità e differenze più pulite per le modifiche alle dipendenze.
;; meglio
(ns examples.ns
(:require
[clojure.string :as s :refer [blank?]]
[clojure.set :as set]
[clojure.java.shell :as sh])
(:import
java.util.Date
java.text.SimpleDateFormat
[java.util.concurrent Executors
LinkedBlockingQueue]))
;; bene
(ns examples.ns
(:require [clojure.string :as s :refer [blank?]]
[clojure.set :as set]
[clojure.java.shell :as sh])
(:import java.util.Date
java.text.SimpleDateFormat
[java.util.concurrent Executors
LinkedBlockingQueue]))
;; male
(ns examples.ns
(:require [clojure.string :as s :refer [blank?]] [clojure.set :as set] [clojure.java.shell :as sh])
(:import java.util.Date java.text.SimpleDateFormat [java.util.concurrent Executors LinkedBlockingQueue]))
In ns
preferisci :require :as
piuttosto che :require :refer
e piuttosto che :require
:refer :all
. Preferisci :require
piuttosto :use
; l’ultima forma dovrebbe essere considerata
deprecata per codice nuovo.
;; bene
(ns examples.ns
(:require [clojure.zip :as zip]))
;; bene
(ns examples.ns
(:require [clojure.zip :refer [lefts rights]]))
;; accettabile
(ns examples.ns
(:require [clojure.zip :refer :all]))
;; male
(ns examples.ns
(:use clojure.zip))
In ns
, ordina i requisiti e le importazioni. Questo facilità la leggibilità e evita la duplicazione, specialmente quando la lista degli spazi dei nomi richiesti / importati è molto lunga.
;; bene
(ns examples.ns
(:require
[baz.core :as baz]
[clojure.java.shell :as sh]
[clojure.set :as set]
[clojure.string :as s :refer [blank?]]
[foo.bar :as foo]))
;; male
(ns examples.ns
(:require
[clojure.string :as s :refer [blank?]]
[clojure.set :as set]
[baz.core :as baz]
[foo.bar :as foo]
[clojure.java.shell :as sh]))
Molti spazi dei nomi di Clojure hanno alias idiomatici che sei
incoraggiato a utilizzare all’interno dei tuoi progetti - ad es. il modo più comune per
rechiedere clojure.string
è: [clojure.string :as str]
.
Note
|
Potrebbe dare l’impressione di mascherare
clojure.core.str , ma non lo fa. Ci si aspetta che
clojure.core/str e clojure.string/* vengano usati in un namespace come str e str/qualcosa senza conflitti.
|
;; bene
(ns ... (:require [clojure.string :as str] ...))
(str/join ...)
;; meno bene - sii idiomatico e usa `str/`
(ns ... (:require [clojure.string :as string] ...))
(string/join ...)
Come notato nella sezione successiva, è generalmente considerato idiomatico usare un alias che è l’ultimo segmento dello spazio dei nomi, se questo lo rende unico, oppure i due segmenti, in genere eliminando parti ridondanti come "clj" o "core".
Tra gli spazi dei nomi core e Contrib di Clojure, i seguenti spazi dei nomi hanno alias idiomatici che seguono questo schema:
Namespace |
Alias idiomatico |
clojure.datafy |
datafy |
clojure.edn |
edn |
clojure.java.io |
io |
clojure.math |
math |
clojure.set |
set |
clojure.walk |
walk |
clojure.zip |
zip |
clojure.core.async |
async |
clojure.data.csv |
csv |
clojure.data.xml |
xml |
clojure.tools.cli |
cli |
Poi ci sono alcuni spazi dei nomi core e Contrib che hanno alias idiomatici più brevi:
Namespace |
Alias idiomatico |
clojure.java.shell |
sh |
clojure.pprint |
pp |
clojure.spec.alpha |
s |
clojure.string |
str |
clojure.core.matrix |
mat |
clojure.tools.logging |
log |
clojure.core.protocols |
p |
clojure.core.reducers |
r |
E tra le librerie della community comunemente usate, ce ne sono anche molte che hanno alias idiomatici ampiamente utilizzati per diversi spazi dei nomi:
Namespace |
Alias idiomatico |
cheshire.core |
json |
clj-yaml.core |
yaml |
clj-http.client |
http |
hugsql.core |
sql |
java-time |
time |
next.jdbc |
jdbc |
Sopra abbiamo coperto una manciata di spazi dei nomi popolari e i loro alias idiomatici. Potresti aver notato che questi sono un po' incoerenti:
-
clojure.string
diventastr
-
clojure.pprint
diventapp
-
clojure.walk
diventawalk
-
clojure.spec.alpha
diventas
È chiaro che l’unica cosa che hanno in comune è che mirano a essere concisi, ma hanno comunque un significato (dare w
come alias a clojure.walk
sarebbe stato conciso, ma non avrebbe molto significato).
Ma cosa fare con tutti gli altri spazi dei nomi là fuori che non hanno alias idiomatici? Beh, faresti meglio a essere coerente nel tuo approccio alla derivazione degli alias per loro, altrimenti le persone che lavorano su una base di codice Clojure condivisa sperimenteranno molta confusione. Ecco alcune regole che dovresti seguire.[6]
1) Rendi l’alias uguale al nome dello spazio dei nomi con le parti iniziali rimosse.
(ns com.example.application
(:require
[clojure.java.io :as io]
[clojure.reflect :as reflect]))
2) Mantieni un numero sufficiente di parti finali per rendere unico ogni alias.
[clojure.data.xml :as data.xml]
[clojure.xml :as xml]
Tip
|
Sì, gli alias dello spazio dei nomi possono contenere dei punti. Fatene buon uso. |
3) Elimina parole ridondanti come "core" e "clj" negli alias.
[clj-time.core :as time]
[clj-time.format :as time.format]
In un progetto, è bene essere coerenti con gli alias dello spazio dei nomi; ad esempio, non richiedere clojure.string
come str
in uno spazio dei nomi ma string
in un altro.
Se segui le due linee guida precedenti sei sostanzialmente coperto, ma se opti per lo schema di aliasing dello spazio dei nomi personalizzato è comunque importante applicarlo
coerentemente all’interno dei vostri progetti.
Le uniche vere difficoltà nella programmazione sono l’invalidazione della cache e dare nomi alle cose.
Quando stai dando un nome ad un namespace preferisci i seguenti due schemi:
-
project.module
-
organization.project.module
Quando segui lo schema di denominazione project.module
e il tuo progetto
ha un singolo spazio dei nomi (di implementazione) è comune chiamarlo project.core
.
Evita il nome project.core
in tutti gli altri casi, come nomi più informativi
sono sempre un’idea migliore.
Usa lisp-case
nei segmenti di namespace compositi (ad esempio bruce.project-euler
).
Note
|
Molte comunità di programmatori non Lisp si riferiscono a lisp-case come
kebab-case , ma sappiamo tutti che Lisp esisteva molto prima del kebab.
|
Usa lisp-case
per i nomi delle funzioni e variabili.
;; bene
(def some-var ...)
(defn some-fun ...)
;; male
(def someVar ...)
(defn somefun ...)
(def some_fun ...)
Usa CapitalCase
per i protocolli, records, structs, e i tipi. (mantieni acronimi come HTTP, RFC, XML maiuscoli.)
Note
|
CapitalCase è anche conosciuto come UpperCamelCase, `CapitalWords e PascalCase .
|
I nomi dei metodi predicati (metodi che restituiscono un valore booleano) dovrebbe terminare con un punto interrogativo (ad esempio, "pari?").
;; bene
(defn palindrome? ...)
;; male
(defn palindrome-p ...) ; in stile Common Lisp
(defn is-palindrome ...) ; in stile Java
I nomi delle funzioni/macro che non sono sicure nelle transazioni STM
dovrebbero terminare con un punto esclamativo (ad es. reset!
).
Usa "->" invece di "to" nei nomi delle funzioni di conversione.
;; bene
(defn f->c ...)
;; non così bene
(defn f-to-c ...)
Usa earmuffs
per cose destinate a essere riassociate (ad es. sono dinamiche).
;; bene
(def ^:dynamic *a* 10)
;; male
(def ^:dynamic a 10)
Non usare una notazione speciale per le costanti; tutto è presunto una costante se non diversamente specificato.
;; bene
(def max-size 10)
;; male
(def MAX-SIZE 10) ; in stile Java
(def +max-size+ 10) ; in stile Common Lisp , costante globale
(def *max-size* 10) ; in stile Common Lisp , variabile globale
Note
|
Notoriamente *clojure-version* sfida questa convenzione, ma dovresti
trattare questa scelta di denominazione come una stranezza storica e non come un esempio da
seguire.
|
Usa _
per destrutturare obiettivi e nomi di argomenti formali di cui
il valore verrà ignorato dal codice a portata di mano.
;; bene
(let [[a b _ c] [1 2 3 4]]
(println a b c))
(dotimes [_ 3]
(println "Hello!"))
;; male
(let [[a b c d] [1 2 3 4]]
(println a b d))
(dotimes [i 3]
(println "Hello!"))
Tuttavia, quando può aiutare la comprensione del tuo codice, può essere utile nominare esplicitamente argomenti o mappe inutilizzati da cui stai destrutturando. In questo caso, anteponi al nome un carattere di sottolineatura per segnalare esplicitamente che la variabile dovrebbe essere inutilizzata.
;; bene
(defn myfun1 [context _]
(assoc context :foo "bar"))
(defn myfun2 [context {:keys [id]}]
(assoc context :user-id id))
;; meglio ancora
(defn myfun1 [context _user]
(assoc context :foo "bar"))
(defn myfun2 [context {:keys [id] :as _user}]
(assoc context :user-id id))
Segui l’esempio di clojure.core
per nomi idiomatici come pred
e coll
.
-
nelle funzioni:
-
f
,g
,h
- input delle funzioni -
n
- input intero, di solito indica grandezza -
index
,i
- intero usato per indici -
x
,y
- numeri -
xs
- sequenza -
m
- mappa -
k
,ks
- chiave, chiavi -
v
,vs
- valore, valori (come in una coppia chiave/valore) -
s
- input stringa -
re
- regular expression -
sym
- simbolo -
coll
- una collezione -
pred
- una closure predicativa -
& more
- input variadico -
xf
- xform, un trasduttore -
ns
- namespace[7]
-
-
nelle macro:
-
expr
- un espressione -
body
- il corpo di una macro -
binding
- un vettore di legame macro
-
-
nei metodi (quando specificato in
defprotocol
,deftype
,defrecord
,reify
, etc):-
this
- per il primo argomento, indicando un riferimento all’oggetto - o in alternativa, un nome coerente che descrive l’oggetto
-
Facoltativamente omettere la nuova riga tra il nome della funzione e l’argomento vettore per defn
quando non c’è docstring.
;; bene
(defn foo
[x]
(bar x))
;; bene
(defn foo [x]
(bar x))
;; male
(defn foo
[x] (bar x))
Posiziona il dispatch-val
di un multimetodo sulla stessa riga del nome della funzione.
;; bene
(defmethod foo :bar [x] (baz x))
(defmethod foo :bar
[x]
(baz x))
;; male
(defmethod foo
:bar
[x]
(baz x))
(defmethod foo
:bar [x]
(baz x))
Facoltativamente, ometti la nuova riga tra il vettore degli argomenti e un corpo della funzione molto corto.
;; bene
(defn foo [x]
(bar x))
;; bene per un corpo funzione corto
(defn foo [x] (bar x))
;; bene per funzioni con multi-arità
(defn foo
([x] (bar x))
([x y]
(if (predicate? x)
(bar x)
(baz x))))
;; male
(defn foo
[x] (if (predicate? x)
(bar x)
(baz x)))
Indenta ogni form di arità di una definizione di funzione allineata verticalmente con i suoi parametri.
;; bene
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
;; male - indentazione extra
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
Ordina le arità di una funzione dal minor numero al maggior numero di argomenti. Il caso comune di funzioni con arità multipla è che alcuni argomenti K specificano completamente il comportamento della funzione, e che le arità N < K applicano parzialmente l’arità K, e le aritò N > K forniscono una piegatura dell’arità K su varargs.
;; bene - è facile cercare l'n-esima arità
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
;; okay - le altre arità sono applicazioni della prima arità
(defn foo
"I have two arities."
([x y]
(+ x y))
([x]
(foo x 1))
([x y z & more]
(reduce foo (foo x (foo y z)) more)))
;; male - disordinata senza motivo
(defn foo
([x] 1)
([x y z] (foo x (foo y z)))
([x y] (+ x y))
([w x y z & more] (reduce foo (foo w (foo x (foo y z))) more)))
Evita funzioni più lunghe di 10 LOC (righe di codice). Idealmente, la maggior parte delle funzioni saranno più brevi di 5 LOC.
Avoid parameter lists with more than three or four positional parameters.
Prefer function pre and post conditions to checks inside a function’s body.
;; good
(defn foo [x]
{:pre [(pos? x)]}
(bar x))
;; bad
(defn foo [x]
(if (pos? x)
(bar x)
(throw (IllegalArgumentException. "x must be a positive number!")))
Avoid the use of namespace-manipulating functions like require
and
refer
. They are entirely unnecessary outside of a REPL
environment.
Avoid forward references. They are occasionally necessary, but such occasions are rare in practice.
Use declare
to enable forward references when forward references are
necessary.
Prefer higher-order functions like map
to loop/recur
.
Don’t shadow clojure.core
names with local bindings.
;; bad - clojure.core/map must be fully qualified inside the function
(defn foo [map]
...)
Use alter-var-root
instead of def
to change the value of a var.
;; good
(def thing 1) ; value of thing is now 1
; do some stuff with thing
(alter-var-root #'thing (constantly nil)) ; value of thing is now nil
;; bad
(def thing 1)
; do some stuff with thing
(def thing nil)
; value of thing is now nil
Use seq
as a terminating condition to test whether a sequence is
empty (this technique is sometimes called nil punning).
;; good
(defn print-seq [s]
(when (seq s)
(prn (first s))
(recur (rest s))))
;; bad
(defn print-seq [s]
(when-not (empty? s)
(prn (first s))
(recur (rest s))))
Prefer vec
over into
when you need to convert a sequence into a vector.
;; good
(vec some-seq)
;; bad
(into [] some-seq)
Use the boolean
function if you need to convert something to an actual boolean value (true
or false
).
;; good
(boolean (foo bar))
;; bad
(if (foo bar) true false)
Note
|
Don’t forget that the only values in Clojure that are "falsey" are false and nil . Everything else
will evaluate to true when passed to the boolean function.
|
You’ll rarely need an actual boolean value in Clojure, but it’s useful to know how to obtain one when you do.
Use when
instead of if
with just the truthy branch, as in (if condition (something…))
or (if … (do …))
.
;; good
(when pred
(foo)
(bar))
;; bad
(if pred
(do
(foo)
(bar)))
Use if-let
instead of let
+ if
.
;; good
(if-let [result (foo x)]
(something-with result)
(something-else))
;; bad
(let [result (foo x)]
(if result
(something-with result)
(something-else)))
Use when-let
instead of let
+ when
.
;; good
(when-let [result (foo x)]
(do-something-with result)
(do-something-more-with result))
;; bad
(let [result (foo x)]
(when result
(do-something-with result)
(do-something-more-with result)))
Use if-not
instead of (if (not …) …)
.
;; good
(if-not pred
(foo))
;; bad
(if (not pred)
(foo))
Use when-not
instead of (when (not …) …)
.
;; good
(when-not pred
(foo)
(bar))
;; bad
(when (not pred)
(foo)
(bar))
Use when-not
instead of (if-not … (do …))
.
;; good
(when-not pred
(foo)
(bar))
;; bad
(if-not pred
(do
(foo)
(bar)))
Prefer printf
over (print (format …))
.
;; good
(printf "Hello, %s!\n" name)
;; ok
(println (format "Hello, %s!" name))
When doing comparisons, leverage the fact that Clojure’s functions <
,
>
, etc. accept a variable number of arguments.
;; good
(< 5 x 10)
;; bad
(and (> x 5) (< x 10))
Prefer %
over %1
in function literals with only one parameter.
;; good
#(Math/round %)
;; bad
#(Math/round %1)
Prefer %1
over %
in function literals with more than one parameter.
;; good
#(Math/pow %1 %2)
;; bad
#(Math/pow % %2)
Don’t wrap functions in anonymous functions when you don’t need to.
;; good
(filter even? (range 1 10))
;; bad
(filter #(even? %) (range 1 10))
Don’t use function literals if the function body will consist of more than one form.
;; good
(fn [x]
(println x)
(* x 2))
;; bad (you need an explicit do form)
#(do (println %)
(* % 2))
Prefer anonymous functions over complement
, comp
and partial
, as this results
in simpler code most of the time.[8]
;; good
(filter #(not (some-pred? %)) coll)
;; okish
(filter (complement some-pred?) coll)
;; Assuming `(:require [clojure.string :as str])`...
;; good
(map #(str/capitalize (str/trim %)) ["top " " test "])
;; okish
(map (comp str/capitalize str/trim) ["top " " test "])
comp
is quite useful when composing transducer chains, though.
;; good
(def xf
(comp
(filter odd?)
(map inc)
(take 5)))
;; good
(map #(+ 5 %) (range 1 10))
;; okish
(map (partial + 5) (range 1 10))
Prefer the use of the threading macros ->
(thread-first) and ->>
(thread-last) to heavy form nesting.
;; good
(-> [1 2 3]
reverse
(conj 4)
prn)
;; not as good
(prn (conj (reverse [1 2 3])
4))
;; good
(->> (range 1 10)
(filter even?)
(map (partial * 2)))
;; not as good
(map (partial * 2)
(filter even? (range 1 10)))
Parentheses are not required when using the threading macros for functions having no argument specified, so use them only when necessary.
;; good
(-> x fizz :foo first frob)
;; bad; parens add clutter and are not needed
(-> x (fizz) (:foo) (first) (frob))
;; good, parens are necessary with an arg
(-> x
(fizz a b)
:foo
first
(frob x y))
The arguments to the threading macros ->
(thread-first) and ->>
(thread-last) should line up.
;; good
(->> (range)
(filter even?)
(take 5))
;; bad
(->> (range)
(filter even?)
(take 5))
Use :else
as the catch-all test expression in cond
.
;; good
(cond
(neg? n) "negative"
(pos? n) "positive"
:else "zero")
;; bad
(cond
(neg? n) "negative"
(pos? n) "positive"
true "zero")
Prefer condp
instead of cond
when the predicate & expression don’t
change.
;; good
(cond
(= x 10) :ten
(= x 20) :twenty
(= x 30) :thirty
:else :dunno)
;; much better
(condp = x
10 :ten
20 :twenty
30 :thirty
:dunno)
Prefer case
instead of cond
or condp
when test expressions are
compile-time constants.
;; good
(cond
(= x 10) :ten
(= x 20) :twenty
(= x 30) :forty
:else :dunno)
;; better
(condp = x
10 :ten
20 :twenty
30 :forty
:dunno)
;; best
(case x
10 :ten
20 :twenty
30 :forty
:dunno)
Use short forms in cond
and related. If not possible give visual
hints for the pairwise grouping with comments or empty lines.
;; good
(cond
(test1) (action1)
(test2) (action2)
:else (default-action))
;; ok-ish
(cond
;; test case 1
(test1)
(long-function-name-which-requires-a-new-line
(complicated-sub-form
(-> 'which-spans multiple-lines)))
;; test case 2
(test2)
(another-very-long-function-name
(yet-another-sub-form
(-> 'which-spans multiple-lines)))
:else
(the-fall-through-default-case
(which-also-spans 'multiple
'lines)))
Use a set
as a predicate when appropriate.
;; good
(remove #{1} [0 1 2 3 4 5])
;; bad
(remove #(= % 1) [0 1 2 3 4 5])
;; good
(count (filter #{\a \e \i \o \u} "mary had a little lamb"))
;; bad
(count (filter #(or (= % \a)
(= % \e)
(= % \i)
(= % \o)
(= % \u))
"mary had a little lamb"))
Use (inc x)
& (dec x)
instead of (+ x 1)
and (- x 1)
.
Use (pos? x)
, (neg? x)
& (zero? x)
instead of (> x 0)
,
(< x 0)
& (= x 0)
.
Use list*
instead of a series of nested cons
invocations.
;; good
(list* 1 2 3 [4 5])
;; bad
(cons 1 (cons 2 (cons 3 [4 5])))
Use the sugared Java interop forms.
;;; object creation
;; good
(java.util.ArrayList. 100)
;; bad
(new java.util.ArrayList 100)
;;; static method invocation
;; good
(Math/pow 2 10)
;; bad
(. Math pow 2 10)
;;; instance method invocation
;; good
(.substring "hello" 1 3)
;; bad
(. "hello" substring 1 3)
;;; static field access
;; good
Integer/MAX_VALUE
;; bad
(. Integer MAX_VALUE)
;;; instance field access
;; good
(.someField some-object)
;; bad
(. some-object someField)
Use the compact metadata notation for metadata that contains only
slots whose keys are keywords and whose value is boolean true
.
;; good
(def ^:private a 5)
;; bad
(def ^{:private true} a 5)
Denote private parts of your code.
;; good
(defn- private-fun [] ...)
(def ^:private private-var ...)
;; bad
(defn private-fun [] ...) ; not private at all
(defn ^:private private-fun [] ...) ; overly verbose
(def private-var ...) ; not private at all
To access a private var (e.g. for testing), use the @#'some.ns/var
form.
Be careful regarding what exactly you attach metadata to.
;; we attach the metadata to the var referenced by `a`
(def ^:private a {})
(meta a) ;=> nil
(meta #'a) ;=> {:private true}
;; we attach the metadata to the empty hash-map value
(def a ^:private {})
(meta a) ;=> {:private true}
(meta #'a) ;=> nil
It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures.
Avoid the use of lists for generic data storage (unless a list is exactly what you need).
Prefer the use of keywords for hash keys.
;; good
{:name "Bruce" :age 30}
;; bad
{"name" "Bruce" "age" 30}
Prefer the use of the literal collection syntax where applicable. However, when defining sets, only use literal syntax when the values are compile-time constants.
;; good
[1 2 3]
#{1 2 3}
(hash-set (func1) (func2)) ; values determined at runtime
;; bad
(vector 1 2 3)
(hash-set 1 2 3)
#{(func1) (func2)} ; will throw runtime exception if (func1) = (func2)
Avoid accessing collection members by index whenever possible.
Prefer the use of keywords as functions for retrieving values from maps, where applicable.
(def m {:name "Bruce" :age 30})
;; good
(:name m)
;; more verbose than necessary
(get m :name)
;; bad - susceptible to NullPointerException
(m :name)
Leverage the fact that most collections are functions of their elements.
;; good
(filter #{\a \e \o \i \u} "this is a test")
;; bad - too ugly to share
Leverage the fact that keywords can be used as functions of a collection.
((juxt :a :b) {:a "ala" :b "bala"})
Avoid the use of transient collections, except for performance-critical portions of the code.
Avoid the use of Java collections.
Avoid the use of Java arrays, except for interop scenarios and performance-critical code dealing heavily with primitive types.
Don’t use the interop syntax to
construct type and record instances. deftype
and defrecord
automatically create constructor functions. Use those instead of
the interop syntax, as they make it clear that you’re dealing with a
deftype
or a defrecord
. See this
article
for more details.
(defrecord Foo [a b])
(deftype Bar [a b])
;; good
(->Foo 1 2)
(map->Foo {:b 4 :a 3})
(->Bar 1 2)
;; bad
(Foo. 1 2)
(Bar. 1 2)
Note that deftype
doesn’t define the map->Type
constructor. It’s available only for records.
Add custom type/record constructors when needed (e.g. to validate properties on record creation). See this article for more details.
(defrecord Customer [id name phone email])
(defn make-customer
"Creates a new customer record."
[{:keys [name phone email]}]
{:pre [(string? name)
(valid-phone? phone)
(valid-email? email)]}
(->Customer (next-id) name phone email))
Feel free to adopt whatever naming convention or structure you’d like for such custom constructors.
Don’t override the auto-generated type/record constructor functions. People expect them to have a certain behaviour and changing this behaviour violates the principle of least surprise. See this article for more details.
(defrecord Foo [num])
;; good
(defn make-foo
[num]
{:pre [(pos? num)]}
(->Foo num))
;; bad
(defn ->Foo
[num]
{:pre [(pos? num)]}
(Foo. num))
Consider wrapping all I/O calls with the io!
macro to avoid nasty
surprises if you accidentally end up calling such code in a
transaction.
Avoid the use of ref-set
whenever possible.
(def r (ref 0))
;; good
(dosync (alter r + 5))
;; bad
(dosync (ref-set r 5))
Try to keep the size of transactions (the amount of work encapsulated in them) as small as possible.
Avoid having both short- and long-running transactions interacting with the same Ref.
Use send
only for actions that are CPU bound and don’t block on I/O
or other threads.
Use send-off
for actions that might block, sleep, or otherwise tie
up the thread.
Avoid atom updates inside STM transactions.
Try to use swap!
rather than reset!
, where possible.
(def a (atom 0))
;; good
(swap! a + 5)
;; not as good
(reset! a 5)
Prefer math functions from clojure.math
over (Java) interop or rolling your own.
;; good
(clojure.math/pow 2 5)
;; okish
(Math/pow 2 5)
The JDK package java.lang.Math
provides access to many useful math
functions. Prior to version 1.11, Clojure relied on using these via interop, but this had
issues with discoverability, primitive performance, higher order application,
and portability. The new clojure.math
namespace provides wrapper functions for
the methods available in java.lang.Math
for long
and double
overloads with fast
primitive invocation.
Prefer string manipulation functions from clojure.string
over Java interop or rolling your own.
;; good
(clojure.string/upper-case "bruce")
;; bad
(.toUpperCase "bruce")
Note
|
Several new functions were added to clojure.string in Clojure
1.8 (index-of , last-index-of , starts-with? , ends-with? and
includes? ). You should avoid using those if you need to support
older Clojure releases.
|
Reuse existing exception types. Idiomatic Clojure code — when it does
throw an exception — throws an exception of a standard type
(e.g. java.lang.IllegalArgumentException
,
java.lang.UnsupportedOperationException
,
java.lang.IllegalStateException
, java.io.IOException
).
Favor with-open
over finally
.
Don’t write a macro if a function will do.
Create an example of a macro usage first and the macro afterwards.
Break complicated macros into smaller functions whenever possible.
A macro should usually just provide syntactic sugar and the core of the macro should be a plain function. Doing so will improve composability.
Prefer syntax-quoted forms over building lists manually.
In this section we’ll go over some common metadata for namespaces and vars that Clojure development tools can leverage.
The most common way to document when a public API was added to a
library is via the :added
metadata.
(def ^{:added "0.5"} foo
42)
(ns foo.bar
"A very useful ns."
{:added "0.8"})
(defn ^{:added "0.5"} foo
(bar))
Tip
|
If you’re into SemVer, it’s a good idea to omit the patch version.
This means you should use 0.5 instead of 0.5.0 . This applies
for all metadata data that’s version related.
|
The most common way to document when a public API was changed in a
library is via the :changed
metadata. This metadata makes sense only for
vars and you should be using it sparingly, as changing the behavior of
a public API is generally a bad idea.
Still, if you decide to do it, it’s best to make that clear to the API users.
(def ^{:added "0.5"
:changed "0.6"} foo
43)
The most common way to mark deprecated public APIs is via the :deprecated
metadata. Normally you’d use as the value the version in which something
was deprecated in case of versioned software (e.g. a library) or simply
true
in the case of unversioned software (e.g. some web application).
;;; good
;;
;; in case we have a version
(def ^{:deprecated "0.5"} foo
"Use `bar` instead."
42)
(ns foo.bar
"A deprecated ns."
{:deprecated "0.8"})
(defn ^{:deprecated "0.5"} foo
(bar))
;; otherwise
(defn ^:deprecated foo
(bar))
;;; bad
;;
;; using the docstring to signal deprecation
(def foo
"DEPRECATED: Use `bar` instead."
42)
(ns foo.bar
"DEPRECATED: A deprecated ns.")
Often you’d combine :deprecated
with :superseded-by
, as there would be
some newer API that supersedes whatever got deprecated.
Typically for vars you’ll use a non-qualified name if the replacement lives in the same namespace, and a fully-qualified name otherwise.
;; in case we have a version
(def ^{:deprecated "0.5"
:superseded-by "bar"} foo
"Use `bar` instead."
42)
(ns foo.bar
"A deprecated ns."
{:deprecated "0.8"
:superseded-by "foo.baz"})
(defn ^{:deprecated "0.5"
:superseded-by "bar"} foo
(bar))
;; otherwise
(defn ^{:deprecated true
:superseded-by "bar"} foo
(bar))
Tip
|
You can also consider adding :supersedes metadata to the newer APIs, basically the inverse of :superseded-by .
|
From time to time you might want to point out some related vars/namespaces that the users of your library might be interested in.
The most common way to do so would be via the :see-also
metadata, which takes a vector of related items.
When talking about vars - items in the same namespace don’t need to fully qualified.
;; refers to vars in the same ns
(def ^{:see-also ["bar" "baz"]} foo
"A very useful var."
42)
;; refers to vars in some other ns
(defn ^{:see-also ["top.bar" "top.baz"]} foo
(bar))
Note
|
Many Clojure programming tools will also try to extract references to other vars from the docstring, but it’s both
simpler and more explicit to use the :see-also metadata instead.
|
Documentation tools like Codox like cljdoc recognize :no-doc
metadata.
When a var or a namespace has :no-doc
metadata, it indicates to these tools that it should be excluded from generated API docs.
To exclude an entire namespace from API docs:
(ns ^:no-doc my-library.impl
"Internal implementation details")
...
To exclude vars within a documented namespace:
(ns my-library.api)
;; private functions do not get documented
(defn- clearly-private []
...)
;; nor do public functions with :no-doc metadata
(defn ^:no-doc shared-helper []
...)
;; this function will be documented
(defn api-fn1
"I am useful to the public"
[]
...)
Unlike other Lisp dialects, Clojure doesn’t have a standard metadata format to specify the indentation of macros. CIDER proposed a tool-agnostic indentation specification based on metadata in 2015.[9] Here’s a simple example:
;; refers to vars in the same ns
(defmacro with-in-str
"[DOCSTRING]"
{:style/indent 1}
[s & body]
...cut for brevity...)
This instructs the indentation engine that this is a macro with one ordinary parameter and a body after it.
;; without metadata (indented as a regular function)
(dop-iin-str some-string
foo
bar
baz)
;; with metadata (indented as macro with one special param and a body)
(with-in-str some-string
foo
bar
baz)
Unfortunately, as of 2020 there’s still no widespread adoption of :style/indent
and many editors and IDEs would just
hardcode the indentation rules for common macros.
Note
|
This approach to indentation ("semantic indentation") is a contested topic in the Clojure community, due to the need for the additional metadata and tooling support. Despite the long tradition of that approach in the Lisp community in general, some people argue to just stop treating functions and macros differently and simply indent everything with a fixed indentation. This article is one popular presentation of that alternative approach. |
Good code is its own best documentation. As you’re about to add a comment, ask yourself, "How can I improve the code so that this comment isn’t needed?" Improve the code and then document it to make it even clearer.
Endeavor to make your code as self-explanatory as possible. If you fail to achieve this follow the rest of the guidelines in this section.
Write heading comments with at least four semicolons. Those typically serve to outline/separate major section of code, or to describe important ideas. Often you’d have a section comment followed by a bunch of top-level comments.
;;;; Section Comment/Heading
;;; Foo...
;;; Bar...
;;; Baz...
Write top-level comments with three semicolons.
;;; I'm a top-level comment.
;;; I live outside any definition.
(defn foo [])
Note
|
While the classic Lisp tradition dictates the use of ;;; for
top-level comments, you’ll find plenty of Clojure code in the wild
that’s using ;; or even ; .
|
Write comments on a particular fragment of code before that fragment and aligned with it, using two semicolons.
(defn foo [x]
;; I'm a line/code fragment comment.
x)
Note
|
While the classic Lisp tradition dictates the use of ;; for
line comments, you’ll find plenty of Clojure code in the wild
that’s using only ; .
|
Write margin comments with one semicolon.
(defn foo [x]
x ; I'm a line/code fragment comment.
)
Avoid using those in situations that would result in hanging closing parentheses.
Always have at least one space between the semicolon and the text that follows it.
;;;; Frob Grovel
;;; This section of code has some important implications:
;;; 1. Foo.
;;; 2. Bar.
;;; 3. Baz.
(defn fnord [zarquon]
;; If zob, then veeblefitz.
(quux zot
mumble ; Zibblefrotz.
frotz))
Comments longer than a word begin with a capital letter and use punctuation. Separate sentences with one space.
;; This is a good comment.
;; this is a bad comment
Obviously punctuation is not the most important thing about a comment, but a bit of extra effort results in better experience for the readers of our comments.
Avoid superfluous comments.
;; bad
(inc counter) ; increments counter by one
Keep existing comments up-to-date. An outdated comment is worse than no comment at all.
Prefer the use of the #_
reader macro over a regular comment when
you need to comment out a particular form.
;; good
(+ foo #_(bar x) delta)
;; bad
(+ foo
;; (bar x)
delta)
Good code is like a good joke - it needs no explanation.
Avoid writing comments to explain bad code. Refactor the code to make it self-explanatory. ("Do, or do not. There is no try." --Yoda)
Annotations should usually be written on the line immediately above the relevant code.
;; good
(defn some-fun
[]
;; FIXME: Replace baz with the newer bar.
(baz))
;; bad
;; FIXME: Replace baz with the newer bar.
(defn some-fun
[]
(baz))
The annotation keyword is followed by a colon and a space, then a note describing the problem.
;; good
(defn some-fun
[]
;; FIXME: Replace baz with the newer bar.
(baz))
;; bad - no colon after annotation
(defn some-fun
[]
;; FIXME Replace baz with the newer bar.
(baz))
;; bad - no space after colon
(defn some-fun
[]
;; FIXME:Replace baz with the newer bar.
(baz))
If multiple lines are required to describe the problem, subsequent lines should be indented as much as the first one.
;; good
(defn some-fun
[]
;; FIXME: This has crashed occasionally since v1.2.3. It may
;; be related to the BarBazUtil upgrade. (xz 13-1-31)
(baz))
;; bad
(defn some-fun
[]
;; FIXME: This has crashed occasionally since v1.2.3. It may
;; be related to the BarBazUtil upgrade. (xz 13-1-31)
(baz))
Tag the annotation with your initials and a date so its relevance can be easily verified.
(defn some-fun
[]
;; FIXME: This has crashed occasionally since v1.2.3. It may
;; be related to the BarBazUtil upgrade. (xz 13-1-31)
(baz))
In cases where the problem is so obvious that any documentation would be redundant, annotations may be left at the end of the offending line with no note. This usage should be the exception and not the rule.
(defn bar
[]
(sleep 100)) ; OPTIMIZE
Use TODO
to note missing features or functionality that should be
added at a later date.
Use FIXME
to note broken code that needs to be fixed.
Use OPTIMIZE
to note slow or inefficient code that may cause
performance problems.
Use HACK
to note "code smells" where questionable coding practices
were used and should be refactored away.
Use REVIEW
to note anything that should be looked at to confirm it
is working as intended. For example: REVIEW: Are we sure this is how the
client does X currently?
Use other custom annotation keywords if it feels appropriate, but be
sure to document them in your project’s README
or similar.
Docstrings are the primary way to document Clojure code. Many definition forms
(e.g. def
, defn
, defmacro
, ns
)
support docstrings and usually it’s a good idea to make good use of them, regardless
of whether the var in question is something public or private.
If a definition form doesn’t support docstrings directly you can still supply them via
the :doc
metadata attribute.
This section outlines some of the common conventions and best practices for documenting Clojure code.
If a form supports docstrings directly prefer them over using :doc
metadata:
;; good
(defn foo
"This function doesn't do much."
[]
...)
(ns foo.bar.core
"That's an awesome library.")
;; bad
(defn foo
^{:doc "This function doesn't do much."}
[]
...)
(ns ^{:doc "That's an awesome library.")
foo.bar.core)
Let the first line in the docstring be a complete, capitalized sentence which concisely describes the var in question. This makes it easy for tooling (Clojure editors and IDEs) to display a short a summary of the docstring at various places.
;; good
(defn frobnitz
"This function does a frobnitz.
It will do gnorwatz to achieve this, but only under certain
circumstances."
[]
...)
;; bad
(defn frobnitz
"This function does a frobnitz. It will do gnorwatz to
achieve this, but only under certain circumstances."
[]
...)
Important tools such as cljdoc support Markdown in docstrings so leverage it for nicely formatted documentation.
;; good
(defn qzuf-number
"Computes the [Qzuf number](https://wikipedia.org/qzuf) of the `coll`.
Supported options in `opts`:
| key | description |
| --------------|-------------|
| `:finite-uni?`| Assume finite universe; default: `false`
| `:complex?` | If OK to return a [complex number](https://en.wikipedia.org/wiki/Complex_number); default: `false`
| `:timeout` | Throw an exception if the computation doesn't finish within `:timeout` milliseconds; default: `nil`
Example:
```clojure
(when (neg? (qzuf-number [1 2 3] {:finite-uni? true}))
(throw (RuntimeException. \"Error in the Universe!\")))
```"
[coll opts]
...)
Document all positional arguments, and wrap them them with backticks (`) so that editors and IDEs can identify them and potentially provide extra functionality for them.
;; good
(defn watsitz
"Watsitz takes a `frob` and converts it to a znoot.
When the `frob` is negative, the znoot becomes angry."
[frob]
...)
;; bad
(defn watsitz
"Watsitz takes a frob and converts it to a znoot.
When the frob is negative, the znoot becomes angry."
[frob]
...)
Wrap any var references in the docstring with ` so that tooling
can identify them. Wrap them with [[..]]
if you want to link to them.
;; good
(defn wombat
"Acts much like `clojure.core/identity` except when it doesn't.
Takes `x` as an argument and returns that. If it feels like it.
See also [[kangaroo]]."
[x]
...)
;; bad
(defn wombat
"Acts much like clojure.core/identity except when it doesn't.
Takes `x` as an argument and returns that. If it feels like it.
See also kangaroo."
[x]
...)
Docstrings should be composed of well-formed English sentences. Every sentence should start with a capitalized word, be grammatically coherent, and end with appropriate punctuation. Sentences should be separated with a single space.
;; good
(def foo
"All sentences should end with a period (or maybe an exclamation mark).
The sentence should be followed by a space, unless it concludes the docstring.")
;; bad
(def foo
"all sentences should end with a period (or maybe an exclamation mark).
The sentence should be followed by a space, unless it concludes the docstring.")
Indent multi-line docstrings by two spaces.
;; good
(ns my.ns
"It is actually possible to document a ns.
It's a nice place to describe the purpose of the namespace and maybe even
the overall conventions used. Note how _not_ indenting the docstring makes
it easier for tooling to display it correctly.")
;; bad
(ns my.ns
"It is actually possible to document a ns.
It's a nice place to describe the purpose of the namespace and maybe even
the overall conventions used. Note how _not_ indenting the docstring makes
it easier for tooling to display it correctly.")
Neither start nor end your docstrings with any whitespace.
;; good
(def foo
"I'm so awesome."
42)
;; bad
(def silly
" It's just silly to start a docstring with spaces.
Just as silly as it is to end it with a bunch of them. "
42)
When adding a docstring — especially to a function using the above form — take care to correctly place the docstring after the function name, not after the argument vector. The latter is not invalid syntax and won’t cause an error, but includes the string as a form in the function body without attaching it to the var as documentation.
;; good
(defn foo
"docstring"
[x]
(bar x))
;; bad
(defn foo [x]
"docstring"
(bar x))
Note
|
Place docstrings for (defprotocol MyProtocol
"MyProtocol docstring"
(foo [this x y z]
"foo docstring")
(bar [this]
"bar docstring")) |
Store your tests in a separate directory, typically test/yourproject/
(as
opposed to src/yourproject/
). Your build tool is responsible for making
them available in the contexts where they are necessary; most templates
will do this for you automatically.
Name your ns yourproject.something-test
, a file which usually lives in
test/yourproject/something_test.clj
(or .cljc
, cljs
).
When using clojure.test
, define your tests
with deftest
and name them something-test
.
;; good
(deftest something-test ...)
;; bad
(deftest something-tests ...)
(deftest test-something ...)
(deftest something ...)
If you are publishing libraries to be used by others, make sure to
follow the Central Repository
guidelines
for choosing your groupId
and artifactId
. This helps to prevent
name conflicts and facilitates the widest possible use. A good
example is Component - its
coordinates are com.stuartsierra/component
.
Another approach that’s popular in the wild is to use a project (or organization)
name as the groupId
instead of domain name. Examples of such naming would be:
-
cider/cider-nrepl
-
nrepl/nrepl
-
nrepl/drawbridge
-
clj-commons/fs
Avoid unnecessary dependencies. For example, a three-line utility function copied into a project is usually better than a dependency that drags in hundreds of vars you do not plan to use.
Code in a functional way, using mutation only when it makes sense.
Be consistent. In an ideal world, be consistent with these guidelines.
Use common sense.
One problem with style guides is that it’s often hard to remember all the guidelines and to apply them consistently. We’re only humans, after all. Fortunately, there are a bunch of tools that can do most of the work for us.
Tip
|
It’s a great idea run such tools as part of your continuous integration (CI). This ensure that all the code in one project is consistent with the style you’re aiming for. |
There are some lint tools created by the Clojure community that might aid you in your endeavor to write idiomatic Clojure code.
-
kibit is a static code analyzer for Clojure which uses core.logic to search for patterns of code for which there might exist a more idiomatic function or macro.
-
clj-kondo is a linter that detects a wide number of discouraged patterns and suggests improvements, based on this style guide.
While most Clojure editors and IDEs can format the code, according to the layout guidelines outlined here, it’s always handy to have some command-line code formatting tools. There are a couple of options for Clojure that do a great job when it comes to formatting the code as suggested in this guide:
Note
|
When it comes to editors - Emacs’s clojure-mode by default will format the code exactly as outlined in the guide.
Other editors might require some configuration tweaking to produce the same results.
|
This guide was started in 2013 by Bozhidar Batsov, following the success of a similar project he had created in the Ruby community.
Bozhidar was very passionate about both Clojure and good programming style and he wanted to bridge the gap between what was covered by the Clojure library coding guidelines and what the style guides for languages like Java, Python and Ruby would typically cover. Bozhidar still serves as the guide’s primary editor, but there’s an entire editor team supporting the project.
Since the inception of the guide we’ve received a lot of feedback from members of the exceptional Clojure community around the world. Thanks for all the suggestions and the support! Together we can make a resource beneficial to each and every Clojure developer out there.
Many people, books, presentations, articles and other style guides influenced the community Clojure style guide. Here are some of them:
The Clojure style guide is stewarded by an editor team of experienced Clojurists that aims to reduce all the input we get (e.g. feedback and suggestions) to a better reference for everyone.
The guide is still a work in progress - some guidelines are lacking examples, some guidelines don’t have examples that illustrate them clearly enough. Improving such guidelines is a great (and simple way) to help the Clojure community!
In due time these issues will (hopefully) be addressed - just keep them in mind for now.
Nothing written in this guide is set in stone. It’s my desire to work together with everyone interested in Clojure coding style, so that we could ultimately create a resource that will be beneficial to the entire Clojure community.
Feel free to open tickets or send pull requests with improvements. Thanks in advance for your help!
You can also support the style guide (and all my Clojure projects like CIDER, nREPL, orchard, etc) with financial contributions via one of the following platforms:
It’s easy, just follow the contribution guidelines below:
-
Fork bbatsov/clojure-style-guide on GitHub
-
Make your feature addition or bug fix in a feature branch.
-
Include a good description of your changes
-
Push your feature branch to GitHub
-
Send a Pull Request
This guide is written in AsciiDoc and is published as HTML using AsciiDoctor. The HTML version of the guide is hosted on GitHub Pages.
Originally the guide was written in Markdown, but was converted to AsciiDoc in 2019.
This work is licensed under a
Creative Commons Attribution 3.0 Unported License
A community-driven style guide is of little use to a community that doesn’t know about its existence. Tweet about the guide, share it with your friends and colleagues. Every comment, suggestion or opinion we get makes the guide just a little bit better. And we want to have the best possible guide, don’t we?
ns
, ma è estremamente improbabile che tu ne abbia mai bisogno nel corpo di una funzione.