项目结构与实践 #
一、项目结构 #
1.1 标准目录结构 #
text
my-project/
├── project.clj
├── README.md
├── resources/
│ ├── public/
│ └── config.edn
├── src/
│ └── my_project/
│ ├── core.clj
│ ├── utils.clj
│ └── handlers/
│ └── api.clj
└── test/
└── my_project/
├── core_test.clj
└── utils_test.clj
1.2 目录说明 #
| 目录 | 用途 |
|---|---|
src/ |
源代码 |
test/ |
测试代码 |
resources/ |
资源文件 |
dev/ |
开发工具代码 |
doc/ |
文档 |
1.3 命名空间与文件 #
text
src/
└── my_project/
├── core.clj -> my-project.core
├── utils.clj -> my-project.utils
└── handlers/
├── api.clj -> my-project.handlers.api
└── web.clj -> my-project.handlers.web
二、命名空间设计 #
2.1 命名空间声明 #
clojure
(ns my-project.core
"Core functionality of the project."
(:require [clojure.string :as str]
[my-project.utils :as utils])
(:import [java.util Date])
(:gen-class))
2.2 require风格 #
clojure
(ns my-project.core
(:require [clojure.string :as str]
[clojure.set :refer [union intersection]]
[my-project.utils :as u]))
(str/upper-case "hello")
(union #{1 2} #{2 3})
(u/helper)
2.3 避免过度引用 #
clojure
(ns my-project.core
(:require [clojure.string :as str]))
(str/split "a,b,c" #",")
(ns my-project.core
(:require [clojure.string :refer [split upper-case lower-case]]))
(split "a,b,c" #",")
2.4 命名空间组织 #
clojure
(ns my-project.core
(:require [my-project.config :as config]
[my-project.db :as db]
[my-project.api :as api]
[my-project.utils :as utils]))
(defn -main []
(config/load!)
(db/connect!)
(api/start!))
三、代码风格 #
3.1 缩进 #
clojure
(defn my-function
[arg1 arg2]
(let [result (+ arg1 arg2)]
(when (pos? result)
(println "Result:" result)
result)))
(defn another-function
[x]
(if (even? x)
"even"
"odd"))
3.2 函数定义 #
clojure
(defn function-name
"Doc string describing the function."
[param1 param2]
(body))
(defn multi-arity
([x] (multi-arity x 1))
([x y] (+ x y)))
3.3 let绑定 #
clojure
(let [a 1
b 2
c (+ a b)]
(* c 2))
3.4 线程宏 #
clojure
(-> value
function1
(function2 arg)
function3)
(->> collection
(map function1)
(filter predicate)
(reduce +))
3.5 条件表达式 #
clojure
(if condition
then-expr
else-expr)
(when condition
expr1
expr2)
(cond
condition1 expr1
condition2 expr2
:else default)
四、测试实践 #
4.1 测试文件组织 #
clojure
(ns my-project.core-test
(:require [clojure.test :refer [deftest is testing]]
[my-project.core :as core]))
(deftest add-test
(testing "basic addition"
(is (= 4 (core/add 2 2))))
(testing "edge cases"
(is (= 0 (core/add 0 0)))
(is (= -1 (core/add 1 -2)))))
4.2 测试断言 #
clojure
(deftest assertions-test
(is (= 1 1))
(is (true? true))
(is (thrown? Exception (throw (Exception. "error"))))
(is (instance? String "hello"))
(is (.startsWith "hello" "hel")))
4.3 测试夹具 #
clojure
(use-fixtures :each
(fn [f]
(setup-db)
(f)
(cleanup-db)))
(deftest with-fixture
(is (db/connected?)))
4.4 属性测试 #
clojure
(require '[clojure.test.check :as tc]
'[clojure.test.check.generators :as gen]
'[clojure.test.check.properties :as prop])
(def prop-add-commutative
(prop/for-all [a gen/int
b gen/int]
(= (core/add a b)
(core/add b a))))
(tc/quick-check 100 prop-add-commutative)
五、错误处理 #
5.1 异常处理 #
clojure
(defn safe-divide [a b]
(try
(/ a b)
(catch ArithmeticException e
:division-by-zero)
(catch Exception e
(throw (ex-info "Division failed"
{:a a :b b}
e)))))
5.2 自定义异常 #
clojure
(defn validate-input [data]
(when (empty? data)
(throw (ex-info "Input cannot be empty"
{:input data}))))
(defn process [data]
(try
(validate-input data)
(do-work data)
(catch clojure.lang.ExceptionInfo e
(let [{:keys [input]} (ex-data e)]
(handle-error input)))))
5.3 Either模式 #
clojure
(defn either [value]
(if (instance? Exception value)
{:error value}
{:ok value}))
(defn bind [result f]
(if (:ok result)
(f (:ok result))
result))
(defn safe-parse [s]
(try
{:ok (Integer/parseInt s)}
(catch Exception e
{:error e})))
(-> (safe-parse "42")
(bind #(hash-map :ok (* % 2))))
六、配置管理 #
6.1 配置文件 #
resources/config.edn:
clojure
{:database {:host "localhost"
:port 5432
:name "mydb"}
:server {:port 3000
:mode :dev}}
6.2 加载配置 #
clojure
(ns my-project.config
(:require [clojure.edn :as edn]
[clojure.java.io :as io]))
(defn load-config []
(-> "config.edn"
io/resource
slurp
edn/read-string))
(def config (load-config))
6.3 环境变量 #
clojure
(defn env [key default]
(or (System/getenv key) default))
(def db-host (env "DB_HOST" "localhost"))
(def db-port (Integer/parseInt (env "DB_PORT" "5432")))
七、日志记录 #
7.1 使用tools.logging #
clojure
(ns my-project.core
(:require [clojure.tools.logging :as log]))
(defn process [data]
(log/info "Processing data:" data)
(try
(let [result (do-work data)]
(log/debug "Result:" result)
result)
(catch Exception e
(log/error e "Processing failed")
(throw e))))
7.2 日志级别 #
clojure
(log/trace "Very detailed info")
(log/debug "Debug information")
(log/info "General information")
(log/warn "Warning message")
(log/error "Error message")
八、性能优化 #
8.1 类型提示 #
clojure
(defn ^String process [^String s]
(.toUpperCase s))
(defn add [^Long a ^Long b]
(+ a b))
8.2 避免反射 #
clojure
(set! *warn-on-reflection* true)
(defn bad [s]
(.length s))
(defn good [^String s]
(.length s))
8.3 惰性序列 #
clojure
(defn process-large-data [data]
(->> data
(map expensive-op)
(filter valid?)
(take 100)))
九、最佳实践总结 #
9.1 代码组织 #
- 一个命名空间一个职责
- 相关函数放在一起
- 公共API放在顶层
9.2 命名约定 #
- 使用kebab-case
- 谓词函数以
?结尾 - 副作用函数以
!结尾 - 私有函数使用
defn-
9.3 文档 #
clojure
(defn my-function
"Brief description of the function.
Parameters:
x - description of x
y - description of y
Returns:
Description of return value.
Example:
(my-function 1 2) => 3"
[x y]
(+ x y))
9.4 测试原则 #
- 测试公共API
- 边界条件测试
- 错误情况测试
- 保持测试简单
十、总结 #
项目实践要点:
| 方面 | 建议 |
|---|---|
| 结构 | 遵循标准目录结构 |
| 命名空间 | 单一职责,清晰命名 |
| 代码风格 | 一致的缩进和格式 |
| 测试 | 覆盖公共API |
| 错误处理 | 使用ex-info |
| 配置 | 外部化配置 |
| 日志 | 使用tools.logging |
关键点:
- 遵循标准项目结构
- 命名空间职责单一
- 保持代码风格一致
- 编写充分的测试
- 外部化配置和日志
Clojure完全指南系列完成!
最后更新:2026-03-27