ソフトウェア工学

概要

要件定義・設計・テスト・保守・品質・技術的負債をつなげる

ソフトウェア工学は、コードを書く技術だけではありません。何を作るかを定義し、どう壊れにくくし、どう長く育てるかを扱う学問です。

要点
ソフトウェア工学の中心は「よいコードを書くこと」だけではなく、「変更が続く現実の中で、壊れずに前へ進めること」です。要件、設計、テスト、保守、品質、チーム運営は分断せず一つの流れで見る方が実務に近いです。

この章で重視すること

  • 設計を UML の記号ではなく、変更への備えとして捉える
  • テストを品質保証の全部だと思わない
  • 保守を後工程ではなく、最初から織り込む
  • 技術的負債を感情論ではなく意思決定の問題として扱う
  • アプリケーションアーキテクチャ と重複せず、より工学全体の視点で整理する

目次

  1. ソフトウェア工学とは何か
  2. ソフトウェア開発の現実
  3. 要件定義
  4. 設計
  5. アーキテクチャとの関係
  6. 実装規律
  7. テスト
  8. 品質特性
  9. 保守と運用
  10. 技術的負債
  11. 見積もりと計画
  12. チーム開発とレビュー
  13. 継続的デリバリと DevOps
  14. 失敗パターン
  15. 参考文献

ソフトウェア工学とは何か

ソフトウェア工学は、ソフトウェアを計画可能に作り、変更可能に保ち、継続的に価値を出すための知識体系です。

単なるプログラミングとの違いは、対象がコードそのものだけでなく、

まで含むことです。

なぜ「工学」なのか

工学と呼ぶからには、再現性、トレードオフ、制約下での最適化を考えます。

  • 時間は有限
  • 人数も有限
  • 品質要求は複数ある
  • 変更は止まらない

この中で、毎回場当たり的に決めるのではなく、判断の型を持つことが工学的な態度です。

ソフトウェア開発の現実

現実の開発では、最初から仕様が完全に分かっていることはほとんどありません。

  • 要件は変わる
  • 人は入れ替わる
  • 依存ライブラリは更新される
  • インフラも変わる
  • ビジネス側の優先順位も変わる

そのため、工学的に重要なのは「最初から完璧に設計すること」より、変更が来ても崩れにくい形を保つことです。

ウォーターフォール対アジャイル、で終わらせない

開発プロセスの議論はしばしば方法論の対立に見えますが、本質は次の問いです。

  • 不確実性はどれくらい高いか
  • 途中で学ぶ余地はあるか
  • リリースを刻めるか
  • 検証をどれだけ早く回せるか

重要なのは流派ではなく、フィードバックをどれだけ早く、安く、正確に回せるかです。

要件定義

要件定義は「画面の項目を洗い出す作業」ではありません。何を達成したいのか、誰にとって何が価値かを明確にする工程です。

要件の種類

  • 機能要件: 何ができるべきか
  • 非機能要件: どの程度の性能、可用性、安全性が必要か
  • 制約条件: 法規制、予算、期限、既存環境

よくある失敗

  • 手段を要件だと思ってしまう
  • 非機能要件を後回しにする
  • 利害関係者ごとの成功条件が違うのに統合しない

要件定義で本当に決めること

要件定義で大事なのは「何を作るか」だけではなく、

  • 何を作らないか
  • どこまでを今回のスコープにするか
  • 何を後で検証するか

を明確にすることです。

ユーザーストーリーだけでは足りない

ユーザーストーリーは便利ですが、それだけでは

  • データ移行
  • 障害時運用
  • 権限管理
  • 監査ログ

のような横断要件が抜けやすいです。

非機能要件を早く出す理由

性能、可用性、セキュリティ、可観測性は後からも足せますが、構造に食い込むことが多いです。後出しにすると、アーキテクチャ全体をやり直すことがあります。

設計

