takt/builtins/ja/policies/coding.md
nrslib 2c7bd4834f Faceted Prompting リネーム: stances→policies, report_formats→output_contracts
5つの関心を Persona, Policy, Instruction, Knowledge, Output Contract に統一。
ディレクトリ、YAMLキー、ソースコード、テンプレート、テスト、ドキュメントを全面更新。
2026-02-07 20:04:09 +09:00

293 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# コーディングポリシー
速さより丁寧さ、実装の楽さよりコードの正確さを優先する。
## 原則
| 原則 | 基準 |
|------|------|
| Simple > Easy | 書きやすさより読みやすさを優先 |
| DRY | 3回重複したら抽出 |
| コメント | Why のみ。What/How は書かない |
| 関数サイズ | 1関数1責務。30行目安 |
| ファイルサイズ | 目安として300行。タスクに応じて柔軟に |
| ボーイスカウト | 触った箇所は少し改善して去る |
| Fail Fast | エラーは早期に検出。握りつぶさない |
## フォールバック・デフォルト引数の禁止
値の流れを不明瞭にするコードは書かない。ロジックを追わないと値が分からないのは悪いコード。
### 禁止パターン
| パターン | 例 | 問題 |
|---------|-----|------|
| 必須データへのフォールバック | `user?.id ?? 'unknown'` | エラーになるべき状態で処理が進む |
| デフォルト引数の濫用 | `function f(x = 'default')` で全呼び出し元が省略 | 値がどこから来るか分からない |
| null合体で渡す口がない | `options?.cwd ?? process.cwd()` で上位から渡す経路なし | 常にフォールバックになる(意味がない) |
| try-catch で空値返却 | `catch { return ''; }` | エラーを握りつぶす |
| 不整合な値のサイレントスキップ | `if (a !== expected) return undefined` | 設定ミスが実行時に黙って無視される |
### 正しい実装
```typescript
// ❌ 禁止 - 必須データへのフォールバック
const userId = user?.id ?? 'unknown'
processUser(userId) // 'unknown' で処理が進んでしまう
// ✅ 正しい - Fail Fast
if (!user?.id) {
throw new Error('User ID is required')
}
processUser(user.id)
// ❌ 禁止 - デフォルト引数で全呼び出し元が省略
function loadConfig(path = './config.json') { ... }
// 全呼び出し元: loadConfig() ← path を渡していない
// ✅ 正しい - 必須引数にして明示的に渡す
function loadConfig(path: string) { ... }
// 呼び出し元: loadConfig('./config.json') ← 明示的
// ❌ 禁止 - null合体で渡す口がない
class Engine {
constructor(config, options?) {
this.cwd = options?.cwd ?? process.cwd()
// 問題: options に cwd を渡す経路がない場合、常に process.cwd() になる
}
}
// ✅ 正しい - 上位から渡せるようにする
function createEngine(config, cwd: string) {
return new Engine(config, { cwd })
}
```
### 許容されるケース
- 外部入力ユーザー入力、API応答のバリデーション時のデフォルト値
- 設定ファイルのオプショナル値(明示的に省略可能と設計されている)
- 一部の呼び出し元のみがデフォルト引数を使用(全員が省略している場合は禁止)
### 判断基準
1. **必須データか?** → フォールバックせず、エラーにする
2. **全呼び出し元が省略しているか?** → デフォルト引数を削除し、必須にする
3. **上位から値を渡す経路があるか?** → なければ引数・フィールドを追加
4. **関連する値に不変条件があるか?** → ロード・セットアップ時にクロスバリデーションする
## 抽象化
### 条件分岐を追加する前に考える
- 同じ条件が他にもあるか → あればパターンで抽象化
- 今後も分岐が増えそうか → Strategy/Mapパターンを使う
- 型で分岐しているか → ポリモーフィズムで置換
```typescript
// ❌ 条件分岐を増やす
if (type === 'A') { ... }
else if (type === 'B') { ... }
else if (type === 'C') { ... } // また増えた
// ✅ Mapで抽象化
const handlers = { A: handleA, B: handleB, C: handleC };
handlers[type]?.();
```
### 抽象度を揃える
1つの関数内では同じ粒度の処理を並べる。詳細な処理は別関数に切り出す。「何をするか」と「どうやるか」を混ぜない。
```typescript
// ❌ 抽象度が混在
function processOrder(order) {
validateOrder(order); // 高レベル
const conn = pool.getConnection(); // 低レベル詳細
conn.query('INSERT...'); // 低レベル詳細
}
// ✅ 抽象度を揃える
function processOrder(order) {
validateOrder(order);
saveOrder(order); // 詳細は隠蔽
}
```
### 言語・フレームワークの作法に従う
- Pythonなら Pythonic に、KotlinならKotlinらしく
- フレームワークの推奨パターンを使う
- 独自の書き方より標準的な書き方を選ぶ
- 不明なときはリサーチする。推測で実装しない
### インターフェース設計
インターフェースは利用側の都合で設計する。実装側の内部構造を露出しない。
| 原則 | 基準 |
|------|------|
| 利用者視点 | 呼び出し側が必要としないものを押し付けない |
| 構成と実行の分離 | 「何を使うか」はセットアップ時に決定し、実行APIはシンプルに保つ |
| メソッド増殖の禁止 | 同じことをする複数メソッドは構成の違いで吸収する |
```typescript
// ❌ メソッド増殖 — 構成の違いを呼び出し側に押し付けている
interface NotificationService {
sendEmail(to, subject, body)
sendSMS(to, message)
sendPush(to, title, body)
sendSlack(channel, message)
}
// ✅ 構成と実行の分離
interface NotificationService {
setup(config: ChannelConfig): Channel
}
interface Channel {
send(message: Message): Promise<Result>
}
```
### 抽象化の漏れ
特定実装が汎用層に現れたら抽象化が漏れている。汎用層はインターフェースだけを知り、分岐は実装側で吸収する。
```typescript
// ❌ 汎用層に特定実装のインポートと分岐
import { uploadToS3 } from '../aws/s3.js'
if (config.storage === 's3') {
return uploadToS3(config.bucket, file, options)
}
// ✅ 汎用層はインターフェースのみ。非対応は生成時にエラー
const storage = createStorage(config)
return storage.upload(file, options)
```
## 構造
### 分割の基準
- 独自のstateを持つ → 分離
- 50行超のUI/ロジック → 分離
- 複数の責務がある → 分離
### 依存の方向
- 上位層 → 下位層(逆方向禁止)
- データ取得はルートView/Controllerで行い、子に渡す
- 子は親のことを知らない
### 状態管理
- 状態は使う場所に閉じ込める
- 子は状態を直接変更しない(イベントを親に通知)
- 状態の流れは単方向
## エラーハンドリング
エラーは一元管理する。各所でtry-catchしない。
```typescript
// ❌ 各所でtry-catch
async function createUser(data) {
try {
const user = await userService.create(data)
return user
} catch (e) {
console.error(e)
throw new Error('ユーザー作成に失敗しました')
}
}
// ✅ 上位層で一元処理
// Controller/Handler層でまとめてキャッチ
// または @ControllerAdvice / ErrorBoundary で処理
async function createUser(data) {
return await userService.create(data) // 例外はそのまま上に投げる
}
```
### エラー処理の配置
| 層 | 責務 |
|----|------|
| ドメイン/サービス層 | ビジネスルール違反時に例外をスロー |
| Controller/Handler層 | 例外をキャッチしてレスポンスに変換 |
| グローバルハンドラ | 共通例外NotFound, 認証エラー等)を処理 |
## 変換処理の配置
変換メソッドはDTO側に持たせる。
```typescript
// ✅ Request/Response DTOに変換メソッド
interface CreateUserRequest {
name: string
email: string
}
function toUseCaseInput(req: CreateUserRequest): CreateUserInput {
return { name: req.name, email: req.email }
}
// Controller
const input = toUseCaseInput(request)
const output = await useCase.execute(input)
return UserResponse.from(output)
```
変換の方向:
```
Request → toInput() → UseCase/Service → Output → Response.from()
```
## 共通化の判断
### 3回ルール
- 1回目: そのまま書く
- 2回目: まだ共通化しない(様子見)
- 3回目: 共通化を検討
### 共通化すべきもの
- 同じ処理が3箇所以上
- 同じスタイル/UIパターン
- 同じバリデーションロジック
- 同じフォーマット処理
### 共通化すべきでないもの
- 似ているが微妙に違うもの(無理に汎用化すると複雑化)
- 1-2箇所しか使わないもの
- 「将来使うかも」という予測に基づくもの
```typescript
// ❌ 過度な汎用化
function formatValue(value, type, options) {
if (type === 'currency') { ... }
else if (type === 'date') { ... }
else if (type === 'percentage') { ... }
}
// ✅ 用途別に関数を分ける
function formatCurrency(amount: number): string { ... }
function formatDate(date: Date): string { ... }
function formatPercentage(value: number): string { ... }
```
## 禁止事項
- **フォールバックは原則禁止** - `?? 'unknown'``|| 'default'``try-catch` で握りつぶすフォールバックを書かない。エラーは上位に伝播させる。どうしても必要な場合はコメントで理由を明記する
- **説明コメント** - コードで意図を表現する。What/How のコメントは書かない
- **未使用コード** - 「念のため」のコードは書かない
- **any型** - 型安全を破壊しない
- **オブジェクト/配列の直接変更** - スプレッド演算子で新規作成
- **console.log** - 本番コードに残さない
- **機密情報のハードコーディング**
- **各所でのtry-catch** - エラーは上位層で一元処理
- **後方互換・Legacy対応の自発的追加** - 明示的な指示がない限り不要
- **安全機構を迂回するワークアラウンド** - 根本修正が正しいなら追加の迂回は不要