← 森 秀之 case study

小さな時計に、9 層のゲートを敷いた理由

NeNe Clock — Java 21 / Swing のデスクトップ時計

NeNe Clock の既定表示。枠も釦も無い温かみのある地色の上に、JetBrains Mono で時刻と日付が並ぶ

枠も釦も無い小さなデスクトップ時計を、「意味ごとに正規の実装経路は 1 本、それを人の記憶ではなく機械が守る」という規律で作りました。見どころは時計そのものではありません。規律を機械化した層と、その機械が「テストは緑なのに壊れていた」ものを何件捕まえたかの記録です。

言語が変わっても、設計と品質ゲートは同じ形で通します。以下の数字はすべて検証リンク付きで、まだ機械で強制できていない項目も隠していません

measured, not claimed — 全数字に検証リンク as of 2026-09-05
32時間
最初の PR merge から v0.2.5 まで
merged PR ↗
43/43
PR(#8〜#90・全件 merged)
pulls ↗
15
ADR(全件に「却下した選択肢」欄)
docs/adr ↗
77
規範の規則 ID(ARC / JAV / SWG / QLT / CNF / FR)
憲章 ↗
31/46
機械強制が active な規則。ほかに 一部 8・planned 6・不能 1
強制マトリクス ↗
14
ゲートが実際に落ちることの証明(P1〜P14)
gate-proofs ↗
01 · 機械が守る

規律を機械化した 9 層

「core は壁時計を読めない」は、レビューで気をつけることではなくビルドの失敗にしてあります。forbidden-apis が JDK のメソッド署名を直接名指しできるからです。メソッド単位は forbidden-apis、パッケージ単位は ArchUnit——同じ規則を 2 か所で見張ることはしません。二重化すると、どちらが効いているのか分からなくなるからです。

javac -Xlint:all -Werror
警告をエラーにする。sealedswitch の網羅性
Gradle module graph
依存の向き・循環の禁止・未承認モジュールの禁止
forbidden-apis
メソッド単位now() / nanoTime() / Math.random() / 既定ゾーン・ロケールを名指しで禁止
Error Prone + NullAway
null の意味を 1 つに固定する
Checkstyle
構造と複雑度の上限
ArchUnit
パッケージ単位:core は Swing も Preferences も知らない
validateConformance
プロジェクト固有の規則(命名・waiver・default・文書整合)
JaCoCo
core 2 モジュールの分岐カバレッジ下限
font-catalog tests
同梱書体が実在し、SHA-256 が一致し、Regular で描けること
02 · 機械が捕まえたもの

「テストは緑なのに壊れていた」4 件

ゲートを敷く価値は、通ったことではなく止まったことで決まります。全件、実行結果の引用つきで記録してあります。

決定性ゲートが半分しか繋がっていなかった

determinism.txt をどのビルドも読んでおらず、証明が落ちて見えたのは別の束が偶然拾っていたから。System.nanoTime()Instant.now() は素通りしていた。

見つけ方 次の作業に入る前に、別の API でもう一度わざと落としてみた

「落ちた」ではなく「何によって落ちたか」まで見ないと、証明にならない

gate-proofs §3.2 ↗
同梱書体 4 つが Thin で描かれていた

Java 21 は可変フォントの軸を選べない。Font.createFont が通るかを見る検査は 30 件すべて緑のままだった。

見つけ方 窓を出して画面を見た

「読み込める」は「正しく描かれる」ではない

gate-proofs §9.4 ↗
単体テストが全緑のまま、画面が 4 か所壊れていた

100pt で時刻が切れる/ポインタ位置によっては操作部が永久に出ない/色見本が寄る/チップが潰れる。

見つけ方 WSLg で窓を出し、Robot で操作して確認した

見た目に機械の検査は無い。だから「テストが緑だから動く」とは書かない

gate-proofs §11.1 ↗
ちらつきを 4 回続けて誤診した

直すたびに悪化するか、変わらなかった。5 回目に刻みを 200ms から 30ms に落として 40 枚撮ったら、アプリの絵は一度も壊れていなかった。白は OS 側の合成層で入っていた。

見つけ方 直すのをやめて、観測できる状態を先に作った

直して悪化したら、それは修正ではなく診断。観測できないものを直そうとしない

gate-proofs §17 ↗
03 · 進め方

AI と組んだときに、何を人がやったか

AI に書かせたことが売りではありません。売りは、規律を先に機械化しておいたので、AI の作業でも何が捕まったかを記録として出せることです。

草案は素材にして、規範は機械強制の体系に書き直した

出発点は別の AI と作った草案でした。内容は妥当でしたが、ほぼ全部が「レビューで気をつけること」です。そのままでは人の注意力に依存するので、ID を振って機械が強制する形に書き換えました。

規範を先に書いた

初日に 71 規則。全部に機械強制の状態(active / 一部 / planned / 不能)を付け、正本は強制マトリクス 1 か所に置いて、文書と実装が食い違ったら merge を止める検査を入れました。

要件が 5 件まとめて来た日は、実装より先にデザインを決めた

手を動かす前に案を並べて方向を決め、捨てた案も残しました。仕様の判断を 2 件覆していますが、覆した理由と捨てた選択肢は ADR に書いてあります。

誤診の代償は、施主の時間で払っている

ちらつきの件では「見てもらう」を 5 回挟みました。測れる状態を先に作っていれば 1 回で済んだ、と日報に書いてあります。AI と組んでも、この種の失敗は消えません。消えるのは、記録が残らないことの方です。

04 · 正直なところ

できていないこと

05 · 続きを読む

元の記録

設計の解説記事(Zenn)規約違反をビルド失敗にする9層のゲート ↗ リポジトリ(MIT・Java 21) ↗ ゲートが実際に落ちることの証明 ↗ 強制マトリクス(planned・不能 も明記) ↗ ADR 0001 厳格さは機械で強制する ↗ 日報(誤診の経過もそのまま) ↗
同じ進め方で相談する
1営業日以内に本人が返信します。