|
| 1 | +# Coding Standards |
| 2 | + |
| 3 | +**Last Updated**: 2025-12-24 |
| 4 | + |
| 5 | +## Import Order |
| 6 | + |
| 7 | +インポートは以下の順序で記述する: |
| 8 | + |
| 9 | +1. **外部ライブラリ** - `@sveltejs/kit`, `valibot`, `drizzle-orm`等 |
| 10 | +2. **$app/*** - SvelteKitの内部API (`$app/server`, `$app/environment`等) |
| 11 | +3. **$lib/*** - プロジェクトの内部モジュール (階層順) |
| 12 | + - `$lib/server/database/*` |
| 13 | + - `$lib/server/services/*` |
| 14 | + - `$lib/server/drivers/*` |
| 15 | + - `$lib/shared/*` |
| 16 | + - `$lib/components/*` |
| 17 | +4. **型インポート** - `import type { ... }`は最後 |
| 18 | + |
| 19 | +### 例 |
| 20 | + |
| 21 | +```ts |
| 22 | +// ✅ 正しい順序 |
| 23 | +import { error } from "@sveltejs/kit"; |
| 24 | +import * as v from "valibot"; |
| 25 | +import { command, query } from "$app/server"; |
| 26 | +import { requireUtCodeMember } from "$lib/server/database/auth.server"; |
| 27 | +import { createArticle } from "$lib/server/database/articles.server"; |
| 28 | +import { purgeCache } from "$lib/server/services/cloudflare/cache.server"; |
| 29 | +import type { Article } from "$lib/shared/models/types"; |
| 30 | + |
| 31 | +// ❌ 誤った順序 |
| 32 | +import { createArticle } from "$lib/server/database/articles.server"; |
| 33 | +import type { Article } from "$lib/shared/models/types"; |
| 34 | +import { error } from "@sveltejs/kit"; |
| 35 | +import { command, query } from "$app/server"; |
| 36 | +``` |
| 37 | + |
| 38 | +## Async/Await パターン |
| 39 | + |
| 40 | +### Fire-and-Forget パターン |
| 41 | + |
| 42 | +非同期処理を「発射して忘れる」場合、必ず`.catch(console.error)`を使用する。 |
| 43 | + |
| 44 | +```ts |
| 45 | +// ✅ 正しいパターン |
| 46 | +purgeCache().catch(console.error); |
| 47 | + |
| 48 | +db.update(article) |
| 49 | + .set({ viewCount: sql`${article.viewCount} + 1` }) |
| 50 | + .where(eq(article.slug, slug)) |
| 51 | + .catch(console.error); |
| 52 | + |
| 53 | +// ❌ 誤ったパターン (サーバークラッシュの原因) |
| 54 | +purgeCache(); // エラーがキャッチされない |
| 55 | + |
| 56 | +// ❌ 冗長なパターン |
| 57 | +purgeCache() |
| 58 | + .then(() => {}) |
| 59 | + .catch(console.error); |
| 60 | + |
| 61 | +// ❌ 古いパターン (Prettierが自動修正) |
| 62 | +purgeCache().then(() => {}, console.error); |
| 63 | +``` |
| 64 | + |
| 65 | +**理由**: サーバー環境では、catchされないPromiseリジェクションはサーバー全体をクラッシュさせる可能性がある。 |
| 66 | + |
| 67 | +### Async/Await の統一 |
| 68 | + |
| 69 | +非同期関数は一貫して`async/await`を使用する。`.then()`チェーンは避ける。 |
| 70 | + |
| 71 | +```ts |
| 72 | +// ✅ 正しいパターン |
| 73 | +export const getArticles = query(async () => { |
| 74 | + await requireUtCodeMember(); |
| 75 | + return await listAllArticles(); |
| 76 | +}); |
| 77 | + |
| 78 | +// ❌ 混在パターン (避ける) |
| 79 | +export const getArticles = query(() => { |
| 80 | + return requireUtCodeMember().then(() => listAllArticles()); |
| 81 | +}); |
| 82 | +``` |
| 83 | + |
| 84 | +## エラーハンドリング |
| 85 | + |
| 86 | +### Remote Functions |
| 87 | + |
| 88 | +Remote functionsでは、エラーは自動的にクライアントに伝播されるため、try-catchは不要。 |
| 89 | + |
| 90 | +```ts |
| 91 | +// ✅ シンプルなパターン |
| 92 | +export const deleteArticle = command(v.string(), async (id) => { |
| 93 | + const session = await requireUtCodeMember(); |
| 94 | + await requireArticleOwnership(session, id); |
| 95 | + await deleteArticle(id); |
| 96 | + purgeCache().catch(console.error); |
| 97 | +}); |
| 98 | + |
| 99 | +// ❌ 不要なtry-catch |
| 100 | +export const deleteArticle = command(v.string(), async (id) => { |
| 101 | + try { |
| 102 | + const session = await requireUtCodeMember(); |
| 103 | + await requireArticleOwnership(session, id); |
| 104 | + await deleteArticle(id); |
| 105 | + } catch (error) { |
| 106 | + throw error; // 不要 |
| 107 | + } |
| 108 | +}); |
| 109 | +``` |
| 110 | + |
| 111 | +例外: ビジネスロジックで特定のエラーを変換する場合のみtry-catchを使用。 |
| 112 | + |
| 113 | +### Database層 |
| 114 | + |
| 115 | +Database層では、エラーメッセージを明確にする。 |
| 116 | + |
| 117 | +```ts |
| 118 | +// ✅ 明確なエラーメッセージ |
| 119 | +export async function createArticle(data: NewArticle) { |
| 120 | + const [created] = await db.insert(article).values(data).returning(); |
| 121 | + if (!created) throw new Error("Failed to create article"); |
| 122 | + return created; |
| 123 | +} |
| 124 | + |
| 125 | +// ❌ 曖昧なエラー |
| 126 | +export async function createArticle(data: NewArticle) { |
| 127 | + const [created] = await db.insert(article).values(data).returning(); |
| 128 | + return created; // undefinedの可能性 |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +## 命名規則 |
| 133 | + |
| 134 | +### ファイル命名 |
| 135 | + |
| 136 | +- **Remote Functions**: `*.remote.ts` |
| 137 | +- **Server Functions**: `*.server.ts` |
| 138 | +- **Shared Logic**: `*.ts` (サフィックスなし) |
| 139 | +- **Tests**: `*.test.ts` |
| 140 | +- **Types**: `types.ts` または `schema.ts` |
| 141 | + |
| 142 | +### 関数命名 |
| 143 | + |
| 144 | +- **Remote Query**: 名詞形 (`getArticles`, `getMember`) |
| 145 | +- **Remote Command**: 動詞形 (`saveArticle`, `editMember`, `removeProject`) |
| 146 | +- **Server Functions**: 動詞形 (`listArticles`, `createMember`, `updateProject`) |
| 147 | + |
| 148 | +```ts |
| 149 | +// Remote Functions (DAL) |
| 150 | +export const getArticles = query(async () => ...); |
| 151 | +export const saveArticle = command(..., async (data) => ...); |
| 152 | + |
| 153 | +// Server Functions (DB) |
| 154 | +export async function listArticles() { ... } |
| 155 | +export async function createArticle(data: NewArticle) { ... } |
| 156 | +``` |
| 157 | + |
| 158 | +## Valibot バリデーション |
| 159 | + |
| 160 | +### Picklist の使用 |
| 161 | + |
| 162 | +列挙型の値は`v.picklist()`を使用する。 |
| 163 | + |
| 164 | +```ts |
| 165 | +// ✅ 型安全なpicklist |
| 166 | +const PROJECT_CATEGORY_VALUES = [ |
| 167 | + "active", |
| 168 | + "ended", |
| 169 | + "hackathon", |
| 170 | + "festival", |
| 171 | + "personal", |
| 172 | +] as const satisfies readonly ProjectCategory[]; |
| 173 | + |
| 174 | +const categorySchema = v.picklist(PROJECT_CATEGORY_VALUES); |
| 175 | + |
| 176 | +// ❌ 型安全でないパターン |
| 177 | +const categorySchema = v.picklist(["active", "ended", "hackathon"]); |
| 178 | +``` |
| 179 | + |
| 180 | +## フォーマット |
| 181 | + |
| 182 | +### インデント |
| 183 | + |
| 184 | +- **タブ文字**を使用 (Prettier設定: `useTabs: true`) |
| 185 | +- スペースは使用しない |
| 186 | + |
| 187 | +### 行の長さ |
| 188 | + |
| 189 | +- 最大120文字 (Prettier設定: `printWidth: 120`) |
| 190 | +- 長いインポートや関数呼び出しは自動で折り返される |
| 191 | + |
| 192 | +### セミコロン |
| 193 | + |
| 194 | +- 常にセミコロンを使用 (Prettier設定: `semi: true`) |
| 195 | + |
| 196 | +### クォート |
| 197 | + |
| 198 | +- ダブルクォートを使用 (Prettier設定: `useTabs: true`) |
| 199 | + |
| 200 | +## ツール |
| 201 | + |
| 202 | +### 自動フォーマット |
| 203 | + |
| 204 | +```sh |
| 205 | +bun tidy # type-check + test-check + biome + prettier |
| 206 | +bun fix # biome + prettier のみ |
| 207 | +``` |
| 208 | + |
| 209 | +### 個別チェック |
| 210 | + |
| 211 | +```sh |
| 212 | +bun type-check # TypeScript型チェック |
| 213 | +bun test-check # 単体テスト |
| 214 | +bun lint-check # Biomeリント |
| 215 | +bun format-check # Prettierフォーマット |
| 216 | +``` |
| 217 | + |
| 218 | +## 検証ルール |
| 219 | + |
| 220 | +コミット前に必ず以下を実行: |
| 221 | + |
| 222 | +1. `bun type-check` - エラー0であること |
| 223 | +2. `bun tidy` - エラー・警告0であること |
| 224 | + |
| 225 | +## 参考 |
| 226 | + |
| 227 | +- CLAUDE.md - プロジェクト固有のワークフロー |
| 228 | +- security.md - セキュリティ設計 |
| 229 | +- project-context.md - プロジェクト概要 |
0 commit comments