APIリファレンスを生成するプロンプトを考える (Dart/Flutter)
先日、 plough
というインタラクティブなネットワークグラフ描画ライブラリ を公開しました。このライブラリのドキュメントはClaude 3.5 Sonnet + MCP で生成しました。README(構成を除く)も含めてほぼすべてです。試行錯誤したプロンプトの知見をメモしておきます。
なお、この記事で触れるドキュメントは主に dart doc
で生成可能なAPIリファレンスです。
ある程度汎用化したサンプルファイルを flutter-documentation-prompt に置いてあります。興味のある方は試してみてください。
使用したAI
- Claude Professional Plan の Claude 3.5 Sonnet
- Claude Desktop + MCP (推奨。コピペの手間が省ける)
目的
今回は公式APIリファレンスと近い構成と内容のドキュメントを生成するための調整を行っています。
プロンプトのフォーマット
Claudeが理解しやすいフォーマットのXMLで記述しました。XMLのタグ名は理解しやすいものであればよいです。タグ名もAIに任せましょう。
何も内容を指示しなかった場合の問題点
特に内容を指示せずに実装を読ませてドキュメントを生成した場合、当然ですが期待通りの内容は得られません。たとえば、クラスコメントでは以下の内容を出力する傾向がありました。
- 大袈裟な概要
- プロパティ一覧(各プロパティ定義の箇所に書くべき)
- メソッド一覧(同上)
- 使い方と妙に長いサンプルコード(動かせるとは限らない)
- 発生する可能性のある例外(これは有用なケースも多い)
- 実装の詳細(アルゴリズムが重要でなければ不要)
- パフォーマンス上の注意
- クロスリファレンス(ただし記法が間違っている)
出力内容は充実しているように見えても、実際に役立つ情報はいまいち含まれません。具体的な実装方法を加味した過剰なコメントになりやすく、実装上の制約や最適化などのある意味行き過ぎた解説が含まれやすくなります。
何を指示すればよいのか
プロンプトを試行錯誤しながら何度も繰り返すうちに、AI (Claude) が生成するコメントの傾向が見えてきました。いずれ無駄になると思われるので傾向の詳細について気にする必要はないと思いますが、「書くべきこと」と「書くべきでないこと」と「よい例」と「悪い例」をまとめて与えると、期待した内容のコメントが出力されやすくなりました。
例を用意する効果は高いですが、文字数がかさむとプロンプトの読み込みにコンテキストを消費しますし、守ってくれない指示も増えるので、ほどほどにしておくのがいいと思います。
想定するドキュメント
Dart/Flutterの公式APIリファレンスの内容の深さと構成を想定しています。プロジェクトの要件や対象読者に合わせて調整する必要があります。
ドキュメンテーションレベル
ドキュメンテーションレベルはファイルやAPIのグループ分けです。グループ内容はプロジェクトの要件に合わせればよいですが、次の項目で基本的な用途に対応できると思います。
- レベル名
- 概要。レベル名と概要もドキュメントに考慮されます。
- 対象API。「データモデル」や「ユーティリティ」など、テキストでの大まかな指示で問題ありません。
- 深さ。何をどの程度まで詳細に説明するかを指示します。
- サンプルの有無。指示なしだとサンプルコードが出力される可能性が高いです。大半のAPIでは過剰な説明でしょうし、そもそもそのコードが本当に動くのかわからないし、下手に増やすとメンテナンスが大変です。
以下は一例です:
<documentation_levels>
<level name="core">
<description>Core level documentation requirements</description>
<targets>Data models, major view components, customizable classes</targets>
<depth>Relationships between related APIs, key considerations</depth>
<examples>Typical use cases</examples>
</level>
<level name="standard">
<description>Standard level documentation requirements</description>
<targets>APIs primarily for reference, classes that don't need customization</targets>
<depth>Concise overview</depth>
<examples>Not needed</examples>
</level>
</documentation_levels>
この例ではコアレベルと標準レベルの2つのレベルを用意しています。コアレベルではデータモデル、ビューに関するAPI、カスタマイズ可能なクラスを扱い、標準レベルでは参照がメイン(ユーザーが生成することが少ない)やカスタマイズ不要なクラスを扱います。具体的な対象APIを指定しなくても実装を読ませればレベルを判断してくれますが、できれば指定するとよいです。
テーマ
書くべきテーマと書くべきでないテーマを指示します。テーマを指示することで有用な情報を選別します。
サンプルでは以下のテーマを指示しています。
- 書くべきテーマ
- ウィジェットの制約
- API同士の関連
- 状態管理
- 書くべきでないテーマ
- パフォーマンスと最適化
- 実装の詳細(指示があれば出力する)
例:
<themes>
<required_themes>
<theme>Widget constraints</theme>
<theme>API relationships</theme>
<theme>State management</theme>
</required_themes>
<excluded_themes>
<theme>Performance optimizations</theme>
<theme>Implementation details (unless specifically required)</theme>
</excluded_themes>
</themes>
ガイドライン
ドキュメント生成にあたって従うべき様々なルールを指示します。ガイドラインの指示は特に重要で、出力内容の傾向に合わせて細かく指示しないと適切な位置に適切なフォーマットで出力されません。
サンプルでは主に以下のルールを指示しています。詳細はファイルを参照してください。
- コメント以外のコード
- クラス宣言 (
class
) 、アノテーション、クラス指示子 (modifier) を残す。クラスコメントを出力するとき、なぜかこれらのコードが消えてしまうことが多々ありました。 - コメント更新の際は既存のコードを編集しない。コメントと同時に既存のコードを変えたり消したりすることが多々ありました。
- クラス宣言 (
- Linter
- アノテーション (
ignore_for_file
など) を残す
- アノテーション (
- クラス
- 構成。「概要の一行、コンセプト、See also」など
- セクションによる複雑な構成を避ける
- 指示しない限りパフォーマンスに関する記述を避ける
- クラスドキュメント内でプロパティやメソッドの解説をしない
- コンストラクタ
- 公開APIの場合は常にドキュメントを書く。指示しないと無視されやすい。
- 引数の required/optional について記述しない
- よい例と悪い例。指示だけだと反映されにくかったので例を示しています。
- プロパティ
- 主に目的と使い方を説明する
- 制約と副作用を説明する
- メソッド
- 構造
- 引数の required/optional について記述しない
- 振る舞い、副作用、戻り値を説明する
- よい例と悪い例
- その他のルール
- クロスリファレンスの記法
- 発生する可能性のある例外を記述する
- デフォルト値の説明をコメントに含めない
-
@freezed
適用クラス
テンプレート
コメントのフォーマットと備考を指示します。具体的なフォーマットを与えておくとガイドラインに従いやすくなり、出力内容の精度が安定します。
以下はクラスコメントの例です。
<template type="class">
<format>/// A class that represents {purpose}.
///
/// {Conceptual explanation of core functionality and behavior}
///
/// See also:
/// * [RelatedClass], for {related functionality}
</format>
<notes>
<note>Start with a clear single-sentence summary of the class purpose</note>
<note>Follow with conceptual explanation</note>
<note>Avoid listing properties or methods in class comments</note>
</notes>
</template>
その他
以下は指示しておくと作業が楽になるプロンプトです。
実行手順の簡略化
あらかじめ処理対象を指定しておくとチャットの手間とトークンを節約できます。特にMCPなどのAIが直接ディレクトリやファイルにアクセス可能な場合に便利です。
今回は主に次の項目を指定しました。
- 対象ファイルと除外ファイル
- 対象APIと除外API
- (公開するパッケージの場合) 非公開APIにもコメントを出力するかどうか
対象ファイルと除外ファイルの例:
<target_files>
<include>
<pattern>lib/**/*.dart</pattern>
</include>
<exclude>
<pattern>**/*_test.dart</pattern>
<pattern>**/*.g.dart</pattern>
<pattern>**/*.freezed.dart</pattern>
</exclude>
</target_files>
この設定では、テストコードやビルドツールにより自動生成されたコードを除外します。
APIの概要
主なAPIの概要をまとめたプロンプトを用意しておくとドキュメントの質が上がります。以下の項目を簡潔に書くだけでも十分です。
- 概要
- 重要度
- ファイルパス
MCPと組み合わせると効率的にソースコードを参照してくれるようになります。
IDE向けのアノテーション
プロンプトファイルの編集をAIに任せるなら、IDEによる自動フォーマットや改行の追加を無効にするアノテーションを記述しておくといいです。プロンプトファイルをIDEで開きながらMCPで直接ファイルにアクセスさせると、コンテキストが保持する内容とずれて編集に失敗する場合があります。
私はIntelliJ IDEAとWritersideを使っているので、以下のアノテーションでフォーマットを無効にしています。
<?xml version="1.0" encoding="UTF-8"?>
<!-- @formatter:off -->
バージョンと最終更新日
プロンプトのバージョンと最終更新日を記述しておき、ドキュメントの生成日時と結びつけてどこかに記録しておくと古いドキュメントの更新の目安になります。
サンプルでは「日付+通し番号」をバージョンとして埋め込んでいます。以下例:
<meta>
<version>2025_01_28_001</version>
<version_rules>
<format>YYYY_MM_DD_NNN</format>
<description>Version number format</description>
<rules>
<rule>Date (YYYY_MM_DD): Documentation rule update date</rule>
<rule>Number (NNN): Treated as a sequential number, continuing even when date changes</rule>
<rule>Example: After 2025_01_28_001 comes 2025_01_29_002, keeping sequence even when date changes</rule>
</rules>
<notes>
<note>Once assigned, version numbers should not be changed</note>
<note>When creating a new version, always use the latest number plus 1</note>
</notes>
</version_rules>
</meta>
実行サンプル
これまでの方針に従ったサンプルのプロンプトで生成したドキュメントを以下に示します。バリデーションを行うテキストフィールドの実装とコメントをClaudeで生成しました。コメントの試行回数は1回で、何も手を加えていません。
import 'package:flutter/material.dart';
import '../../flutter_value_validators.dart';
import 'validated_form.dart';
import 'validatable_field.dart';
/// A text field that integrates with form validation.
///
/// This widget extends [TextField] with validation capabilities, allowing it to
/// participate in form-level validation through [ValidatedForm]. It supports
/// both manual and automatic validation modes.
///
/// See also:
/// * [ValidatedForm], which coordinates validation across multiple fields
/// * [StringValidator], which provides common string validation rules
class ValidatedTextField extends StatefulWidget {
/// Creates a validated text field.
const ValidatedTextField({
super.key,
required this.validator,
this.onChanged,
this.decoration,
this.controller,
this.errorBuilder,
this.autovalidate = false,
this.obscureText = false,
});
/// The validator used to validate the field's value.
final Validator<String> validator;
/// Called when the user changes the text in the field.
final ValueChanged<String>? onChanged;
/// The decoration to show around the text field.
final InputDecoration? decoration;
/// Controls the text being edited.
final TextEditingController? controller;
/// A builder that creates custom error displays.
final Widget Function(BuildContext context, ValidationError error)? errorBuilder;
/// Whether to validate the field whenever its value changes.
final bool autovalidate;
/// Whether to hide the text being edited.
final bool obscureText;
State<ValidatedTextField> createState() => _ValidatedTextFieldState();
}
感想
ドキュメント作成で難しいのは、「開発者が伝えたい情報」と「ユーザーが本当に必要な情報」が必ずしも一致しないことです。AIで生成する場合はさらに「AIが生成した意図しない情報」が加わります。生成と破棄を繰り返すうちに「適切なコメントを出力しているのはAIのほうでは…?」と疑心暗鬼になり、絶え間ないレビューで消耗します。
ただ、AIによる意図しない情報は新しいヒントでもあるので、流れ作業でガチャするべきではないと個人的に思います。手作業だとドキュメントを書く頃には実装の詳細を忘れているし、テーマが自分の興味や関心事に偏りがちですしね。
よかったらバッジをいただけると嬉しいです。コーヒー代とモチベにさせていただきます。
Discussion