コミュニティによるClojureスタイルガイド
Switch branches/tags
Nothing to show
Clone or download
Pull request Compare This branch is 87 commits ahead, 109 commits behind bbatsov:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
README.md

README.md

このスタイルガイドは、Bozhidar Batsov氏によるbbatsov/clojure-style-guideの日本語訳フォークです。

Creative Commons License本翻訳版のライセンスはクリエイティブ・コモンズ 表示 3.0 非移植 ライセンスとします。原著作者はBozhidar Batsov氏です。原文のライセンスについてもご注意ください。

意訳していて、原文の意味を損なわない程度に言葉を加えたり省略している部分があります。また、訳が間違っている可能性があります。逐次修正は行いますが、原文を優先するようにしてください。用語の翻訳には、プログラミングClojure第2版を参考にしています。翻訳に対する意見や修正は、IssueやPull Requestを作成していただけると助かります。なお、規約自体に対する意見は原文リポジトリのほうにお願いします。


Clojureスタイルガイド

良い手本は大切だ。
-- Officer Alex J. Murphy / RoboCop

このClojureスタイルガイドは、実際のClojureプログラマーが他のClojureプログラマーに保守してもらえるコードを書くための、ベスト・プラクティスを勧めるものだ。 実際の使われ方を反映したスタイルガイドは利用されるが、理想的であっても人々に受け入れられないような規約を持つスタイルガイドは全く利用されない危険がある——たとえそれがどれほど良いものであっても。

ガイドは関連する規約ごとにいくつかのセクションに分かれて構成されている。規約には理論的根拠を付けるようにした(ただし、根拠が明らかな場合は省略している)。

これらの規約は、出し抜けに考えだされたものではない。これらの多くは、私のプロソフトウェアエンジニアとしての幅広い仕事や、Clojureコミュニティメンバーからのフィードバックと意見、そして、"Clojure Programming""The Joy of Clojure"のような高い評価を受けている様々なリソースに基づいている。

このスタイルガイドはまだ作成途中だ。そのため、いくつかのセクションが欠けていたり、不完全であったり、いくつかの規約には例がなかったり、それが明快でなかったりする。これらの問題が解決されるまで、それを念頭に置いてほしい。

また、Clojure開発コミュニティがライブラリのコーディング規約の一覧をまとめていることも覚えておいてほしい。

Pandocを利用することで、このスタイルガイドのPDF版やHTML版を生成することができる。

このスタイルガイドは以下の言語に翻訳されている:

目次

ソースコードのレイアウトと構造

ほとんど全ての人は、自分のスタイル以外のあらゆるスタイルは汚くて読みにくい、と思っている。「自分のスタイル以外の」を削れば、それはおそらく正しい…
-- Jerry Coffin (インデントについて)

  • 各インデントには スペース を使う。タブは使わない。 [リンク]

  • ボディパラメータをもつフォームのボディには2つのスペースを使う。これには全てのdefフォーム、ローカル束縛をもつスペシャルフォームおよびマクロ(例:loop, let, when-let)、そしてwhen, cond, as->, 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 [thing1 "some stuff"
          thing2 "other stuff"]
      {:thing1 thing1
       :thing2 thing2})
    
    ;; 悪い
    (let [thing1 "some stuff"
      thing2 "other stuff"]
      {:thing1 thing1
      :thing2 thing2})
  • 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)))
  • Unixスタイルの改行コードを使用する。(*BSD/Solaris/Linux/OSXユーザはデフォルトで問題ないが、Windowsユーザは特に注意すること。) [リンク]

    • Gitを使っているなら、次の設定を追加して、Windowsの改行コードを防ぐのもいい。
    bash$ git config --global core.autocrlf true
    
  • 開き括弧((, {, [)の前の文字と、閉じ括弧(), }, ])の後の文字は、括弧との間にスペースを設ける。 逆に、開き括弧とそれに続く文字、閉じ括弧と直前の文字の間にはスペースを入れない。 [リンク]

    ;; 良い
    (foo (bar baz) quux)
    
    ;; 悪い
    (foo(bar baz)quux)
    (foo ( bar baz ) quux)

