ソフトウェア工学
概要
要件定義・設計・テスト・保守・品質・技術的負債をつなげる
ソフトウェア工学は、コードを書く技術だけではありません。何を作るかを定義し、どう壊れにくくし、どう長く育てるかを扱う学問です。
この章で重視すること
- 設計を UML の記号ではなく、変更への備えとして捉える
- テストを品質保証の全部だと思わない
- 保守を後工程ではなく、最初から織り込む
- 技術的負債を感情論ではなく意思決定の問題として扱う
アプリケーションアーキテクチャと重複せず、より工学全体の視点で整理する
目次
- ソフトウェア工学とは何か
- ソフトウェア開発の現実
- 要件定義
- 設計
- アーキテクチャとの関係
- 実装規律
- テスト
- 品質特性
- 保守と運用
- 技術的負債
- 見積もりと計画
- チーム開発とレビュー
- 継続的デリバリと DevOps
- 失敗パターン
- 参考文献
ソフトウェア工学とは何か
ソフトウェア工学は、ソフトウェアを計画可能に作り、変更可能に保ち、継続的に価値を出すための知識体系です。
単なるプログラミングとの違いは、対象がコードそのものだけでなく、
- 要件
- 設計
- プロセス
- チーム
- 品質
- 保守
まで含むことです。
なぜ「工学」なのか
工学と呼ぶからには、再現性、トレードオフ、制約下での最適化を考えます。
- 時間は有限
- 人数も有限
- 品質要求は複数ある
- 変更は止まらない
この中で、毎回場当たり的に決めるのではなく、判断の型を持つことが工学的な態度です。
ソフトウェア開発の現実
現実の開発では、最初から仕様が完全に分かっていることはほとんどありません。
- 要件は変わる
- 人は入れ替わる
- 依存ライブラリは更新される
- インフラも変わる
- ビジネス側の優先順位も変わる
そのため、工学的に重要なのは「最初から完璧に設計すること」より、変更が来ても崩れにくい形を保つことです。
ウォーターフォール対アジャイル、で終わらせない
開発プロセスの議論はしばしば方法論の対立に見えますが、本質は次の問いです。
- 不確実性はどれくらい高いか
- 途中で学ぶ余地はあるか
- リリースを刻めるか
- 検証をどれだけ早く回せるか
重要なのは流派ではなく、フィードバックをどれだけ早く、安く、正確に回せるかです。
要件定義
要件定義は「画面の項目を洗い出す作業」ではありません。何を達成したいのか、誰にとって何が価値かを明確にする工程です。
要件の種類
- 機能要件: 何ができるべきか
- 非機能要件: どの程度の性能、可用性、安全性が必要か
- 制約条件: 法規制、予算、期限、既存環境
よくある失敗
- 手段を要件だと思ってしまう
- 非機能要件を後回しにする
- 利害関係者ごとの成功条件が違うのに統合しない
要件定義で本当に決めること
要件定義で大事なのは「何を作るか」だけではなく、
- 何を作らないか
- どこまでを今回のスコープにするか
- 何を後で検証するか
を明確にすることです。
ユーザーストーリーだけでは足りない
ユーザーストーリーは便利ですが、それだけでは
- データ移行
- 障害時運用
- 権限管理
- 監査ログ
のような横断要件が抜けやすいです。
非機能要件を早く出す理由
性能、可用性、セキュリティ、可観測性は後からも足せますが、構造に食い込むことが多いです。後出しにすると、アーキテクチャ全体をやり直すことがあります。
設計
設計は、コードを書き始める前にすべてを図にすることではありません。どこを変わりやすい場所として扱い、どこを安定させるかを決めることです。
設計で見るべき観点
- 責務の分割
- 依存方向
- データの境界
- 失敗時の振る舞い
- テストしやすさ
良い設計の目安
- 変更理由がまとまっている
- 影響範囲が閉じている
- 境界が説明できる
- 例外系を無視していない
設計は未来の変更コストを下げる活動
設計の成果物は図そのものではなく、将来の変更に対する耐性です。
たとえば「料金計算ロジックが頻繁に変わる」と分かっているなら、その変化が UI、DB、外部 API にまで伝播しないように境界を作る必要があります。
凝集と結合
設計で古典的に重要なのは、高凝集・低結合です。
- 高凝集: 一つのモジュールの責務がまとまっている
- 低結合: 他モジュールへの依存が少なく、狭い
この2つは今でもかなり強い指針です。
設計レビューで見る観点
- 名前と責務は一致しているか
- この境界は変更理由で切れているか
- 例外系や失敗時の流れは設計されているか
- 監視や運用の視点が入っているか
アーキテクチャとの関係
アーキテクチャは設計の中でも特に大きな決定です。モジュール分割、デプロイ単位、データ境界、チーム境界に影響します。
アプリケーションアーキテクチャ が扱うのは構造の選択ですが、ソフトウェア工学ではそれを
- 要件
- テスト
- 運用
- 変更頻度
と結びつけて評価します。
アーキテクチャを早く決めすぎる危険
マイクロサービス、イベント駆動、CQRS のような構造は魅力的ですが、要件や組織の成熟度に合わないと負債になります。
アーキテクチャの選択は「かっこいい構成を選ぶこと」ではなく、
- チーム境界
- デプロイ頻度
- 障害許容度
- データ整合性要求
に見合う構造を選ぶことです。
実装規律
実装規律は「きれいなコードを書く」以上の意味を持ちます。
- 命名
- 小さな関数
- 一貫したエラー処理
- ログ方針
- 境界でのバリデーション
が崩れると、変更コストが跳ね上がります。
実装規律は個人技ではなくチーム規律
個人がきれいなコードを書くことより、チーム全体で一貫した書き方を維持することが大事です。
- フォーマッタ
- Linter
- 命名規約
- 例外処理方針
- ログの粒度
を共有すると、コードレビューの摩擦も減ります。
境界で厳密に、中では簡潔に
入力境界で検証を厳密に行い、内部では仮定を少し強く置けるようにすると、コード全体の見通しがよくなります。
テスト
テストは単なるバグ検出ではなく、振る舞いの仕様化でもあります。
主な種類
- 単体テスト
- 結合テスト
- E2E テスト
- 性能テスト
- セキュリティテスト
- 回帰テスト
テストの誤解
- テスト件数が多ければ安心、ではない
- E2E だけでは不安定になる
- 単体テストだけでも不足する
重要なのは、どの層で何を保証するかの分担です。
テストピラミッド
よく知られた考え方として、テストピラミッドがあります。
- 下層: 単体テストを多く、速く
- 中層: 結合テストで境界を確認
- 上層: E2E を少数に絞る
これは E2E を否定する考え方ではなく、コストに応じて保証の置き場所を調整する発想です。
良いテストの条件
- 速い
- 独立している
- 意図が読める
- 壊れたとき原因が追いやすい
テストしにくい設計は、設計のシグナル
モックが大量に必要、環境依存が強い、状態初期化が長い、といったときは、実装だけでなく設計境界が悪い可能性があります。
プロダクトコードとテストコードの関係
テストコードは使い捨てではありません。長く保守される資産なので、
- 重複を減らす
- ただし抽象化しすぎない
- テスト名で意図を残す
ことが大切です。
品質特性
品質は「バグが少ないか」だけではありません。
代表的には次の観点があります。
- 正確性
- 保守性
- 可用性
- 性能効率
- セキュリティ
- 可観測性
- 使いやすさ
非機能要件と品質特性は強く結びつきます。
品質特性はしばしば衝突する
たとえば、
- 強い整合性を取ると性能が落ちる
- セキュリティを高めると利便性が落ちる
- 可観測性を高めるとコストが増える
といった衝突があります。品質は単一指標ではなく、優先順位づけの問題です。
可観測性を品質の一部として扱う
現代では、壊れないこと以上に「壊れたとき早く分かること」が重要です。
- ログ
- メトリクス
- トレース
- アラート
を設計時点から考える必要があります。
保守と運用
ソフトウェアのコストの多くは保守で発生します。
保守には次があります。
- 不具合修正
- 仕様変更
- 性能改善
- ライブラリ更新
- 監視と障害対応
書いた後が本番です。最初からログ、メトリクス、トレーシング、移行手順を考えておく必要があります。
保守の4分類
保守はよく次のように分けられます。
- 修正保守: バグ修正
- 適応保守: 環境変化への対応
- 完全化保守: 機能改善
- 予防保守: 将来問題を起こしにくくする改善
このうち予防保守は後回しにされやすいですが、長期コストに大きく効きます。
運用を後工程にしない
実際の障害では、コードの正しさだけでなく
- デプロイ手順
- ロールバック
- feature flag
- データ移行
- インシデント時の権限
が重要になります。
技術的負債
技術的負債は「汚いコード」の別名ではありません。短期的利益のために将来コストを受け入れる意思決定です。
負債の例
- 十分なテストなしで急いで出す
- 境界が曖昧なまま機能追加を続ける
- 古い依存関係を放置する
- ドキュメントと実装がずれる
問題になるのはいつか
- 変更のたびに影響範囲が読めない
- 障害時に原因が追えない
- 新メンバーの立ち上がりが遅い
負債は借りてよいが、記録しないのが危険
負債を取ること自体が悪ではありません。むしろ短期の価値を優先するために必要なこともあります。
危険なのは、
- どこが暫定か分からない
- なぜそうしたかが残っていない
- 返済条件が決まっていない
状態です。
負債返済のタイミング
- 大きな変更の前
- 障害が続いている領域
- オンボーディング負荷が高い領域
- 依存更新が止まっている領域
では、返済効果が大きく出やすいです。
見積もりと計画
ソフトウェアの見積もりは難しいです。なぜなら、未知が多く、実装前に全部を正確に数えられないからです。
それでも見積もりが必要なら、
- 不確実性を明示する
- スパイクを入れる
- 機能の粒度をそろえる
- バッファを持つ
ことが大事です。
見積もりは約束ではなく予測
見積もりを契約のように扱うと、現場では嘘が増えます。必要なのは、
- 現時点の知識での予測
- 不確実性の幅
- 何が分かれば精度が上がるか
を共有することです。
バーンダウンよりリスクの見える化
計画管理では作業量だけでなく、
- 外部依存
- 技術的不確実性
- データ準備
- 仕様未確定領域
を見える化した方が実務では効くことが多いです。
チーム開発とレビュー
レビューは誤り検出だけでなく、知識共有と設計整流の場です。
よいレビューの観点:
- 仕様意図に沿っているか
- 境界条件を見ているか
- 将来の変更を妨げないか
- テストが意図を表しているか
レビューで見ない方がよいもの
ツールで自動化できる
- フォーマット
- import 順
- 単純な lint
に人間の時間を使いすぎない方がよいです。レビューでは設計意図や境界条件に集中した方が価値が高いです。
チームの共有知識を増やす
属人化を減らすには、
- 設計判断を PR に残す
- ADR を書く
- 変更理由をコミットに残す
といった活動が効きます。
継続的デリバリと DevOps
開発と運用を分断しないために、CI/CD と観測基盤が重要になります。
- 自動テスト
- 自動デプロイ
- feature flag
- rollback
- canary release
は、速く出すためだけでなく、安全に出すための仕組みです。
CI/CD の本質
CI/CD はパイプラインを作ることではなく、「小さく出して早く戻す」ための能力です。
trunk based development と feature branch
ブランチ戦略も宗教論ではなく、
- 統合頻度
- テスト速度
- リリース手法
との組み合わせで決まります。
DevOps は役職名ではなく性質
開発と運用の壁を低くし、責任と観測をつなぐ考え方です。組織設計、権限、ツール、文化の全部に関わります。
失敗パターン
- アーキテクチャだけ立派で実装規律が伴わない
- テストが遅すぎて回らない
- 非機能要件を最後に考える
- 技術的負債の返済タイミングを持たない
- 運用担当が設計に入っていない
もう少し具体的な失敗例
- モックだらけで結合不良に気づかない
- 権限設計を後回しにして全面改修になる
- スキーマ変更のロールバックがなく本番で詰む
- 監視が弱く、障害検知より先にユーザーが気づく
- 非同期化したが可観測性がなく原因不明になる
現場で使う判断軸
日々の判断を支える簡易的な軸として、次の質問が有効です。
- この変更で将来の変更は楽になるか、苦しくなるか
- 壊れたときに気づけるか
- 影響範囲を説明できるか
- ロールバックできるか
- この近道はあとで返済可能か
大きな方法論より、こうした小さな判断軸の積み重ねが品質を左右します。
ケーススタディ
ケース1: 仕様変更が多い料金計算
EC サービスで、料金計算ルールが毎月変わるとします。
ありがちな悪い形:
- 画面層に計算ロジックが散らばる
- SQL に計算式が埋まる
- テストが画面経由の E2E に偏る
このとき重要なのは、
- 計算ルールを独立した境界に寄せる
- 入出力を固定し、中のルールだけ差し替えやすくする
- ルール単位の単体テストを厚くする
ことです。
ケース2: 障害が起きるまで監視が弱い
サービス自体は動いているが、障害検知がユーザー報告頼みのケースです。
この状況では、コード品質だけでなく、
- 何を正常とみなすか
- どのメトリクスで異常を知るか
- どこでアラートを上げるか
が設計されていません。これは運用の問題であると同時に、設計の不足でもあります。
ケース3: マイクロサービス化したのに遅い
サービス分割後に開発速度がむしろ落ちることがあります。
よくある原因:
- 境界が業務責務でなく組織事情だけで切られている
- テストとローカル再現が極端に重い
- データ整合性の設計が弱い
- observability が不足し、障害時に跨ぎ追跡できない
このケースでは、サービス数を増やしたこと自体より、分割の前提条件が足りていたかを見直す必要があります。
判断問題
問題1
要件定義の会議で、「まず画面一覧を全部出しましょう」という話から始まりました。このとき、何が抜けやすいでしょうか。
問題2
E2E テストは多いのに障害が減りません。次に疑うべきなのは何でしょうか。
問題3
今すぐ出荷しないと機会損失が大きい機能があります。ただしテストと設計は十分ではありません。このとき、技術的負債として最低限何を残すべきでしょうか。
問題4
コードレビューで import 順や細かなフォーマット議論に多くの時間を使っています。どう改善するのが自然でしょうか。
問題5
高可用性が必要なサービスなのに、ロールバック手順が手順書レベルで曖昧です。これはどの章の観点で問題化されるでしょうか。
判断問題の考え方
問題1 の考え方
画面中心で要件を始めると、
- 非機能要件
- 運用要件
- 権限
- 監査
- データ移行
が抜けやすいです。
問題2 の考え方
E2E の量より、保証の分担が悪い可能性があります。単体、結合、契約テスト、監視、障害解析の設計を見直すべきです。
問題3 の考え方
負債を取ること自体より、
- どこが暫定か
- どう返すか
- いつ返すか
を残すことが重要です。
問題4 の考え方
自動化できる論点は formatter や linter に寄せ、人間のレビューは設計意図や境界条件へ寄せるのが自然です。
問題5 の考え方
これは 保守と運用、継続的デリバリと DevOps、品質特性 の複合問題です。高可用性はコードだけで達成されません。
まとめ
ソフトウェア工学は、要件定義から保守までを分断せずに扱うための考え方です。設計、実装、テスト、運用、品質、技術的負債を一本の流れとして捉えることが、実務でぶれない土台になります。