Role models are important.
-- Officer Alex J. Murphy / RoboCop
原文地址:https://github.com/bbatsov/clojure-style-guide
这份Clojure代码规范旨在提供一系列的最佳实践,让现实工作中的Clojure程序员能够写出易于维护的代码,并能与他人协作和共享。一份反应真实需求的代码规范才能被人接收,而那些理想化的、甚至部分观点遭到程序员拒绝的代码规范注定不会长久——无论它有多出色。
这份规范由多个章节组成,每个章节包含一组相关的规则。我会尝试去描述每条规则背后的理念(过于明显的理念我就省略了)。
这些规则并不是我凭空想象的,它们出自于我作为一个专业软件开发工程师长久以来的工作积累,以及Clojure社区成员们的反馈和建议,还有各种广为流传的Clojure编程学习资源,如《Clojure Programming》、《The Joy of Clojure》等。
这份规范还处于编写阶段,部分章节有所缺失,内容并不完整;部分规则没有示例,或者示例还不能完全将其描述清楚。未来这些问题都会得到改进,只是请你了解这一情况。
你可以使用Transmuter生成一份本规范的PDF或HTML格式的文档。
本指南的翻译可在以下几种语言中:
几乎所有人都认为任何代码风格都是丑陋且难以阅读的,除了自己的之外。 把这句话中的“除了自己之外”去掉,那差不多就能成立了。
—— Jerry Coffin 关于代码缩进的评论
-
使用两个空格进行缩进,不使用制表符。 [链接]
-
使用2个空格来缩进含参数部分的形式, 。这些形式包括所有的
def
形式 ,特实形式和宏, 以及本地绑定形式 (例如:loop
,let
,when-let
) 和许多像when
,cond
,as->
,cond->
,case
,with-*
等的宏。 [链接];; 很好 (when something (something-else)) (with-out-str (println "Hello, ") (println "world!")) ;; 糟糕 - 四个空格 (when something (something-else)) ;; 糟糕 - 一个空格 (with-out-str (println "Hello, ") (println "world!"))
-
垂直排列函数参数。 [链接]
;; 很好 (filter even? (range 1 10)) ;; 糟糕 (filter even? (range 1 10))
-
使用一个空格缩进函数(宏)参数 当函数没有参数独占一行。 [链接]
;; 很好 (filter even? (range 1 10)) (or ala bala portokala) ;; 糟糕 - 两个空格缩进 (filter even? (range 1 10)) (or ala bala portokala)
-
对齐let绑定,以及map类型中的关键字。 [链接]
;; 很好 (let [thing1 "some stuff" thing2 "other stuff"] {:thing1 thing1 :thing2 thing2}) ;; 糟糕 (let [thing1 "some stuff" thing2 "other stuff"] {:thing1 thing1 :thing2 thing2})
-
针对没有文档字串的 defn,选择性忽略函数名与参数向量之间的新行。 [链接]
;; 很好 (defn foo [x] (bar x)) ;; 很好 (defn foo [x] (bar x)) ;; 糟糕 (defn foo [x] (bar x))
-
将一个多重方法的
dispatch-val
与函数名放置在同一行。 [链接];; 很好 (defmethod foo :bar [x] (baz x)) (defmethod foo :bar [x] (baz x)) ;; 糟糕 (defmethod foo :bar [x] (baz x)) (defmethod foo :bar [x] (baz x))
-
当为采用上述形式的函数添加字符串文档 - 注意正确应放置到函数名后面,而不是参数列表 后面。后者虽然不是无效语法,不会造成错误, 但是这仅仅只是将字符串作为函数体的一种形式,而不将其链接为 该变量的文档。 [链接]
;; 很好 (defn foo "docstring" [x] (bar x)) ;; 糟糕 (defn foo [x] "docstring" (bar x))
-
选择性忽略短的参数向量与函数体之间的新行。 [链接]
;; 很好 (defn foo [x] (bar x)) ;; 适合简单的函数 (defn foo [x] (bar x)) ;;适合包含多元参数列表的函数 (defn foo ([x] (bar x)) ([x y] (if (predicate? x) (bar x) (baz x)))) ;; 糟糕 (defn foo [x] (if (predicate? x) (bar x) (baz x)))
-
多元函数定义,各元数形式垂直对齐参数。 [链接]
;; 很好 (defn foo "I have two arities." ([x] (foo x 1)) ([x y] (+ x y))) ;; 糟糕 - 多出的缩进 (defn foo "I have two arities." ([x] (foo x 1)) ([x y] (+ x y)))
-
按照从少到多的参数,排序函数的多元数形式。多元素的情况下,共同 功能是某个k参数完全指定函数的 行为,并且元素个数 Ñ < K 部分地应用在K元数,和 元素 N> K提供在K元数超过可变参数的实现。 [链接]
;; 很好 - 很容易扫描第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)))
-
缩进多行的文档字串。 [链接]
;; 很好 (defn foo "Hello there. This is a multi-line docstring." [] (bar)) ;; 糟糕 (defn foo "Hello there. This is a multi-line docstring." [] (bar))
-
使用Unix风格的换行符(*BSD、Solaris、Linux、OSX用户无需设置,Windows用户则需要格外注意了) [链接]
- 如果你使用 Git ,你也许会想加入下面这个配置,来保护你的项目被 Windows 的行编码侵入:
bash$ git config --global core.autocrlf true
-
若有任何文字在左括号、中括号、大括号前(
(
,[
,{
),或是在右括号、中括号、大括号之后()
,]
,}
),将文字与括号用一个空格分开。反过来说,在左括号后、右括号前不要有空格。 [链接];; 很好 (foo (bar baz) quux) ;; 糟糕 (foo(bar baz)quux) (foo ( bar baz ) quux)
Syntactic sugar causes semicolon cancer.
-- Alan Perlis
-
不要在序列化的集合类型的字面常量语法里使用逗号。 [链接]
;; 很好 [1 2 3] (1 2 3) ;; 糟糕 [1, 2, 3] (1, 2, 3)
-
明智的使用逗号与换行来加强 map 的可读性。 [链接]
;; 很好 {:name "Bruce Wayne" :alter-ego "Batman"} ;; 很好, 且会增强可读性 {:name "Bruce Wayne" :alter-ego "Batman"} ;; 很好, 且较为紧凑 {:name "Bruce Wayne", :alter-ego "Batman"}
-
将所有尾括号放在同一行。 [链接]
;; 很好; 同一行 (when something (something-else)) ;; 糟糕; 不同行 (when something (something-else) )
-
顶层形式用空行间隔开来。 [链接]
;; 很好 (def x ...) (defn foo ...) ;; 糟糕 (def x ...) (defn foo ...)
一个例外是相关
def
分组在一起。;; 很好 (def min-rows 10) (def max-rows 20) (def min-cols 15) (def max-cols 30)
-
函数或宏定义中间不要放空行。一个例外,可制成以指示分组 比如发现成对结构
let
和cond
。 [链接] -
可行的场合下,避免每行超过 80 字符。 [链接]
-
避免尾随的空白。 [链接]
-
一个文件、一个命名空间。 [链接]
-
每个命名空间用
ns
形式开始,加上refer
、require
、use
以及import
。 [链接](ns examples.ns (:refer-clojure :exclude [next replace remove]) (:require [clojure.string :as s :refer [blank?]] [clojure.set :as set] [clojure.java.shell :as sh]) (:import java.util.Date java.text.SimpleDateFormat [java.util.concurrent Executors 链接edBlockingQueue]))
-
在
ns
宏中优先使用:require :as
胜于:require :refer
胜于:require :refer :all
. 优先使用:require
胜于:use
; 后者的形式应该是 考虑使用新的代码。 [链接];; 很好 (ns examples.ns (:require [clojure.zip :as zip])) ;; 很好 (ns examples.ns (:require [clojure.zip :refer [lefts rights])) ;; 可以接受的 (ns examples.ns (:require [clojure.zip :refer :all])) ;; 糟糕 (ns examples.ns (:use clojure.zip))
-
避免单段的命名空间。 [链接]
;; 很好 (ns example.ns) ;; 糟糕 (ns example)
-
避免使用过长的命名空间(不超过五段)。 [链接]
-
函数避免超过 10 行代码。事实上,大多数函数应保持在5行代码以内。 [链接]
-
参数列表避免超过 3 个或 4 个位置参数。 [链接]
-
避免向前引用。它们偶尔必要的,但这样的场合 实际上很罕见。 [链接]
-
避免使用操作命名空间的函数,像是:
require
与refer
。他们在 REPL 之外完全用不到。 [链接] -
使用declare实现向前引用。 [链接]
-
优先使用
map
这类高阶函数,而非loop/recur
。 [链接] -
优先使用前置、后置条件来检测函数参数和返回值。 [链接]
;; 很好 (defn foo [x] {:pre [(pos? x)]} (bar x)) ;; 糟糕 (defn foo [x] (if (pos? x) (bar x) (throw (IllegalArgumentException. "x must be a positive number!")))
-
不要在函数中定义变量。 [链接]
;; 非常糟糕 (defn foo [] (def x 5) ...)
-
本地变量名不应覆盖clojure.core中定义的函数。 [链接]
;; 糟糕 - 这样一来函数中调用`clojure.core/map`时就需要指定完整的命名空间了。 (defn foo [map] ...)
-
使用
alter-var-root
替代def
去改变变量的值。;; good (def thing 1) ; value of thing is now 1 ; do some stuff with thing (alter-var-root #'thing (constantly nil)) ; value of thing is now nil ;; bad (def thing 1) ; do some stuff with thing (def thing nil) ; value of thing is now nil
-
使用
seq
来判断一个序列是否为空(这个技巧有时候称为 *nil punning)。 [链接];; 很好 (defn print-seq [s] (when (seq s) (prn (first s)) (recur (rest s)))) ;; 糟糕 (defn print-seq [s] (when-not (empty? s) (prn (first s)) (recur (rest s))))
-
需要将序列转换向量,优先使用
vec
而不是into
。 [链接];; 很好 (vec some-seq) ;; 糟糕 (into [] some-seq)
-
使用
when
替代(if ... (do ...)
。 [链接];; 很好 (when pred (foo) (bar)) ;; 糟糕 (if pred (do (foo) (bar)))
-
使用
if-let
替代let
+if
。 [链接];; 很好 (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
。 [链接];; 很好 (when-let [result (foo x)] (do-something-with result) (do-something-more-with result)) ;; 糟糕 (let [result (foo x)] (when result (do-something-with result) (do-something-more-with result)))
-
使用
if-not
替代(if (not ...) ...)
。 [链接];; 很好 (if-not pred (foo)) ;; 糟糕 (if (not pred) (foo))
-
使用
when-not
替代(when (not ...) ...)
。 [链接];; 很好 (when-not pred (foo) (bar)) ;; 糟糕 (when (not pred) (foo) (bar))
-
使用
when-not
替代(if-not ... (do ...)
。 [链接];; 很好 (when-not pred (foo) (bar)) ;; 糟糕 (if-not pred (do (foo) (bar)))
-
使用
not=
替代(not (= ...))
。 [链接];; 很好 (not= foo bar) ;; 糟糕 (not (= foo bar))
-
使用
printf
替代(print (format ...))
。 [链接];; 很好 (printf "Hello, %s!\n" name) ;; 好 (println (format "Hello, %s!" name))
-
在做比较,请考虑, Clojure的函数
<
>
等,接受可变数量的参数的函数。 [链接];; 很好 (< 5 x 10) ;; 糟糕 (and (> x 5) (< x 10))
-
当匿名函数只有一个参数时,优先使用
%
,而非%1
。 [链接];; 很好 #(Math/round %) ;; 糟糕 #(Math/round %1)
-
当匿名函数有多个参数时,优先使用
%1
,而非%
。 [链接];; 很好 #(Math/pow %1 %2) ;; 糟糕 #(Math/pow % %2)
-
只有在必要的时候才使用匿名函数。 [链接]
;; 很好 (filter even? (range 1 10)) ;; 糟糕 (filter #(even? %) (range 1 10))
-
若函数体由一个以上形式组成,不要使用匿名函数。 [链接]
;; 很好 (fn [x] (println x) (* x 2)) ;; 糟糕 (你需要明确得使用到do) #(do (println %) (* % 2))
-
在特定情况下优先使用
complement
,而非匿名函数。 [链接];; 很好 (filter (complement some-pred?) coll) ;; 糟糕 (filter #(not (some-pred? %)) coll)
这个规则应该在函数有明确的反函数时忽略(如:even? 与 odd?)。
-
某些情况下可以用
comp
使代码更简洁。 [链接];; Assuming `(:require [clojure.string :as str])`... ;; 很好 (map #(str/capitalize (str/trim %)) ["top " " test "]) ;; 更好 (map (comp str/capitalize str/trim) ["top " " test "])
-
某些情况下可以用
partial
使代码更简洁。 [链接];; 很好 (map #(+ 5 %) (range 1 10)) ;; (或许) 更好 (map (partial + 5) (range 1 10))
-
当遇到嵌套调用时,建议使用
->
宏和->>
宏。 [链接];; 很好 (-> [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)))
-
当需要连续调用Java类的方法时,优先使用
..
,而非->
。 [链接];; 很好 (-> (System/getProperties) (.get "os.name")) ;; 更好 (.. System getProperties (get "os.name"))
-
在
cond
和condp
中,使用:else
来处理不满足条件的情况。 [链接];; 很好 (cond (< n 0) "negative" (> n 0) "positive" :else "zero")) ;; 糟糕 (cond (< n 0) "negative" (> n 0) "positive" true "zero"))
-
当比较的变量和方式相同时,优先使用
condp
,而非cond
。 [链接];; 很好 (cond (= x 10) :ten (= x 20) :twenty (= x 30) :forty :else :dunno) ;; 更好 (condp = x 10 :ten 20 :twenty 30 :forty :dunno)
-
当条件是常量时,优先使用
case
,而非cond
或condp
。 [链接];; 很好 (cond (= x 10) :ten (= x 20) :twenty (= x 30) :forty :else :dunno) ;; 更好 (condp = x 10 :ten 20 :twenty 30 :forty :dunno) ;; 最佳 (case x 10 :ten 20 :twenty 30 :forty :dunno)
-
如果不能在视觉上使用注释与空行两两分组提示,则在
cond
相关的的形式中,使用短形式。 [链接];; 很好 (cond (test1) (action1) (test2) (action2) :else (default-action)) ;; 还行 (cond ;; test case 1 (test1) (long-function-name-which-requires-a-new-line (complicated-sub-form (-> 'which-spans multiple-lines))) ;; test case 2 (test2) (another-very-long-function-name (yet-another-sub-form (-> 'which-spans multiple-lines))) :else (the-fall-through-default-case (which-also-spans 'multiple 'lines)))
-
某些情况下,使用
set
作为判断条件。 [链接];; 很好 (remove #{1} [0 1 2 3 4 5]) ;; 糟糕 (remove #(= % 1) [0 1 2 3 4 5]) ;; 很好 (count (filter #{\a \e \i \o \u} "mary had a little lamb")) ;; 糟糕 (count (filter #(or (= % \a) (= % \e) (= % \i) (= % \o) (= % \u)) "mary had a little lamb"))
-
使用
(inc x)
和(dec x)
替代(+ x 1)
和(- x 1)
。 [链接] -
使用
(pos? x)
、(neg? x)
、以及(zero? x)
替代(> x 0)
、(< x 0)
、和(= x 0)
。 [链接] -
Useinstead of a series of nestedinvocations. 使用
list*
替代内部嵌套多个cons
。 [链接]# 很好 (list* 1 2 3 [4 5]) # 糟糕 (cons 1 (cons 2 (cons 3 [4 5])))
-
进行Java交互时,优先使用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)
-
Use the compact metadata notation for metadata that contains only slots whose keys are keywords and whose value is boolean . 当元数据的键是关键字和值是
true
,使用紧凑形式标记元数据。 [链接];; 很好 (def ^:private a 5) ;; 糟糕 (def ^{:private true} a 5)
-
指出代码的私有部分。 [链接]
;; 很好 (defn- private-fun [] ...) (def ^:private private-var ...) ;; 糟糕 (defn private-fun [] ...) ; not private at all (defn ^:private private-fun [] ...) ; overly verbose (def private-var ...) ; not private at all
-
使用
@#'some.ns/var
形式,访问私有变量(如:为了测试)。 [链接] -
Be careful regarding what exactly do you attach metadata to. 小心你添加元数据的对象。 [链接]
;; 我们添加元数据到变量 `a`中 (def ^:private a {}) (meta a) ;=> nil (meta #'a) ;=> {:private true} ;; 我们添加元数据到空的hash-map中 (def a ^:private {}) (meta a) ;=> {:private true} (meta #'a) ;=> nil
编程中真正的难点只有两个:验证缓存的有效性;命名。 -- Phil Karlton
-
命名空间建议使用以下两种方式: [链接]
项目名称.模块名称
组织名称.项目名称.模块名称
-
对于命名空间中较长的元素,使用
lisp-case
格式,如(bruce.project-euler
)。 [链接] -
使用
lisp-case
格式来命名函数和变量。 [链接];; 很好 (def some-var ...) (defn some-fun ...) ;; 糟糕 (def someVar ...) (defn somefun ...) (def some_fun ...)
-
使用
CamelCase
来命名接口(protocol)、记录(record)、结构和类型(struct & type)。对于HTTP、RFC、XML等缩写,仍保留其大写格式。 [链接] -
对于返回布尔值的函数名称,使用问号结尾,(如: even?)。 [链接]
;; 很好 (defn palindrome? ...) ;; 糟糕 (defn palindrome-p ...) ; Common Lisp style (defn is-palindrome ...) ; Java style
-
The names of functions/macros that are not safe in STM transactions should end with an exclamation mark (e.g.
reset!
). 当方法或宏不能在STM中安全使用时,须以感叹号结尾,(如:reset!)。 [链接] -
命名类型转换函数时使用
->
,而非to
。 [链接];; 很好 (defn f->c ...) ;; 不够好 (defn f-to-c ...)
-
对于可供重绑定的变量(即动态变量),使用星号括起,(如:earmuffs)。 [链接]
;; good (def ^:dynamic *a* 10) ;; bad (def ^:dynamic a 10)
-
无需对常量名进行特殊的标识,因为所有的变量都应该是常量,除非有特别说明。 [链接]
-
对于解构过程中或参数列表中忽略的元素,使用
_
来表示。 [链接];; 很好 (let [[a b _ c] [1 2 3 4]] (println a b c)) (dotimes [_ 3] (println "Hello!")) ;; 糟糕 (let [[a b c d] [1 2 3 4]] (println a b d)) (dotimes [i 3] (println "Hello!"))
-
参考
clojure.core
中的命名规范,如pred
、coll
。 [链接]- 函数:
f
,g
,h
- 参数内容是一个函数n
- 整数,通常是一个表示大小的值index
,i
- 整数索引x
,y
- 数值xs
- 序列m
- 映射s
- 字符串re
- 正则表达式coll
- 集合pred
- 谓词闭包& more
- 可变参数xf
- xform, 一个转换器
- 宏:
expr
- 表达式body
- 宏的主体binding
- 一个向量,包含宏的绑定
- 函数:
用100种函数去操作同一种数据结构,要好过用10种函数操作10种数据结构。
-- Alan J. Perlis
-
避免使用列表(list)来存储数据(除非它真的就是你想要的)。 [链接]
-
优先使用关键字(keyword),而非普通的哈希键。 [链接]
;; 很好 {:name "Bruce" :age 30} ;; 糟糕 {"name" "Bruce" "age" 30}
-
编写集合时,优先使用字面的语法形式,而非构造函数。但是,在定义唯一值集合(set)时,只有当元素都是常量时才可使用字面语法,否则应使用构造函数。 [链接]
;; 很好 [1 2 3] #{1 2 3} (hash-set (func1) (func2)) ; 元素在运行时确定 ;; 糟糕 (vector 1 2 3) (hash-set 1 2 3) #{(func1) (func2)} ; 若(func1)和(func2)的值相等,则会抛出运行时异常。
-
避免使用数值索引来访问集合元素。 [链接]
-
优先使用关键字来获取哈希表(map)中的值。 [链接]
(def m {:name "Bruce" :age 30}) ;; 很好 (:name m) ;; 太过啰嗦 (get m :name) ;; 糟糕 - 可能抛出空指针异常 (m :name)
-
集合可以被用作函数。 [链接]
;; 很好 (filter #{\a \e \o \i \u} "this is a test") ;; 糟糕 - 不够美观
-
关键字可以被用作函数。 [链接]
((juxt :a :b) {:a "ala" :b "bala"})
-
只有在非常强调性能的情况下才可使用瞬时集合(transient collection)。 [链接]
-
避免使用Java集合。 [链接]
-
避免使用Java数组,除非遇到需要和Java类进行交互,或需要高性能地处理基本类型时才可使用。 [链接]
-
建议所有的IO操作都使用
io!
宏进行包装,以免不小心在事务中调用了这些代码。 [链接] -
避免使用
ref-set
。 [链接](def r (ref 0)) ;; 很好 (dosync (alter r + 5)) ;; 糟糕 (dosync (ref-set r 5))
-
控制事务的大小,(即事务所执行的工作越少越好)。 as small as possible. [链接]
-
避免出现短期事务和长期事务访问同一个引用(Ref)的情形。 [链接]
-
避免在事务中更新原子。 [链接]
-
尽量使用
swap!
,而不是reset!
。 [链接](def a (atom 0)) ;; 很好 (swap! a + 5) ;; 不够好 (reset! a 5)
-
优先使用
clojure.string
中提供的字符串操作函数,而不是Java中提供的或是自己编写的函数。 [链接];; 很好 (clojure.string/upper-case "bruce") ;; 糟糕 (.toUpperCase "bruce")
-
复用已有的异常类型,符合语言习惯的 Clojure 代码,当真的抛出异常时,会抛出标准类型的异常 (如:
java.lang.IllegalArgumentException
,java.lang.UnsupportedOperationException
,java.lang.IllegalStateException
,java.io.IOException
). [链接] -
优先使用
with-open
,而非finally
。 [链接]
-
如果可以用函数实现相同功能,不要编写一个宏。 [链接]
-
首先编写一个宏的用例,尔后再编写宏本身。 [链接]
-
尽可能将一个复杂的宏拆解为多个小型的函数。 [链接]
-
宏只应用于简化语法,其核心应该是一个普通的函数。 [链接]
-
使用语法转义(syntax-quote,即反引号),而非手动构造list。 [链接]
好的代码本身就是文档。因此在添加注释之前,先想想自己该如何改进代码,让它更容易理解。做到这一点后,再通过注释让代码更清晰。
-- Steve McConnell
-
学会编写容易理解的代码,然后忽略下文的内容。真的! [链接]
-
对于标题型的注释,使用至少四个分号起始。 [链接]
-
对于顶层注释,使用三个分号起始。 [链接]
-
为某段代码添加注释时,使用两个分号起始,且应与该段代码对齐。 [链接]
-
对于行尾注释,使用一个分号起始即可。 [链接]
-
分号后面要有一个空格。 [链接]
;;;; Frob Grovel ;;; 这段代码有以下前提: ;;; 1. Foo. ;;; 2. Bar. ;;; 3. Baz. (defn fnord [zarquon] ;; If zob, then veeblefitz. (quux zot mumble ; Zibblefrotz. frotz))
-
避免冗余的注释。 [链接]
;; 糟糕 (inc counter) ; increments counter by one
-
注释要和代码同步更新。过期的注释还不如没有注释。 at all. [链接]
-
有时,使用
#_
宏要优于普通的注释 [链接];; 很好 (+ foo #_(bar x) delta) ;; 糟糕 (+ foo ;; (bar x) delta)
好的代码和好的笑话一样,不需要额外的解释。
-- Russ Olsen
- 避免使用注释去描述一段写得很糟糕的代码。重构它,让它更为可读。(做或者不做,没有尝试这一说。--Yoda) [链接]
-
标识应该写在对应代码的上一行。 [链接]
-
标识后面是一个冒号和一个空格,以及一段描述文字。 [链接]
-
如果标识的描述文字超过一行,则第二行需要进行缩进。 [链接]
-
将自己姓名的首字母以及当前日期附加到标识描述文字中 [链接]
(defn some-fun [] ;; FIXME: 这段代码在v1.2.3之后偶尔会崩溃。 ;; 这可能和升级BarBazUtil有关。(xz 13-1-31) (baz))
-
对于功能非常明显,实在无需添加注释的情况,可以在行尾添加一个标识。 [链接]
(defn bar [] (sleep 100)) ; OPTIMIZE
-
使用
TODO
来表示需要后期添加的功能或特性。 [链接] -
使用
FIXME
来表示需要修复的问题。 [链接] -
使用
OPTIMIZE
来表示会引起性能问题的代码,并需要修复。 [链接] -
使用
HACK
来表示这段代码并不正规,需要在后期进行重构。 [链接] -
使用
REVIEW
来表示需要进一步审查这段代码,如:REVIEW: 你确定客户会正确地操作X吗?
[链接] -
可以使用其它你认为合适的标识关键字,但记得一定要在项目的
README
文件中描述这些自定义的标识。 [链接]
这里有一些由Clojure的社区创建的工具,可能会帮助你 在你努力写出地道的Clojure代码。
- Slamhound是一种能自动从你的现有的代码生成合适的
ns
声明。 - kibit 是一个用于Clojure的静态代码分析器。 core.logic 为代码搜索可能存在一个更惯用模式函数或宏。
本文中的所有内容都还没有最后定型,我很希望能够和所有对Clojure代码规范感兴趣的同仁一起编写此文,从而形成一份对社区有益的文档。
你可以随时创建讨论话题,或发送合并申请。我在这里提前表示感谢。
You can also support the style guide with financial contributions via gittip.
这项工作是根据 知识共享署名3.0 本地化许可协议 许可。
一份由社区驱动的代码规范如果得不到社区本身的支持和认同,那它就毫无意义了。微博转发这份指南,分享给你的朋友或同事。我们得到的每个注解、建议或意见都可以让这份指南变得更好一点。而我们想要拥有的是最好的指南,不是吗?
Cheers,
Bozhidar