構文糖衣はセミコロンのガンを引き起こす。
-- Alan Perlis

  • シーケンシャルコレクションのリテラルの要素の間にコンマを使わない。 [リンク]

    ;; 良い
    [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)
    )
  • トップレベルのフォームの間には空白行を挟む。 [リンク]

    ;; 良い
    (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)
  • 関数やマクロ定義の中には空白行を入れない。ただし、letcondなどの中においてペアをグループ分けするために入れるのは良い。 [リンク]

  • 1行が80文字を超えないようにする。 [リンク]

  • 行末の空白を避ける。 [リンク]

  • 名前空間ごとにファイルを分ける。 [リンク]

  • 全ての名前空間は、複数のrefer, require, importからなるnsフォームで始める。順序は慣習的にrefer, require, importの順とする。 [リンク]

    (ns examples.ns
      (:refer-clojure :exclude [next replace remove])
      (: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 example.ns)
    
    ;; 悪い
    (ns example)
  • 無駄に長い名前空間を使わない(例えば、5セグメントを超えるような)。 [リンク]

  • 関数は10行を超えないようにする。理想的には、ほとんどの関数は5行より短くしたほうが良い。 [リンク]

  • 3つか4つを超えるパラメータを持つパラメータリストの使用を避ける。 [リンク]

  • 前方参照を避ける。前方参照は時として必要になるが、実際にはそのような機会はまれだ。 [リンク]

構文

  • requirereferのような名前空間を扱う関数の使用を避ける。これらはREPL環境以外では必要ないものだ。 [リンク]

  • 前方参照が必要なとき、前方参照を可能にするにはdeclareを使う。 [リンク]

  • loop/recurよりもmapのように、より高階な関数のほうが好ましい。 [リンク]

  • 関数本体内では、コンディションマップによる入力値、出力値のチェックがより良い。 [リンク]

    ;; 良い
    (defn foo [x]
      {:pre [(pos? x)]}
      (bar x))
    
    ;; 悪い
    (defn foo [x]
      (if (pos? x)
        (bar x)
        (throw (IllegalArgumentException. "x must be a positive number!")))
  • 関数内でvarを定義しない。 [リンク]

    ;; 非常に悪い
    (defn foo []
      (def x 5)
      ...)
  • ローカル束縛によって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)))
  • (if (not ...) ...)の代わりにif-notを使う。 [リンク]

    ;; 良い
    (if-not pred
      (foo))
    
    ;; 悪い
    (if (not pred)
      (foo))
  • (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)))
  • (not (= ...))の代わりにnot=を使う。 [リンク]

    ;; 良い
    (not= foo bar)
    
    ;; 悪い
    (not (= 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)))
  • 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)
  • テスト式がコンパイル時に固定の場合、condcondpの代わりに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

命名規約

プログラミングで本当に難しいのは、キャッシュの無効化と命名の仕方だけだ。
-- Phil Karlton

  • 名前空間は次の2つの名づけ方が好ましい。 [リンク]

    • project.module
    • organization.project.module
  • 複数単語からなる名前空間セグメントには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![リンク]

  • 変換のための関数名にはtoではなく->を用いる。 [リンク]

    ;; 良い
    (defn f->c ...)
    
    ;; あまり良くない
    (defn f-to-c ...)
  • 再束縛を想定しているものには*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!"))
  • predcollのような慣用名には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個用意した方が良い。
