Это перевод оригинального руководства, составленного Божидаром Бацовым (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.
Переводы доступны на следующих языках:
- Оригинал на английском
- Китайский
- Японский
- Корейский
- Португальский (дорабатывается).
- Русский - вы здесь.
- Организация кода
- Синтаксис
- Именование (переменных, функций и т.д.)
- Коллекции
- Изменение состояния (ориг. Mutation)
- Строки
- Исключения
- Макросы
- Комментарии
- Аннотации (ориг. Comment Annotations)
- Документирование
- Общие рекомендации (ориг. Existential)
- Инструменты
- Тестирование
- Создание библиотек
Почти каждый уверен, что все стили кода, кроме его собственного, - уродливые и нечитаемые. Если убрать "кроме его собственного", то, возможно, они правы...
-- 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]
-
Избегайте использования функций, влияющих на пространство имен, например,
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]
-
Подумайте над оборачиванием всех 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]
-
Аннотации обычно должны размещаться непосредственно над связанным кодом. [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.
Примечание переводчика:
Данный репозиторий содержит лишь перевод оригинального руководства. Соответственно, все вопросы и предложения, не касающиеся конкретно перевода, должны направляться в оригинальный репозиторий.
Данное руководство использует лицензию
Creative Commons Attribution 3.0 Unported License
Руководство, написанное сообществом, не может оказывать много помощи сообществу, которое не знает о его существовании. Расскажите о данном гайде друзьям и коллегам. Каждый комментарий, каждое предложение и мнение, которые мы получаем, делают это руководство немного лучше. А мы хотим иметь наилучшее возможное руководство, не правда ли?
Удачи,
Божидар