ソフトウェア設計について考えていると、「演繹法」という言葉に出会うことがあります。
しかし、演繹法と聞くと何か特別な思考法のように感じるかもしれません。
私自身、演繹法について改めて考えてみた結果、
「普段当たり前にやっていることに名前が付いているだけではないか?」
と感じました。
この記事では、演繹法とソフトウェア設計の関係について考えてみます。
演繹法とは
演繹法とは、
一般的なルール(前提)から、個別の結論を導く考え方
です。
有名な例として、
- 人間はみな死ぬ
- ソクラテスは人間である
- したがってソクラテスは死ぬ
というものがあります。
重要なのは、
前提が正しければ、結論も正しい
という点です。
私たちは日常的に演繹している
実はソフトウェア開発者は、日常的に演繹法を使っています。
例えば、次のようなインターフェースがあったとします。
interface PaymentRepository {
suspend fun save(payment: Payment)
}
このコードを見たとき、多くの開発者は自然に次のように考えるはずです。
- save は保存処理である
- 保存したデータは後で取得できるはず
- 保存したデータが勝手に壊れてはいけないはず
これはすべて、
save は保存する
↓
だから○○であるべき
という演繹です。
特別なことをしているわけではありません。
普段の設計レビューやコードレビューで当たり前に行っている思考です。
問題は前提が曖昧なときに起こる
ただし、演繹法には重要なポイントがあります。
それは、
前提が明確であること
です。
例えば、次のようなインターフェースを考えます。
interface PaymentRepository {
suspend fun delete(id: Int)
}
これを見た多くの人は、
- delete は削除処理である
- 削除したデータは取得できなくなるはず
と考えるでしょう。
しかし、実際には仕様として、
論理削除を行う
というルールが存在するかもしれません。
この場合、
delete は物理削除である
という前提を、利用者側が勝手に補っていたことになります。
つまり、問題は演繹法そのものではなく、
前提が曖昧であること
にあります。
設計とは何をしているのか
ここで気付いたことがあります。
ソフトウェア設計とは、実装方法を決めることだと考えられがちです。
しかし実際には、
開発者同士が共有すべき前提を定義すること
の方が本質なのではないでしょうか。
例えば、
interface PaymentRepository {
suspend fun findById(id: Int): Payment?
}
というインターフェースには、
- IDは一意である
- 存在しない場合は null を返す
- 例外は投げない
などの前提が含まれます。
これらが明文化されていれば、
- 利用者は安心して呼び出せる
- 実装者は守るべき契約が分かる
- テスト担当者は期待値を定義できる
ようになります。
一方で前提が定義されていないと、
Aさんの想定
≠
Bさんの想定
≠
テスト担当者の想定
という状況になり、認識齟齬や不具合の原因になります。
演繹法と設計の関係
設計されたインターフェースや仕様は、利用者にとっての前提になります。
そして利用者はその前提から、
- このメソッドはどう振る舞うべきか
- この実装は契約を守っているか
- このテストケースは十分か
を演繹的に判断します。
つまり、
設計
↓
前提
↓
演繹
↓
実装・利用・テスト
という流れになっています。
演繹法は特別な思考法ではなく、設計段階で定義された前提を利用して、各種判断を行うための思考プロセスそのものだと考えました。
まとめ
演繹法について考えていたつもりが、最終的には設計の本質について考えることになりました。
ソフトウェア開発で問題が起きる原因は、実装技術の不足よりも、
前提の共有不足
であることが少なくありません。
だからこそ設計の役割は、実装方法を定義することではなく、
「開発者が勝手な前提を持ち込まなくても済むように、前提を明文化する行為」
なのだと考えました。