設計は、コードを書き始める前にすべてを図にすることではありません。どこを変わりやすい場所として扱い、どこを安定させるかを決めることです。

設計で見るべき観点

  • 責務の分割
  • 依存方向
  • データの境界
  • 失敗時の振る舞い
  • テストしやすさ

良い設計の目安

  • 変更理由がまとまっている
  • 影響範囲が閉じている
  • 境界が説明できる
  • 例外系を無視していない

設計は未来の変更コストを下げる活動

設計の成果物は図そのものではなく、将来の変更に対する耐性です。

たとえば「料金計算ロジックが頻繁に変わる」と分かっているなら、その変化が 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. この変更で将来の変更は楽になるか、苦しくなるか
  2. 壊れたときに気づけるか
  3. 影響範囲を説明できるか
  4. ロールバックできるか
  5. この近道はあとで返済可能か

大きな方法論より、こうした小さな判断軸の積み重ねが品質を左右します。

ケーススタディ

ケース1: 仕様変更が多い料金計算

EC サービスで、料金計算ルールが毎月変わるとします。

ありがちな悪い形:

  • 画面層に計算ロジックが散らばる
  • SQL に計算式が埋まる
  • テストが画面経由の E2E に偏る

このとき重要なのは、

  • 計算ルールを独立した境界に寄せる
  • 入出力を固定し、中のルールだけ差し替えやすくする
  • ルール単位の単体テストを厚くする

ことです。

ケース2: 障害が起きるまで監視が弱い

サービス自体は動いているが、障害検知がユーザー報告頼みのケースです。

この状況では、コード品質だけでなく、

  • 何を正常とみなすか
  • どのメトリクスで異常を知るか
  • どこでアラートを上げるか

が設計されていません。これは運用の問題であると同時に、設計の不足でもあります。

ケース3: マイクロサービス化したのに遅い

サービス分割後に開発速度がむしろ落ちることがあります。

よくある原因:

  • 境界が業務責務でなく組織事情だけで切られている
  • テストとローカル再現が極端に重い
  • データ整合性の設計が弱い
  • observability が不足し、障害時に跨ぎ追跡できない

このケースでは、サービス数を増やしたこと自体より、分割の前提条件が足りていたかを見直す必要があります。

判断問題

問題1

要件定義の会議で、「まず画面一覧を全部出しましょう」という話から始まりました。このとき、何が抜けやすいでしょうか。

問題2

E2E テストは多いのに障害が減りません。次に疑うべきなのは何でしょうか。

問題3

今すぐ出荷しないと機会損失が大きい機能があります。ただしテストと設計は十分ではありません。このとき、技術的負債として最低限何を残すべきでしょうか。

問題4

コードレビューで import 順や細かなフォーマット議論に多くの時間を使っています。どう改善するのが自然でしょうか。

問題5

高可用性が必要なサービスなのに、ロールバック手順が手順書レベルで曖昧です。これはどの章の観点で問題化されるでしょうか。

判断問題の考え方

問題1 の考え方

画面中心で要件を始めると、

  • 非機能要件
  • 運用要件
  • 権限
  • 監査
  • データ移行

が抜けやすいです。

問題2 の考え方

E2E の量より、保証の分担が悪い可能性があります。単体、結合、契約テスト、監視、障害解析の設計を見直すべきです。

問題3 の考え方

負債を取ること自体より、

  • どこが暫定か
  • どう返すか
  • いつ返すか

を残すことが重要です。

問題4 の考え方

自動化できる論点は formatter や linter に寄せ、人間のレビューは設計意図や境界条件へ寄せるのが自然です。

問題5 の考え方

これは 保守と運用継続的デリバリと DevOps品質特性 の複合問題です。高可用性はコードだけで達成されません。

まとめ

ソフトウェア工学は、要件定義から保守までを分断せずに扱うための考え方です。設計、実装、テスト、運用、品質、技術的負債を一本の流れとして捉えることが、実務でぶれない土台になります。