Skip to content

第 12 章: エラーハンドリングと Spec

12.1 はじめに

この章では Clojure のエラーハンドリングと、clojure.spec によるデータバリデーションを学びます。

12.2 例外処理

try / catch / finally

Clojure は JVM 上で動作するため、Java の例外機構を利用できます。

(try
  (type/generate-string (type/->FizzBuzzTypeNotDefined)
                        (model/create-fizz-buzz-value 1))
  (catch clojure.lang.ExceptionInfo e
    (println "エラー:" (ex-message e))
    (println "データ:" (ex-data e))))
;; エラー: FizzBuzz タイプが未定義です
;; データ: {:type :not-defined}

ex-info による構造化エラー

ex-info は例外にデータマップを付与できる Clojure 固有の関数です。

;; 例外の生成
(throw (ex-info "FizzBuzz タイプが未定義です"
                {:type :not-defined
                 :requested-type 99}))

;; 例外データの取得
(try
  (cmd/execute (cmd/->FizzBuzzValueCommand 1 99))
  (catch clojure.lang.ExceptionInfo e
    {:message (ex-message e)
     :data (ex-data e)}))
;=> {:message "FizzBuzz タイプが未定義です"
;    :data {:type :not-defined}}

ex-info vs Java 例外

特徴 ex-info Java 例外
データ付与 マップで構造化データ メッセージ文字列のみ
ExceptionInfo 任意の例外クラス
活用 ビジネスロジック システムレベル

12.3 条件分岐によるエラー処理

nil を活用したエラー処理

;; some-> マクロ(nil ならショートサーキット)
(some-> 15
        model/create-fizz-buzz-value
        (->> (type/generate-string (type/->FizzBuzzType01))))
;=> "FizzBuzz"

(some-> nil
        model/create-fizz-buzz-value
        (->> (type/generate-string (type/->FizzBuzzType01))))
;=> nil

when-let / if-let

(defn safe-fizzbuzz [n]
  (when-let [value (and (pos? n) (model/create-fizz-buzz-value n))]
    (type/generate-string (type/->FizzBuzzType01) value)))

(safe-fizzbuzz 3)   ;=> "Fizz"
(safe-fizzbuzz -1)  ;=> nil

12.4 clojure.spec によるバリデーション

Spec の定義

clojure.spec.alpha を使って、データの仕様を宣言的に定義します。

(require '[clojure.spec.alpha :as s])

;; 基本的な Spec 定義
(s/def ::number pos-int?)
(s/def ::type-num #{1 2 3})
(s/def ::fizzbuzz-string (s/or :number string?
                               :fizz #(= % "Fizz")
                               :buzz #(= % "Buzz")
                               :fizzbuzz #(= % "FizzBuzz")))

バリデーション

;; 値の検証
(s/valid? ::number 5)     ;=> true
(s/valid? ::number -1)    ;=> false
(s/valid? ::type-num 1)   ;=> true
(s/valid? ::type-num 99)  ;=> false

;; エラー情報の取得
(s/explain-data ::number -1)
;=> {:problems [{:path [], :pred pos-int?, :val -1, :via [::number], :in []}]}

関数仕様の定義

(s/fdef fizzbuzz
  :args (s/cat :n ::number)
  :ret string?)

;; 仕様に基づくテスト生成
(require '[clojure.spec.test.alpha :as stest])
(stest/check `fizzbuzz)

コマンドの Spec

(s/def ::fizzbuzz-value-command
  (s/keys :req-un [::number ::type-num]))

(s/valid? ::fizzbuzz-value-command
          {:number 15 :type-num 1})
;=> true

12.5 テストでの活用

(deftest error-handling-test
  (testing "未定義タイプでコマンド実行すると例外が発生する"
    (is (thrown? clojure.lang.ExceptionInfo
                 (cmd/execute (cmd/->FizzBuzzValueCommand 1 99)))))

  (testing "例外のデータを検証する"
    (try
      (cmd/execute (cmd/->FizzBuzzValueCommand 1 99))
      (catch clojure.lang.ExceptionInfo e
        (is (= {:type :not-defined} (ex-data e)))))))

12.6 まとめ

この章では以下のことを学びました。

  • try / catch / finally による例外処理
  • ex-info / ex-data による構造化エラー情報
  • some->when-let による nil 安全なエラー処理
  • clojure.spec によるデータバリデーション
  • s/fdef による関数仕様の定義
  • テストでの例外検証パターン

第 4 部が完了しました。Clojure の関数型プログラミングの全体像を学びました。高階関数、永続データ構造、パイプライン処理、そして Spec によるデータバリデーションが Clojure プログラミングの強力な基盤となります。