clj-debug について
clj-debug は、Clojure 開発者向けの REPL デバッグスキルです。ソースファイルを変更する前に nrepl で値を調べて修正を試す方法と、変更した Integrant コンポーネントのリセットが必要な場合を説明します。
主な機能
- 01nrepl で値を捕捉して確認
- 02ソースファイルを編集せず修正を検証
- 03エラーとスタックトレースから問題箇所を特定
- 04必要に応じた Integrant コンポーネントのリセット
- 05コミット前の一時的な def の削除
活用シーン
- 01関数の引数や呼び出し規約の誤りの診断
- 02ソースに反映する前の修正の検証
- 03古い動作が残るコンポーネントの調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ humorless/ clj-native-agent --skill clj-debugインストール後、Claude Code で/clj-debugを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
CHANGELOG.mdIMPROVEMENT_SUMMARY.mdevals/evals.jsoniteration-1-grading.mdSIDE_BY_SIDE_COMPARISON.md詳細
ClojureでREPLベースのデバッグを行う理由
ログを追加してテストを再実行すると、フィードバックループが遅くなります。ClojureのREPLを使えば、失敗した箇所で値を直接調べ、修正をすぐにテストし、ソースファイルに手を加える前に修正内容を検証できます。これは、printlnや正式なデバッガーを使うよりも高速で安全です。
基本パターン:ログを追加する前に立ち止まる
危険信号: デバッグのためにprintln、tap>、(log ...)を追加しようとしている、またはソースコードを変更しようとしている。
代わりに: nreplでインラインのdefを使います。ファイルを変更せずに変更後のコードを直接評価し、値を捕捉して調べ、修正をテストしてから、自信を持ってコミットします。
インラインdefパターン:捕捉して調べる
関数が失敗したら、関数内に一時的な(def variable-name variable-name)を追加し、変更後の関数をnreplで評価します。これにより、ソースファイルを変更せずに値を捕捉できます。
例:呼び出し規約のバグを見つける
関数が失敗します:
(defn foo [& [{:keys [a b c] :as m}]]
(+ a b c))
(foo :a 1 :b 2 :c 3)
;; => java.lang.NullPointerException
nreplで、引数を捕捉するためにインラインdefを加えた関数を書きます:
;; Evaluate this in nrepl (don't modify the source file):
(defn foo [& [{:keys [a b c] :as m}]]
(def m m) ;; TODO: remove before commit
(+ a b c))
;; Now call it:
(foo :a 1 :b 2 :c 3)
;; => java.lang.NullPointerException
;; Inspect what was captured:
m
;; => :a
;; Aha! m is :a, not a map. The caller is passing wrong format.
これでバグが分かりました。この関数が想定しているのはキーワード引数ではなく、マップ{:a 1 :b 2 :c 3}です。
例:コミット前にnreplで修正をテストする
問題を理解したら、nreplで修正版をテストします:
;; Test 1: Fix the calling convention
(foo {:a 1 :b 2 :c 3})
;; => 6 ✓ Works!
;; Test 2: Or fix the function to accept varargs correctly
(defn foo [& [{:keys [a b c] :as m}]]
;; No def here — we understand the bug now
(+ a b c))
(foo {:a 1 :b 2 :c 3})
;; => 6 ✓ Verified!
;; Or change the function signature:
(defn foo [& args]
(def args args) ;; Capture to verify varargs format
(let [{:keys [a b c]} (first args)]
(+ a b c)))
(foo {:a 1 :b 2 :c 3})
;; args => ({:a 1, :b 2, :c 3}) ✓ Correct format now
nreplで修正が機能したら、自信を持ってソースファイルを更新します。コミットする前に、(def ...)の行を削除してください。
発見と修正のワークフロー
これが一連の完全なサイクルです。各ステップの評価には/breplを使います:
digraph debugging_workflow {
rankdir=TB;
node [shape=box, style=rounded];
edge [fontsize=10];
"Test Fails or\nError Occurs" -> "Read Error\nMessage &\nStack Trace";
"Read Error\nMessage &\nStack Trace" -> "Identify Where\nto Inspect\n(Which function/\nwhich args?)";
"Identify Where\nto Inspect\n(Which function/\nwhich args?)" -> "Write Function\nwith inline def\nin nrepl\n(def var var)";
"Write Function\nwith inline def\nin nrepl\n(def var var)" -> "Evaluate Modified\nFunction in nrepl\n(defn foo ...)";
"Evaluate Modified\nFunction in nrepl\n(defn foo ...)" -> "Call Function\nwith Test Args\n(foo ...)";
"Call Function\nwith Test Args\n(foo ...)" -> "Inspect Captured\nValue at REPL\n(var / keys / type)";
"Inspect Captured\nValue at REPL\n(var / keys / type)" -> "Form Hypothesis\n(Why is this\nwrong?)";
"Form Hypothesis\n(Why is this\nwrong?)" -> "Test Corrected Code\nin nrepl\n(defn foo ...)";
"Test Corrected Code\nin nrepl\n(defn foo ...)" -> "Verify Fix Works\n(Call & verify\nresults)";
"Verify Fix Works\n(Call & verify\nresults)" -> "Update Source File\n(Remove inline def,\napply fix)";
"Update Source File\n(Remove inline def,\napply fix)" -> "Run Test Suite\n(Confirm fix\nsolves problem)";
}
Integrantコンポーネントの操作
Integrantコンポーネントを使うコードの場合、次の点を理解しておいてください:**コンポーネントのコードを変更してnreplで評価しても、実行中のコンポーネントに変更が反映されないことがあります。**新しいコードが読み込まれていても、古い動作が確認されることがあります。
コンポーネントをリセットするタイミング
次の判断フローを使います:
digraph integrant_decision {
rankdir=TB;
node [shape=diamond, style=rounded];
start [label="Modified code that\nthe component\nuses?", shape=diamond];
no_reset [label="No reset needed\n(Only inspecting\nwith def or\ntesting pure\nfunctions)", shape=box, style=rounded];
do_reset [label="Reset component\nto pick up\nchanges", shape=box, style=rounded];
start -> no_reset [label="No"];
start -> do_reset [label="Yes"];
}
リセット方法:
;; After evaluating your code change in nrepl:
(require '[your.component :as comp] :reload)
;; Reset the component to pick up the new code:
(ig/halt! @system :component-key)
(alter-var-root #'@system (fn [s] (ig/init (:system/config @system) :component-key)))
;; Now test again — the component reflects your change
クイックチェックリスト
- エラーメッセージとスタックトレースを読みましたか?
- 調べる関数と引数を特定しましたか?
- nreplで
(def var var)を含む関数を書きましたか? - 変更後の関数定義を評価しましたか?
- 関数を呼び出して値を捕捉しましたか?
- 捕捉した変数(キー、型、値)を調べましたか?
- 何が問題かについて仮説を立てましたか?
- まずnreplで修正後のコードをテストしましたか?
- ソースファイルを変更する前に、修正が機能することを確認しましたか?
- コミット前に
def行を削除しましたか? - コンポーネントのライフサイクル(Integrantの状態)を考慮しましたか?