译者:JuanitoFatas
转载:http://lisp.tw/2013/02/19/clojure-style-guide/
Clojure 风格指南
这篇 Clojure 风格指南向你推荐现实世界中的最佳实践,Clojure 程序员如何写出可被别的 Clojure 程序员维护的代码。一份风格指南反映出现实世界中的用法,并带有一个理想,避免已经公认是危险的事物被人继续使用,不管看起来是多么的好。
本指南依照相关规则分成数个小节。我尽力在规则之后说明理由(如果省略的话,我相信理由是显而易见的)。
我没有想到所有的规则 —– 他们大致上是基于,我作为一个专业软体工程师的广泛生涯,从 Clojure 社群成员所得到的反馈及建议,和数个高度评价的 Clojure 编程资源,像是 "Clojure Programming"以及 "The Joy of Clojure"。
本指南仍在完善中 –– 缺少某些章节,某些不完整,某些规则缺少例子,某些规则例子演示不够清楚。在完稿时,将会解决这些议题 –– 现在就先记在心上就好。
你可以使用 Transmuter 来产生本指南的一份 PDF 或 HTML 复本。
目录
- 组织源代码与排版
- 语法
- 命名
- 复合类型
- Mutation
- 字串
- 异常
- 宏
-
注解
- 注释
- 基本原则
组织源代码与排版
几乎每人都深信,每一个除了自己的风格都又丑又难读。
把 "除了自己的" 拿掉,他们或许是对的...
-- Jerry Coffin (论缩排)
-
每个缩排层级使用两个空格。不要使用 Hard Tabs。
;; good (when something (something-else)) ;; bad - 四个空格 (when something (something-else))
-
垂直排列函数参数。
;; good (filter even? (range 1 10)) ;; bad (filter even? (range 1 10))
-
排列
let
的绑定与 map 的关键字。;; good (let [thing1 "some stuff" thing2 "other stuff"] {:thing1 thing1 :thing2 thing2}) ;; bad (let [thing1 "some stuff" thing2 "other stuff"] {:thing1 thing1 :thing2 thing2})
-
针对没有文档字串的
defn
,选择性忽略函数名与参数向量之间的新行。;; good (defn foo [x] (bar x)) ;; good (defn foo [x] (bar x)) ;; bad (defn foo [x] (bar x))
-
选择性忽略短的参数向量与函数体之间的新行。
;; good (defn foo [x] (bar x)) ;; 短的函数这样写很好 (defn goo [x] (bar x)) ;; 多参数的函数这样写很好 (defn foo ([x] (bar x)) ([x y] (if (predicate? x) (bar x) (baz x)))) ;; bad (defn foo [x] (if (predicate? x) (bar x) (baz x)))
-
缩排多行的文档字串。
;; good (defn foo "Hello there. This is a multi-line docstring." [] (bar)) ;; bad (defn foo "Hello there. This is a multi-line docstring." [] (bar))
-
使用 Unix 风格的行编码(BSD/Solaris/Linux/OSX 的用户不用担心,,Windows 用户要格外小心。)
-
如果你使用 Git ,你也许会想加入下面这个配置,来保护你的项目被 Windows 的行编码侵入:
$ git config --global core.autocrlf true
-
-
若有任何文字在左括号、中括号、大括号前(
(
,[
,{
),或是在右括号、中括号、大括号之后()
,]
,}
),将文字与括号用一个空格分开。反过来说,在左括号后、右括号前不要有空格。;; good (foo (bar baz) quux) ;; bad (foo(bar baz)quux) (foo ( bar baz ) quux)
-
不要在循序的复合类型的字面常量语法里使用逗号。
;; good [1 2 3] (1 2 3) ;; bad [1, 2, 3] (1, 2, 3)
-
明智的使用逗号与断行来加强 map 的可读性。
;; good {:name "Bruce Wayne" :alter-ego "Batman"} ;; good and arguably a bit more readable {:name "Bruce Wayne" :alter-ego "Batman"} ;; good and arguably more compact {:name "Bruce Wayne", :alter-ego "Batman"}
-
将所有尾随括号放在同一行。
;; good (when something (something-else)) ;; bad (when something (something-else) )
-
顶层形式用空行间隔开来。
;; good (def x ...) (defn foo ...) ;; bad (def x ...) (defn foo ...)
-
函数或宏定义中间不要放空行。
- 可行的场合下,避免每行超过 80 字符。
- 避免尾随的空白。
- 一个文件、一个命名空间。
-
每个命名空间用
ns
形式开始,加上refer
、require
、use
以及import
。(ns examples.ns (:refer-clojure :exclude [next replace remove]) (:require (clojure [string :as string] [set :as set]) [clojure.java.shell :as sh]) (:use (clojure zip xml)) (:import java.util.Date java.text.SimpleDateFormat (java.util.concurrent Executors LinkedBlockingQueue)))
-
避免单段的命名空间。
;; good (ns example.ns) ;; bad (ns example)
-
避免使用过长的命名空间(也就是超过 5 段)
-
函数避免超过 10 行代码。多数函数应小于 5 行。
-
参数列表避免超过 3 个或 4 个位置参数(positional parameters)。
语法
- 避免使用操作命名空间的函数,像是:
require
与refer
。他们在 REPL 之外完全用不到。 - 使用
declare
来启用 forward references。 -
偏好像是
map
与loop/recur
的高阶函数。 -
函数体内偏好使用 pre 函数与 post 条件来检查。
;; good (defn foo [x] {:pre [(pos? x)]} (bar x)) ;; bad (defn foo [x] (if (pos? x) (bar x) (throw (IllegalArgumentException "x must be a positive number!")))
-
不要在函数内定义变量。
;; very bad (defn foo [] (def x 5) ...)
-
不要用局域绑定遮蔽
clojure.core
内的名称。;; bad - you're forced to used clojure.core/map fully qualified inside (defn foo [map] ...)
-
使用
seq
作为终止条件来测试序列是否为空(这个技巧有时候称为 nil punning)。;; good (defn print-seq [s] (when (seq s) (prn (first s)) (recur (rest s)))) ;; bad (defn print-seq [s] (when-not (empty? s) (prn (first s)) (recur (rest s))))
-
用
when
取代(if ... (do ...)
。;; good (when pred (foo) (bar)) ;; bad (if pred (do (foo) (bar)))
-
用
if-let
取代let
+if
。;; good (if-let [result :foo] (something-with result) (something-else)) ;; bad (let [result :foo] (if result (something-with result) (something-else)))
-
用
when-let
取代let
+when
。;; good (when-let [result :foo] (do-something-with result) (do-something-more-with result)) ;; bad (let [result :foo] (when result (do-something-with result) (do-something-more-with result)))
-
用
if-not
取代(if (not ...) ...)
。;; good (if-not (pred) (foo)) ;; bad (if (not pred) (foo))
-
用
when-not
取代(when (not ...) ...)
。;; good (when-not pred (foo) (bar)) ;; bad (when (not pred) (foo) (bar))
-
用
not=
取代(not (= ...))
。;; good (not= foo bar) ;; bad (not (= foo bar))
-
偏好
%
胜于%1
在只有一个参数的函数字面常量。;; good #(Math/round %) ;; bad #(Math/round %1)
-
偏好
%1
胜于%
在超过一个参数的函数字面常量。;; good #(Math/pow %1 %2) ;; bad #(Math/pow % %2)
-
不要在不必要的情况用匿名函数包装函数。
;; good (filter even? (range 1 10)) ;; bad (filter #(even? %) (range 1 10))
-
若函数体由一个以上形式组成,不要使用函数的字面常量语法。
;; good (fn [x] (println x) (* x 2)) ;; bad (you need an explicit do form) #(do (println %) (* % 2))
-
complement
与使用匿名函数相比,喜好使用前者。;; good (filter (complement some-pred?) coll) ;; bad (filter #(not (some-pred? %)) coll)
这个规则应该在函数有明确的反函数时忽略(如:
even?
与odd?
)。 -
在可以产生更简洁代码的情况时利用
comp
。;; good (map #(capitalize (trim %)) ["top " " test "]) ;; better (map (comp capitalize trim) ["top " " test "])
-
在可以产生更简洁代码的情况时利用
partial
。;; good (map #(+ 5 %) (range 1 10)) ;; (arguably) better (map (partial + 5) (range 1 10))
-
偏好使用 threading macros
->
(thread-first)及->>
(thread-last)来简化嵌套形式。;; good (-> [1 2 3] reverse (conj 4) prn) ;; not as good (prn (conj (reverse [1 2 3]) 4)) ;; good (->> (range 1 10) (filter even?) (map (partial * 2))) ;; not as good (map (partial * 2) (filter even? (range 1 10)))
-
当连锁调用 Java interop 的方法时,偏好
..
胜于->
。;; good (-> (System/getProperties) (.get "os.name")) ;; better (.. System getProperties (get "os.name"))
-
在
cond
与condp
使用:else
作为最后的测试表达式。;; good (cond (< n 0) "negative" (> n 0) "positive" :else "zero")) ;; bad (cond (< n 0) "negative" (> n 0) "positive" true "zero"))
-
当谓词与表达式不变时,偏好用
condp
来取代cond
。;; good (cond (= x 10) :ten (= x 20) :twenty (= x 30) :forty :else :dunno) ;; much better (condp = x 10 :ten 20 :twenty 30 :forty :dunno)
-
当测试表达式是编译期时间常量时,偏好使用
case
取代cond
或condp
。;; good (cond (= x 10) :ten (= x 20) :twenty (= x 30) :forty :else :dunno) ;; better (condp = x 10 :ten 20 :twenty 30 :forty :dunno) ;; best (case x 10 :ten 20 :twenty 30 :forty :dunno)
-
适当的时机下使用
set
作为谓词。;; bad (remove #(= % 0) [0 1 2 3 4 5]) ;; good (remove #{0} [0 1 2 3 4 5]) ;; bad (count (filter #(or (= % \a) (= % \e) (= % \i) (= % \o) (= % \u)) "mary had a little lamb")) ;; good (count (filter #{\a \e \i \o \u} "mary had a little lamb"))
-
使用
(inc x)
&(dec x)
而不是(+ x 1)
and(- x 1)
。 -
使用
(pos? x)
,(neg? x)
&(zero? x)
而不是(> x 0)
,(< x 0)
&(= x 0)
。 -
使用包装好的 Java interop 形式。
;;; object creation ;; good (java.util.ArrayList. 100) ;; bad (new java.util.ArrayList 100) ;;; static method invocation ;; good (Math/pow 2 10) ;; bad (. Math pow 2 10) ;;; instance method invocation ;; good (.substring "hello" 1 3) ;; bad (. "hello" substring 1 3) ;;; static field access ;; good Integer/MAX_VALUE ;; bad (. Integer MAX_VALUE) ;;; instance field access ;; good (.someField some-object) ;; bad (. some-object some-field)
命名
程式设计的真正难题是替事物命名及无效的缓存。
-- Phil Karlton
- 遇到给命名空间取名时,偏好下列两种架构:
project.module
organization.project.module
- 多段的命名空间,使用
lisp-case
。 - 函数与变量名使用
lisp-case
。 - 协议、记录、结构及类型使用驼峰形式(专有缩略词保持大写:HTTP、RFC、XML)
- 谓词方法的名字(返回布尔值的方法)以问号结尾(例:
even?
)。 - STM 事务里不安全的函数、宏的名字以惊叹号结尾(例:
reset!
)。 -
conversation 函数使用
->
取代to
。;; good (defn f->c ...) ;; not so good (defn f-to-c ...)
-
使用
*earmuffs*
耳套(星号)给将会重新绑定的东西(也就是动态的)。 - 常量不要使用特殊的表示法;除非特别说明,假设一切都是常量。
- Use
_
for destructuring targets and formal arguments names whose value will be ignored by the code at hand. - 惯用名遵循
clojure.core
的范例,如pred
与coll
。- 函数:
-
f
,g
,h
- 函数输入 -
n
- 整数输入(通常是大小) -
index
- 整数索引 -
x
,y
- 数字 -
s
- 字串输入 -
coll
- 复合类型 -
pred
- 谓词闭包 -
& more
- 可变输入
-
- 宏:
-
expr
- 表达式 -
body
- 宏的主体 -
binding
- 宏的绑定向量
-
- 函数:
复合类型
单数据结构与百个函数,好过十个函数与数据结构
-- Alan J. Perlis
- 避免使用列表来储存通用的数据(除非列表正是你所需要的)。
-
哈希键偏好使用关键字。
;; good {:name "Bruce" :age 30} ;; bad {"name" "Bruce" "age" 30}
-
在允许的场合下,偏好使用复合类型的字面常量语法。但在定义集合时,当数值为编译期时间常量时,仅使用字面常量语法。
;; good [1 2 3] #{1 2 3} (hash-set (func1) (func2)) ; values determined at runtime ;; bad (vector 1 2 3) (hash-set 1 2 3) #{(func1) (func2)} ; will throw runtime exception if (func1) = (func2)
-
在任何情况下避免通过索引来访问复合类型的成员。
-
在允许的场合下,偏好使用作为关键字的函数来从 map 取出数值。
(def m {:name "Bruce" :age 30}) ;; good (:name m) ;; bad - 太罗嗦 (get m :name) ;; bad - 有 NullPointerException 之虞 (m :name)
-
利用多数复合类型是其元素的函数这个事实。
;; good (filter #{\a \e \o \i \u} "this is a test") ;; 差劲 - 烂到不敢给你看
-
利用关键字可以当作复合类型的函数这个事实。
((juxt :a :b) {:a "ala" :b "bala"})
-
避免使用过渡的复合类型,除非在攸关性能的部分代码使用。
-
避免使用 Java 的 collections。
-
除了 interop 与攸关性能的代码(大量处理原生类型的代码)外,避免使用 Java 的数组。
Mutation
Refs
- 考虑看看将所有带有
io!
宏的 IO 调用包起来,来避免在事务中不小心调用到这些代码。 - 无论何时都避免使用
ref-set
。 - 试著使事务的大小(封装在事务里的工作量)越小越好。
- 避免有短期、长期与同一个 Ref 互动的事务。
Agents
- 仅针对 CPU 绑定或不阻塞 IO、其他线程的动作使用
send
。 - 给看起来可能会阻塞、睡眠或阻碍线程的动作使用
send-off
。
原子
- 避免在 STM 事务里更新原子。
- 无论何时都避免使用
reset!
。
字串
-
偏好使用
clojure.string
里定义的字串操作函数,而不是 Java interop,或是自己写。;; good (clojure.string/upper-case "bruce") ;; bad (.toUpperCase "bruce")
异常
- 重用现有的异常类型。符合语言习惯的 Clojure 代码,当真的抛出异常时,会抛出标准类型的异常(如
java.lang.IllegalArgumentException
、java.lang.UnsupportedOperationException
、java.lang.IllegalStateException
、java.io.IOException
)。 - 偏好使用
with-open
胜于finally
。
宏
- 不要在函数可以办到的情况下使用宏。
- 先撰写宏的用途的示例子,再开始撰写宏。
- 不管是什么时候,只要可能的话,将复杂的宏拆成较小的函数。
- 宏应该仅作为提供语法糖的功能,其核心为清晰的函数。这么做会改善可组合性 (composability)。
- 偏好引用形式语法胜于手动构造列表
注解
良好的代码是最佳的文档。当你要加一个注释时,扪心自问,
"如何改善代码让它不需要注释?" 改善代码然后记录下来使它更简洁。
-- Steve McConnell
-
撰写本身即文档的代码并忽略本节。我是认真的!
-
至少用四个分号来写标题注解。
-
用三个分号来写顶层级别的注解。
-
使用两个分号来给一段代码写注解,分号放在代码之前。
-
使用一个分号来写加注式的注解。
-
分号与文字之间至少有一个空格。
;;;; 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))
-
注解是完整的句子时,应该将第一个字大写,并用一个句号结束注解。普遍来说,使用正确的标点符号。句与句之间用一个空白隔开。
-
避免多余的注解。
;; bad (inc counter) ; increments counter by one
-
持续更新注解。过时的注解比没有注解还糟糕。
-
当你需要注解一个特定的形式时,偏好使用
#_
读取宏胜于一般的注解。;; good (+ foo #_(bar x) delta) ;; bad (+ foo ;; (bar x) delta)
好代码就像是好的笑话 - 它不需要解释
-- Russ Olsen
- 避免撰写注解来解释糟糕的代码。重构代码使其一目了然 (要嘛就做,要嘛不做 –― 不要只是试试看。–– Yoda)
注释
- 注释通常会直接写在相关代码的那行后面。
- 注释关键字后面接著一个冒号与空格,接著是描述问题的说明。
- 如果描述问题需要多行时,之后的行需与第一行对齐。
-
将注释打上名字缩写与日期标签,这样之后才可轻松识别出来。
(defn some-fun [] ;; FIXME: This has crashed occasionally since v1.2.3. It may ;; be related to the BarBazUtil upgrade. (xz 13-1-31) (baz))
-
在问题简单到任何文档都会显得冗余的情况下,可在最后一行留下注释。这种用途是个例外,而不是个规则。
(defn bar [] (sleep 100)) ; OPTIMIZE
- 使用
TODO
来标记之后应被加入的未实现功能或特色。 - 使用
FIXME
来标记一个需要修复的代码。 - 使用
OPTIMIZE
来标记可能影响性能的缓慢或效率低落的代码。 - 使用
HACK
来标记代码异味,其中包含了可疑的编码实践以及应该需要重构。 - 使用
REVIEW
来标记任何需要审视及确认正常动作的地方。举例来说:REVIEW: 我们确定用户现在是这么做的吗?
- 如果你觉得适当的话,使用其他习惯的注解关键字,但记得把它们记录在项目的
README
或类似的地方。
基本原则
- 用函数式风格来编程。适当的避免 mutation。
- 保持一致。在理想的世界里,与这些准则保持一致。
- 使用常识。
贡献
在本指南所写的每个东西都不是定案。这只是我渴望想与同样对 Clojure 编程风格有兴趣的大家一起工作,以致于最终我们可以替整个 Clojure 社群创造一个有益的资源。
欢迎开票或发送一个带有改进的更新请求。在此提前感谢你的帮助!
口耳相传
一份社群策动的风格指南,对一个社群来说,只是让人知道有这个社群。微博转发这份指南,分享给你的朋友或同事。我们得到的每个注解、建议或意见都可以让这份指南变得更好一点。而我们想要拥有的是最好的指南,不是吗?
相关推荐
《Clojure编程语言的社区编码风格指南》是Clojure开发者们共同制定的一份规范文档,旨在提高代码的可读性和可维护性,促进团队间的协作。这份风格指南涵盖了Clojure编程中的命名规则、代码结构、函数设计、宏的使用...
- **函数式编程**:Clojure强调函数式编程风格,包括高阶函数、纯函数和惰性序列。例如,`map`、`filter`和`reduce`等函数是Clojure中的核心工具。 - **宏**:Clojure的宏允许在编译时生成代码,这对于编写元编程或...
《Clojure编程语言的社区编码风格指南》是Clojure开发者们共同制定的一份中文版规范文档,旨在提高代码的可读性、可维护性和一致性。这份指南覆盖了从命名约定、函数定义、数据结构处理到代码组织等多个方面,是...
《Clojure风格指南:社区共创的编程规范》 Clojure是一种功能强大的动态编程语言,它在Lisp家族中占据一席之地,以其简洁的语法、强大的数据处理能力和并发支持受到开发者喜爱。为了确保代码的可读性、可维护性和...
《Programming Clojure》不仅是一本优秀的学习指南,还是一本宝贵的参考手册,适合所有希望在JVM上构建高效、可扩展应用程序的开发者阅读。无论是初学者还是有经验的开发者,都能从中获得启发和收获。
- **函数式编程**:探讨 Clojure 如何支持纯函数式编程风格,包括高阶函数、闭包和惰性求值等概念。 2. **进阶篇**:这部分将深入探讨更复杂的 Clojure 数据分析技术和工具。 - **数据分析流程**:详细介绍数据...
《Living Clojure》是一本专为初学者设计的Clojure编程语言指南,旨在帮助读者快速掌握这门功能强大的 Lisp 风格的编程语言。Clojure 是一种静态类型的、基于 JVM 的编程语言,强调并发性、内存安全和声明式编程风格...
《The Joy of Clojure》这本书并非针对初学者的编程指南,而是为那些已经具备一定编程基础(特别是Java或函数式编程语言如Lisp的经验)的程序员提供了一次深入探索Clojure编程语言精髓的机会。本书的核心在于介绍...
《Clojure样式指南》是为Clojure编程语言提供的一份重要的参考文档,旨在规范和提升代码的可读性、可维护性和团队协作效率。这份俄文翻译版使得更多的俄语开发者能够理解和遵循最佳实践,从而编写出更加优雅、一致的...
使用HikariCP的Clojure包装器,开发者可以享受到HikariCP的高性能优势,同时保持Clojure的代码风格和便利性。例如,Clojure的宏和函数式编程特性可以让数据库连接池的配置变得简洁且易于维护。此外,由于Clojure与...
编码样式约定和标准编码样式约定和标准的精选清单。目录编程语言Arduino的。 。 。C 。 。 。C# 。 。 。 教程的C#样式指南。...Clojure -Clojure编程语言的社区... 官方Java样式指南-raywenderlich.com的官方Java样式指
对于想要研究这个系统的开发者,需要具备Clojure语言基础,理解Lisp风格的语法和函数式编程思想。同时,熟悉Java和JVM生态系统也会大有帮助。 9. **部署与运行**: 部署可能涉及到构建工具如Leiningen或Boot,将...
它不仅为有经验的面向对象编程程序员提供了一个如何采用函数式编程风格的指南,而且还能够帮助他们从熟悉的设计模式中过渡出来。 函数式编程模式在Scala与Clojure中有着不同的实现,但它们都遵循一些共同的原则。...
指南-Java开发人员指南。JavaScript -社区驱动的AngularJS应用程序开发的最佳实践集。 -易于阅读的JS最佳实践,可接受的编码标准和Web链接的快速参考。 -该文档是GoogleJavaScript编程语言源代码编码标准的完整定义...
6. **Clojure风格指南和最佳实践**: - 代码规范:遵循Clojure社区的代码风格和命名约定,提高代码可读性和团队协作效率。 - 重构技巧:在Spacemacs中利用内置工具进行代码重构,提升代码质量。 7. **Spacemacs...
JVM 上功能强大的指南。 并行、实用、强大、轻松、流畅、无摩擦、无止境、开放、有趣设置自己并开始使用 REPL — 工具和开发、设置、初始化启动首先安装 Leiningen 做对了) 然后设置你的IDE。 Emacs 是非常流行的...
`cljstyle` 遵循了这些社区的约定,如 `clojure.style`,它为 Clojure 开发者提供了一份详尽的代码风格指南,包括命名规则、缩进、空格使用等各个方面。 **使用 cljstyle** 安装 `cljstyle` 很简单,通常可以通过 ...
学习Clojure动机这是一个专用的回购协议,旨在帮助激励成员以及其他任何人学习编程语言Clojure。 此仓库的约定是让贡献者在其github句柄下的文件夹下命名其研究和贡献的名称空间。... ”风格指南教学大纲
一个很棒的风格指南列表。 该列表分为编程语言、架构、平台、框架等类别。 提示:您只需按Command + F或Ctrl + F即可查找关键字。 内容 安卓 AngularJS 自动售货机 主干.js C 咖啡脚本 总价 夏普 Clojure CommonLisp...