配信ボタンを押す前のあの数十秒、私はいつも少しだけ手が止まります。エミュレータでざっと触り、主要な画面を眺め、それで本当に大丈夫かと自問する時間です。個人開発で複数のアプリを抱えていると、この「最後の目視確認」が積み重なり、リリースの腰がだんだん重くなっていきます。
2026年6月24日、Android CLI が v1.0 の安定版になりました。IDE を開かずに、セマンティック解析・Compose プレビューの描画・UI テストの実行までをコマンドラインから回せる、という点が要点です。私が惹かれたのは新機能そのものよりも、これが「検証をどこに置くか」という設計の自由度を広げてくれることでした。
IDE を前提にした検証が、無人運用の足かせになる
これまで配信前の検証の多くは、IDE のなかで人が手を動かすことを暗黙の前提にしていました。プレビューを目で見て、テストを右クリックで走らせ、結果を眺める。一人で一画面に向かっているうちは、これでも回ります。
問題は、自動化を一段進めたくなったときに現れます。スケジュール実行のエージェントにビルドまで任せても、最後の検証だけは人が IDE を開かないと進まない。ここで流れが切れます。無人の文脈に IDE という有人の道具が挟まると、その手前で全部が止まってしまうのです。
Android CLI が変えるのは「検証の置き場所」です
Android CLI の意義は、検証を IDE の外、つまりコマンドラインと終了コードの世界へ運び出せることにあります。終了コードで合否が返るなら、検証はパイプラインの一段になります。シェルからも、cron からも、エージェントの実行ループからも、同じ呼び方で叩けます。
ここで設計上の判断が一つ生まれます。すべてを機械に任せるのではなく、「機械に預ける検査」と「人が引き取る判断」をどう線引きするか、です。本稿ではその線引きを三つの層として整理します。
検証ゲートを三つの層に分ける
配信前に拾いたい不具合は、性質がまるで違います。同じゲートに混ぜると、どれを機械が止めてよいのか曖昧になります。私は次の三層に分けて考えています。
層 主に拾う不具合 無人で止めてよいか
セマンティック解析 未解決の参照・型の不整合・到達不能コード はい (決定的に判定できます)
プレビュー描画 レイアウト崩れ・はみ出し・描画時の例外 条件付き(基準画像との差分で)
UI テスト 操作フローの破綻・状態遷移の取りこぼし はい(再現性が取れていれば)
第一層:静的に決着する検査
セマンティック解析は、答えが揺れません。参照が解決できない、型が合わない、到達しないコードがある。これらは人の好みが入る余地がなく、機械が止めて構いません。無人ゲートの最初の関門として最適です。
第二層:描画して初めて分かる崩れ
Compose プレビューの描画は、静的解析では見えない崩れを拾います。ただし「崩れているか」の判断には基準が要ります。私は基準画像を密度ごとに置き、描画結果との差分が許容しきい値(私の運用では0.5%)を超えたら人に回す、という助言的な扱いにしています。ここを機械に断定させると、意図した微調整まで止めてしまうためです。
第三層:操作して初めて出る不具合
UI テストは、画面を実際にたどって状態遷移を確かめます。再現性が取れているテストであれば、落ちたら配信を止めて問題ありません。逆に、たまに落ちる不安定なテストを止める根拠にすると、無人運用そのものへの信頼が崩れます。不安定なテストは、ゲートに入れる前に直すか外すかの判断が要ります。
終了コードを「契約」として設計する
無人運用で最も効くのは、各検査が終了コードで結果を返すという約束です。サブコマンドの名前は公式ドキュメントに追従させればよく、固定すべきはこの契約のほうです。
#!/usr/bin/env bash
set -euo pipefail
# 無人パイプラインの検証ゲート。各検査を段階的に呼び、
# どこかで落ちたら以降を打ち切ります。サブコマンド名は
# ドキュメントに追従させ、「終了コードで返す」契約だけを固定します。
REPORT_DIR = "reports/$( date +%Y%m%d-%H%M%S)"
mkdir -p " $REPORT_DIR "
run_stage () {
local name = " $1 " ; shift
echo "▶ $name "
if " $@ " > " $REPORT_DIR / $name .log" 2>&1 ; then
echo " ✅ $name "
else
local code = $?
echo " $name $code " >> " $REPORT_DIR /failures.txt"
echo " ❌ $name (exit= $code )"
return " $code "
fi
}
run_stage analyze android analyze --format json --out " $REPORT_DIR /analyze.json"
run_stage preview android preview render --baseline baselines/ --out " $REPORT_DIR /preview"
run_stage uitest android test ui --module app --out " $REPORT_DIR /uitest.xml"
このラッパーは、検査がどんな実装でも「落ちたら failures.txt に名前と終了コードを残す」という一点だけを保証します。後段はこのファイルだけを見れば判断できます。
機械が落とせる失敗と、人へ戻す失敗を分ける
三層を一律に扱うと、無人運用は脆くなります。決定的な検査の失敗は配信を止める。助言的な検査の差分は、止めずに人の確認待ちへ回す。この仕分けをコードで明示しておくと、翌朝の自分が迷いません。
import json, sys, pathlib
REPORT = pathlib.Path(sys.argv[ 1 ])
# 決定的な検査は落ちたら止める。助言的な検査は人へ回す。
BLOCKING = { "analyze" , "uitest" }
ADVISORY = { "preview" }
failures = {}
fpath = REPORT / "failures.txt"
if fpath.exists():
for line in fpath.read_text().splitlines():
stage, code = line.split()
failures[stage] = int (code)
block = [s for s in failures if s in BLOCKING ]
review = [s for s in failures if s in ADVISORY ]
digest = REPORT / "digest.md"
digest.write_text(
f "# 配信前チェック { REPORT .name }\n\n "
f "- 停止: { ', ' .join(block) or 'なし' }\n "
f "- 要確認: { ', ' .join(review) or 'なし' }\n "
)
# 終了コードを呼び出し側への返答にする(0=通過 / 1=人へ / 2=停止)
sys.exit( 2 if block else ( 1 if review else 0 ))
呼び出し側は、2 なら配信を止め、1 なら人の確認キューに積み、0 ならそのまま進む、と分岐できます。重要なのは、人へ戻す失敗を「失敗」として扱わないことです。差分は判断材料であって、停止の理由ではありません。
密度と言語のマトリクスを無人で網羅する
壁紙アプリを作っていた頃、描画の崩れは特定の密度と言語の組み合わせだけで出ることが何度もありました。手作業ではどうしても網羅が甘くなります。CLI で描画を回せるなら、組み合わせを総当たりにできます。
DENSITIES = "mdpi hdpi xhdpi xxhdpi"
LOCALES = "ja en"
for d in $DENSITIES; do
for l in $LOCALES; do
android preview render --density " $d " --locale " $l " \
--baseline "baselines/ $d - $l " --out "out/ $d - $l " \
|| echo "diff: $d / $l " >> review-queue.txt
done
done
密度4種と言語2種で8通り。人が一つずつ見ていた頃と比べ、見落としは目に見えて減りました。描画自体は1画面あたり数秒で、8通りでも待ち時間は知れています。差分が出た組み合わせだけが review-queue.txt に残るので、人が見るのは崩れた箇所に絞られます。私はこの「全部回して、崩れだけ人に渡す」形を強く推奨します。
この仕組みが効いてくるのは、検証の先に配信が控えている場面です。私自身は、ここを通った成果物だけを Google Play の内部テストトラックへ上げる、という順番にしています。検証ゲートが配信の手前で一段はさまっていると、崩れたまま実機の検証者へ届く事故が起きにくくなります。逆に言えば、ゲートを甘くすると、その緩さはそのまま内部テストへ流れ込みます。無人化を進めるほど、この一段の精度が配信品質を決めると感じています。
失敗したときに、翌朝の自分が困らないログ
無人運用で怖いのは、夜中に静かに落ちて、朝にはなぜ落ちたのか分からないことです。ここで本番運用のつまずきが効いてきます。私が痛い目を見たのは、ログを標準出力に流しただけで保存していなかったときでした。再現しようにも手がかりが残っていなかったのです。
対策は地味です。実行ごとにタイムスタンプ付きのディレクトリを切り、各検査の生ログと、人が読む digest.md の両方を残す。これだけで、朝の確認が「digest を見て、必要なら生ログへ降りる」という二段で済みます。注意点として、digest には固有名詞や鍵の類を書かないよう、出力を絞っておくほうが安全です。
どこまで任せ、どこを握るか
Android CLI が与えてくれたのは、検証を IDE の外へ出す自由です。けれども、すべてを機械に委ねるという話ではありません。決定的な検査は機械に預け、描画の崩れのような「程度の判断」は自分の手元に残す。この線引きこそが、無人運用を長く続けるための背骨だと私は考えています。
次の一歩として、まずはセマンティック解析の一層だけをゲートに組み込んでみてください。決定的で、誤検知が少なく、効果がすぐ分かります。そこで信頼が育ってから、描画と UI テストを足していく順序が、個人開発の身の丈には合っています。
最後までお付き合いいただき、ありがとうございました。同じように無人運用を組み立てている方の、設計の手がかりになれば幸いです。