コンテンツにスキップ
  • 初心者向け

FAQ

このページは katachi がなぜこの形をしているのか に答えます。 導入手順や書き方は 初めてのアーキテクチャ定義 を参照してください。

なぜ「宣言されていないファイルは違反」なのですか

Section titled “なぜ「宣言されていないファイルは違反」なのですか”

アーキテクチャが壊れるとき、壊れ方はたいてい 「ルールに違反するものが書かれた」ではなく 「誰も想定していない場所に何かが増えた」 です。

禁止リスト方式(「domain から android.* を import してはいけない」)は、 思いついた禁止事項しか守れません。 思いつかなかった壊れ方は、検査に引っかからずに通ってしまいます。 そして一番多いのは、思いつかなかった壊れ方です。

katachi は逆に、現れてよいものを全部書かせます。 書いていないものは違反です。 これなら「想定していなかった」が起きません。想定していないもの自体が違反だからです。

違うのは列挙し損ねたものがどちらへ倒れるか、その1点だけです。

セキュリティ分野にも同じ考え方があります

想定外のものをまず疑う考え方は、セキュリティの分野にもあります。

これは fail-safe defaults(安全側に倒れるデフォルト値)と呼ばれる設計原則の1つです。

禁止を列挙する方式では、列挙し損ねたものが検査を通ってしまいます。 シグネチャ方式のマルウェア対策が未知の検体を止められないのと同じで、 抜けたときに、気づかないうちに通ってしまう側へ倒れます。 許可を列挙する方式なら、抜けたときは「通らない」側へ倒れます。 どちらに倒れてほしいか、という選択です。

代償も同じです。許可リストは列挙が大変で、運用が重い。 それでも守る価値のある境界では採用されます。katachi の「最初が重い」も同じ話です。

もっとも katachi はセキュリティのための道具ではありません。 借りているのは判断の形だけで、守っているのはアーキテクチャです。

この設計の代償は 最初が重い ことです。 プロジェクトにあるファイルを全部説明しきるまでテストは Green になりません。 それでも、説明しきれないファイルがあるという事実のほうが情報 だと 考えています。それは本当に要るファイルですか。

既存のプロジェクトに入れたら、違反だらけになりませんか

Section titled “既存のプロジェクトに入れたら、違反だらけになりませんか”

なります。それが正常な出発点です。

初回の実行で出てくる違反の一覧は、そのまま 「このプロジェクトには、誰も説明できていないファイルがこれだけある」 という報告です。 katachi が壊したのではなく、もともとそうだったものが見えただけです。

出てきたパスを1つずつ、定義に足すか・移すか・消すかで片付けていきます。 これはそのまま、チームがアーキテクチャについて合意し直す作業でもあります。

すぐには片付けきれない場合は、実験的な機能の baseline(architecture { baseline() })で 今ある違反をとりあえず許容し、新しい違反だけをエラーにすることもできます。 許容は一時的なもので、直した違反は baseline のファイルから消していきます。書き方は baseline を参照してください。

Konsist や ArchUnit があるのに、なぜ別のものが要るのですか

Section titled “Konsist や ArchUnit があるのに、なぜ別のものが要るのですか”

katachi と Konsist・ArchUnit では、見ている範囲が違います。

Konsist や ArchUnit は 「対象を絞ってから、その中身を検証する」 道具です。

UseCaseKonsistTest.kt
// ここで絞る
Konsist.scopeFromProject()
.classes()
.withNameEndingWith("UseCase")
// 絞ったもののみ検証
.assertTrue { /* ... */ }

この書き方では、絞った範囲に入らないものは検証されません。

  • ディレクトリsrc/main/kotlin/com/example/
    • ディレクトリuseCase/
      • GetUserUseCase.kt Konsist の視界に入る
    • UserHelper.kt 名前が違うので視界に入らない
    • ディレクトリmisc/
      • memo.md そもそもディレクトリごと視界に入らない

katachi は逆向きです。プロジェクト全体を走査して、宣言と突き合わせます。

flowchart TB
    subgraph K["katachi が見る範囲 = プロジェクト全体"]
        subgraph S["Konsist の scope"]
            U1["GetUserUseCase.kt"]
            U2["UpdateUserUseCase.kt"]
        end
        H["UserHelper.kt"]
        M["misc/memo.md"]
    end

    style S fill:#5B8DEF22,stroke:#5B8DEF
    style K fill:#9BC7B622,stroke:#9BC7B6

