テンプレート全体像
良いドキュメントには共通した構成があります。以下をテンプレートとして活用してください。最初からすべてを埋める必要はなく、骨格を作ってから肉付けしていく方式で進めます。
# [ドキュメント名]
---
owner: @username
last_reviewed: YYYY-MM-DD
next_review: YYYY-MM-DD
---
## 概要
このドキュメントは〇〇チームの△△エンジニアを対象に、
〜〜に関するルールと判断基準を定めます。
**スコープ**: このドキュメントが対象とする範囲
**対象外**: このドキュメントがカバーしない範囲
**関連ドキュメント**: [リンク]
## 用語定義(Glossary)
| 用語 | 定義 |
|------|------|
| 〇〇 | △△のこと。〜〜と混同されやすいが、違いは〜〜 |
## 背景・このドキュメントを作った理由
(なぜこのルールが必要になったか。過去の課題・インシデントなど)
## 原則・ルール
### [ルール1のタイトル]
**なぜ**: ~という理由から
**ルール**: ~すること
**例外**: ~の場合は除く(理由:〇〇)
## 具体例
### Good ✅
```code
(良い例のコード)
```
↑ これが良い理由: ~
### Bad ❌
```code
(悪い例のコード)
```
↑ これが悪い理由: ~という問題が生じる
## アンチパターン
| アンチパターン | 何が問題か | 代わりにすべきこと |
|----------------|------------|-------------------|
| 〇〇する | △△という問題が発生する | □□すること |
## チェックリスト
- [ ] ~を確認したか
- [ ] ~を満たしているか
## 参考資料
- [公式ドキュメント](URL)
「なぜ」を必ず書く
ルールだけ書かれたドキュメントは、背景を知る人が去った後に「なぜこうなっているのかわからない」状態を生みます。これは「死んだルール」の典型的な発生パターンです。以下の2つのルール記述を比較してください。
── ❌ Bad: なぜが書かれていない ──
グローバル変数を使用しないこと。
── ✅ Good: なぜと例外が書かれている ──
グローバル変数を使用しないこと。
理由:
グローバル変数はどこからでも変更可能なため、予期しない副作用が発生しやすく、
テストが困難になります。2023年Q3のリファクタリングで、グローバル状態への
依存が原因でバグが多発し、修正に2週間かかりました(インシデント #INC-234参照)。
例外:
設定値(環境変数・フィーチャーフラグ)は、モジュールトップレベルの定数として
定義することを許可します。この場合も変更不可(const/readonly)にしてください。
2つ目の書き方なら、「この状況は例外に当たるか?」という判断を、ドキュメントを書いた人がいなくても自律的に行えます。インシデント番号への参照は「なぜこのルールが存在するのか」を具体的に示す非常に強力な手法です。
「なぜ」を書くタイミング
理由は後から思い出せないことが多いため、ルールを決めた直後に書くことが重要です。レビューで「この書き方はやめよう」と決まったなら、そのレビューのコメントをそのままドキュメントに転記するだけでも十分です。
「レビューコメント → ドキュメント」の即時転記フロー
PRレビューで「この書き方は今後やめましょう」という決定がなされたとき、その場でドキュメントのIssue/PRを作成します。「レビューのたびに指摘するより、ドキュメントに一度書く方が楽」という文化が根付くと、自然にドキュメントが育ちます。
Good / Bad の例を添える
抽象的な文章だけでは、解釈が人によって異なります。具体的なコード例を添えることで、意図が正確に伝わります。特に「どちらでも良さそうに見える」ルールほど、具体例が効果を発揮します。
以下は非同期処理のエラーハンドリング規約の例です。
// ❌ Bad: エラーを握りつぶしている
async function fetchUser(id: number) {
try {
const user = await api.getUser(id);
return user;
} catch (e) {
console.log(e); // ログだけ出してundefinedを返す
// → 呼び出し元はエラーが起きたかどうか判断できない
}
}
// ❌ Bad: エラー型が不明確
async function fetchUser(id: number) {
try {
return await api.getUser(id);
} catch (e: any) { // any型は使わない
throw e;
}
}
// ✅ Good: 型安全かつ呼び出し元で処理を委ねる
class UserNotFoundError extends Error {
constructor(userId: number) {
super(`User not found: ${userId}`);
this.name = 'UserNotFoundError';
}
}
async function fetchUser(id: number): Promise<User> {
const user = await api.getUser(id);
if (!user) {
throw new UserNotFoundError(id);
}
return user;
}
// 呼び出し元での型安全な catch
try {
const user = await fetchUser(123);
} catch (e) {
if (e instanceof UserNotFoundError) {
// 404ページへリダイレクト
} else {
throw e; // 未知のエラーは再throwする
}
}
Bad例は「なぜ悪いか」まで説明する
悪い例を見せるだけでは学習になりません。「何が問題か」をコメントで添えることで、読者が「自分のコードは大丈夫か」をセルフチェックできます。実際のコードベースから情報を抽象化した上で抜粋した例は、説得力が特に高くなります。
例外・アンチパターンの明記
標準ドキュメントでありがちな失敗が、「原則だけ書いて例外を書かない」ことです。現場では必ず例外的な状況が発生します。例外を書かないと、メンバーが例外を適用すべき場面で躊躇したり、不適切な場面で「例外だから」と言い訳に使われたりします。
原則: SQLクエリはORMを使用すること
理由:
生SQLは可読性が低く、SQLインジェクションのリスクが高まります。
ORMを使うことでパラメータのエスケープが自動化されます。
例外(以下の場合は生SQLを使用してよい):
1. ORMで表現できないパフォーマンスクリティカルなクエリ
→ 例: 数百万件のデータを対象にした複雑なJOINと集計
2. ORMでは利用できないDB固有機能
→ 例: PostgreSQLのFULL TEXT SEARCH、LATERAL JOIN等
例外使用時のルール(濫用防止):
- コメントに「なぜORMを使わないか」を必ず記載すること
- コードレビューで2名以上のApproveを必須とする
- パフォーマンステストの結果(ORMとの比較)をPRに添付する
例外の条件を「何がOKか」だけでなく「例外を使うときの手続き」まで定義することで、濫用を防ぎながら正当なケースは柔軟に対応できます。
アンチパターン集として独立させる
過去のインシデントや繰り返し指摘されたレビューコメントをもとに、アンチパターン集を独立したドキュメントとして管理する方法があります。「なぜやってはいけないか」に加えて「参考インシデント」を添えると、読み手に強い印象を与えられます。
# アンチパターン集: React コンポーネント設計
## AP-001: Propsのバケツリレー(Props Drilling)
### 何が問題か
コンポーネントA → B → C → D とPropsを渡し続けるパターン。
途中のB・Cは使わないPropsを受け取り続けるため、
コンポーネントの結合度が上がり、リファクタリングが困難になる。
### 見分け方
同じprops名が3層以上のコンポーネントに存在する場合。
### 代わりにすべきこと
- 3層以上またがる場合はContext APIまたはZustand等の状態管理ライブラリを使う
- コンポーネントの責務を見直し、データを必要とするコンポーネントの近くに状態を置く
### 参考インシデント
2024-05のリリースで、テーマカラーの変更が10コンポーネントに波及し、
修正に丸1日かかった(PR #891)。Context移行後は1ファイルの変更で済んだ。
チェックリスト形式の活用
ドキュメントの末尾に「このルールが守られているか確認するためのチェックリスト」を置くと、コードレビュー時の拠り所になります。特に「自分でセルフレビューするときの確認リスト」として機能させると、レビュアーへの負担が減ります。
## PRセルフレビューチェックリスト
### API設計
- [ ] エンドポイントはRESTful命名規則に従っているか(名詞・複数形)
- [ ] エラーレスポンスは統一フォーマットを使用しているか
- [ ] 新規エンドポイントのOpenAPIドキュメントを追加したか
### セキュリティ
- [ ] ユーザー入力を直接SQLに渡していないか(SQLインジェクション対策)
- [ ] 認証・認可のチェックが適切に実装されているか
- [ ] 機密情報(パスワード・トークン)がログに出力されていないか
- [ ] レート制限が必要なエンドポイントに設定されているか
### パフォーマンス
- [ ] N+1クエリが発生していないか(SQLのログで確認)
- [ ] 外部APIへの同期呼び出しがタイムアウト設定されているか
### テスト
- [ ] 正常系・異常系(バリデーションエラー、認証エラー)のテストがあるか
- [ ] 境界値(空文字、最大長、0件)のテストがあるか
チェックリストとCIの連携
チェックリストはCIと連携させることも検討しましょう。静的解析ツールで自動チェックできる項目は自動化し、人間のレビューが必要な判断を要する項目だけ手動チェックリストに残すと効率的です。「認証・認可のチェックが適切か」などの判断系はCIで自動化できないため、人間のレビューが特に重要な項目として残します。
書きやすくするためのコツ
完璧を目指さない:「完璧なドキュメントを書いてから公開しよう」という考え方は挫折の元です。50%の完成度でも公開し、チームからフィードバックをもらいながら育てる方が、結果的に良いドキュメントになります。<!-- TODO: 後で詳細を追加する --> という注記を残しながら進めましょう。
箇条書きから始めて後で肉付けする:まず箇条書きで「何を書くか」のアウトラインを作り、後から説明・例・理由を追加していく方法が効率的です。いきなり文章で書こうとすると、構成に悩んで手が止まります。
Step 1: 箇条書きでアウトラインを作る(30分)
- ルール1: 関数は1つの責務のみ持つ
- ルール2: 関数名は動詞で始める
- ルール3: 引数は3つ以下にする
Step 2: 各項目に「なぜ」を1行追加する(30分)
- ルール1: 関数は1つの責務のみ持つ
なぜ: テストが単純になり、変更の影響範囲が明確になるため
Step 3: Good/Bad例を1つずつ追加する(1時間)
Step 4: チームにドラフトレビューを依頼する
→ フィードバックをもとに修正(完璧でなくてOK)
既存コードから逆引きしてルール化する:「理想のルールを考える」より「今のコードがどうなっているかを観察し、良いパターンを標準として言語化する」アプローチの方が現実的で、チームの受容度も高くなります。
具体的には、チームで「これは良い実装だ」と評価されたPRを5〜10件リストアップし、共通するパターンを抽出して「なぜ良いか」を言語化します。「誰かが外から持ち込んだルール」より「チームのベストプラクティスの言語化」の方が、メンバーに受け入れられやすいのが実感としてあります。
| コツ | 効果 | 注意点 |
|---|---|---|
| 完璧を目指さない | 早く共有・フィードバックを受けられる | TODO注記で未完成箇所を明示する |
| 箇条書きから始める | 構成に悩まずアウトラインが完成する | 後で必ず肉付けのスケジュールを入れる |
| 既存コードから逆引き | チームの受容度が高い | 良いPRを選ぶ目利きが必要 |
| レビューコメントを即転記 | 「決めた直後」に書けるので理由を忘れない | レビューURLも記録しておくと後で参照できる |
次のPARTで扱うこと
PART 05では、ドキュメントを管理するツール(Docs as Code・Notion・自動生成ツール)の選び方と、「ドキュメントが腐る」プロセスの根本原因、定期レビューの仕組み、変更通知の自動化(GitHub Actions)を解説します。