Giter VIP home page Giter VIP logo

clojure-style-guide's Introduction

Clojure 代码规范

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 形式开始,加上 referrequireuse 以及 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 个位置参数。 [链接]

  • 避免向前引用。它们偶尔必要的,但这样的场合 实际上很罕见。 [链接]

语法

  • 避免使用操作命名空间的函数,像是:requirerefer。他们在 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"))
  • condcondp 中,使用 :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 ,而非 condcondp[链接]

    ;; 很好
    (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 中的命名规范,如 predcoll[链接]

    • 函数:
      • fgh - 参数内容是一个函数
      • 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类进行交互,或需要高性能地处理基本类型时才可使用。 [链接]

可变

引用(Refs)

  • 建议所有的IO操作都使用 io! 宏进行包装,以免不小心在事务中调用了这些代码。 [链接]

  • 避免使用 ref-set[链接]

    (def r (ref 0))
    
    ;; 很好
    (dosync (alter r + 5))
    
    ;; 糟糕
    (dosync (ref-set r 5))
  • 控制事务的大小,(即事务所执行的工作越少越好)。 as small as possible. [链接]

  • 避免出现短期事务和长期事务访问同一个引用(Ref)的情形。 [链接]

代理(Agents)

  • send 仅使用于计算密集型、不会因IO等因素阻塞的线程。 [链接]

  • send-off 则用于会阻塞、休眠的线程。 [链接]

原子(Atoms)

  • 避免在事务中更新原子。 [链接]

  • 尽量使用 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))
  • 对于完整的句子的注释,句首字母应该大写,句与句之间用一个空格分隔。 one space. [链接]

  • 避免冗余的注释。 [链接]

    ;; 糟糕
    (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.

Support via Gittip

证书

创作共用许可 这项工作是根据 知识共享署名3.0 本地化许可协议 许可。

宣传

一份由社区驱动的代码规范如果得不到社区本身的支持和认同,那它就毫无意义了。微博转发这份指南,分享给你的朋友或同事。我们得到的每个注解、建议或意见都可以让这份指南变得更好一点。而我们想要拥有的是最好的指南,不是吗?

Cheers,
Bozhidar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.