-- Alan J. Perlis

  • 汎用的なデータ置き場としてリストを使うことを避ける(リストが本当に必要な場合を除く)。 [リンク]

  • マップのキーにはキーワードを用いたほうが良い。 [リンク]

    ;; 良い
    {: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")
    
    ;; 悪い - 汚すぎて書けない
  • キーワードはコレクションの関数として使えることを活用する。 [リンク]

    ((juxt :a :b) {:a "ala" :b "bala"})
  • パフォーマンス問題がクリティカルとなる部分を除いて、一時的(transient)コレクションの使用を避ける。 [リンク]

  • Javaのコレクションの使用を避ける。 [リンク]

  • Java呼び出しや、プリミティブ型を多用するパフォーマンスクリティカルなコードを除いて、Javaの配列の使用を避ける。 [リンク]

状態

ref

  • トランザクションの中で思いがけず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のほうが好ましい。 [リンク]

マクロ

  • その処理が関数でできるならマクロを書かない。 [リンク]

  • まずマクロの使用例を作成し、その後でマクロを作る。 [リンク]

  • 可能なら、複雑なマクロはより小さい機能に分割する。 [リンク]

  • マクロは通常、構文糖衣を提供するものであるべきで、そのコアは単純な機能であるべきだ。そうすることでより構造化されるだろう。 [リンク]

  • 自分でリストを組み立てるよりも、構文クオートを使用するほうが好ましい。 [リンク]

コメント

良いコードとは、それ自体が最良のドキュメントになっているものだ。コメントを付けようとしたとき、自分の胸に聞いてみるといい、「どうやってコードを改良して、このコメントを不要にできるだろうか?」ってね。より美しくするために、コードを改良してからドキュメント化するんだ。
-- Steve McConnell

  • 出来る限り、コードを見れば何をしているのか分かるように努める。 [リンク]

  • ヘッダーコメントには最低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))
  • 2単語以上のコメントは大文字で始め、句読点を用いる。各文は1つのスペースで分ける。 [リンク]

  • 無意味なコメントを避ける。 [リンク]

    ;; 悪い
    (inc counter) ; increments counter by one
  • コメントは常に更新していなければならない。古いコメントは、コメントがないことよりも害悪だ。 [リンク]

  • 特定のフォームをコメントアウトする必要があるときは、通常のコメントではなく#_リーダマクロを用いたほうが良い。 [リンク]

    ;; 良い
    (+ foo #_(bar x) delta)
    
    ;; 悪い
    (+ foo
       ;; (bar x)
       delta)

良いコードというのは面白いジョークのようなものだ。説明する必要がない。
-- Russ Olsen

  • 悪いコードを説明するためにコメントを書くことを避ける。コードをリファクタリングして、コメントが不要なようにするべきだ。(「やるか、やらないだ。やってみるではない」--Yoda) [リンク]

コメントアノテーション

  • アノテーションは通常、当該コードの直前に書かれるべきだ。 [リンク]

  • アノテーションキーワードの後にはコロンとスペースを入れ、その後で詳細を書く。 [リンク]

  • 詳細が複数行に渡る場合、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 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
  cricumstances."
  []
  ...)

;; 悪い
(defn frobnitz
  "This function does a frobnitz. It will do gnorwatz to
  achieve this, but only under certain cricumstances."
  []
  ...)
  • 全ての引数をドキュメント化し、それらをバッククォート(`)で囲む。そうすることで、エディタや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."
  [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."
  [x]
  ...)
  • ドキュメント文字列は正しい英語の文で構成されるべきだ。全ての文は大文字で始まり、適切な句読点で終わる。また、各々の文の間には1つのスペースをはさむ。 [リンク]
;; 良い
(def foo
  "All sentences should end with a period (or maybe an exclamation mark).
  And the period should be followed by a space, unless it's the last sentence.")

;; 悪い
(def foo
  "all sentences should end with a period (or maybe an exclamation mark).
  And the period should be followed by a space, unless it's the last sentence")
  • 複数行に渡るドキュメント文字列は、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 doc string 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 doc string 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 doc string 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))

実際のコードでは

  • 関数型的にコードを書き、そのほうが適切なときのみミュータブルにする。 [リンク]

  • 一貫させる。理想的には、このガイドの通りにする。 [リンク]

  • 常識的に考える。 [リンク]

ツール

慣用的なClojureコードを書くのを助けてくれるツールがClojureコミュニティによって作られている。

  • Slamhoundは既存のコードから適切なns定義を自動的に生成してくれる。

  • kibitはClojure向けの静的コード解析ツールだ。より慣用的な関数やマクロの探索にはcore.logicを用いている。

テスト

  • テストコードは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のガイドラインにしたがってgroupIdartifactIdを選ぶ。これにより名前の衝突が避けられ、幅広い利用が促進される。良い例としてComponentがあげられる。 [リンク]

  • 不必要な依存を避ける。たとえば、何百もの使う予定のないvarを含んだライブラリに依存するよりも、3行のユーティリティ関数をプロジェクトにコピーしてしまうほうが良い。 [リンク]

  • コアの機能とインテグレーション部分は別々のアーティファクトにする。そうすれば、ユーザはあなたのライブラリを無関係なツール依存に制限されることなく利用できる。たとえば、Componentはコア機能を提供し、reloadedはLeiningenとのインテグレーションを提供している。 [リンク]

広めてください

コミュニティドリブンのスタイルガイドは、その存在を知らないコミュニティではあまり役に立ちません。どうか、このガイドについてツイートをして、あなたの友達や同僚と共有してください。頂いたあらゆるコメントや提案、意見がほんの少しずつ、このガイドを形作っていくのです。みんなで最高のスタイルガイドを作りましょう。

Cheers,
Bozhidar