「あるべきでないものがある」を見つけるには、全体を見る側の道具が要ります。

では Konsist は要らなくなるのですか

Section titled “では Konsist は要らなくなるのですか”

いいえ、併用するのがおすすめの使い方です。katachi と Konsist では役割が違います。

何を見るか
katachi ファイルがどこにあるか。プロジェクト全体の構造
Konsist ファイルの中身がどうあるか。クラス・関数・可視性

「UseCase は useCase/ ディレクトリに置く」は katachi の仕事です。 「UseCase は invoke 関数を1つだけ持つ」は Konsist の仕事です。

katachi の定義の中に Konsist の制約を書けるようにしてあるので、 テストで assert(FileConstraintCheck()) を呼べば、配置の違反と中身の違反が1つのレポートにまとまります。

「ファイルの中身を検証する」という立ち位置は、Konsist だけのものではありません。 detekt のカスタムルールや Konture を使っているなら、検証したいルールによってはそれらが Konsist の代わりになるかもしれません。

テストが1本しかないのはなぜですか

Section titled “テストが1本しかないのはなぜですか”

1回走らせれば全部わかるようにしたいからです。

ルールごとにテストを分けると、1つ落ちた時点でそのテストは止まります。 直して再実行して、次の違反を知って、また直す。違反の数だけ往復することになります。

katachi は assert() を1回呼ぶだけで、リポジトリ全体の違反が1つの失敗メッセージに集まります。 10箇所おかしければ10箇所が一度に出ます。読むのは1回です。

1回の実行で返ってくるもの
Katachi check failed: 3 violations (Unexpected: 2, Missing: 1)
[UnexpectedFile] file:///path/to/project/src/main/kotlin/com/example/GetUserUseCase.kt
No role is defined for this file.
Nearby locations:
UseCase file:///path/to/project/src/main/kotlin/com/example/useCase/
How to fix:
- Move it to one of the locations above
...
[UnexpectedDirectory] file:///path/to/project/src/main/kotlin/com/example/misc
...
[MissingFile] file:///path/to/project/src/main/kotlin/com/example/useCase/DeleteUserUseCase.kt
...

1行目に総数と内訳があります。ここを読めば、残りを読む前に規模が分かります。

これは人間にとっても楽ですが、AI Agent にとっては決定的です。 1回のテスト実行で直す材料が揃うか、10回往復するかの差になります。

テストが通るなら、ドキュメント生成は要らないのでは

Section titled “テストが通るなら、ドキュメント生成は要らないのでは”

ドキュメント生成はオプション機能ですが、使うことを推奨します。 テストは人間向けの説明ではないからです。

assert() が守ってくれるのは「定義どおりになっていること」だけです。 新規参画者や AI Agent が知りたいのは「なぜこの構造なのか」ですが、それはテストの出力には出てきません。

かといってドキュメントを別に書けば、それが腐ります。 腐ったドキュメントは 無いよりたちが悪い。実装と食い違っていることに誰も気づかないからです。

そこで katachi は、同じ定義から両方を出します。Role に書いた summary や description が、検査の対象であると同時にドキュメントの本文になります。

flowchart LR
    D["architecture { }<br/>Role・配置・説明"]
    D --> T["テスト<br/>宣言どおりか検査する"]
    D --> M["ドキュメント<br/>何を置く場所か説明する"]
    D -.-> C["自作 Processor<br/>集計・抽出・別形式への出力"]

    style D fill:#5B8DEF22,stroke:#5B8DEF,stroke-width:2px
    style C fill:#BDBDBD22,stroke:#BDBDBD,stroke-dasharray:4 3,color:#8A8A8A

アーキテクチャ定義が Single Source of Truth になるので、片方だけが古くなることは構造上起きません。

テストとドキュメントは katachi が用意しているものですが、この2つに限りません。 同じ定義から自分で何かを取り出すこともできます(ArchitectureProcessor とそのカスタマイズ)。

Role の名前や粒度は、どう決めればいいのですか

Section titled “Role の名前や粒度は、どう決めればいいのですか”

チームが今その言葉を使っているかどうかを基準にしてください。

katachi の Role は、新しい概念を導入するためのものではありません。 すでに口頭やレビューで使われている言葉(「これは UseCase だよね」「Repository に置いて」)を、 そのまま宣言として書き出すものです。

そのため、定義はチームの合意物であり、重要なレビューの対象です。 AI Agent に初期の定義を作らせても、それは既存コードからの推測にすぎません。 最終的にチームが読んで合意することを前提にしています。