KOTLIN ARCHITECTURE TEST
katachi
宣言していないファイルは
存在を許されない
5分でわかる katachi
00 — INTRO
こんなこと
ありませんか?
katachi
03 / 27
00 — INTRO
utils/ が何でも置き場に
とりあえず置いたファイルが、いつの間にか増えていく
毎回「これ、ここに置く?」
置き場所の逸脱は、レビューで見逃しがち
AI Agent が想定外の場所に作る
ルールは docs に書いてあるのに、守られない
設計ドキュメントが古いまま
書いた当時の構成と、今のコードがずれている
katachi
04 / 27
01
CHAPTER 01 — PROBLEM
なぜ ディレクトリ構成が
ぐちゃぐちゃに
なりがちか
01 — PROBLEM
ドキュメントには、強制力がない
「UseCase はこう書く」は、新メンバーや AI Agent に簡単に破られる
docs/architecture.md
UseCase は useCase/ に置く
invoke 関数は1つだけ
…と書いてあるだけ
時間・人・AI
実際のコード
com/example/
├── useCase/
│ └── GetUserUseCase.kt
└── DeleteUserUseCase.kt
配置違反
katachi
06 / 27
01 — PROBLEM
崩れるのはいつも、1ファイルから
原因は「違反」より「誰も想定しない場所に何かが増えた」こと
src/main/kotlin/com/example/
├── useCase/
│ └── GetUserUseCase.kt
├── UserHelper.kt
想定外
└── misc/
想定外
└── memo.md
katachi
07 / 27
01 — PROBLEM
絞った網の外は、検証されない
Konsist や ArchUnit は「対象を絞ってから、中身を検証する」ツール
src/main/kotlin/com/example/
├── useCase/
│ └── GetUserUseCase.kt
Konsist の視界(*UseCase だけ)
├── UserHelper.kt
名前が違うので、視界に入らない
└── misc/memo.md
ディレクトリごと、視界に入らない
katachi
08 / 27
02
CHAPTER 02 — SOLUTION
宣言した場所だけを
許す
02 — SOLUTION
役割と置き場所を、宣言する
ファイルの種類(Role)と置いてよい場所を、Kotlin DSL で書く
val projectArchitecture = architecture {
"UseCase" { // 役割(Role)
layout {
// ⭐️ ここに置いたものだけが存在を許される
"src/main/kotlin/com/example/useCase" / "*UseCase".ktFile()
}
}
}
katachi
10 / 27
DENY BY DEFAULT
宣言した場所だけが
存在を許される
宣言されていないファイルは、すべて違反として報告される
katachi
11 / 27
03
CHAPTER 03 — TRY IT
実際に
試してみる
03 — TRY IT 1 / 4
依存を足して、テストを1本書く
準備はこれだけ。JUnit から普通のユニットテストとして動く
build.gradle.kts
testImplementation("me.tbsten.katachi:katachi:<version>")
ProjectArchitectureTest.kt
class ProjectArchitectureTest {
@Test
fun `プロジェクトの構成が定義どおりになっている`() {
// ⭐️ これ1行でプロジェクト全体を検査
projectArchitecture.assert()
}
}
katachi
13 / 27
03 — TRY IT 2 / 4
まずは UseCase だけ、宣言してみる
val projectArchitecture = architecture {
"domain".group {
"UseCase" {
summary = "各画面で発生するアプリ固有の1つの振る舞い"
layout {
// ⭐️ usecase/ の *UseCase.kt だけを許可
"domain/src/main/kotlin/usecase" {
"*UseCase.kt".file()
}
}
}
}
}
katachi
14 / 27
03 — TRY IT 3 / 4
走らせると、宣言外が赤くなる
違反には「近くの正しい置き場所」と「直し方」が付いてくる
$ ./gradlew :architecture-test:test
出力イメージ
Katachi check failed: 1 violation (Unexpected: 1, Missing: 0)
[UnexpectedFile] domain/src/main/kotlin/GetUserUseCase.kt
No role is defined for this file.
Nearby locations:
UseCase domain/src/main/kotlin/usecase/
How to fix:
- Move it to one of the locations above
katachi
15 / 27
03 — TRY IT 4 / 4
提案どおり動かせば、緑に戻る
定義に足す・移す・消すで片付ければ、構成が定義と一致する
BEFORE — 違反 1件
domain/src/main/kotlin/
├── GetUserUseCase.kt
違反
└── usecase/
移動
AFTER — 違反 0件
domain/src/main/kotlin/
└── usecase/
└── GetUserUseCase.kt
OK
Test passed — 構成が定義どおりになった
katachi
16 / 27
04
CHAPTER 04 — DESIGN
設計思想
04 — WHY DENY BY DEFAULT
許可リストは、安全側に倒れる
Saltzer & Schroeder(1975)の fail-safe defaults と同じ原則
DENY LIST — 禁止リスト
思いついた禁止事項しか
守れない
想定外 → 静かに通ってしまう
ALLOW LIST — katachi
現れてよいものを
全部書く
想定外 → 通らない(違反になる)
ファイアウォールの default deny、CSP の default-src 'none' も同じ考え方
katachi
18 / 27
04 — ADOPTION
最初は違反だらけで、いい
初回の違反一覧は「誰も説明できていないファイル」の一覧
STEP 1
違反一覧が出る
説明できない
ファイルが見える
STEP 2 — 1つずつ片付ける
定義に足す
移す
消す
STEP 3
チームで
合意し直す
アーキテクチャの再確認
※ baseline(既存状態の一括受け入れ)はロードマップ上で未実装。現状は process() で違反リストを取得して差し引ける
katachi
19 / 27
04 — SINGLE SOURCE OF TRUTH
1つの定義から、テストもドキュメントも
summary や description が、検査対象であり同時にドキュメント本文になる
architecture { }
役割・配置・説明
テスト
宣言どおりになっているかを検査
ドキュメント
「なぜこの構造か」を説明(v0.2 予定)
自作 Processor
集計・抽出・別形式での出力
katachi
20 / 27
05
CHAPTER 05 — ECOSYSTEM
他ツールとの併用と
定義づくり
05 — COMPARISON
「存在してよいか」を問うのは katachi だけ
他ツールは、すでにあるコードの中身や関係を検証する
ツール
対象
実行方法
併用
katachi
ファイルの配置可否
ユニットテスト
—
Konsist
クラス・関数の中身
ユニットテスト
推奨
detekt
静的解析・コードスメル
Gradle / CLI
可能
ArchUnit
クラス依存・層関係
ユニットテスト
可能
ProjectGuard
モジュール依存
Gradle
可能
Konture
モジュール・パッケージ構造
ユニットテスト
可能
katachi
22 / 27
05 — WITH KONSIST
置き場所は katachi、中身は Konsist
定義内に Konsist の制約を書けば、配置違反と中身違反が1レポートに
"UseCase" {
layout {
"src/main/kotlin/com/example/useCase" / "*UseCase".ktFile()
}
// ⭐️ 中身のルールも同じ定義に書ける
"1つの invoke 関数のみを持つこと".konsist {
classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
}
}
katachi
23 / 27
05 — NAMING ROLES
役割名は、チームが今使う言葉で
新しい概念は持ち込まない。レビューで交わす言葉を、そのまま宣言にする
「これは UseCase だよね」
「Repository に置いて」
「画面の状態は ViewModel で持とう」
「API 呼び出しは DataSource に閉じて」
"UseCase" { … }
"Repository" { … }
"ViewModel" { … }
"DataSource" { … }
定義はチームの合意物。AI Agent が作った初期定義は、推測にすぎない
katachi
24 / 27
06
CHAPTER 06 — SUMMARY
まとめ
SUMMARY
Deny by default で
ディレクトリ構造を
テストする