このスタイルガイドは、 Bozhidar Batsov 氏による bbatsov/clojure-style-guide の日本語翻訳版です。
本翻訳版のライセンスは クリエイティブ・コモンズ 表示 3.0 非移植 ライセンス とします。原著作者はBozhidar Batsov氏です。原文のライセンスについてもご注意ください。
意訳していて、原文の意味を損なわない程度に言葉を加えたり省略している部分があります。また、訳が間違っている可能性があります。逐次修正は行いますが、原文を優先するようにしてください。用語の翻訳には、 プログラミングClojure第2版 を参考にしています。翻訳に対する意見や修正は、IssueやPull Requestを作成していただけると助かります。なお、規約自体に対する意見は 原文リポジトリ にお願いします。
良い手本は大切だ。
Tip
|
このスタイルガイドのナビゲーション付きの見やすいバージョンが https://totakke.github.io/clojure-style-guide/ にあります。 |
このClojureスタイルガイドは、実際のClojureプログラマーが他のClojureプログラマーに保守してもらえるコードを書くための、ベストプラクティスを勧めるものだ。 実際の使われ方を反映したスタイルガイドは利用されるが、理想的であっても人々に受け入れられないような規約を持つスタイルガイドは全く利用されない危険がある — たとえそれがどれほど優れたものであったとしても。
ガイドは関連する規約ごとにいくつかのセクションに分かれて構成されている。規約には理論的根拠を付けるようにした(ただし、根拠が明らかな場合は省略している)。
これらの規約は、出し抜けに考えだされたものではない。これらの多くは、私のプロソフトウェアエンジニアとしての幅広い仕事や、Clojureコミュニティメンバーからのフィードバックと意見、そして、 "Clojure Programming" や "The Joy of Clojure" のような高い評価を受けている様々なリソースに基づいている。
Note
|
このスタイルガイドはまだ作成途中だ。そのため、いくつかのセクションが欠けていたり、不完全であったり、いくつかの規約には例がなかったり、それが明快でなかったりする。やがてこれらの問題は解決されていくだろうが — 今はそれを念頭に置いてほしい。 |
また、Clojure開発コミュニティが ライブラリのコーディング規約 の一覧をまとめていることも覚えておいてほしい。
AsciiDoctor PDF を利用することで、このスタイルガイドのPDF版を生成することができます。また、 AsciiDoctor を 使う ことで、HTML版を生成できます。
# README.pdfの生成
asciidoctor-pdf -a allow-uri-read README.adoc
# README.htmlの生成
asciidoctor
Tip
|
生成されたドキュメント中でシンタックスハイライトをしたい場合は、 gem install rouge |
このスタイルガイドは以下の言語に翻訳されている:
ほとんど全ての人は、自分のスタイル以外のあらゆるスタイルは汚くて読みにくい、と思っている。「自分のスタイル以外の」を削れば、それはおそらく正しい…
可能なら、1行が80文字を超えないようにする。
多くの人が、1行につき最大80文字なのは単に古い時代の名残であり、現代ではほとんど意味がないと感じている。実際、現代のディスプレイならば1行に容易に200文字以上を表示できる。しかし依然として、コードの行を短く保つことにはいくつかの利点が存在する。
何よりもまず、数多くの研究から、人間は垂直方向により早く読み進めることができ、長い行は文章を読みにくくするということがわかっている。すでに述べたように、このスタイルガイドの方針の1つは、人間が読むことを前提としたコードの最適化だ。
さらに、エディタのウィンドウ幅が制限されることで複数のファイルを横並びに開いておくことができ、2つのバージョンを隣に表示するコードレビューツールを使う際に便利だ。
多くのツールのデフォルトの折返し機能は、コードの視覚的な構造を壊してしまうため、理解することがより難しくなる。たとえツールが行の最後に折り返しのマーカーを付けてくれるのだとしても、ウィンドウ幅80文字のエディタで折り返しを避けるために文字数制限が選択される。いくつかのWebベースのツールでは、動的な折返し機能を提供していないものもある。
長い行を強く好むチームもいる。1つのチームが主にメンテナンスするコードであれば、制限を100文字以内や120文字以内に増やしても良い。それでも、120文字を超える長さにすることは止めてほしい。
インデントには スペース を使う。タブは使わない。
ボディパラメータをもつフォームのボディには2つのスペースを使う。これには全ての def
フォーム、ローカル束縛をもつスペシャルフォームおよびマクロ(例: loop
, let
, when-let
)、そして when
, cond
, as->
, cond->
, case
, with-*
などの多くのマクロが含まれる。
;; 良い
(when something
(something-else))
(with-out-str
(println "Hello, ")
(println "world!"))
;; 悪い - 4つのスペース
(when something
(something-else))
;; 悪い - 1つのスペース
(with-out-str
(println "Hello, ")
(println "world!"))
複数行にわたる関数(マクロ)の引数は左揃えにする。
;; 良い
(filter even?
(range 1 10))
;; 悪い
(filter even?
(range 1 10))
関数(マクロ)名と同じ行に引数をもたない関数(マクロ)では、インデントには1つのスペースを用いる。
;; 良い
(filter
even?
(range 1 10))
(or
ala
bala
portokala)
;; 悪い - 2つのスペースによるインデント
(filter
even?
(range 1 10))
(or
ala
bala
portokala)
let
(および let
系) の束縛を左揃えにする。
;; 良い
(let [thing1 "some stuff"
thing2 "other stuff"]
(foo thing1 thing2))
;; 悪い
(let [thing1 "some stuff"
thing2 "other stuff"]
(foo thing1 thing2))
マップのキーを左揃えにする。
;; 良い
{:thing1 thing1
:thing2 thing2}
;; 悪い
{:thing1 thing1
:thing2 thing2}
;; 悪い
{:thing1 thing1
:thing2 thing2}
Unixスタイルの改行コードを使用する。 [1]
Tip
|
Gitを使っているなら、次の設定を追加して、Windowsの改行コードを防ぐのもいい。 $ git config --global core.autocrlf true |
開き括弧((
, {
, [
)の前の文字と、閉じ括弧()
, }
, ]
)の後の文字は、括弧との間にスペースを設ける。
逆に、開き括弧とそれに続く文字、閉じ括弧と直前の文字の間にはスペースを入れない。
;; 良い
(foo (bar baz) quux)
;; 悪い
(foo(bar baz)quux)
(foo ( bar baz ) quux)
構文糖衣はセミコロンのガンを引き起こす。
シーケンシャルコレクションのリテラルの要素の間にコンマを使わない。
;; 良い
[1 2 3]
(1 2 3)
;; 悪い
[1, 2, 3]
(1, 2, 3)
コンマや改行を使い、マップリテラルの可読性を向上させることを検討する。
;; 良い
{:name "Bruce Wayne" :alter-ego "Batman"}
;; 良い、より読みやすい
{:name "Bruce Wayne"
:alter-ego "Batman"}
;; 良い、よりコンパクト
{:name "Bruce Wayne", :alter-ego "Batman"}
後ろ側に連続する括弧は、別々の行にせず、同じ行に含める。
;; 良い。同じ行になっている。
(when something
(something-else))
;; 悪い。別の行になっている。
(when something
(something-else)
)
トップレベルのフォームの間には1行の空白行を挟む。
;; 良い
(def x ...)
(defn foo ...)
;; 悪い
(def x ...)
(defn foo ...)
;; 悪い
(def x ...)
(defn foo ...)
例外として、関連する def
はまとめてしまっても良い。
;; 良い
(def min-rows 10)
(def max-rows 20)
(def min-cols 15)
(def max-cols 30)
関数やマクロ定義の中には空白行を入れない。ただし、 let
や cond
等において、ペアをグループ分けするために入れるのは良い。
行末の空白を避ける。
1つの名前空間には1つのファイルを用い、1つのファイルには1つの名前空間を用いる。
;; 良い
(ns foo.bar)
;; 悪い
(ns foo.bar)
(ns baz.qux)
;; 悪い
(in-ns quux.quuz)
(in-ns quuz.corge)
;; 悪い
(ns foo.bar) もしくは (in-ns foo.bar) を複数のファイル内で用いる
無駄に長い名前空間を使わない(例えば、5セグメントを超えるような)。
全ての名前空間は、複数の refer
, require
, import
からなる ns
フォームで始める。順序は慣習的に refer
, require
, import
の順とする。
(ns examples.ns
(:refer-clojure :exclude [next replace remove])
(:require [clojure.string :as s :refer [blank?]])
(:import java.util.Date))
複数の依存を記述する場合、新しい行から書き始め、1つごとに改行しても良い。そうすることでソートが容易になり、読みやすくなる。また、依存の変更によるdiffを減らすことができる。
;; より良い
(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]))
;; 良い
(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]))
;; 悪い
(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]))
ns
フォームでは :require :refer :all
よりも :require :refer
、それよりも :require :as
が好ましい。また :use
よりも :require
が好ましい。今後新しいコードでは :use
を非推奨とするか検討すべきだ。
;; 良い
(ns examples.ns
(:require [clojure.zip :as zip]))
;; 良い
(ns examples.ns
(:require [clojure.zip :refer [lefts rights]]))
;; 正当な理由があれば使ってもよい
(ns examples.ns
(:require [clojure.zip :refer :all]))
;; 悪い
(ns examples.ns
(:use clojure.zip))
ns
フォームではrequireとimportをソートする。特にrequire/importする名前空間が非常に多い場合には、ソートすることで可読性が向上し、重複を避けられるようになる。
;; 良い
(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]))
;; 悪い
(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]))
defn
において、ドキュメント文字列を持たない場合は、関数名と引数ベクタの間の改行を省略しても良い。
;; 良い
(defn foo
[x]
(bar x))
;; 良い
(defn foo [x]
(bar x))
;; 悪い
(defn foo
[x] (bar x))
マルチメソッドの dispatch-val
は関数名と同じ行に置く。
;; 良い
(defmethod foo :bar [x] (baz x))
(defmethod foo :bar
[x]
(baz x))
;; 悪い
(defmethod foo
:bar
[x]
(baz x))
(defmethod foo
:bar [x]
(baz x))
関数本体が短い場合、引数ベクタと関数本体の間の改行は省略しても良い。
;; 良い
(defn foo [x]
(bar x))
;; 関数本体が短い場合は良い
(defn foo [x] (bar x))
;; マルチアリティ関数には良い
(defn foo
([x] (bar x))
([x y]
(if (predicate? x)
(bar x)
(baz x))))
;; 悪い
(defn foo
[x] (if (predicate? x)
(bar x)
(baz x)))
関数定義における各アリティのフォームのインデントは、そのパラメータと左揃えにする。
;; 良い
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
;; 悪い - 過剰なインデント
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
関数のアリティは、引数が最も少ないものから多いものの順に並べる。マルチアリティ関数の通例として、K個の引数を持つものが関数の振る舞いを定義していて、N個(< K)の引数を持つアリティはK引数のアリティの部分適用、N個(> K)の引数を持つアリティは可変長引数であるK引数のアリティの畳み込み、という場合がある。
;; 良い - n番目のアリティを見つけやすい
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
;; ok - 他のアリティは2引数のアリティの適用
(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)))
;; 悪い - 明確な理由のない順序
(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)))
関数はLOC (lines of code)が10行を超えないようにする。理想的には、ほとんどの関数はLOCが5行より短いほうが良い。
3つか4つを超えるパラメータを持つパラメータリストの使用を避ける。
関数本体内では、コンディションマップによる入力値、出力値のチェックがより良い。
;; 良い
(defn foo [x]
{:pre [(pos? x)]}
(bar x))
;; 悪い
(defn foo [x]
(if (pos? x)
(bar x)
(throw (IllegalArgumentException. "x must be a positive number!")))
require
や refer
のような名前空間を扱う関数の使用を避ける。これらはREPL環境以外では必要ないものだ。
前方参照を避ける。前方参照は時として必要になるが、実際にはそのような機会はまれだ。
前方参照が必要なとき、前方参照を可能にするには declare
を使う。
loop/recur
よりも map
のように、より高階な関数のほうが好ましい。
ローカル束縛によって clojure.core
の名前を隠さない。
;; 悪い - 関数内ではclojure.core/mapを完全修飾しなければならなくなる
(defn foo [map]
...)
varの値を変更するには、 def
の代わりに alter-var-root
を使う。
;; 良い
(def thing 1) ; thingの値は1
; thingを用いた何らかの処理
(alter-var-root #'thing (constantly nil)) ; thingの値はnil
;; 悪い
(def thing 1)
; thingを用いた何らかの処理
(def thing nil)
; thingの値はnil
シーケンスが空かどうかをチェックするには seq
を使う(このテクニックはしばしば nil punning と呼ばれる)。
;; 良い
(defn print-seq [s]
(when (seq s)
(prn (first s))
(recur (rest s))))
;; 悪い
(defn print-seq [s]
(when-not (empty? s)
(prn (first s))
(recur (rest s))))
シーケンスをベクタに変換する必要があるときは、 into
よりも vec
を用いたほうが良い。
;; 良い
(vec some-seq)
;; 悪い
(into [] some-seq)
(if ... (do ...))
の代わりに when
を使う。
;; 良い
(when pred
(foo)
(bar))
;; 悪い
(if pred
(do
(foo)
(bar)))
let
+ if
の代わりに if-let
を使う。
;; 良い
(if-let [result (foo x)]
(something-with result)
(something-else))
;; 悪い
(let [result (foo x)]
(if result
(something-with result)
(something-else)))
let
+ when
の代わりに when-let
を使う。
;; 良い
(when-let [result (foo x)]
(do-something-with result)
(do-something-more-with result))
;; 悪い
(let [result (foo x)]
(when result
(do-something-with result)
(do-something-more-with result)))
(when (not ...) ...)
の代わりに when-not
を使う。
;; 良い
(when-not pred
(foo)
(bar))
;; 悪い
(when (not pred)
(foo)
(bar))
(if-not … (do …))
の代わりに when-not
を使う。
;; 良い
(when-not pred
(foo)
(bar))
;; 悪い
(if-not pred
(do
(foo)
(bar)))
(print (format …))
の代わりに printf
を使う。
;; 良い
(printf "Hello, %s!\n" name)
;; ok
(println (format "Hello, %s!" name))
比較を行うときは、Clojure関数の <
や >
などは可変長引数を許していることを覚えておこう。
;; 良い
(< 5 x 10)
;; 悪い
(and (> x 5) (< x 10))
ただ1つのパラメータを持つ関数リテラルでは、 %1
よりも %
のほうが好ましい。
;; 良い
#(Math/round %)
;; 悪い
#(Math/round %1)
複数のパラメータを持つ関数リテラルでは、 %
よりも %1
のほうが好ましい。
;; 良い
#(Math/pow %1 %2)
;; 悪い
#(Math/pow % %2)
必要ないなら無名関数でラップしない。
;; 良い
(filter even? (range 1 10))
;; 悪い
(filter #(even? %) (range 1 10))
関数本体が2つ以上のフォームを含む場合は、関数リテラルを使用しない。
;; 良い
(fn [x]
(println x)
(* x 2))
;; 悪い (doフォームを明示的に使わなければならない)
#(do (println %)
(* % 2))
無名関数よりも complement
を用いたほうが良い。
;; 良い
(filter (complement some-pred?) coll)
;; 悪い
(filter #(not (some-pred? %)) coll)
この規約は、反対の述語が別の関数としてある場合は無視するべきだ。(例: even?
と odd?
)
関数合成するには、無名関数よりも comp
が好ましい。
;; `(:require [clojure.string :as str])` を仮定して...
;; 良い
(map #(str/capitalize (str/trim %)) ["top " " test "])
;; より良い
(map (comp str/capitalize str/trim) ["top " " test "])
カリー化するには、無名関数よりも partial
が好ましい。
;; 良い
(map #(+ 5 %) (range 1 10))
;; (きっと) より良い
(map (partial + 5) (range 1 10))
深いネストよりもスレッディングマクロ ->
(thread-first)と ->>
(thread-last)の使用が好ましい。
;; 良い
(-> [1 2 3]
reverse
(conj 4)
prn)
;; あまり良くない
(prn (conj (reverse [1 2 3])
4))
;; 良い
(->> (range 1 10)
(filter even?)
(map (partial * 2)))
;; あまり良くない
(map (partial * 2)
(filter even? (range 1 10)))
スレッディングマクロ ->
(thread-first)と ->>
(thread-last) の引数は揃える。
;; 良い
(->> (range)
(filter even?)
(take 5))
;; 悪い
(->> (range)
(filter even?)
(take 5))
cond
で残り全ての条件をキャッチするときは :else
を使う。
;; 良い
(cond
(neg? n) "negative"
(pos? n) "positive"
:else "zero")
;; 悪い
(cond
(neg? n) "negative"
(pos? n) "positive"
true "zero")
述語と式が変わらない場合、 cond
よりも condp
のほうが良い。
;; 良い
(cond
(= x 10) :ten
(= x 20) :twenty
(= x 30) :thirty
:else :dunno)
;; より良い
(condp = x
10 :ten
20 :twenty
30 :thirty
:dunno)
テスト式がコンパイル時に固定の場合、 cond
や condp
の代わりに case
を使うのが良い。
;; 良い
(cond
(= x 10) :ten
(= x 20) :twenty
(= x 30) :forty
:else :dunno)
;; より良い
(condp = x
10 :ten
20 :twenty
30 :forty
:dunno)
;; 最も良い
(case x
10 :ten
20 :twenty
30 :forty
:dunno)
cond
などの中では短いフォームを用いる。それが無理なら、コメントや空白行を使用して、ペアグループを見えやすくする。
;; 良い
(cond
(test1) (action1)
(test2) (action2)
:else (default-action))
;; まあ良い
(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)))
set
を述語として使うことができる。
;; 良い
(remove #{1} [0 1 2 3 4 5])
;; 悪い
(remove #(= % 1) [0 1 2 3 4 5])
;; 良い
(count (filter #{\a \e \i \o \u} "mary had a little lamb"))
;; 悪い
(count (filter #(or (= % \a)
(= % \e)
(= % \i)
(= % \o)
(= % \u))
"mary had a little lamb"))
(+ x 1)
や (- x 1)
の代わりに (inc x)
や (dec x)
を使う。
(> x 0)
, (< x 0)
, (= x 0)
の代わりに (pos? x)
, (neg? x)
, (zero? x)
を使う。
ネストされた cons
を呼び出す代わりに list*
を使う。
;; 良い
(list* 1 2 3 [4 5])
;; 悪い
(cons 1 (cons 2 (cons 3 [4 5])))
糖衣されたJava呼び出しフォームを用いる。
;;; オブジェクト生成
;; 良い
(java.util.ArrayList. 100)
;; 悪い
(new java.util.ArrayList 100)
;;; 静的メソッドの呼び出し
;; 良い
(Math/pow 2 10)
;; 悪い
(. Math pow 2 10)
;;; インスタンスメソッドの呼び出し
;; 良い
(.substring "hello" 1 3)
;; 悪い
(. "hello" substring 1 3)
;;; 静的フィールドへのアクセス
;; 良い
Integer/MAX_VALUE
;; 悪い
(. Integer MAX_VALUE)
;;; インスタンスフィールドへのアクセス
;; 良い
(.someField some-object)
;; 悪い
(. some-object someField)
キーがキーワード、値がブール値 true
のスロットしか持たないメタデータには、簡易メタデータ表記を使う。
;; 良い
(def ^:private a 5)
;; 悪い
(def ^{:private true} a 5)
コード中のプライベート部分には印を付ける。
;; 良い
(defn- private-fun [] ...)
(def ^:private private-var ...)
;; 悪い
(defn private-fun [] ...) ; 全くプライベートでない
(defn ^:private private-fun [] ...) ; 冗長な記述だ
(def private-var ...) ; 全くプライベートでない
(例えばテストのために)プライベートなvarにアクセスするには、 @#'some.ns/var
フォームを使う。
メタデータを何に付加するかについては、よく注意したほうが良い。
;; `a` で参照されるvarにメタデータを付加している
(def ^:private a {})
(meta a) ;=> nil
(meta #'a) ;=> {:private true}
;; 空のハッシュマップ値にメタデータを付加している
(def a ^:private {})
(meta a) ;=> {:private true}
(meta #'a) ;=> nil
プログラミングで本当に難しいのは、キャッシュの無効化と命名の仕方だけだ。
複数単語からなる名前空間セグメントには lisp-case
を使う(例: bruce.project-euler
)
関数名や変数名には lisp-case
を使う。
;; 良い
(def some-var ...)
(defn some-fun ...)
;; 悪い
(def someVar ...)
(defn somefun ...)
(def some_fun ...)
プロトコル、レコード、構造体、型には CamelCase
を用いる。(HTTP, RFC, XMLのような頭字語は大文字を保持する。)
述語(ブール値を返す関数)の名前はクエスチョンマーク(?)で終わるべきだ。(例: even?
)
;; 良い
(defn palindrome? ...)
;; 悪い
(defn palindrome-p ...) ; Common Lispスタイル
(defn is-palindrome ...) ; Javaスタイル
STMトランザクションの中で安全でない関数・マクロの名前はエクスクラメーションマーク(!)で終わるべきだ。(例: reset!
)
再束縛を想定しているものには earmuffs
を使う(つまりdynamicなものだ)。
;; 良い
(def ^:dynamic *a* 10)
;; 悪い
(def ^:dynamic a 10)
定数のために特別な表記をしない。特定のものを除いて、全ては定数である。
直後のコードで使用されない分配束縛や引数名には _
を使う。
;; 良い
(let [[a b _ c] [1 2 3 4]]
(println a b c))
(dotimes [_ 3]
(println "Hello!"))
;; 悪い
(let [[a b c d] [1 2 3 4]]
(println a b d))
(dotimes [i 3]
(println "Hello!"))
コードの理解を助けるためならば、使用しない引数や分配束縛のマップに明示的に名前を付けても良い。この場合、その変数が実際には使われないことを示すため、先頭にアンダースコアを付加する。
;; 良い
(defn myfun1 [context _]
(assoc context :foo "bar"))
(defn myfun2 [context {:keys [id]}]
(assoc context :user-id id))
;; より良い
(defn myfun1 [context _user]
(assoc context :foo "bar"))
(defn myfun2 [context {:keys [id] :as _user}]
(assoc context :user-id id))
pred
や coll
のような慣用名には clojure.core
の例が参考になる。
-
関数内では、
-
f
,g
,h
- 関数入力 -
n
- サイズを示す整数値 -
index
,i
- 整数のインデックス -
x
,y
- 数値 -
xs
- シーケンス -
m
- マップ -
s
- 文字列入力 -
re
- 正規表現 -
coll
- コレクション -
pred
- 述語クロージャ -
& more
- 可変長引数 -
xf
- xform、transducer
-
-
マクロ内では、
-
expr
- 式 -
body
- マクロ本体 -
binding
- マクロの束縛ベクタ
-
10種のデータ構造を処理できる機能を10個用意するより、1種のデータ構造を処理できる機能を100個用意した方が良い。
汎用的なデータ置き場としてリストを使うことを避ける(リストが本当に必要な場合を除く)。
マップのキーにはキーワードを用いたほうが良い。
;; 良い
{:name "Bruce" :age 30}
;; 悪い
{"name" "Bruce" "age" 30}
可能なら、コレクションのリテラル構文を用いたほうが良い。ただしセットを定義するときは、コンパイル時に定数である値についてのみリテラル構文を使用する。
;; 良い
[1 2 3]
#{1 2 3}
(hash-set (func1) (func2)) ; 実行時に決定する値
;; 悪い
(vector 1 2 3)
(hash-set 1 2 3)
#{(func1) (func2)} ; もし (func1) = (func2) だったら実行時例外が投げられる
可能なら、コレクションの要素にインデックスでアクセスすることを避ける。
可能なら、マップから値を取得する関数としてキーワードを用いるのが良い。
(def m {:name "Bruce" :age 30})
;; 良い
(:name m)
;; 必要以上の記述だ
(get m :name)
;; 悪い - NullPointerExceptionが発生する可能性が高い
(m :name)
ほとんどのコレクションはその要素の関数であることを活用する。
;; 良い
(filter #{\a \e \o \i \u} "this is a test")
;; 悪い - 汚すぎて書けない
パフォーマンス問題がクリティカルとなる部分を除いて、一時的(transient)コレクションの使用を避ける。
Javaのコレクションの使用を避ける。
Java呼び出しや、プリミティブ型を多用するパフォーマンスクリティカルなコードを除いて、Javaの配列の使用を避ける。
タイプやレコードのインスタンスを作るのにJava呼び出しを用いない。 deftype
や defrecord
が自動的に生成したコンストラクタ関数を使用する。そうすることで、 deftype
や defrecord
を利用していることが明確になる。詳しくは この記事 を参照する。
(defrecord Foo [a b])
(deftype Bar [a b])
;; 良い
(->Foo 1 2)
(map->Foo {:b 4 :a 3})
(->Bar 1 2)
;; 悪い
(Foo. 1 2)
(Bar. 1 2)
deftype
は map->Type
というコンストラクタを作らないことに注意する。レコードでのみ使用できる。
必要なら独自のタイプ/レコードのコンストラクタを追加する(例:レコード生成時にプロパティのバリデーションを行うため)。詳しくは この記事 を参照する。
(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))
このようなカスタムコンストラクタには、好きな命名規則や構造を用いて構わない。
自動生成されたタイプ/レコードのコンストラクタ関数を上書きしない。それらのコンストラクタ関数は特定の振る舞いをすると想定されているため、この挙動を変更することは驚き最小の原則に反する。詳しくは この記事 を参照する。
(defrecord Foo [num])
;; 良い
(defn make-foo
[num]
{:pre [(pos? num)]}
(->Foo num))
;; 悪い
(defn ->Foo
[num]
{:pre [(pos? num)]}
(Foo. num))
トランザクションの中で思いがけずI/Oコールを呼んでしまったときの問題を回避するため、全てのI/Oコールを io!
マクロでラップすることを考える。
出来る限り ref-set
は使用しない。
(def r (ref 0))
;; 良い
(dosync (alter r + 5))
;; 悪い
(dosync (ref-set r 5))
トランザクションのサイズ(包んでいる処理の量)を出来る限り小さく保つようにする。
同一のrefとやり取りを行う、短期のトランザクションと長期のトランザクションを両方持つことを避ける。
それがCPUバウンドで、かつI/Oや他スレッドをブロックしない処理のときだけ send
を用いる。
それがスレッドをブロック、スリープさせたり、そうでなくても停滞させるかもしれない処理には send-off
を用いる。
STMトランザクションの中でアトムを更新することを避ける。
可能なら、 reset!
よりも swap!
を使うようにする。
(def a (atom 0))
;; 良い
(swap! a + 5)
;; あまり良くない
(reset! a 5)
文字列処理は、Java呼び出しや独自実装よりも、 clojure.string
の関数を使うほうが好ましい。
;; 良い
(clojure.string/upper-case "bruce")
;; 悪い
(.toUpperCase "bruce")
既存の例外型を再利用する。慣用的なClojureコードでは、例外を投げるとき、基本的な例外型を用いている(例: java.lang.IllegalArgumentException
, java.lang.UnsupportedOperationException
, java.lang.IllegalStateException
, java.io.IOException
)。
finally
よりも with-open
のほうが好ましい。
その処理が関数でできるならマクロを書かない。
まずマクロの使用例を作成し、その後でマクロを作る。
可能なら、複雑なマクロはより小さい機能に分割する。
マクロは通常、構文糖衣を提供するものであるべきで、そのコアは単純な機能であるべきだ。そうすることでより構造化されるだろう。
自分でリストを組み立てるよりも、構文クオートを使用するほうが好ましい。
良いコードとは、それ自体が最良のドキュメントになっているものだ。コメントを付けようとしたとき、自分の胸に聞いてみるといい、「どうやってコードを改良して、このコメントを不要にできるだろうか?」ってね。より美しくするために、コードを改良してからドキュメント化するんだ。
出来る限り、コードを見れば何をしているのかわかるように努める。
ヘッダーコメントには最低4つのセミコロンを用いる。
トップレベルのコメントには3つのセミコロンを用いる。
特定のコード部分の直前にコメントを書くときは、コード部分とインデントを揃え、2つのセミコロンを用いる。
行末コメントには1つのセミコロンを用いる。
セミコロンとそれに続くテキストの間には、常に少なくとも1つのスペースを入れる。
;;;; 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))
コメントは常に更新していなければならない。古いコメントは、コメントがないことよりも害悪だ。
特定のフォームをコメントアウトする必要があるときは、通常のコメントではなく #_
リーダマクロを用いたほうが良い。
;; 良い
(+ foo #_(bar x) delta)
;; 悪い
(+ foo
;; (bar x)
delta)
良いコードというのは面白いジョークのようなものだ。説明する必要がない。
悪いコードを説明するためにコメントを書くことを避ける。コードをリファクタリングして、コメントが不要なようにするべきだ。(「やるか、やらぬかだ。やってみるなどない」 — ヨーダ)
アノテーションは通常、当該コードの直前に書かれるべきだ。
;; 良い
(defn some-fun
[]
;; FIXME: Replace baz with the newer bar.
(baz))
;; 悪い
;; FIXME: Replace baz with the newer bar.
(defn some-fun
[]
(baz))
アノテーションキーワードの後にはコロンとスペースを入れ、その後で詳細を書く。
;; 良い
(defn some-fun
[]
;; FIXME: Replace baz with the newer bar.
(baz))
;; 悪い - アノテーションの後にコロンがない
(defn some-fun
[]
;; FIXME Replace baz with the newer bar.
(baz))
;; 悪い - コロンの後にスペースがない
(defn some-fun
[]
;; FIXME:Replace baz with the newer bar.
(baz))
詳細が複数行にわたる場合、2行目以降は1行目に合わせてインデントするべきだ。
;; 良い
(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))
;; 悪い
(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))
アノテーションには記述者のイニシャルと日付を入れる。そうすればその妥当性を容易に示せる。
(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))
ドキュメント化が不必要なほどに問題が明らかな箇所では、当該行の末尾に説明なしでアノテーションを付けても良い。この使用法は例外的であるべきで、規約ではない。
(defn bar
[]
(sleep 100)) ; OPTIMIZE
後日追加されるべき機能には TODO
を使う。
コードが壊れていて、修正の必要がある箇所には FIXME
を使う。
パフォーマンス問題の原因となりうる、遅かったり非効率なコードには OPTIMIZE
を使う。
疑わしいコーディングの仕方がされており、リファクタリングすべき「コード・スメル」には HACK
を用いる。
意図するように動くかどうか確認すべき箇所には REVIEW
を使う。例: REVIEW: Are we sure this is how the client does X currently?
そのほうが適切だと思えば、その他独自のアノテーションキーワードを用いる。ただし、プロジェクトの README
などに忘れずにドキュメント化しておく。
ドキュメント文字列は、Clojureコードにドキュメントを付加するための最も基本的な方法だ。多くの定義フォーム(例: def
, defn
, defmacro
, ns
)はドキュメント文字列をサポートしており、そのvarがパブリックであるかプライベートであるかに関わらず、基本的にはドキュメント文字列を活用するのが良い。
定義フォームがドキュメント文字列を直接的にサポートしていない場合でも、メタデータの :doc
属性にドキュメントを記述することができる。
このセクションでは、Clojureコードのドキュメンテーションを行う上で、いくつかの慣用的方法とベストプラクティスを紹介する。
フォームがドキュメント文字列を直接的にサポートしている場合、 :doc
メタデータよりもそれを用いるほうが良い。
;; 良い
(defn foo
"This function doesn't do much."
[]
...)
(ns foo.bar.core
"That's an awesome library.")
;; 悪い
(defn foo
^{:doc "This function doesn't do much."}
[]
...)
(ns ^{:doc "That's an awesome library.")
foo.bar.core)
ドキュメント文字列の最初の行は、大文字で始まる完結した文で、そのvarを簡潔に説明するものにする。これによって、ツール(ClojureエディタやIDE)が様々な場面でドキュメント文字列の要約を簡単に表示できるようになる。
;; 良い
(defn frobnitz
"This function does a frobnitz.
It will do gnorwatz to achieve this, but only under certain
circumstances."
[]
...)
;; 悪い
(defn frobnitz
"This function does a frobnitz. It will do gnorwatz to
achieve this, but only under certain circumstances."
[]
...)
cljdoc などの有用なツールは、ドキュメント文字列内におけるMarkdownをサポートしているため、フォーマットをきれいに整えるのに利用すると良い。
;; 良い
(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]
...)
全ての引数をドキュメント化し、それらをバッククォート(`)で囲む。そうすることで、エディタやIDEが引数を識別できるようになり、より高度な機能を提供できる可能性がある。
;; 良い
(defn watsitz
"Watsitz takes a `frob` and converts it to a znoot.
When the `frob` is negative, the znoot becomes angry."
[frob]
...)
;; 悪い
(defn watsitz
"Watsitz takes a frob and converts it to a znoot.
When the frob is negative, the znoot becomes angry."
[frob]
...)
ドキュメント文字列でのvarの参照を ` で囲み、ツールが識別できるようにする。リンクを張りたい場合は [[..]]
で囲う。
;; 良い
(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]
...)
;; 悪い
(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]
...)
ドキュメント文字列は正しい英語の文で構成されるべきだ。全ての文は大文字で始まり、文法的に一貫していて、適切な句読点で終わる。また、各々の文の間には1つのスペースをはさむ。
;; 良い
(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.")
;; 悪い
(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.")
複数行にわたるドキュメント文字列は、2つのスペースでインデントする。
;; 良い
(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.")
;; 悪い
(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.")
ドキュメント文字列の最初と最後には余計な空白を入れない。
;; 良い
(def foo
"I'm so awesome."
42)
;; 悪い
(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)
ドキュメント文字列を付加するときは、上記フォームを用いる関数は特に、ドキュメント文字列は引数ベクタの後ろではなく、関数名の後ろに置くことに注意する。前者は文法的には間違っておらずエラーにもならないが、そのvarにドキュメントは付加されず、関数本体に1つのフォームとしてその文字列が含まれることになる。
;; 良い
(defn foo
"docstring"
[x]
(bar x))
;; 悪い
(defn foo [x]
"docstring"
(bar x))
テストコードは test/yourproject/
などの( src/yourproject/
とは)別ディレクトリに配置する。ビルドツールは必要に応じてこれらのディレクトリを用意してくれる。ほとんどのテンプレートは自動的にこれらのディレクトリを生成する。
名前空間は yourproject.something-test
のように命名し、ファイルは test/yourproject/something_test.clj
(あるいは .cljc
, cljs
)に普通は作成する。
clojure.test
を用いるときは、 deftest
でテストを定義し、 something-test
と名付ける。
;; 良い
(deftest something-test ...)
;; 悪い
(deftest something-tests ...)
(deftest test-something ...)
(deftest something ...)
他の人が使えるようにライブラリを公開する場合、 Central Repositoryのガイドライン にしたがって groupId
と artifactId
を選ぶ。これにより名前の衝突が避けられ、幅広い利用が促進される。 Component が良い例で、識別子は com.stuartsierra/component
だ。
異なるアプローチとして、groupId
にドメインではなくプロジェクト名(あるいは組織名)がよく用いられる。
例:
-
cider/cider-nrepl
-
nrepl/nrepl
-
nrepl/drawbridge
-
clj-commons/fs
不必要な依存を避ける。たとえば、何百もの使う予定のないvarを含んだライブラリに依存するよりも、3行のユーティリティ関数をプロジェクトにコピーしてしまうほうが良い。
慣用的なClojureコードを書くのを助けてくれるLintツールが、Clojureコミュニティによって作られている。
-
Slamhound は既存のコードから適切な
ns
定義を自動的に生成してくれる。 -
kibit はClojure向けの静的コード解析ツールだ。より慣用的な関数やマクロの探索には core.logic を用いている。
-
clj-kondo は、このスタイルガイドに基づいて、多くの非推奨パターンを発見し、改善案を提案してくれるLintツールだ。
このスタイルガイドはまだまだ書き換えることができます。Clojureのコーディングスタイルに関心のある皆さんと一緒に取り組み、最終的にはClojureコミュニティ全体にとって有益な情報源を作り上げたいと思っています。
遠慮なく改良案の Pull Requestを作って ください。どうかよろしくお願いします。
コミュニティドリブンのスタイルガイドは、その存在を知らないコミュニティではあまり役に立ちません。どうか、このガイドについてツイートをして、あなたの友達や同僚と共有してください。頂いたあらゆるコメントや提案、意見がほんの少しずつ、このガイドを形作っていくのです。みんなで最高のスタイルガイドを作りましょう。