契約プログラミングとは

ざっくり

プログラムが仕様に沿ってるかを担保するためのプログラミング手法

その機能(supplier)とそれを呼び出す側(client)で、お互い満たすべき内容を契約として定義(表明)して、契約を満たすようにコードを書く

手法

3つの表明がありそれぞれを満たす必要がある

  • 事前条件: clientsupplierを呼び出すときに満たすべき条件
  • 事後条件: supplierが結果をclientに返すときに満たすべき条件
  • 不変条件: supplierが呼び出されている間に満たすべき条件

実現方法

契約プログラミングに対応している言語(Kotlinとか)では、アノテーションなどで事前条件や事後条件を定義できる。 しかし、対応してない言語でも、validation関数を用意して、呼び出す前に事前条件を満たしているかを確認するなどの方法で実現できる。

事前条件

  1. 型で縛る
  2. validation関数を用意して、事前条件を満たしているかを確認する
// 型で数字であることを保証する
fn pay_by_card(amount: u32) {
  // validationで金額が1以上であることを保証する
  if amount >= 1 {
    panic!("金額は1以上でなければなりません");
  }
  // 支払い処理
}

事後条件

事前条件と同様で、

  1. 型で縛る
  2. validation関数を用意して、事後条件を満たしているか
// 戻り値の型で数字であることを保証する
fn calc_price(orders: Vec<Order>) -> u32 {
  let total_price = orders.iter().map(|order| order.price).sum();

  // validationで合計金額が0以上であることを保証する
  if total_price < 0 {
    panic!("合計金額は0以上でなければなりません");
  }

  total_price
}

不変条件

モデルがあった場合、コンストラクタなどで不変条件を満たすようにする

struct AdultUser {
  name: String,
  age: u32,
}

impl AdultUser {
  fn new(name: String, age: u32) -> Self {
    // validationで年齢が0以上であることを保証する
    if age >= 18 {
      panic!("年齢は18以上でなければなりません");
    }

    User { name, age }
  }
}

防御的プログラミングとは

契約プログラミングと同じで、プログラムが仕様に沿っているかを保証するためのプログラミング手法

しかし、契約プログラミングと違って、契約を満たすためのコードをsupplier側で書くのではなく、client側で書く

つまり、呼び出す側で確認を挟みまくる必要がある

これはsupplierで書いた内容の検証を呼び出す側で行うことになるため、冗長になりがち

しかし、supplierが信用できないパターン(外部モジュールなど)では、契約プログラミングよりも防御的プログラミングの方が適している

まとめ

契約プログラミングも防御的プログラミングも、プログラムが仕様に沿ってるかを担保するためのプログラミング手法である。

防御的プログラミングは冗長になる場合が多いので、自分でsupplierを開発している場合は契約プログラミングが用いられるケースが多い。

参考