# The Clojure Style Guide ## Примечание переводчика Это перевод [оригинального руководства](https://github.com/bbatsov/clojure-style-guide), составленного Божидаром Бацовым ([Bozhidar Batsov](https://github.com/bbatsov)). У меня возникли некоторые сложности с переводом некоторых терминов, поэтому я сразу привожу их список: * 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 - изменяемые. см. [документацию](https://clojure.org/reference/transients) * будет дополняться Также я иногда буду оставлять в скобках оригинал выражения/термина. ## Введение > Role models are important.
> -- Officer Alex J. Murphy / RoboCop Данное руководство предлагает наилучшие рекомендации по оформлению Clojure кода для того, чтобы Clojure-программисты могли писать код, который смогут поддерживать другие Clojure-программисты. Руководство по оформлению кода, отражающее практики, используемые в реальном мире, будет использоваться людьми, а руководство, придерживающееся отвергнутых людьми, которым он должен помочь, рискует не быть используемым ими вовсе (не важно, насколько он при этом хорош). Руководство поделено на несколько разделов, объединяющих связанные между собой правила. Я постарался добавить пояснения к правилам (если же они опущены, значит, я предположил, что они и так очевидны и в пояснениях не нуждаются). Я не взял все эти идеи с потолка. Они основаны по большей части на моем личном обширном опыте разработки, комментариях и предложениях членов Clojure сообщества и некоторых авторитетных источников информации по Clojure, например, ["Clojure Programming"](http://www.clojurebook.com/) и ["The Joy of Clojure"](http://joyofclojure.com/). Руководство все еще разрабатывается: некоторые разделы отсутствуют, некоторые все еще не завершены, каким-то правилам не хватает хороших примеров. Когда-нибудь эти недостатки будут устранены, а пока просто держите их в уме. Обратите внимание, что разработчики Clojure также поддерживают список [стандартов по разработке библиотек](http://dev.clojure.org/display/community/Library+Coding+Standards) Вы можете сгенерировать PDF или HTML копию данного руководства с помощью [Pandoc](http://pandoc.org/). Переводы доступны на следующих языках: * [Оригинал на английском](https://github.com/bbatsov/clojure-style-guide) * [Китайский](https://github.com/geekerzp/clojure-style-guide/blob/master/README-zhCN.md) * [Японский](https://github.com/totakke/clojure-style-guide/blob/ja/README.md) * [Корейский](https://github.com/kwakbab/clojure-style-guide/blob/master/README-koKO.md) * [Португальский](https://github.com/theSkilled/clojure-style-guide/blob/pt-BR/README.md) (дорабатывается). * Русский - вы здесь. ## Содержание * [Организация кода](#source-code-layout--organization) * [Синтаксис](#syntax) * [Именование (переменных, функций и т.д.)](#naming) * [Коллекции](#collections) * [Изменение состояния](#mutation) (ориг. Mutation) * [Строки](#strings) * [Исключения](#exceptions) * [Макросы](#macros) * [Комментарии](#comments) * [Аннотации](#comment-annotations) (ориг. Comment Annotations) * [Документирование](#documentation) * [Общие рекомендации](#existential) (ориг. Existential) * [Инструменты](#tooling) * [Тестирование](#testing) * [Создание библиотек](#library-organization) ## Организация кода > Почти каждый уверен, что все стили кода, кроме его собственного, - уродливые и > нечитаемые. Если убрать "кроме его собственного", то, возможно, они > правы...
> -- Jerry Coffin (об отступах) * Используйте **пробелы** для отступов. Никаких табов. [[link](#spaces)] * Используйте 2 пробела для тела формы. Формы с телом включают `def`-подобные конструкции, специальные формы и макросы, вводящие локальное связывание (например, `loop`, `let`, `when-let`) и многие макросы вроде `when`, `cond`, `as->`, `cond->`, `case`, `with-*` и т.д. [[link](#body-indentation)] ```Clojure ;; хорошо (when something (something-else)) (with-out-str (println "Hello, ") (println "world!")) ;; плохо - четыре пробела (when something (something-else)) ;; плохо - один пробел (with-out-str (println "Hello, ") (println "world!")) ``` * Выравнивайте по вертикали аргументы функции (макроса), если они расположены на нескольких строках. [[link](#vertically-align-fn-args)] ```Clojure ;; хорошо (filter even? (range 1 10)) ;; плохо (filter even? (range 1 10)) ``` * Используйте отступ в один пробел для аргументов функции (макроса), если на одной строке с именем функции их (аргументов) нет. [[link](#one-space-indent)] ```Clojure ;; хорошо (filter even? (range 1 10)) (or ala bala portokala) ;; плохо - два пробела (filter even? (range 1 10)) (or ala bala portokala) ``` * Выравнивайте по вертикали `let`-связки (переменные) и ключи для хешей [[link](#vertically-align-let-and-map)] ```Clojure ;; хорошо (let [thing1 "some stuff" thing2 "other stuff"] {:thing1 thing1 :thing2 thing2}) ;; плохо (let [thing1 "some stuff" thing2 "other stuff"] {:thing1 thing1 :thing2 thing2}) ``` * Можете не использовать перенос строки между именем функции и ее параметрами в `defn`, если отсутствует док. строка. [[link](#optional-new-line-after-fn-name)] ```Clojure ;; хорошо (defn foo [x] (bar x)) ;; хорошо (defn foo [x] (bar x)) ;; плохо (defn foo [x] (bar x)) ``` * Размещайте `dispatch-val` (см. [`defmethod`](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/defmethod)) на той же строке, что и имя функции. [[link](#multimethod-dispatch-val-placement)] ```Clojure ;; хорошо (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](#oneline-short-fn)] ```Clojure ;; хорошо (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](#multiple-arity-indentation)] ```Clojure ;; хорошо (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](#multiple-arity-order)] ```Clojure ;; хорошо - легко найти 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](#crlf)] * Если вы используете Git, то можете добавить следующую настройку в конфигурацию своего проекта, чтобы уберечь себя от Windows-переносов: ``` bash$ git config --global core.autocrlf true ``` * Если какой-либо текст предшествует открытой скобке (`(`, `{`, `[`) или следует за закрытой скобкой, то отделяйте его от скобки пробелом. И наоборот, не добавляйте пробелов между открытой скобки и последующим текстов и между текстом и последующей скобкой [[link](#bracket-spacing)] ```Clojure ;; хорошо (foo (bar baz) quux) ;; плохо (foo(bar baz)quux) (foo ( bar baz ) quux) ``` > Синтаксический сахар вызывает рак точек с запятой (semicolon cancer). > -- Alan Perlis * Не используйте запятые между элементами последовательностей (списки, векторы) [[link](#no-commas-for-seq-literals)] ```Clojure ;; хорошо [1 2 3] (1 2 3) ;; плохо [1, 2, 3] (1, 2, 3) ``` * Подумайте об улучшении читаемости хеш-литералов с помощью использования запятых и переносов. [[link](#opt-commas-in-map-literals)] ```Clojure ;; хорошо {:name "Bruce Wayne" :alter-ego "Batman"} ;; хорошо и в некотором роде более читаемо {:name "Bruce Wayne" :alter-ego "Batman"} ;; хорошо и при этом более компактно (чем предыдущий пример) {:name "Bruce Wayne", :alter-ego "Batman"} ``` * Оставляйте конечные скобки на одной строке [[link](#gather-trailing-parens)] ```Clojure ;; хорошо - на одной строке (when something (something-else)) ;; плохо - на разных строках (when something (something-else) ) ``` * Оставляйте пустые строки между верхнеуровневыми формами [[link](#empty-lines-between-top-level-forms)] ```Clojure ;; хорошо (def x ...) (defn foo ...) ;; плохо (def x ...) (defn foo ...) ``` Исключение из правил - группировка нескольких связанных `def` ```Clojure ;; хорошо (def min-rows 10) (def max-rows 20) (def min-cols 15) (def max-cols 30) ``` * Не оставляйте пустые строки посреди определения функции или макроса. Исключением может быть группировки парных конструкций, например, в `let` и `cond`. [[link](#no-blank-lines-within-def-forms)] * Старайтесь избегать написания строк длиннее 80 символов [[link](#80-character-limits)] * Избегайте лишних пробелов в конце (строк) [[link](#no-trailing-whitespace)] * Для каждого пространства имен создавайте отдельный файл [[link](#one-file-per-namespace)] * Определяйте каждое пространство имен с помощью `ns` с использованием `:refer`, `:require` и `:import`. Желательно, в таком порядке. [[link](#comprehensive-ns-declaration)] ```Clojure (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](#prefer-require-over-use)] ```Clojure ;; хорошо (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](#no-single-segment-namespaces)] ```Clojure ;; хорошо (ns example.ns) ;; плохо (ns example) ``` * Избегайте не использовать слишком длинные названия пространства имен (т.е. состоящие более чем из 5 частей). [[link](#namespaces-with-5-segments-max)] * Избегайте функций, состоящих из более 10 строк. В идеале, большинство функций должны быть менее 5 строк длиной. [[link](#10-loc-per-fn-limit)] * Избегайте создания функций с более чем тремя-четырьмя позиционными параметрами. (прим. переводчика: `[x y & more]` - это всего три параметра, хотя аргументов можно передать гораздо больше) [[link](#4-positional-fn-params-limit)] * Не используйте ранние определения (forward references). Иногда они необходимы, то это довольно редкий случай на практике. [[link](#forward-references)] ## Syntax * Избегайте использования функций, влияющих на пространство имен, например, `require` и `refer`. Они совершенно не нужны, если вы не находитесь в REPL. [[link](#ns-fns-only-in-repl)] * Используйте `declare`, когда вам *необходимы* ранние определения. [[link](#declare)] * Старайтесь использовать функции высшего порядка (такие как `map`) вместо `loop/recur`. [[link](#higher-order-fns)] * Старайтесь использовать пред- и пост- условия функции вместо проверок внутри тела функции [[link](#pre-post-conditions)] ```Clojure ;; хорошо (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](#dont-def-vars-inside-fns)] ```Clojure ;; очень плохо (defn foo [] (def x 5) ...) ``` * Не перекрывайте имена из `clojure.core` локальными именами. [[link](#dont-shadow-clojure-core)] ```Clojure ;; плохо - если вам понадобится функция map, вам придется использовать ее ;; с помощью clojure.core/map (defn foo [map] ...) ``` * Используйте `alter-var-root` вместо `def` для изменения значения переменной. [[link]](#alter-var) ```Clojure ;; хорошо (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](#nil-punning)] ```Clojure ;; хорошо (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](#to-vector)] ```Clojure ;; хорошо (vec some-seq) ;; плохо (into [] some-seq) ``` * Используйте `when` вместо `(if ... (do ...)`. [[link](#when-instead-of-single-branch-if)] ```Clojure ;; хорошо (when pred (foo) (bar)) ;; плохо (if pred (do (foo) (bar))) ``` * Используйте `if-let` вместо `let` + `if`. [[link](#if-let)] ```Clojure ;; хорошо (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)] ```Clojure ;; хорошо (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)] ```Clojure ;; хорошо (if-not pred (foo)) ;; плохо (if (not pred) (foo)) ``` * Используйте `when-not` вместо `(when (not ...) ...)`. [[link](#when-not)] ```Clojure ;; хорошо (when-not pred (foo) (bar)) ;; плохо (when (not pred) (foo) (bar)) ``` * Используйте `when-not` вместо `(if-not ... (do ...)`. [[link](#when-not-instead-of-single-branch-if-not)] ```Clojure ;; хорошо (when-not pred (foo) (bar)) ;; плохо (if-not pred (do (foo) (bar))) ``` * Используйте `not=` Вместо `(not (= ...))`. [[link](#not-equal)] ```Clojure ;; хорошо (not= foo bar) ;; плохо (not (= foo bar)) ``` * Используйте `printf` вместо `(print (format ...))`. [[link](#printf)] ```Clojure ;; хорошо (printf "Hello, %s!\n" name) ;; сойдет (println (format "Hello, %s!" name)) ``` * При сравнениях не забывайте, что функции `<`, `>` и т.д. принимают переменное количество аргументов. [[link](#multiple-arity-of-gt-and-ls-fns)] ```Clojure ;; хорошо (< 5 x 10) ;; плохо (and (> x 5) (< x 10)) ``` * Используйте `%` вместо `%1` в литералах функции с одним параметром [[link](#single-param-fn-literal)] ```Clojure ;; хорошо #(Math/round %) ;; плохо #(Math/round %1) ``` * Используйте `%1` вместо `%` в литералах функций с несколькими параметрами. [[link](#multiple-params-fn-literal)] ```Clojure ;; хорошо #(Math/pow %1 %2) ;; плохо #(Math/pow % %2) ``` * Не оборачивайте функции в анонимные функции, если это не требуется. [[link](#no-useless-anonymous-fns)] ```Clojure ;; хорошо (filter even? (range 1 10)) ;; плохо (filter #(even? %) (range 1 10)) ``` * Не используйте литералы функций, если тело функции будет состоять из более чем одной формы. [[link](#no-multiple-forms-fn-literals)] ```Clojure ;; хорошо (fn [x] (println x) (* x 2)) ;; плохо (you need an explicit do form) #(do (println %) (* % 2)) ``` * Предпочитайте `complement` вместо использования анонимной функции. [[link](#complement)] ```Clojure ;; хорошо (filter (complement some-pred?) coll) ;; плохо (filter #(not (some-pred? %)) coll) ``` Это правило, очевидно, должно быть проигнорированно, если дополнение функции уже существует в форме другой функции (например, `even?` и `odd?`). * Используйте `comp`, когда это упрощает код. [[link](#comp)] ```Clojure ;; Предполагается `(:require [clojure.string :as str])`... ;; хорошо (map #(str/capitalize (str/trim %)) ["top " " test "]) ;; лучше (map (comp str/capitalize str/trim) ["top " " test "]) ``` * Используйте `partial`, когда это упрощает код. [[link](#partial)] ```Clojure ;; хорошо (map #(+ 5 %) (range 1 10)) ;; (кажется) лучше (map (partial + 5) (range 1 10)) ``` * Используйте threading макрос `->` (thread-first) and `->>` (thread-last) в глубоко вложенных формах. [[link](#- `` - hreading-macros)] ```Clojure ;; хорошо (-> [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](#else-keyword-in-cond)] ```Clojure ;; хорошо (cond (neg? n) "negative" (pos? n) "positive" :else "zero") ;; плохо (cond (neg? n) "negative" (pos? n) "positive" true "zero") ``` * Предпочитайте `condp` вместо `cond`, когда предикатное выражение не меняется. [[link](#condp)] ```Clojure ;; хорошо (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](#case)] ```Clojure ;; хорошо (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](#shor-forms-in-cond)] ```Clojure ;; хорошо (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](#set-as-predicate)] ```Clojure ;; хорошо (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](#inc-and-dec)] * Используйте `(pos? x)`, `(neg? x)` и `(zero? x)` вместо `(> x 0)`, `(< x 0)` и `(= x 0)`. [[link](#pos-and-neg)] * Используйте `list*` вместо последовательных вызовов `cons`. [[link](#list-star-instead-of-nested-cons)] ```Clojure ;; хорошо (list* 1 2 3 [4 5]) ;; плохо (cons 1 (cons 2 (cons 3 [4 5]))) ``` * Используйте синт. сахар для работы с Java. [[link](#sugared-java-interop)] ```Clojure ;;; создание объекта ;; хорошо (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](#compact-metadata-notation-for-true-flags)] ```Clojure ;; хорошо (def ^:private a 5) ;; плохо (def ^{:private true} a 5) ``` * Помечайте приватные части кода. [[link](#private)] ```Clojure ;; хорошо (defn- private-fun [] ...) (def ^:private private-var ...) ;; плохо (defn private-fun [] ...) ; не приватная (defn ^:private private-fun [] ...) ; слишком многословно (def private-var ...) ; не приватная ``` * Для доступа к приватной переменной (например, в рамках тестирования) используйте форму `@#'some.ns/var`. [[link](#access-private-var)] * Будьте внимательны с тем, к чему конкретно вы привязываете метаданные. [[link](#attach-metadata-carefully)] ```Clojure ;; мы привязываем метаданные к переменной `a` (def ^:private a {}) (meta a) ;=> nil (meta #'a) ;=> {:private true} ;; мы привязываем метаданные к хешу (def a ^:private {}) (meta a) ;=> {:private true} (meta #'a) ;=> nil ``` ## Именование > Единственными сложностями программирования являются проверка актуальности > кеша и именование. > -- Phil Karlton * Когда именуете пространство имен, предпочитайте следующие шаблоны: [[link](#ns-naming-schemas)] * `проект.модуль` * `компания.проект.модуль` * Используйте `lisp-case` в отдельных частях пространства имен (например, `bruce.project-euler`) [[link](#lisp-case-ns)] * Используйте `lisp-case` для имен функций и переменных. [[link](#lisp-case)] ```Clojure ;; хорошо (def some-var ...) (defn some-fun ...) ;; плохо (def someVar ...) (defn somefun ...) (def some_fun ...) ``` * Используйте `CamelCase` для протоколов, записей, структур и типов. Сохраняйте акронимы вроде HTTP, RFC, XML в верхнем регистре. [[link](#CamelCase-for-protocols-records-structs-and-types)] * Имена предикатов (функций, возвращающих булевое значение) должны оканчиваться вопросительным знаком (например, `even?`). [[link](#pred-with-question-mark)] ```Clojure ;; хорошо (defn palindrome? ...) ;; плохо (defn palindrome-p ...) ; стиль Common Lisp (defn is-palindrome ...) ; стиль Java ``` * Имена функций/макросов, которые небезопасны в транзакциях [STM](http://en.wikipedia.org/wiki/Software_transactional_memory) должны оканчиваться восклицательным знаком (например, `reset!`) [[link](#changing-state-fns-with-exclamation-mark)] * Используйте `->` вместо `to` в названиях приводящих (к типу) функций. [[link](#arrow-instead-of-to)] ```Clojure ;; хорошо (defn f->c ...) ;; не так хорошо (defn f-to-c ...) ``` * Используйте `*звездочки*` (в ориг. earmuffs) для того, что планируется переопределить. [[link](#earmuffs-for-dynamic-vars)] ```Clojure ;; хорошо (def ^:dynamic *a* 10) ;; плохо (def ^:dynamic a 10) ``` * Не используйте никакой особенной записи для констант - предполагается, что все есть константы, если не указано обратное. [[link](#dont-flag-constants)] * Используйте `_` при именовании параметров (в т.ч. при деструктурировании), которые не будут использоваться в коде. [[link](#underscore-for-unused-bindings)] ```Clojure ;; хорошо (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](#idiomatic-names)] * в функциях: * `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](#avoid-lists)] * Старайтесь использовать ключевые слова (ключи, keywords) в качестве ключей хеша. [[link](#keywords-for-hash-keys)] ```Clojure ;; хорошо {:name "Bruce" :age 30} ;; плохо {"name" "Bruce" "age" 30} ``` * Старайтесь использовать литералы коллекций, если возможно. Однако для множеств используйте литералы только в том случае, когда значениями являются константы, известные на этапе компиляции. [[link](#literal-col-syntax)] ```Clojure ;; хорошо [1 2 3] #{1 2 3} (hash-set (func1) (func2)) ; значения определяются во время выполнения ;; плохо (vector 1 2 3) (hash-set 1 2 3) #{(func1) (func2)} ; во время выполнения выбросит исключение, если (func1) = (func2) ``` * Старайтесь не обращаться к членам коллекций по индексу, если возможно. [[link](#avoid-index-based-coll-access)] * Старайтесь использовать ключи как функции для доступа к значениям хешей, если возможно. [[link](#keywords-as-fn-to-get-map-values)] ```Clojure (def m {:name "Bruce" :age 30}) ;; хорошо (:name m) ;; слишком многословно (get m :name) ;; плохо - возможна ошибка NullPointerException (m :name) ``` * Используйте факт, что большинство коллекций являются функциями. [[link](#colls-as-fns)] ```Clojure ;; хорошо (filter #{\a \e \o \i \u} "this is a test") ;; плохо - слишком страшно, чтобы показывать ``` * Используйте факт, что ключи могут быть использованы как функции над коллекцией. [[link](#keywords-as-fns)] ```Clojure ((juxt :a :b) {:a "ala" :b "bala"}) ``` * Не используйте изменяемые (transient) коллекции, если только это не критическая часть кода, требующая максимального быстродействия. [[link](#avoid-transient-colls)] * Избегайте использования коллекций из Java. [[link](#avoid-java-colls)] * Избегайте использования Java массивов. Разве что для случаев интеграции и критического кода, много работающего с примитивами. [[link](#avoid-java-arrays)] ## Изменение состояния ### Транзакционные ссылки (Refs) * Подумайте над оборачиванием всех IO вызовов макросом `io!`, чтобы избежать гадких сюрпризов, если вы случайно запустите этот код внутри транзакции [[link](#refs-io-macro)] * Избегайте использования `ref-set`, если возможно. [[link](#refs-avoid-ref-set)] ```Clojure (def r (ref 0)) ;; хорошо (dosync (alter r + 5)) ;; плохо (dosync (ref-set r 5)) ``` * Старайтесь держать размер транзакций (количество работы, инкапсулированное в них) настолько маленьким насколько возможно. [[link](#refs-small-transactions)] * Избегайте наличия коротких и длительных транзакций, работающих с одной и той же ссылкой. [[link](#refs-avoid-short-long-transactions-with-same-ref)] ### Агенты * Используйте `send` только для действий, привязанных к процессору и не блокирующих IO или другие потоки. [[link](#agents-send)] * Используйте `send-off` для действий, которые могут заблокировать, "усыпить" (sleep) или как-то иначе подвесить поток. [[link](#agents-send-off)] ### Атомы * Избегайте обновления атомов внутри STM транзакции. [[link](#atoms-no-update-within-transactions)] * Старайтесь использовать `swap!` вместо `reset!`, где возможно. [[link](#atoms-prefer-swap-over-reset)] ```Clojure (def a (atom 0)) ;; хорошо (swap! a + 5) ;; Не так хорошо (reset! a 5) ``` ## Строки * Старайтесь использовать для работы со строками функции из `clojure.string` вместо интеграции с Java или введения собственных. [[link](#prefer-clojure-string-over-interop)] ```Clojure ;; хорошо (clojure.string/upper-case "bruce") ;; плохо (.toUpperCase "bruce") ``` ## Исключения * Используйте существующие исключения. Хороший Clojure код выбрасывает исключения стандартных типов (если выбрасывает). Например, `java.lang.IllegalArgumentException`, `java.lang.UnsupportedOperationException`, `java.lang.IllegalStateException`, `java.io.IOException`. [[link](#reuse-existing-exception-types)] * Используйте `with-open` вместо `finally`. [[link](#prefer-with-open-over-finally)] ## Макросы * Не пишите макросы, если хороша и функция. [[link](#dont-write-macro-if-fn-will-do)] * Перед макросом напишите пример его использования. [[link](#write-macro-usage-before-writing-the-macro)] * Разбивайте сложные макросы на меньшие функции. [[link](#break-complicated-macros)] * Макрос обычно должен быть всего-лишь синтаксическим сахаром над обычной функций. В таком случае вам будет легче комбинировать функции. [[link](#macros-as-syntactic-sugar)] * Старайтесь использовать syntax-quote (\`) вместо того, чтобы создавать список "вручную". [[link](#syntax-quoted-forms)] ## Комментарии > Хороший код является лучшей документацией для самого себя. Если вы собираетесь > добавить комментарий, то спросите себя: "Как я могу улучшить свой код, чтобы > этот комментарий был не нужен?". Улучшите код и задокументируйте его, чтобы он > стал еще более чистым
> -- Steve McConnell * Старайтесь писать самодокументируемый код. [[link](#self-documenting-code)] * Пишите заглавный комментарий минимум с четырьмя точками с запятой (`;;;;`). [[link](#four-semicolons-for-heading-comments)] * Пишите комментарии верхнего уровня с тремя точками с запятой (`;;;`). [[link](#three-semicolons-for-top-level-comments)] * Пишите комментарии к фрагменту кода перед ним и выравнивайте их, используя две точки с запятой (`;;`). [[link](#two-semicolons-for-code-fragment)] * Пишите боковые комментарии, начиная с одной точки с запятой (`;`). [[link](#one-semicolon-for-margin-comments)] * Всегда оставляйте как минимум один пробел после точек с запятой. [[link](#semicolon-space)] ```Clojure ;;;; 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)) ``` * Комментарии, состоящие из более чем одного слова, начинаются с большой буквы и используют пунктуацию. Отделяйте предложения [одним пробелом](http://en.wikipedia.org/wiki/Sentence_spacing). [[link](#english-syntax)] * Не оставляйте очевидных комментариев [[link](#no-superfluous-comments)] ```Clojure ;; плохо (inc counter) ; увеличивает counter на единицу ``` * Комментарии должны быть актуальными. Устаревший комментарий хуже, чем отсутствие комментария вообще. [[link](#comment-upkeep)] * Используйте макрос `#_` вместо обычного комментирования, когда вам нужно закомментировать форму. [[link](#dash-underscore-reader-macro)] ```Clojure ;; хорошо (+ foo #_(bar x) delta) ;; плохо (+ foo ;; (bar x) delta) ``` > Хороший код, как и хорошая шутка, не требует объяснения.
> -- Russ Olsen * Старайтесь не писать коммментарии, чтобы объяснить плохой код. Измените код, чтобы он стал понятным ("Делай или не делай, не надо пытаться." --Йода). [[link](#refactor-dont-comment)] ### Аннотации (Comment Annotations) * Аннотации обычно должны размещаться непосредственно над связанным кодом. [[link](#annotate-above)] * Между ключевым словом аннотации и описанием должны стоять двоеточие и пробел. Например: `TODO: add something cool`. [[link](#annotate-keywords)] * Если описание занимает несколько строк, то каждая строка должна быть выровнена в соответствии с первой (т.е. после ключевого слова). [[link](#indent-annotations)] * Помечайте аннотацию своими инициалами и датой, чтобы актуальность было легче отследить. [[link](#sing-and-date-annotations)] ```Clojure (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](#rare-eol-annotations)] ```Clojure (defn bar [] (sleep 100)) ; OPTIMIZE ``` * Используйте `TODO`, чтобы отметить недостающие фичи или функциональность, которую нужно будет добавить позже. [[link](#todo)] * Используйте `FIXME`, чтобы отметить сломанный код, который нужно исправить. [[link](#fixme)] * Используйте `OPTIMIZE`, чтобы отметить медленный или неэффективный код, который может вызвать проблемы с производительностью. [[link](#optimize)] * Используйте `HACK`, чтобы отметить "пахнущий" код (костыли, прим. переводчика), который использует сомнительные приемы и должен быть отрефакторен. [[link](#hack)] * Используйте `REVIEW`, чтобы отметить то, с чем нужно внимательнее ознакомиться, чтобы убедиться, что оно работает так, как нужно. Например: `REVIEW: Are we sure this is how the client does X currently?` [[link](#review)] * Используйте другие ключевые слова для аннотаций, если они кажутся приемлемыми. Но не забудьте добавить их в `README` вашего проекта. [[link](#document-annotations)] ## Документирование Док. строки - основной способ документирования Clojure кода. Многие формы-определения (например, `def`, `defn`, `defmacro`, `ns`) поддерживают док. строки и использовать их - хорошая идея вне зависимости от того, является ли описываемое публичным или приватным. Если форма-определение не поддерживает док. строки, то вы все равно можете добавить документацию с помощью аттрибута метаданных `:doc`. В этом разделе выделены некоторые общие практики по документированию Clojure кода. * Если возможно, используйте док. строки вместо `:doc`. [[link](#prefer-docstrings)] ```clojure ;; хорошо (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](#docstring-summary)] ```clojure ;; хорошо (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](#document-pos-arguments)] ```clojure ;; хорошо (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](#document-references)] ```clojure ;; хорошо (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](#docstring-grammar)] ```clojure ;; хорошо (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](#docstring-indentation)] ```clojure ;; хорошо (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](#docstring-leading-trailing-whitespace)] ```clojure ;; хорошо (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](#docstring-after-fn-name)] ```Clojure ;; хорошо (defn foo "docstring" [x] (bar x)) ;; плохо (defn foo [x] "docstring" (bar x)) ``` ## Общие рекомендации * Пишите в функциональном стиле, используя изменяемое состояние тогда, когда это необходимо. [[link](#be-functional)] * Будьте последовательны. В идеале, будьте последовательны по этому руководству. [[link](#be-consistent)] * Используйте здравый смысл. [[link](#common-sense)] ## Инструменты Есть несколько полезных штук, созданных Clojure сообществом, которые могут помочь вам в вашем стремлении писать хороший Clojure код. * [Slamhound](https://github.com/technomancy/slamhound) - инструмент, который будет генерировать верные `ns` определения из существующего кода. * [kibit](https://github.com/jonase/kibit) - статический анализатор кода для Clojure, который использует [core.logic](https://github.com/clojure/core.logic) для поиска кода, который можно переписать с помощью существующей функции или макроса. ## Тестирование * Храните ваши тесты в отдельной директории, обычно `test/yourproject` (в контрасте с `src/yourproject`). Ваш инструмент сборки (build tool) должен обеспечить доступ к ним, когда это необходимо. Большинство шаблонов работают с тестами автоматически. [[link](#test-directory-structure)] * Именуйте пространство имен теста как `yourproject.something-test`, файл обычно будет выглядеть как `test/yourproject/something_test.clj` (или `.cljc`, `cljs`). [[link](#test-ns-naming)] * При использовании `clojure.test`, определяйте ваши тесты с помощью `deftest` и называйте их `something-test`. [[link](#test-naming)] ```clojure ;; хорошо (deftest something-test ...) ;; плохо (deftest something-tests ...) (deftest test-something ...) (deftest something ...) ``` ## Создание библиотек * Если вы публикуете библиотеки, используемые другими людьми, обязательно следуйте [руководству Central Repository](http://central.sonatype.org/pages/choosing-your-coordinates.html) при выборе `groupId` и `artifactId`. Это позволит предотвратить конфликты имен и облегчить широкое ее использование. Хорошим примером является [Component](https://github.com/stuartsierra/component). [[link](#lib-coordinates)] * Избегайте ненужных зависимостей. Например, трехстрочная вспомогательная функция, скопированная в проект обычно лучше, чем зависисмость, тянущая за собой сотни наименований, которые вы не собираетесь использовать. [[link](#lib-min-dependencies)] * Предоставляйте основную функциональность и различные интеграции в отдельных артефактах. В таком случае, пользователи смогут воспользоваться вашей библиотекой, не ограничивая себя вашими инструментальными предпочтениями. Например, [Component](https://github.com/stuartsierra/component) предоставляет основной функционал, а [reloaded](https://github.com/stuartsierra/reloaded) предоставляет интеграцию с leiningen. [[link](#lib-core-separate-from-tools)] # Помощь проекту Написанное в этом руководстве не является истинной последней инстанции. Я хочу работать совместно с каждым, кто заинтересован в хорошем оформлении кода Clojure, чтобы мы смогли создать ресурс, полезный для всего Clojure сообщества. Не стесняйтесь открывать тикеты и создавать pull-реквесты с улучшениями. Заранее спасибо за помощь! Также вы можете помочь руководству финансово с помощью [gittip](https://www.gittip.com/bbatsov). [![Support via Gittip](https://rawgithub.com/twolfson/gittip-badge/0.2.0/dist/gittip.png)](https://www.gittip.com/bbatsov) Примечание переводчика: Данный репозиторий содержит лишь перевод [оригинального руководства](https://github.com/bbatsov/clojure-style-guide). Соответственно, все вопросы и предложения, не касающиеся конкретно перевода, должны направляться в оригинальный репозиторий. # License ![Creative Commons License](http://i.creativecommons.org/l/by/3.0/88x31.png) Данное руководство использует лицензию [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/deed.en_US) # Расскажите всем Руководство, написанное сообществом, не может оказывать много помощи сообществу, которое не знает о его существовании. Расскажите о данном гайде друзьям и коллегам. Каждый комментарий, каждое предложение и мнение, которые мы получаем, делают это руководство немного лучше. А мы хотим иметь наилучшее возможное руководство, не правда ли? Удачи,
[Божидар](https://twitter.com/bbatsov)