Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1931 lines (1533 sloc) 69.4 KB

The Clojure Style Guide

Примечание переводчика

Это перевод оригинального руководства, составленного Божидаром Бацовым (Bozhidar Batsov). У меня возникли некоторые сложности с переводом некоторых терминов, поэтому я сразу привожу их список:

  • forms - формы (например, special forms - специальные формы)
  • body - тело (как в выражении "тело функции").
  • binding - связывание, связка (например, local binding - локальное связывание, two-way binding - двустороннее связывание, let-bindings - связки let)
  • keywords - ключи (:whatever).
  • hashmap, map - хеш ({:key value})
  • docstring - док. строка
  • argument vector - параметры, список параметров (например, в определении (defn f [x y] ...) argument vector - это [x y]). Важно! в контексте определения функции я использую термин параметр, а в контексте вызова - аргумент.
  • multi-arity - многоарность, многоарная (функция)
  • top-level - верхний уровень, верхнеуровневый
  • forward reference - раннее определение
  • transient - изменяемые. см. документацию
  • будет дополняться

Также я иногда буду оставлять в скобках оригинал выражения/термина.

Введение

Role models are important.
-- Officer Alex J. Murphy / RoboCop

Данное руководство предлагает наилучшие рекомендации по оформлению Clojure кода для того, чтобы Clojure-программисты могли писать код, который смогут поддерживать другие Clojure-программисты. Руководство по оформлению кода, отражающее практики, используемые в реальном мире, будет использоваться людьми, а руководство, придерживающееся отвергнутых людьми, которым он должен помочь, рискует не быть используемым ими вовсе (не важно, насколько он при этом хорош).

Руководство поделено на несколько разделов, объединяющих связанные между собой правила. Я постарался добавить пояснения к правилам (если же они опущены, значит, я предположил, что они и так очевидны и в пояснениях не нуждаются).

Я не взял все эти идеи с потолка. Они основаны по большей части на моем личном обширном опыте разработки, комментариях и предложениях членов Clojure сообщества и некоторых авторитетных источников информации по Clojure, например, "Clojure Programming" и "The Joy of Clojure".

Руководство все еще разрабатывается: некоторые разделы отсутствуют, некоторые все еще не завершены, каким-то правилам не хватает хороших примеров. Когда-нибудь эти недостатки будут устранены, а пока просто держите их в уме.

Обратите внимание, что разработчики Clojure также поддерживают список стандартов по разработке библиотек

Вы можете сгенерировать PDF или HTML копию данного руководства с помощью Pandoc.

Переводы доступны на следующих языках:

Содержание

Организация кода

