2026年5月頃、Claude.ai(チャット)を使って趣味で作ったツールの開発記録です(全4回の第2回)。
はじめに
コードを書く前に、最初にAIへ渡すプロンプトを作りました。今回はその中身と、レイヤー設計について書きます。
過去の失敗
以前AIでコードを書いたときは、次のような失敗がありました。
| 失敗 | 原因 |
|---|---|
| 実行するとエラーになる | 要件が曖昧なままコードを生成させた |
| 全機能を一度に作ってエラーだらけ | 段階的に進める指示がなかった |
| 使い方がわからない | 説明を求めなかった |
| 出力の上限で途中で止まる | 1回の依頼が大きすぎた |
最初のプロンプト
上の失敗を踏まえて、最初のプロンプトに次の内容を入れました。
■ 役割・目的
シニアエンジニアとしてUDS診断ツールをTDD+ATDDで開発する
■ アーキテクチャ要件
Application / UDS / Protocol / Transport の4層構成
DoIPとUDS on CANをStrategyパターンで切り替え可能にする
■ 開発ステップ(段階的に)
Step1: 0x22のみ
Step2: 0x10, 0x3E
Step3: GUI追加
...
■ 開発手順(1Stepごとに)
テスト作成 → 最小実装 → リファクタリング
→ README更新 → Gitコミット用ファイル出力
組込み開発でいう、要件定義書と開発計画書を合わせたものです。1回の依頼が小さくなり、Stepごとに動作確認しながら進められました。
レイヤー設計
┌─────────────────────────────────────────┐
│ Application Layer DiagnosticService │
├─────────────────────────────────────────┤
│ UDS Layer ReadDataByIdentifier │
│ ECUReset, ClearDTC...│
├─────────────────────────────────────────┤
│ Protocol Layer ProtocolBase(I/F) │
│ DoIPProtocol │
├─────────────────────────────────────────┤
│ Transport Layer TCP / CAN │
└─────────────────────────────────────────┘
組込み開発のレイヤーに当てはめると次のとおりです。
| ツールのレイヤー | 組込み開発で近いもの |
|---|---|
| Application Layer | アプリケーション層 |
| UDS Layer | ミドルウェア |
| Protocol Layer | HAL |
| Transport Layer | ドライバ |
レイヤーを分けたことで、AIへの依頼を「このレイヤーのこのクラスを実装して」という単位にできました。依頼が小さいほど、出力の確認も楽になります。
Strategyパターンで通信方式を切り替える
# Protocol層の抽象インターフェース
class ProtocolBase(ABC):
@abstractmethod
def connect(self) -> None: ...
@abstractmethod
def send(self, uds_payload: bytes) -> None: ...
@abstractmethod
def receive(self) -> bytes: ...
# DoIP実装
class DoIPProtocol(ProtocolBase):
def connect(self) -> None:
self._client = DoIPClient(
ecu_ip_address=self._host,
ecu_logical_address=self._target,
tcp_port=self._port,
)
def send(self, uds_payload: bytes) -> None:
self._client.send_diagnostic(uds_payload)
def receive(self) -> bytes:
return self._client.receive_diagnostic()
# UDS on CANを作る場合(同じI/Fを実装する。今回は未実装)
class UDSonCANProtocol(ProtocolBase):
def connect(self) -> None: ...
def send(self, uds_payload: bytes) -> None: ...
def receive(self) -> bytes: ...
上位のDiagnosticServiceは、どのプロトコルを使っているかを知りません。
# 渡すプロトコルを変えるだけで通信方式が変わる
service = DiagnosticService(protocol=DoIPProtocol(...))
service = DiagnosticService(protocol=UDSonCANProtocol(...)) # 実装した場合
result = service.read_data_by_identifier(did=0xF190)
上位が下位の詳細を知らずに済む、という点はHAL設計と同じです。
ライブラリを使うか、自前で作るか
最初は学習のために自前で実装するつもりでしたが、途中でライブラリを使う方針に変えました。
| 観点 | 自前実装 | ライブラリ |
|---|---|---|
| 保守性 | SIDを追加するたびに修正が必要 | ライブラリ側が対応 |
| 信頼性 | バグを作り込むリスクがある | 利用実績がある |
| 開発速度 | フレーム処理の実装に時間がかかる | ツール本体の処理に集中できる |
役割分担は次のようにしました。
udsoncan → UDSフレームの生成・解析
doipclient → DoIPフレームの生成・TCP通信
(どちらもProtocol層・UDS層のクラスでラップする)
AIが間違えた例
AIが書いた requirements.txt のパッケージ名が間違っていました。
# AIが書いたもの(間違い)
python-udsoncan>=2.0
# 正しいパッケージ名
udsoncan>=1.0
pip install でエラーになって気づきました。GitHubのリポジトリ名が python-udsoncan なので、それと混同したと思われます。
AIの出力は、実行してみるまで正しいかわかりません。実行して確かめる手段として、テストを用意しました(#3で説明します)。
まとめ
- 最初のプロンプトに、設計方針と開発ステップを書いた
- レイヤーを分けたことで、AIへの依頼を小さくできた
- AIは間違えるので、実行して確認する前提で進めた
次回
#3では、実ECUなしでテストするために作ったECUシミュレータについて書きます。