s平面の左側

左側なので安定してます(制御工学の話は出てきません)

「バグを減らすために『気をつけて』作る」のではなく「バグらせるのが難しくなるように『考えて』作る」

この記事について

Willgate Advent Calendar 2018」11 日目の記事です。

記事が投稿された日付は気にしてはいけない。いいね?

きっかけ

およそ 2 ヶ月前に下書きになっていた記事があったので掘り返し。

この記事を書き始めたころの twitter のタイムラインにこんなツイートが流れてきました。

私もこれに近い考えをずっと抱いてきていたので、引用リツイート + コメントせずにはいられませんでした。

今回は、この「バグらないように『気をつけて』作るのではなく、バグる余地が無いように『考えて』作る」という考え方について、もう少し深掘りしてみます。

背景

ソフトウェアに限らず、広くエンジニアリング(工学)の世界には「フールプルーフ」という考え方があります。

直訳すると「馬鹿除け」と身も蓋も無い言い方ですが、トヨタ生産方式の文脈では「ポカヨケ」と呼ばれ、そのまま "Poka-yoke" という英単語にもなっています*1

これはすなわち「人間はミス(ポカ)をするものという前提のもと、仕組みによって正しい挙動・安全を担保しよう」という考え方で、代表例としては「(片手が巻き込まれないように)両手でスイッチを押さないと動作しない工作機械」などが挙げられます。

現在の私の仕事は、ミスによって人の命が失われたり怪我をしたりするような環境でありませんが、バグを生み出してしまえば何かしらの損害*2につながることには変わりません。

この考えから私は「どうしたらバグが入り込む余地がないように作れるのか」ということに強い関心を抱き続けてきました。

具体例

どんなに気を付けても人間の注意力には限界があります。先述の通り「人間はミスをするもの」なのです。

例えば「注文」というデータを、次のように(連想)配列を使って表現したとします。

<?php

$order = [
    'order_id'   => 1,
    'client_id'  => 1,
    'ordered_at' => '2018-10-04 00:00:00',
    'products'   => [
        [
            'product_id' => 1,
            'amount'     => 20,
        ],
        [
            'product_id' => 2,
            'amount'     => 10,
        ],
    ],
];

このように表現すると、次のような「ミスをする余地」が生じてしまいます。

属性の漏れ

<?php

$order = [
    'order_id'   => 1,
    // client_id を忘れている
    'ordered_at' => '2018-10-04 00:00:00',
    'products'   => [
        [
            'product_id' => 1,
            'amount'     => 20,
        ],
        [
            'product_id' => 2,
            'amount'     => 10,
        ],
    ],

属性名の typo

<?php

$order = [
    'order_id'   => 1,
    'cilent_id'  => 1, // client_id を typo している
    'ordered_at' => '2018-10-04 00:00:00',
    'products'   => [
        [
            'product_id' => 1,
            'amount'     => 20,
        ],
        [
            'product_id' => 2,
            'amount'     => 10,
        ],
    ],
];

違う属性の値を代入

<?php

$order = [
    'order_id'   => 1,
    'ordered_at'  => 1,
    'client_id' => '2018-10-04 00:00:00', // client_id と ordered_at の値が逆に指定している
    'products'   => [
        [
            'product_id' => 1,
            'amount'     => 20,
        ],
        [
            'product_id' => 2,
            'amount'     => 10,
        ],
    ],
];

上記はすべて PHP の文法上は正しいのですぐにエラーにならず、検知が遅れます。

また、注文した商品の総数を計算するには foreach などを使ってループを書くことになります。 これによてコードのネストは深くなり、見通しが悪くなりがちです。

では配列ではなくクラスを使って「注文」やその属性を表現してみるとどうなるでしょう。

下記はあくまで一例です。

<?php

class OrderId
{
    protected $value;

    public function __construct(int $value)
    {
        $this->value = $value;
    }
}

class ClientId
{
    protected $value;

    public function __construct(int $value)
    {
        $this->value = $value;
    }
}

class Order
{
    protected $id;
    protected $clientId;
    protected $orderedAt;
    protected $products;

    public function __construct(OrderId $id, ClientId $clientId, \DateTime $orderedAt, array $products)
    {
        $this->id = $id;
        $this->clientId = $clientId;
        $this->orderedAt = $orderedAt;
        $this->products = $products;
    }

    public function calculateTotalProductAmount(): int
    {
        $amount = 0;
        foreach ($this->products as $product) {
            $amount += $product['amount'];
        }

        return $amount;
    }
}

このようにすれば、配列で表現していたときに存在していた「ミスする余地」が無くなります。なにかしらミスがあれば即座に構文エラーとして検知することができます。

また、注文した商品の総数の計算ロジックもメソッド内に閉じられるのでコードの見通しも良くなります。

さらに「このデータが何なのか」という情報も、配列で表現していたときには「辛うじて変数名から推測できる」くらいだったものが、クラス(型)を持つことで明確になるというメリットもあります。

このようにして設計の力によって「バグらせることを難しくできる」という風に考えられるわけです。

余談

これは私見になりますが「ミスをする余地」のあるプログラムを書くのは、プログラミング初心者だけでなく、頭の良い人*3の場合も多い気がします。

頭が良いのでその人はこの方法でもミスすることがありません。なのでこの方法に疑問を持つ機会が無いのだと思います。

しかしそんなコードがひとたび「普通の人」に引き継がれれば、それはきっと悲劇の始まりでしょう。

だから「バグる余地が無いように『考えて』作る」という考え方を広めていきたい、と思うのです。

ですが最近になって、この考え方についてさらに思うことが生まれてきました。

これはちょっと長くなりそうなので別エントリに書くことにします。

*1:実際、 foolproof とどちらが使われるのでしょうか?

*2:プロジェクトの遅延、サービス停止による機会損失など

*3:ここでは脳のメモリ容量が大きい人、みたいなニュアンス