2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

src/config でゼロハードコーディング — 「業務的意味を持つ値」を集約する設計パターン

2
Posted at

この記事は約 5 分で読めます。

筆者プロフィール: ソフトウェアエンジニア。「知った気にならない。いつまでも学び続ける」を信条に、業務と個人開発の両輪で技術を磨いています。AI 駆動開発で複数の個人開発アプリを構築・運用中。
👉 ポートフォリオ: 筆者ホームページ

「同じ定数を 3 箇所にハードコードして、変更時に 1 箇所漏れて事故」 — これを構造的に防ぐ src/config/ 集約パターン を整理します。運用中の SaaS 「たすきば Knowledge Relay」 で 6 ヶ月運用して、変更時の grep が常に 1 箇所で済む状態を保っています。

サービスの機能紹介・画面イメージ・コンセプトは公式プロダクトページをご覧ください。
👉 たすきば Knowledge Relay — 公式プロダクトページ

何を src/config/ に入れるか

src/config/
├── master-data.ts          # プロジェクト状態 / リスク種別 / 重要度 enum
├── security.ts             # bcrypt cost / ログイン失敗ロック回数 / セッション有効期限
├── validation.ts           # 文字数上限 / 入力長制約
├── app-routes.ts           # 画面遷移パス
├── theme-definitions.ts    # テーマ色定義
├── billing.ts              # プラン定数 / 課金単価 / 上限値
└── ...

ポイントは 「業務的意味を持つ」 という基準。

種別 配置先
技術的な定数 (タイムアウト、リトライ回数) src/lib/ 配下のヘルパ内
業務的意味のある定数 (プロジェクト状態、Beginner プラン月 50 回上限) src/config/

1. なぜハードコードを許さないのか

実装中、よくこういう状況が起きます。

// src/app/(dashboard)/projects/page.tsx
<Input maxLength={200} placeholder="プロジェクト名" />

// src/services/project.service.ts
const projectSchema = z.object({
  name: z.string().max(200),  // 文字数を別ファイルで定義
});

// src/components/forms/ProjectForm.tsx
<span>{name.length} / 200 文字</span>  // また別の場所で 200

「プロジェクト名の文字数上限を 200 → 300 に変えたい」という要望が来ると、上記 3 箇所をすべて修正する必要があります。1 つでも漏らすと、UI と API でバリデーション結果が食い違う

src/config/validation.ts に集約すれば:

// src/config/validation.ts
/**
 * プロジェクト名の最大文字数。
 * UI と Zod schema の両方からこの定数を参照する。
 *
 * @business 業務観点での意味: プロジェクト一覧で 1 行に収まる長さの上限。
 */
export const PROJECT_NAME_MAX_LENGTH = 200;

1 箇所変更で全部反映 されます。


2. JSDoc で「なぜこの値か」を残す

src/config/ の定数には、必ず JSDoc を付けます。

// src/config/security.ts
/**
 * bcrypt のコストパラメータ。
 *
 * 12 を採用している理由:
 * - 10 はやや弱い (現在の GPU 攻撃を考えると 12 推奨)
 * - 14 は MFA 検証時に体感遅延が出る (200ms 超)
 * - 12 は OWASP Password Storage Cheat Sheet (2026 年版) で推奨
 *
 * @see https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
 */
export const BCRYPT_COST = 12;

JSDoc がないと「なんで 12 なんだっけ?」が再起します。KDD として書いておくと、半年後の自分が助かる


3. Client Component → service の value import 問題

ここでひとつ落とし穴があります。

Next.js App Router で Client Component から service の定数を import する と、Prisma が client bundle に混入して build が失敗します。

// ✗ NG: client component から service を import
'use client';
import { PROJECT_NAME_MAX_LENGTH } from '@/services/project.service';

project.service.ts は内部で prisma を import しているため、bundle 解析がそれをたどって client にも Prisma を入れようとする。結果、Webpack で巨大なエラー

これを防ぐため、src/config/prisma を一切 import しない純粋な定数モジュール として保ちます。

// ✓ OK: config からだけ import する
'use client';
import { PROJECT_NAME_MAX_LENGTH } from '@/config/validation';

ルールはシンプル:

src/config/ 配下の .tsprisma / auth / DB 関連を一切 import しない。


4. 課金関連定数は特に厳重に

課金関連の定数 (プラン上限・単価) は、変更時に grep を 4 軸 (生値 / 表示文字列 / 自然文 / アサーション) で打つ必要 があります。

生値 BEGINNER_SEAT_LIMIT = 5
表示文字列 '5,000円' (toLocaleString)
自然文 「5 席まで無料」(ドキュメント)
アサーション toContainText('5 席') (E2E)

対策として、たすきばは:

1. 価格表示の生値を 1 つの定数にまとめる
2. その定数を JSDoc で「変更時はこれらを必ず一緒に更新」と注記
3. テストファイルも from '@/config/billing' で参照させる

5. 「業務的意味を持つ」の境界線

判断に迷うことがある:

  • これは技術的な定数か、業務的な定数か?
  • これは src/lib/ に入れるべきか、src/config/ に入れるべきか?

ルール:

ユーザに見える概念に紐づく定数は src/config/

具体例:

定数 場所 理由
プロジェクト状態の enum src/config/master-data.ts ユーザに見える業務概念
プロジェクト名の最大文字数 src/config/validation.ts UI に表示される制約
bcrypt cost src/config/security.ts 業務観点でのセキュリティ判断
HTTP リトライ回数 src/lib/http.ts ユーザに見えない技術詳細
データベース接続プールサイズ src/lib/db.ts ユーザに見えない技術詳細
Voyage embedding の次元数 src/config/master-data.ts 業務観点で「精度設計」に直結

おわりに — 3 つのルール

ハードコードを物理的に消すための 3 つのルール:

1. 業務的意味を持つ定数は src/config/ に集約する
2. JSDoc で「なぜこの値か」を残す
3. src/config/ は Prisma / auth を import しない

たすきばはこのルールを 6 ヶ月厳守してきた結果、変更時の grep が常に 1 箇所で済む 状態になっています。

個人開発でも、商用 SaaS と同じ品質を出すための、地味だが効く設計判断 です。

本記事の設計は、運用中の SaaS 「たすきば Knowledge Relay」 で実装しています。
👉 たすきば Knowledge Relay — 公式プロダクトページ

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?