コーディングルール
このドキュメントは Speakeasy プロジェクトにおける厳守必須のコーディングルールです。すべての開発者(人間・AI 含む)がこのルールに従う必要があります。
I. モジュール設計
Section titled “I. モジュール設計”ES Modules のみ
Section titled “ES Modules のみ”// OKimport { createDrizzleClient } from '@speakeasy/cellar';
// NG — require は禁止const cellar = require('@speakeasy/cellar');サブパスインポート禁止
Section titled “サブパスインポート禁止”パッケージの公開 API からのみインポートします。内部モジュールへの直接アクセスは禁止です。
// OK — 公開 API からインポートimport { contactSchema } from '@speakeasy/jigger/schemas';
// NG — 内部パスへの直接アクセスimport { contactSchema } from '@speakeasy/jigger/src/schemas/contact';バレルエクスポート
Section titled “バレルエクスポート”すべてのディレクトリに index.ts を配置し、公開メンバーのみをエクスポートします。
// src/utils/index.ts — エクスポートのみ、ロジック禁止export { formatDate } from './format-date';export { generateId } from './generate-id';export type { DateFormat } from './types';デフォルトエクスポート禁止
Section titled “デフォルトエクスポート禁止”// OKexport const createClient = () => { /* ... */};
// NG — export default は禁止export default function createClient() { /* ... */}II. 関数型プログラミング
Section titled “II. 関数型プログラミング”唯一の例外は Cloudflare Workers の WorkerEntrypoint と Durable Objects です。
// OK — 関数と型で構成const createContactService = (params: { readonly db: DrizzleClient }) => { // ...};
// NG — クラスは禁止class ContactService { constructor(private db: DrizzleClient) {}}Enum 禁止
Section titled “Enum 禁止”// OK — as const オブジェクトconst MESSAGE_TYPE = { NORMAL: 'normal', BROADCAST: 'broadcast', SYSTEM: 'system'} as const;type MessageType = (typeof MESSAGE_TYPE)[keyof typeof MESSAGE_TYPE];
// OK — Union 型type UserRole = 'admin' | 'user';
// NG — enum は禁止enum MessageType { NORMAL = 'normal', BROADCAST = 'broadcast'}イミュータビリティ
Section titled “イミュータビリティ”// OK — const のみ使用const count = items.length;const result = items.map((item) => item.name);
// NG — let/var は禁止let count = 0;var result = [];
// OK — readonly プロパティtype Contact = { readonly id: string; readonly name: string; readonly tags: readonly string[];};
// NG — mutable プロパティtype Contact = { id: string; name: string; tags: string[];};オブジェクト引数パターン
Section titled “オブジェクト引数パターン”2つ以上の引数がある関数は、単一のオブジェクト引数を使用します。
// OK — オブジェクト引数const sendMessage = (params: { readonly roomId: string; readonly content: string; readonly attachments?: readonly string[];}) => { /* ... */};
// NG — 個別引数const sendMessage = (roomId: string, content: string) => { /* ... */};マジックナンバー禁止
Section titled “マジックナンバー禁止”// OK — 定数定義const HEARTBEAT_INTERVAL_MS = 30_000;const MAX_RECONNECT_ATTEMPTS = 10;
// NG — マジックナンバーsetInterval(heartbeat, 30000);III. エラーハンドリング
Section titled “III. エラーハンドリング”throw 禁止
Section titled “throw 禁止”// OK — ResultAsync(neverthrow)import { ResultAsync, errAsync, okAsync } from 'neverthrow';
const findContact = (params: { readonly id: string }): ResultAsync<Contact, AppError> => { // ...};
// OK — 判別共用体type FindResult = | { readonly ok: true; readonly data: Contact } | { readonly ok: false; readonly error: AppError };
// NG — throw は禁止const findContact = (id: string): Contact => { throw new Error('Not found');};try/catch 禁止
Section titled “try/catch 禁止”例外: アンチコラプションレイヤー(外部ライブラリとの境界)でのみ許可されます。
// OK — エッジのアンチコラプションレイヤーでのみ許可const parseExternalResponse = (raw: unknown): Result<Data, ParseError> => { try { // 外部ライブラリの呼び出し const parsed = externalLib.parse(raw); return ok(parsed); } catch (e) { return err({ type: 'parse_error', message: String(e) }); }};Zod: safeParse のみ
Section titled “Zod: safeParse のみ”// OKconst result = contactSchema.safeParse(input);if (!result.success) { return err(result.error);}
// NG — parse は禁止(例外を throw するため)const data = contactSchema.parse(input);型アサーション禁止
Section titled “型アサーション禁止”// OK — 型ガードconst isContact = (value: unknown): value is Contact => { return typeof value === 'object' && value !== null && 'id' in value;};
// NG — as Type は禁止const contact = data as Contact;
// NG — ! は禁止const name = contact.name!;フラットコード(ネスト深度 1)
Section titled “フラットコード(ネスト深度 1)”// OK — ガード節で早期リターンconst processContact = (contact: Contact | null) => { if (!contact) return err({ type: 'not_found' }); if (!contact.isActive) return err({ type: 'inactive' });
return ok(contact);};
// NG — ネストが深いconst processContact = (contact: Contact | null) => { if (contact) { if (contact.isActive) { return ok(contact); } } return err({ type: 'error' });};IV. テスト
Section titled “IV. テスト”統合テスト優先
Section titled “統合テスト優先”サーバーサイドのロジックは実際の DB を使った統合テストを行います。DB 操作のモックは避けてください。
// OK — 実 DB テストdescribe('連絡先作成', () => { it('連絡先を作成し DB に保存される', async () => { const result = await createContact({ db: testDb, params: { name: '田中太郎', shopId: testShopId } }); expect(result.isOk()).toBe(true);
const saved = await testDb.query.contact.findFirst({ where: eq(contact.id, result.value.id) }); expect(saved).toBeDefined(); });});テストフレームワーク
Section titled “テストフレームワーク”| 対象 | ツール | 実行 |
|---|---|---|
| ユニットテスト | Vitest | bun test |
| Workers テスト | @cloudflare/vitest-pool-workers | bun test |
| ブラウザテスト | Playwright + Vitest Browser | bun test |
| Storybook テスト | @storybook/addon-vitest | bun test |
V. ドキュメント
Section titled “V. ドキュメント”| 対象 | 言語 |
|---|---|
| コードコメント | 日本語 |
| TSDoc | 日本語 |
| 変数名・関数名 | 英語 |
| ドキュメント | 日本語 |
| コミットメッセージ | 日本語 |
TSDoc 必須
Section titled “TSDoc 必須”すべてのパブリック関数に TSDoc を記述します:
/** * 連絡先を ID で検索する * * @param params - 検索パラメータ * @param params.id - 連絡先 ID * @param params.db - Drizzle クライアント * @returns 連絡先データ、見つからない場合は NotFoundError * @example * ```typescript * const result = await findContactById({ id: 'contact_123', db }); * if (result.isOk()) { * console.log(result.value.name); * } * ``` */const findContactById = (params: { readonly id: string; readonly db: DrizzleClient;}): ResultAsync<Contact, NotFoundError> => { // ...};VI. インポートルール
Section titled “VI. インポートルール”パッケージプレフィックス
Section titled “パッケージプレフィックス”すべてのワークスペースパッケージは @speakeasy/ プレフィックスを使用:
import { createDrizzleClient } from '@speakeasy/cellar';import { contactSchema } from '@speakeasy/jigger/schemas';import { Button } from '@speakeasy/glass';変数シャドウイング禁止
Section titled “変数シャドウイング禁止”const items = ['a', 'b'];
// NG — 外部スコープの items をシャドウイングitems.map((items) => items.toUpperCase());
// OKitems.map((item) => item.toUpperCase());ルール一覧(チェックリスト)
Section titled “ルール一覧(チェックリスト)”| # | ルール | カテゴリ |
|---|---|---|
| 1 | ES Modules のみ(require 禁止) | モジュール |
| 2 | サブパスインポート禁止 | モジュール |
| 3 | バレルエクスポート(index.ts) | モジュール |
| 4 | index.ts はエクスポートのみ | モジュール |
| 5 | export default 禁止 | モジュール |
| 6 | クラス禁止 | 関数型 |
| 7 | Enum 禁止 | 関数型 |
| 8 | let/var 禁止 | 関数型 |
| 9 | readonly 必須 | 関数型 |
| 10 | オブジェクト引数パターン | 関数型 |
| 11 | 変数シャドウイング禁止 | 関数型 |
| 12 | マジックナンバー禁止 | 関数型 |
| 13 | throw 禁止 | エラー |
| 14 | try/catch 禁止(ACL 除く) | エラー |
| 15 | ResultAsync / 判別共用体 | エラー |
| 16 | safeParse のみ | エラー |
| 17 | 型アサーション禁止 | エラー |
| 18 | ネスト深度 1(ガード節) | エラー |
| 19 | 実 DB 統合テスト | テスト |
| 20 | TSDoc 必須(日本語) | ドキュメント |