← CS/SE Fundamentals
ソフトウェアエンジニアリングの実践 (Software Engineering Practices)
はじめに
優れたソフトウェアは、優れたコードだけでは作れない。バージョン管理、テスト、CI/CD、コードレビュー -- これらのプロセスと実践が、チームとして持続的に価値を届ける力を支えている。
この章では、日常の開発業務で実践すべき SE の基本を解説する。
バージョン管理 (Git)
Git の基本モデル
Git はスナップショットベースの分散バージョン管理システム。
Working Directory → Staging Area → Local Repository → Remote Repository
(作業) (git add) (git commit) (git push)
重要な概念:
- コミット: スナップショット + メタデータ (著者、日時、メッセージ)
- ブランチ: コミットの系列への軽量なポインタ
- マージ: 異なるブランチの変更を統合
- リベース: コミット履歴を直線的に整理
ブランチ戦略
GitHub Flow (シンプルで推奨):
main ─────────────────────────────────────────→
\ /
feature/add-patient-search ──→ PR → merge
mainから feature ブランチを作成- 変更をコミット
- Pull Request を作成
- レビューを受けてマージ
- feature ブランチを削除
Git Flow (リリース管理が複雑な場合):
main ─────────────────────────────────→
\ /
develop ─────────────────────────────────→
\ /
release/1.0 ───────→
\
feature/xxx ────→
実務での選択: 大半のプロジェクトでは GitHub Flow で十分。リリースサイクルが複雑な場合のみ Git Flow を検討。
コミットメッセージ
良いコミットメッセージは「なぜ」を伝える。
# Bad
fix bug
update code
WIP
# Good
fix: 予約時間の重複チェックが午前0時をまたぐ場合に失敗する問題を修正
患者が23:00-01:00のような日をまたぐ時間帯を指定した場合、
重複チェックのロジックが正しく動作していなかった。
start_time と end_time の比較を日付を考慮した形に修正。
Refs: RAICA-1234
Conventional Commits の形式が広く使われている:
<type>(<scope>): <description>
feat: 新機能
fix: バグ修正
docs: ドキュメントのみの変更
refactor: リファクタリング
test: テストの追加・修正
chore: ビルドプロセスやツールの変更
コードレビュー
レビューの目的
- バグの早期発見: 本番に出る前に問題を見つける
- 知識の共有: チームメンバーが他の部分のコードを理解する
- 設計の改善: 複数の視点でより良い設計を探る
- 品質基準の維持: コーディング規約やベストプラクティスの徹底
効果的なレビューの仕方
レビュアーとして:
- まず PR の目的と背景を理解する
- 設計レベルの問題を優先する (命名の好みよりアーキテクチャの問題)
- 「なぜ」を問う質問をする (「これはなぜこの方法を選んだ?」)
- 具体的な提案を含める (「こうすべき」ではなく「こうするのはどうか」)
- コードの良い点も指摘する
レビューイとして:
- PR のサイズを小さく保つ (200-400行が目安)
- PR の説明を丁寧に書く (変更の目的、影響範囲、テスト方法)
- セルフレビューを先にする
- フィードバックを個人攻撃と受け取らない
レビューチェックリスト
□ 機能要件を満たしているか
□ エッジケースは考慮されているか
□ セキュリティ上の問題はないか (入力バリデーション、認可チェック)
□ パフォーマンス上の懸念はないか (N+1、不要な計算)
□ テストは十分か
□ エラーハンドリングは適切か
□ 命名は明確か
□ 既存のパターンや規約に従っているか
テスト戦略
テストピラミッド
/ E2E テスト \ 少数・高コスト・遅い
/ (10-20%) \
/─────────────────\
/ Integration テスト \ 適度な数
/ (20-30%) \
/───────────────────────\
/ Unit テスト \ 大量・低コスト・高速
/ (50-70%) \
/──────────────────────────────\
Unit テスト
個々の関数やクラスを単独でテストする。
// テスト対象
function calculateAge(birthDate: Date, referenceDate: Date): number {
let age = referenceDate.getFullYear() - birthDate.getFullYear();
const monthDiff = referenceDate.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && referenceDate.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
// テスト
describe('calculateAge', () => {
it('誕生日前は1歳引かれる', () => {
const birth = new Date('1990-06-15');
const ref = new Date('2026-03-06');
expect(calculateAge(birth, ref)).toBe(35);
});
it('誕生日当日は加算される', () => {
const birth = new Date('1990-03-06');
const ref = new Date('2026-03-06');
expect(calculateAge(birth, ref)).toBe(36);
});
});
Unit テストの原則:
- 速い: 数ミリ秒で完了
- 独立: 他のテストに依存しない
- 再現性: 何度実行しても同じ結果
- 外部依存をモック: DB、API、ファイルシステムはモックする
Integration テスト
複数のコンポーネントが正しく連携するかをテストする。
// API エンドポイントのテスト
describe('POST /api/appointments', () => {
it('有効な予約を作成できる', async () => {
const response = await request(app)
.post('/api/appointments')
.set('Authorization', `Bearer ${validToken}`)
.send({
patientId: 'patient-123',
doctorId: 'doctor-456',
startTime: '2026-03-10T10:00:00Z',
});
expect(response.status).toBe(201);
expect(response.body.status).toBe('scheduled');
});
it('時間が重複する場合は409を返す', async () => {
// 既存の予約を作成
await createAppointment({ ... });
const response = await request(app)
.post('/api/appointments')
.set('Authorization', `Bearer ${validToken}`)
.send({ /* 同じ時間帯 */ });
expect(response.status).toBe(409);
});
});
E2E テスト (End-to-End)
ユーザーの実際の操作フローをテストする。Playwright, Cypress などを使用。
// E2E テスト (Playwright)
test('患者を検索して予約を作成できる', async ({ page }) => {
await page.goto('/appointments/new');
await page.fill('[data-testid="patient-search"]', '田中');
await page.click('[data-testid="patient-result-0"]');
await page.click('[data-testid="timeslot-10-00"]');
await page.click('[data-testid="confirm-button"]');
await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});
E2E テストの注意点:
- 実行が遅い → 重要なフローに絞る
- フレイキー (不安定) になりやすい → 適切な待機処理を入れる
- メンテナンスコストが高い → 安定したセレクターを使う
CI/CD
CI (Continuous Integration)
コードの変更を頻繁にメインブランチに統合し、自動テストで品質を確認する。
# GitHub Actions の例
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm run type-check
- run: npm test
CI で実行すべきこと:
- リント (ESLint, Prettier)
- 型チェック (TypeScript)
- Unit テスト + Integration テスト
- セキュリティスキャン (
npm audit) - ビルドの検証
CD (Continuous Delivery / Deployment)
- Continuous Delivery: いつでもデプロイできる状態を維持
- Continuous Deployment: テストが通れば自動的にデプロイ
コードプッシュ → CI (テスト・ビルド) → ステージング → (承認) → 本番
デプロイ戦略:
| 戦略 | 説明 | リスク |
|---|---|---|
| ローリングデプロイ | 段階的にインスタンスを入れ替え | 低 |
| Blue/Green | 新旧環境を切り替え | 低 (ロールバックが容易) |
| カナリアリリース | 少数のユーザーに先行公開 | 低 (問題を早期検出) |
| ビッグバンデプロイ | 一斉に切り替え | 高 |
技術的負債の管理
技術的負債とは
「今は動くが、将来の変更を難しくする設計上の妥協」のこと。借金と同じで、放置すると利子 (追加の開発コスト) が膨らむ。
技術的負債の種類
| 種類 | 例 | 対処 |
|---|---|---|
| 意図的・慎重 | "締切のためにテストを省略。次スプリントで追加" | 計画的に返済 |
| 意図的・無謀 | "テスト書く時間ないから書かない" | 避けるべき |
| 無意識・慎重 | "より良い設計を後で発見した" | 学びとして受け入れ、改善 |
| 無意識・無謀 | "レイヤードアーキテクチャって何?" | 学習で防ぐ |
管理の実践
- 可視化する: 技術的負債をチケットとして記録
- 定期的に返済する: スプリントの一定割合 (例: 20%) を負債返済に充てる
- 新規の負債を抑制する: コードレビューで品質を担保
- 影響度で優先順位をつける: 変更が頻繁な箇所の負債を優先的に返済
ドキュメンテーション
書くべきドキュメント
| 種類 | 対象読者 | 例 |
|---|---|---|
| ADR (Architecture Decision Record) | チーム | アーキテクチャ上の意思決定の記録 |
| API ドキュメント | API 利用者 | OpenAPI/Swagger |
| Runbook | 運用担当 | 障害対応の手順書 |
| オンボーディングガイド | 新メンバー | 開発環境構築、コード概要 |
ADR の形式
# ADR-001: メッセージキューに AWS SQS を採用
## ステータス
承認済み (2026-03-01)
## コンテキスト
非同期処理のためのメッセージキューが必要。
## 決定
AWS SQS を採用する。
## 理由
- AWS エコシステムとの統合が容易
- マネージドサービスで運用負荷が低い
- チームに AWS の知見がある
## 却下した選択肢
- RabbitMQ: 自前でホスティングが必要
- Kafka: 現在の規模にはオーバースペック
## 影響
- SQS のメッセージサイズ上限 (256KB) を考慮した設計が必要
Agent-first 開発においてこれが重要な理由
SE の実践は、AI コーディングエージェントとの協業の品質を直接左右する。
- テストを指示できる: 「この関数の Unit テストを書いて。エッジケースとして空配列と null も含めて」と具体的にテスト戦略を伝えられる
- CI の失敗を診断できる: エージェントに「lint エラーを修正して」「型エラーを解消して」と的確な修正指示ができる
- コミットの粒度を制御できる: 「この変更はリファクタリングと機能追加で別のコミットにして」と分割を指示できる
- 技術的負債を認識できる: エージェントの出力が負債を増やしていないかを評価し、必要に応じて改善を指示できる
- PR の品質を担保できる: エージェントが作成した PR の説明文やテストカバレッジが適切かを判断できる
エージェントは「コードを書く」が、「ソフトウェアを届ける」にはプロセス全体の理解が必要だ。
Further Reading
- Pro Git (書籍/Web) - Git の包括的なガイド (日本語版あり)
- Google Engineering Practices - Google のコードレビューガイド
- Testing Trophy (Kent C. Dodds) - テスト戦略の現代的なアプローチ
- Conventional Commits - コミットメッセージの規約
- ADR GitHub Organization - Architecture Decision Records のリソース
- Accelerate (書籍) - 開発組織のパフォーマンス指標