Почти каждый уверен, что все стили кода, кроме его собственного, - уродливые и нечитаемые. Если убрать "кроме его собственного", то, возможно, они правы...
-- Jerry Coffin (об отступах)

  • Используйте пробелы для отступов. Никаких табов. [link]

  • Используйте 2 пробела для тела формы. Формы с телом включают def-подобные конструкции, специальные формы и макросы, вводящие локальное связывание (например, loop, let, when-let) и многие макросы вроде when, cond, as->, cond->, case, with-* и т.д. [link]

    ;; хорошо
    (when something
      (something-else))
    
    (with-out-str
      (println "Hello, ")
      (println "world!"))
    
    ;; плохо - четыре пробела
    (when something
        (something-else))
    
    ;; плохо - один пробел
    (with-out-str
     (println "Hello, ")
     (println "world!"))
  • Выравнивайте по вертикали аргументы функции (макроса), если они расположены на нескольких строках. [link]

    ;; хорошо
    (filter even?
            (range 1 10))
    
    ;; плохо
    (filter even?
      (range 1 10))
  • Используйте отступ в один пробел для аргументов функции (макроса), если на одной строке с именем функции их (аргументов) нет. [link]

    ;; хорошо
    (filter
     even?
     (range 1 10))
    
    (or
     ala
     bala
     portokala)
    
    ;; плохо - два пробела
    (filter
      even?
      (range 1 10))
    
    (or
      ala
      bala
      portokala)
  • Выравнивайте по вертикали let-связки (переменные) и ключи для хешей [link]

    ;; хорошо
    (let [thing1 "some stuff"
          thing2 "other stuff"]
      {:thing1 thing1
       :thing2 thing2})
    
    ;; плохо
    (let [thing1 "some stuff"
      thing2 "other stuff"]
      {:thing1 thing1
      :thing2 thing2})
  • Можете не использовать перенос строки между именем функции и ее параметрами в defn, если отсутствует док. строка. [link]

    ;; хорошо
    (defn foo
      [x]
      (bar x))
    
    ;; хорошо
    (defn foo [x]
      (bar x))
    
    ;; плохо
    (defn foo
      [x] (bar x))
  • Размещайте dispatch-val (см. defmethod) на той же строке, что и имя функции. [link]

    ;; хорошо
    (defmethod foo :bar [x] (baz x))
    
    (defmethod foo :bar
      [x]
      (baz x))
    
    ;; плохо
    (defmethod foo
      :bar
      [x]
      (baz x))
    
    (defmethod foo
      :bar [x]
      (baz x))
  • Можете опустить перенос строки между списком параметров и коротким телом функции [link]

    ;; хорошо
    (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)))
  • Для каждой арности функции расставляйте отступы в соответствии с параметрами. [link]

    ;; хорошо
    (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 параметров полностью определяют поведение функции, меньшая арность использует K-арность и большие (>K) арности предоставляют возможность передать функции переменное количество аргументов (например, с помощью &). [link]

    ;; хорошо - легко найти n-ую арность
    (defn foo
      "I have two arities."
      ([x]
       (foo x 1))
      ([x y]
       (+ x y)))
    
    ;; сойдет - остальные арности используют двуарный вариант
    (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 нужно проявлять осторожность). [link]

    • Если вы используете Git, то можете добавить следующую настройку в конфигурацию своего проекта, чтобы уберечь себя от Windows-переносов:
    bash$ git config --global core.autocrlf true
    
  • Если какой-либо текст предшествует открытой скобке ((, {, [) или следует за закрытой скобкой, то отделяйте его от скобки пробелом. И наоборот, не добавляйте пробелов между открытой скобки и последующим текстов и между текстом и последующей скобкой [link]

    ;; хорошо
    (foo (bar baz) quux)
    
    ;; плохо
    (foo(bar baz)quux)
    (foo ( bar baz ) quux)

Синтаксический сахар вызывает рак точек с запятой (semicolon cancer). -- Alan Perlis

  • Не используйте запятые между элементами последовательностей (списки, векторы) [link]

    ;; хорошо
    [1 2 3]
    (1 2 3)
    
    ;; плохо
    [1, 2, 3]
    (1, 2, 3)
  • Подумайте об улучшении читаемости хеш-литералов с помощью использования запятых и переносов. [link]

    ;; хорошо
    {:name "Bruce Wayne" :alter-ego "Batman"}
    
    ;; хорошо и в некотором роде более читаемо
    {:name "Bruce Wayne"
     :alter-ego "Batman"}
    
    ;; хорошо и при этом более компактно (чем предыдущий пример)
    {:name "Bruce Wayne", :alter-ego "Batman"}
  • Оставляйте конечные скобки на одной строке [link]

    ;; хорошо - на одной строке
    (when something
      (something-else))
    
    ;; плохо - на разных строках
    (when something
      (something-else)
    )
  • Оставляйте пустые строки между верхнеуровневыми формами [link]

    ;; хорошо
    (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. [link]

  • Старайтесь избегать написания строк длиннее 80 символов [link]

  • Избегайте лишних пробелов в конце (строк) [link]

  • Для каждого пространства имен создавайте отдельный файл [link]

  • Определяйте каждое пространство имен с помощью ns с использованием :refer, :require и :import. Желательно, в таком порядке. [link]

    (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 :as вместо :require :refer и, тем более, :require :refer :all. Также предпочитайте :require вместо :use. Последнее должно считаться устаревшим в новом коде. [link]

    ;; хорошо
    (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))
  • Старайтесь не использовать односоставные пространства имен. [link]

    ;; хорошо
    (ns example.ns)
    
    ;; плохо
    (ns example)
  • Избегайте не использовать слишком длинные названия пространства имен (т.е. состоящие более чем из 5 частей). [link]

  • Избегайте функций, состоящих из более 10 строк. В идеале, большинство функций должны быть менее 5 строк длиной. [link]

  • Избегайте создания функций с более чем тремя-четырьмя позиционными параметрами. (прим. переводчика: [x y & more] - это всего три параметра, хотя аргументов можно передать гораздо больше) [link]

  • Не используйте ранние определения (forward references). Иногда они необходимы, то это довольно редкий случай на практике. [link]

Syntax

  • Избегайте использования функций, влияющих на пространство имен, например, require и refer. Они совершенно не нужны, если вы не находитесь в REPL. [link]

  • Используйте declare, когда вам необходимы ранние определения. [link]

  • Старайтесь использовать функции высшего порядка (такие как map) вместо loop/recur. [link]

  • Старайтесь использовать пред- и пост- условия функции вместо проверок внутри тела функции [link]

    ;; хорошо
    (defn foo [x]
      {:pre [(pos? x)]}
      (bar x))
    
    ;; плохо
    (defn foo [x]
      (if (pos? x)
        (bar x)
        (throw (IllegalArgumentException. "x must be a positive number!")))
  • Don't define vars inside functions. [link]

    ;; очень плохо
    (defn foo []
      (def x 5)
      ...)
  • Не перекрывайте имена из clojure.core локальными именами. [link]

    ;; плохо - если вам понадобится функция map, вам придется использовать ее
    ;; с помощью clojure.core/map
    (defn foo [map]
      ...)
  • Используйте alter-var-root вместо def для изменения значения переменной. [link]

    ;; хорошо
    (def thing 1)
    ; какие-то действия над thing
    (alter-var-root #'thing (constantly nil)) ; значение thing теперь nil
    
    ;; плохо
    (def thing 1)
    ; какие-то действия над thing
    (def thing nil) ; значение thing теперь nil
  • Используйте seq, когда нужно проверить, что последовательность пуста (этот прием иногда называется nil punning). [link]

    ;; хорошо
    (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))))
  • Предпочитайте vec вместо into, когда вам нужно превратить последовательность в вектор. [link]

    ;; хорошо
    (vec some-seq)
    
    ;; плохо
    (into [] some-seq)
  • Используйте when вместо (if ... (do ...). [link]

    ;; хорошо
    (when pred
      (foo)
      (bar))
    
    ;; плохо
    (if pred
      (do
        (foo)
        (bar)))
  • Используйте if-let вместо let + if. [link]

    ;; хорошо
    (if-let [result (foo x)]
      (something-with result)
      (something-else))
    
    ;; плохо
    (let [result (foo x)]
      (if result
        (something-with result)
        (something-else)))
  • Используйте when-let вместо let + when. [link]

    ;; хорошо
    (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 ...) ...). [link]

    ;; хорошо
    (if-not pred
      (foo))
    
    ;; плохо
    (if (not pred)
      (foo))
  • Используйте when-not вместо (when (not ...) ...). [link]

    ;; хорошо
    (when-not pred
      (foo)
      (bar))
    
    ;; плохо
    (when (not pred)
      (foo)
      (bar))
  • Используйте when-not вместо (if-not ... (do ...). [link]

    ;; хорошо
    (when-not pred
      (foo)
      (bar))
    
    ;; плохо
    (if-not pred
      (do
        (foo)
        (bar)))
  • Используйте not= Вместо (not (= ...)). [link]

    ;; хорошо
    (not= foo bar)
    
    ;; плохо
    (not (= foo bar))
  • Используйте printf вместо (print (format ...)). [link]

    ;; хорошо
    (printf "Hello, %s!\n" name)
    
    ;; сойдет
    (println (format "Hello, %s!" name))
  • При сравнениях не забывайте, что функции <, > и т.д. принимают переменное количество аргументов. [link]

    ;; хорошо
    (< 5 x 10)
    
    ;; плохо
    (and (> x 5) (< x 10))
  • Используйте % вместо %1 в литералах функции с одним параметром [link]

    ;; хорошо
    #(Math/round %)
    
    ;; плохо
    #(Math/round %1)
  • Используйте %1 вместо % в литералах функций с несколькими параметрами. [link]

    ;; хорошо
    #(Math/pow %1 %2)
    
    ;; плохо
    #(Math/pow % %2)
  • Не оборачивайте функции в анонимные функции, если это не требуется. [link]

    ;; хорошо
    (filter even? (range 1 10))
    
    ;; плохо
    (filter #(even? %) (range 1 10))
  • Не используйте литералы функций, если тело функции будет состоять из более чем одной формы. [link]

    ;; хорошо
    (fn [x]
      (println x)
      (* x 2))
    
    ;; плохо (you need an explicit do form)
    #(do (println %)
         (* % 2))
  • Предпочитайте complement вместо использования анонимной функции. [link]

    ;; хорошо
    (filter (complement some-pred?) coll)
    
    ;; плохо
    (filter #(not (some-pred? %)) coll)

    Это правило, очевидно, должно быть проигнорированно, если дополнение функции уже существует в форме другой функции (например, even? и odd?).

  • Используйте comp, когда это упрощает код. [link]

    ;; Предполагается `(:require [clojure.string :as str])`...
    
    ;; хорошо
    (map #(str/capitalize (str/trim %)) ["top " " test "])
    
    ;; лучше
    (map (comp str/capitalize str/trim) ["top " " test "])
  • Используйте partial, когда это упрощает код. [link]

    ;; хорошо
    (map #(+ 5 %) (range 1 10))
    
    ;; (кажется) лучше
    (map (partial + 5) (range 1 10))
  • Используйте threading макрос -> (thread-first) and ->> (thread-last) в глубоко вложенных формах. [[link](#- `` - hreading-macros)]

    ;; хорошо
    (-> [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)))
  • Используйте :else, когда нужно перехватить значение, не проходящее остальные условия. [link]

    ;; хорошо
    (cond
      (neg? n) "negative"
      (pos? n) "positive"
      :else "zero")
    
    ;; плохо
    (cond
      (neg? n) "negative"
      (pos? n) "positive"
      true "zero")
  • Предпочитайте condp вместо cond, когда предикатное выражение не меняется. [link]

    ;; хорошо
    (cond
      (= x 10) :ten
      (= x 20) :twenty
      (= x 30) :thirty
      :else :dunno)
    
    ;; намного лучше
    (condp = x
      10 :ten
      20 :twenty
      30 :thirty
      :dunno)
  • Предпочитайте case вместо cond и condp, когда условные выражения - константы, известные еще на этапе компиляции. [link]

    ;; хорошо
    (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 и подобных. Если это невозможно, то визуально разделите формы на пары с помощью комментариев и пустых строк. [link]

    ;; хорошо
    (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 как предикат, если возможно. [link]

    ;; хорошо
    (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"))
  • Используйте (inc x) и (dec x) вместо (+ x 1) и (- x 1). [link]

  • Используйте (pos? x), (neg? x) и (zero? x) вместо (> x 0), (< x 0) и (= x 0). [link]

  • Используйте list* вместо последовательных вызовов cons. [link]

    ;; хорошо
    (list* 1 2 3 [4 5])
    
    ;; плохо
    (cons 1 (cons 2 (cons 3 [4 5])))
  • Используйте синт. сахар для работы с Java. [link]

    ;;; создание объекта
    ;; хорошо
    (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. [link]

    ;; хорошо
    (def ^:private a 5)
    
    ;; плохо
    (def ^{:private true} a 5)
  • Помечайте приватные части кода. [link]

    ;; хорошо
    (defn- private-fun [] ...)
    
    (def ^:private private-var ...)
    
    ;; плохо
    (defn private-fun [] ...) ; не приватная
    
    (defn ^:private private-fun [] ...) ; слишком многословно
    
    (def private-var ...) ; не приватная
  • Для доступа к приватной переменной (например, в рамках тестирования) используйте форму @#'some.ns/var. [link]

  • Будьте внимательны с тем, к чему конкретно вы привязываете метаданные. [link]

    ;; мы привязываем метаданные к переменной `a`
    (def ^:private a {})
    (meta a) ;=> nil
    (meta #'a) ;=> {:private true}
    
    ;; мы привязываем метаданные к хешу
    (def a ^:private {})
    (meta a) ;=> {:private true}
    (meta #'a) ;=> nil

Именование

Единственными сложностями программирования являются проверка актуальности кеша и именование. -- Phil Karlton

  • Когда именуете пространство имен, предпочитайте следующие шаблоны: [link]

    • проект.модуль
    • компания.проект.модуль
  • Используйте lisp-case в отдельных частях пространства имен (например, bruce.project-euler) [link]

  • Используйте lisp-case для имен функций и переменных. [link]

    ;; хорошо
    (def some-var ...)
    (defn some-fun ...)
    
    ;; плохо
    (def someVar ...)
    (defn somefun ...)
    (def some_fun ...)
  • Используйте CamelCase для протоколов, записей, структур и типов. Сохраняйте акронимы вроде HTTP, RFC, XML в верхнем регистре. [link]

  • Имена предикатов (функций, возвращающих булевое значение) должны оканчиваться вопросительным знаком (например, even?). [link]

    ;; хорошо
    (defn palindrome? ...)
    
    ;; плохо
    (defn palindrome-p ...) ; стиль Common Lisp
    (defn is-palindrome ...) ; стиль Java
  • Имена функций/макросов, которые небезопасны в транзакциях STM должны оканчиваться восклицательным знаком (например, reset!) [link]

  • Используйте -> вместо to в названиях приводящих (к типу) функций. [link]

    ;; хорошо
    (defn f->c ...)
    
    ;; не так хорошо
    (defn f-to-c ...)
  • Используйте *звездочки* (в ориг. earmuffs) для того, что планируется переопределить. [link]

    ;; хорошо
    (def ^:dynamic *a* 10)
    
    ;; плохо
    (def ^:dynamic a 10)
  • Не используйте никакой особенной записи для констант - предполагается, что все есть константы, если не указано обратное. [link]

  • Используйте _ при именовании параметров (в т.ч. при деструктурировании), которые не будут использоваться в коде. [link]

    ;; хорошо
    (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!"))
  • Следуйте примеру clojure.core и используйте типичные имена вроде pred и coll. [link]

    • в функциях:
      • f, g, h - для функций
      • n - целочисленный параметр (обычно размер)
      • index, i - целочисленный индекс
      • x, y - числа
      • xs - последовательность
      • m - хеш
      • s - строка
      • re - регулярное выражение
      • coll - коллекция
      • pred - предикат
      • & more - при переменном числе аргументов
      • xf - xform, transducer
    • в макросах:
      • expr - выражение
      • body - тело макроса
      • binding - связки макроса (список параметров)

Коллекции

Лучше иметь 100 функций, работающих с одной структурой данных нежели 10 функций, работающих с 10 структурами
-- Alan J. Perlis

  • Избегайте использования списков для хранения данных (разве что список - это именно то, что вам нужно) [link]

  • Старайтесь использовать ключевые слова (ключи, keywords) в качестве ключей хеша. [link]

    ;; хорошо
    {:name "Bruce" :age 30}
    
    ;; плохо
    {"name" "Bruce" "age" 30}
  • Старайтесь использовать литералы коллекций, если возможно. Однако для множеств используйте литералы только в том случае, когда значениями являются константы, известные на этапе компиляции. [link]

    ;; хорошо
    [1 2 3]
    #{1 2 3}
    (hash-set (func1) (func2)) ; значения определяются во время выполнения
    
    ;; плохо
    (vector 1 2 3)
    (hash-set 1 2 3)
    #{(func1) (func2)} ; во время выполнения выбросит исключение, если (func1) = (func2)
  • Старайтесь не обращаться к членам коллекций по индексу, если возможно. [link]

  • Старайтесь использовать ключи как функции для доступа к значениям хешей, если возможно. [link]

    (def m {:name "Bruce" :age 30})
    
    ;; хорошо
    (:name m)
    
    ;; слишком многословно
    (get m :name)
    
    ;; плохо - возможна ошибка NullPointerException
    (m :name)
  • Используйте факт, что большинство коллекций являются функциями. [link]

    ;; хорошо
    (filter #{\a \e \o \i \u} "this is a test")
    
    ;; плохо - слишком страшно, чтобы показывать
  • Используйте факт, что ключи могут быть использованы как функции над коллекцией. [link]

    ((juxt :a :b) {:a "ala" :b "bala"})
  • Не используйте изменяемые (transient) коллекции, если только это не критическая часть кода, требующая максимального быстродействия. [link]

  • Избегайте использования коллекций из Java. [link]

  • Избегайте использования Java массивов. Разве что для случаев интеграции и критического кода, много работающего с примитивами. [link]

Изменение состояния

Транзакционные ссылки (Refs)

  • Подумайте над оборачиванием всех IO вызовов макросом io!, чтобы избежать гадких сюрпризов, если вы случайно запустите этот код внутри транзакции [link]

  • Избегайте использования ref-set, если возможно. [link]

    (def r (ref 0))
    
    ;; хорошо
    (dosync (alter r + 5))
    
    ;; плохо
    (dosync (ref-set r 5))
  • Старайтесь держать размер транзакций (количество работы, инкапсулированное в них) настолько маленьким насколько возможно. [link]

  • Избегайте наличия коротких и длительных транзакций, работающих с одной и той же ссылкой. [link]

Агенты

  • Используйте send только для действий, привязанных к процессору и не блокирующих IO или другие потоки. [link]

  • Используйте send-off для действий, которые могут заблокировать, "усыпить" (sleep) или как-то иначе подвесить поток. [link]

Атомы

  • Избегайте обновления атомов внутри STM транзакции. [link]

  • Старайтесь использовать swap! вместо reset!, где возможно. [link]

    (def a (atom 0))
    
    ;; хорошо
    (swap! a + 5)
    
    ;; Не так хорошо
    (reset! a 5)

Строки

  • Старайтесь использовать для работы со строками функции из clojure.string вместо интеграции с Java или введения собственных. [link]

    ;; хорошо
    (clojure.string/upper-case "bruce")
    
    ;; плохо
    (.toUpperCase "bruce")

Исключения

  • Используйте существующие исключения. Хороший Clojure код выбрасывает исключения стандартных типов (если выбрасывает). Например, java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException, java.lang.IllegalStateException, java.io.IOException. [link]

  • Используйте with-open вместо finally. [link]

Макросы

  • Не пишите макросы, если хороша и функция. [link]

  • Перед макросом напишите пример его использования. [link]

  • Разбивайте сложные макросы на меньшие функции. [link]

  • Макрос обычно должен быть всего-лишь синтаксическим сахаром над обычной функций. В таком случае вам будет легче комбинировать функции. [link]

  • Старайтесь использовать syntax-quote (`) вместо того, чтобы создавать список "вручную". [link]

Комментарии

Хороший код является лучшей документацией для самого себя. Если вы собираетесь добавить комментарий, то спросите себя: "Как я могу улучшить свой код, чтобы этот комментарий был не нужен?". Улучшите код и задокументируйте его, чтобы он стал еще более чистым
-- Steve McConnell

  • Старайтесь писать самодокументируемый код. [link]

  • Пишите заглавный комментарий минимум с четырьмя точками с запятой (;;;;). [link]

  • Пишите комментарии верхнего уровня с тремя точками с запятой (;;;). [link]

  • Пишите комментарии к фрагменту кода перед ним и выравнивайте их, используя две точки с запятой (;;). [link]

  • Пишите боковые комментарии, начиная с одной точки с запятой (;). [link]

  • Всегда оставляйте как минимум один пробел после точек с запятой. [link]

    ;;;; 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))
  • Комментарии, состоящие из более чем одного слова, начинаются с большой буквы и используют пунктуацию. Отделяйте предложения одним пробелом. [link]

  • Не оставляйте очевидных комментариев [link]

    ;; плохо
    (inc counter) ; увеличивает counter на единицу
  • Комментарии должны быть актуальными. Устаревший комментарий хуже, чем отсутствие комментария вообще. [link]

  • Используйте макрос #_ вместо обычного комментирования, когда вам нужно закомментировать форму. [link]

    ;; хорошо
    (+ foo #_(bar x) delta)
    
    ;; плохо
    (+ foo
       ;; (bar x)
       delta)

Хороший код, как и хорошая шутка, не требует объяснения.
-- Russ Olsen

  • Старайтесь не писать коммментарии, чтобы объяснить плохой код. Измените код, чтобы он стал понятным ("Делай или не делай, не надо пытаться." --Йода). [link]

Аннотации (Comment Annotations)

  • Аннотации обычно должны размещаться непосредственно над связанным кодом. [link]

  • Между ключевым словом аннотации и описанием должны стоять двоеточие и пробел. Например: TODO: add something cool. [link]

  • Если описание занимает несколько строк, то каждая строка должна быть выровнена в соответствии с первой (т.е. после ключевого слова). [link]

  • Помечайте аннотацию своими инициалами и датой, чтобы актуальность было легче отследить. [link]

    (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))
  • В случае, когда проблема настолько очевидна, что ее описание будет лишним, аннотации могут быть оставлены в конце проблемной строки в виде ключевого слова без пояснения. Такое использование должно быть исключением, но не правилом. [link]

    (defn bar
      []
      (sleep 100)) ; OPTIMIZE
  • Используйте TODO, чтобы отметить недостающие фичи или функциональность, которую нужно будет добавить позже. [link]

  • Используйте FIXME, чтобы отметить сломанный код, который нужно исправить. [link]

  • Используйте OPTIMIZE, чтобы отметить медленный или неэффективный код, который может вызвать проблемы с производительностью. [link]

  • Используйте HACK, чтобы отметить "пахнущий" код (костыли, прим. переводчика), который использует сомнительные приемы и должен быть отрефакторен. [link]

  • Используйте REVIEW, чтобы отметить то, с чем нужно внимательнее ознакомиться, чтобы убедиться, что оно работает так, как нужно. Например: REVIEW: Are we sure this is how the client does X currently? [link]

  • Используйте другие ключевые слова для аннотаций, если они кажутся приемлемыми. Но не забудьте добавить их в README вашего проекта. [link]

Документирование

Док. строки - основной способ документирования Clojure кода. Многие формы-определения (например, def, defn, defmacro, ns) поддерживают док. строки и использовать их - хорошая идея вне зависимости от того, является ли описываемое публичным или приватным.

Если форма-определение не поддерживает док. строки, то вы все равно можете добавить документация с помощью аттрибута метаданных :doc.

В этом разделе выделены некоторые общие практики по документированию Clojure кода.

  • Если возможно, используйте док. строки вместо :doc. [link]
;; хорошо
(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)
  • Пишите док. строки законченными предложениями с большой буквы, разумно описывающими сущность, к которой привязаны. Благодаря этому ваши инструменты (текстовые редакторы и IDE) смогут отображать краткое описание сущности во многих местах. [link]
;; хорошо
(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 смогут определять их (параметры) и предоставлять дополнительную функциональность для них. [link]
;; хорошо
(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]
  ...)
  • Оборачивайте ссылки на другие сущности (функции, константы) в обратные `ковычки`, чтобы ваши инструменты могли определять их. [link]
;; хорошо
(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]
  ...)
  • Док.строки должны быть грамотно оформленными предложениями на английском языке, т.е. каждое предложение должно начинаться с прописной буквы и заканчиваться точкой (или другим подходящим знаком препинания). Предложения должны отделяться пробелом. [link]
;; хорошо
(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")
  • Оставляйте отступ в два пробела в многострочных док. строках. [link]
;; хорошо
(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.")
  • Не начинайте и не заканчивайте док. строки пробелом. [link]
;; хорошо
(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)
  • Когда пишете док. строку, размещайте его после имени функции, а не после списка параметров. Последнее не является нарушением синтаксиса и не вызовет никаких ошибок, но включает строку как часть тела функции без привязки к документации. [link]
;; хорошо
(defn foo
  "docstring"
  [x]
  (bar x))

;; плохо
(defn foo [x]
  "docstring"
  (bar x))

Общие рекомендации

  • Пишите в функциональном стиле, используя изменяемое состояние тогда, когда это необходимо. [link]

  • Будьте последовательны. В идеале, будьте последовательны по этому руководству. [link]

  • Используйте здравый смысл. [link]

Инструменты

Есть несколько полезных штук, созданных Clojure сообществом, которые могут помочь вам в вашем стремлении писать хороший Clojure код.

  • Slamhound - инструмент, который будет генерировать верные ns определения из существующего кода.

  • kibit - статический анализатор кода для Clojure, который использует core.logic для поиска кода, который можно переписать с помощью существующей функции или макроса.

Тестирование

  • Храните ваши тесты в отдельной директории, обычно test/yourproject (в контрасте с src/yourproject). Ваш инструмент сборки (build tool) должен обеспечить доступ к ним, когда это необходимо. Большинство шаблонов работают с тестами автоматически. [link]

  • Именуйте пространство имен теста как yourproject.something-test, файл обычно будет выглядеть как test/yourproject/something_test.clj (или .cljc, cljs). [link]

  • При использовании clojure.test, определяйте ваши тесты с помощью deftest и называйте их something-test. [link]

    ;; хорошо
    (deftest something-test ...)
    
    ;; плохо
    (deftest something-tests ...)
    (deftest test-something ...)
    (deftest something ...)

Создание библиотек

  • Если вы публикуете библиотеки, используемые другими людьми, обязательно следуйте руководству Central Repository при выборе groupId и artifactId. Это позволит предотвратить конфликты имен и облегчить широкое ее использование. Хорошим примером является Component. [link]

  • Избегайте ненужных зависимостей. Например, трехстрочная вспомогательная функция, скопированная в проект обычно лучше, чем зависисмость, тянущая за собой сотни наименований, которые вы не собираетесь использовать. [link]

  • Предоставляйте основную функциональность и различные интеграции в отдельных артефактах. В таком случае, пользователи смогут воспользоваться вашей библиотекой, не ограничивая себя вашими инструментальными предпочтениями. Например, Component предоставляет основной функционал, а reloaded предоставляет интеграцию с leiningen. [link]

Помощь проекту

Написанное в этом руководстве не является истинной последней инстанции. Я хочу работать совместно с каждым, кто заинтересован в хорошем оформлении кода Clojure, чтобы мы смогли создать ресурс, полезный для всего Clojure сообщества.

Не стесняйтесь открывать тикеты и создавать pull-реквесты с улучшениями. Заранее спасибо за помощь!

Также вы можете помочь руководству финансово с помощью gittip.

Support via Gittip

Примечание переводчика:

Данный репозиторий содержит лишь перевод оригинального руководства. Соответственно, все вопросы и предложения, не касающиеся конкретно перевода, должны направляться в оригинальный репозиторий.

License

Creative Commons License Данное руководство использует лицензию Creative Commons Attribution 3.0 Unported License

Расскажите всем

Руководство, написанное сообществом, не может оказывать много помощи сообществу, которое не знает о его существовании. Расскажите о данном гайде друзьям и коллегам. Каждый комментарий, каждое предложение и мнение, которые мы получаем, делают это руководство немного лучше. А мы хотим иметь наилучшее возможное руководство, не правда ли?

Удачи,
Божидар

You can’t perform that action at this time.