-
clojure是一门动态类型的语言,在类型检查方面并没有c++/java 这种静态类型语言好用,
所以多个模块之间进行接口参数传递时,由于接口文档设计不严谨等原因,
总会发生接口参数类型错误,参数个数不正确等问题,给代码调试带来很大的挑战,
因此在clojure 中,对接口参数的进行类型和范围的检查是非常必要的。
为此,我们找到了clojure.spec这个库(https://clojure.github.io/clojure/branch-master/clojure.spec-api.html),
正好可以解决以上问题。 -
原来spec 只要在任何地方 s/def 任何地方都可以用. 因为都注册在了 (s/registry)
体会到了类型的好处(以前主要是体会感觉到不灵活,不足). 但如果没类型,每个函数都要去手动写代码校验. 这个也很痛苦和低效.
现在用spec 规定好每个参数的具体类型.虽然僵化了点. 但对团队规范/程序正确性来说,提升了一大步.值得. -
spec 的另一个核心作用在于 配合 test.check 生成测试数据. 并且spec本身依赖 test.check
(s/exercise-fn `slarp) ;非常强大的功能. 能把输入参数和结果 用结构化数据输出来. (注意:自动! good)
(stest/check 'slarp) -
要用好工具,首先必须要学习使用工具,并且不断重复锻炼提升水平. 工具才能发挥作用.
实例 ring-spec
(ns ring.core.spec
(:require [clojure.spec.alpha:as s]
[clojure.spec.gen.alpha:as gen]
[clojure.string:as str]
[ring.core.protocols:as p]))
(defn- lower-case? [s]
(= s (str/lower-case s)))
(defn- trimmed? [s]
(= s (str/trim s)))
(defn- char-range [a b]
(map char (range (int a) (inc (int b)))))
(def ^:private lower-case-chars
(set (char-range \a \z)))
(def ^:private alphanumeric-chars
(set (concat (char-range \A \Z) (char-range \a \z) (char-range \0 \9))))
(def ^:private uri-chars
(into alphanumeric-chars #{\- \. \_ \~ \/ \+ \,}))
(def ^:private field-name-chars
(into alphanumeric-chars #{\! \# \$ \% \& \' \* \+ \- \. \^ \_ \` \| \~}))
(def ^:private whitespace-chars
#{(char 0x09) (char 0x20)})
(def ^:private visible-chars
(set (map char (range 0x21 (inc 0x7e)))))
(def ^:private obs-text-chars
(set (map char (range 0x80 (inc 0xff)))))
(def ^:private field-value-chars*
(into whitespace-chars visible-chars))
(def ^:private field-value-chars
(into field-value-chars* obs-text-chars))
(defn- field-name-chars? [s]
(every? field-name-chars s))
(defn- field-value-chars? [s]
(every? field-value-chars s))
(defn- gen-string [chars]
(gen/fmap str/join (gen/vector (gen/elements chars))))
(defn- gen-query-string []
(->> (gen/tuple (gen/not-empty (gen/string-alphanumeric)) (gen-string uri-chars))
(gen/fmap (fn [[k v]] (str k"=" v)))
(gen/vector)
(gen/fmap #(str/join "&" %))))
(defn- gen-method []
(gen/fmap keyword (gen/not-empty (gen-string lower-case-chars))))
(defn- gen-input-stream []
(gen/fmap #(java.io.ByteArrayInputStream. %) (gen/bytes)))
(defn- gen-exception []
(gen/fmap (fn [s] (Exception. s)) (gen/string-alphanumeric)))
;; Internal
(s/def :ring.core/error
(-> #(instance? Throwable %) (s/with-gen gen-exception)))
(s/def :ring.http/field-name
(-> (s/and string? not-empty field-name-chars?)
(s/with-gen #(gen/not-empty (gen-string field-name-chars)))))
(s/def :ring.http/field-value
(-> (s/and string? field-value-chars? trimmed?)
(s/with-gen #(gen/fmap str/trim (gen-string field-value-chars*)))))
;; Request
(s/def :ring.request/server-port (s/int-in 1 65535))
(s/def :ring.request/server-name string?)
(s/def :ring.request/remote-addr string?)
(s/def :ring.request/uri
(-> (s/and string? #(str/starts-with? %"/"))
(s/with-gen (fn [] (gen/fmap #(str "/" %) (gen-string uri-chars))))))
(s/def :ring.request/query-string
(s/with-gen string? gen-query-string))
(s/def :ring.request/scheme #{:http :https})
(s/def :ring.request/request-method
(-> (s/and keyword? (comp lower-case? name))
(s/with-gen gen-method)))
(s/def :ring.request/protocol
(s/with-gen string? #(gen/return "HTTP/1.1")))
(s/def :ring.request/header-name
(-> (s/and :ring.http/field-name lower-case?)
(s/with-gen #(gen/fmap str/lower-case (s/gen :ring.http/field-name)))))
(s/def :ring.request/header-value :ring.http/field-value)
(s/def :ring.request/headers
(s/map-of :ring.request/header-name :ring.request/header-value))
(s/def :ring.request/body
(s/with-gen #(instance? java.io.InputStream %) gen-input-stream))
(s/def :ring/request
(s/keys :req-un [:ring.request/server-port
:ring.request/server-name
:ring.request/remote-addr
:ring.request/uri
:ring.request/scheme
:ring.request/protocol
:ring.request/headers
:ring.request/request-method]
:opt-un [:ring.request/query-string
:ring.request/body]))
;; Response
(s/def :ring.response/status (s/int-in 100 600))
(s/def :ring.response/header-name :ring.http/field-name)
(s/def :ring.response/header-value
(s/or :one :ring.http/field-value :many (s/coll-of :ring.http/field-value)))
(s/def :ring.response/headers
(s/map-of :ring.response/header-name :ring.response/header-value))
(s/def :ring.response/body
(-> #(satisfies? p/StreamableResponseBody %)
(s/with-gen #(gen/one-of [(gen/return nil)
(gen/string-ascii)
(gen/list (gen/string-ascii))
(gen-input-stream)]))))
(s/def :ring/response
(s/keys :req-un [:ring.response/status
:ring.response/headers]
:opt-un [:ring.response/body]))
;; Handler
(s/def :ring.sync.handler/args
(s/cat :request :ring/request))
(s/def :ring.async.handler/args
(s/cat :request :ring/request
:respond (s/fspec :args (s/cat :response :ring/response):ret any?)
:raise (s/fspec :args (s/cat :error :ring.core/error):ret any?)))
(s/def :ring.sync.handler/ret :ring/response)
(s/def :ring.async.handler/ret any?)
(s/fdef :ring.sync/handler
:args :ring.sync.handler/args
:ret :ring.sync.handler/ret)
(s/fdef :ring.async/handler
:args :ring.async.handler/args
:ret :ring.async.handler/ret)
(s/def :ring.sync+async/handler
(s/and :ring.sync/handler
:ring.async/handler))
(s/def :ring/handler
(s/or :sync :ring.sync/handler
:async :ring.async/handler
:sync+async :ring.sync+async/handler))
(gen/generate (s/gen :ring.sync.handler/args))
(gen/generate (s/gen :ring.sync.handler/ret))
(gen/generate (s/gen :ring.async.handler/args))
(gen/generate (s/gen :ring.async.handler/ret))
(gen/generate (s/gen :ring/handler))
(gen/generate (s/gen :ring.http/field-name))
(gen/generate (s/gen :ring.http/field-value))
(gen/generate (s/gen :ring.request/server-port))
(gen/generate (s/gen :ring.request/server-name))
(gen/generate (s/gen :ring.request/uri))
(gen/generate (s/gen :ring.request/query-string));比较综合的一个生成例子
(gen/generate (s/gen :ring.request/request-method))
(gen/generate (s/gen :ring.request/protocol))
(gen/generate (s/gen :ring.request/header-name))
(gen/generate (s/gen :ring.request/header-value))
(gen/generate (s/gen :ring.request/headers))
(slurp (gen/generate (s/gen :ring.request/body)))
(gen/generate (s/gen :ring/request))
;上面是request 下面是response
(gen/generate (s/gen :ring.response/status))
(gen/generate (s/gen :ring.response/header-name))
(gen/generate (s/gen :ring.response/header-value))
(gen/generate (s/gen :ring.response/headers))
(gen/generate (s/gen :ring.response/body))
(gen/generate (s/gen :ring/response))
网友评论