この記事について
「Willgate Advent Calendar 2018」11 日目の記事です。
記事が投稿された日付は気にしてはいけない。いいね?
きっかけ
およそ 2 ヶ月前に下書きになっていた記事があったので掘り返し。
この記事を書き始めたころの twitter のタイムラインにこんなツイートが流れてきました。
エンバグを断罪するような思考そもそも気に入らない。個人の注意力に依存するのは工学的じゃない。罪はバグが発生しやすい設計にあると考えるのがエンジニアじゃないの? って思う。個々のバグに対して迷惑被ったと怒るのはエンドユーザー視点しかない人だと思う
— 田中ひさてる (@tanakahisateru) 2018年10月2日
私もこれに近い考えをずっと抱いてきていたので、引用リツイート + コメントせずにはいられませんでした。
本当にこれ。
— おかしょい (@okashoi) 2018年10月2日
バグらないように「気をつけて」作るんじゃなく、バグる余地が無いように「考えて」作る。
似ているようで、全く違う。 https://t.co/sr98ZYiL7C
今回は、この「バグらないように『気をつけて』作るのではなく、バグる余地が無いように『考えて』作る」という考え方について、もう少し深掘りしてみます。
背景
ソフトウェアに限らず、広くエンジニアリング(工学)の世界には「フールプルーフ」という考え方があります。
直訳すると「馬鹿除け」と身も蓋も無い言い方ですが、トヨタ生産方式の文脈では「ポカヨケ」と呼ばれ、そのまま "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の場合も多い気がします。
頭が良いのでその人はこの方法でもミスすることがありません。なのでこの方法に疑問を持つ機会が無いのだと思います。
しかしそんなコードがひとたび「普通の人」に引き継がれれば、それはきっと悲劇の始まりでしょう。
だから「バグる余地が無いように『考えて』作る」という考え方を広めていきたい、と思うのです。
ですが最近になって、この考え方についてさらに思うことが生まれてきました。
これはちょっと長くなりそうなので別エントリに書くことにします。