【97】ステートに注目する
「ステート(状態)」というものについて、世間の人は面白い感覚を持っているようです。今朝、私はコーヒーを淹れるのに必要なものを買うために、近所の店に寄って買い物をしました。私はカフェインがないと一日たりとも仕事はできないのです。私が好きなのはカフェラテなので、牛乳を買おうと思ったのですが、牛乳がどこにも見当たりません。そこで店員に尋ねました。すると、
「すみません。牛乳は完全に売り切れてます」という答えが返ってきたのです。
プログラマから見れば、これは変な話です。売り切れは売り切れであり、それ以外ではあり得ません。「少し売り切れ」、「かなり売り切れ」などということはないのです。もしかすると店ではもう一週間も牛乳が品切れで、店員の言葉にはそれが込められていたのかもしれませんが、品切れが一週間だろうが 1 日だろうが、私にとって結果は同じです。カフェラテでなくエスプレッソで 1 日過ごすしかないということです。
もちろん、普段の生活では、ステートの扱いが厳密でなくて困ることはあまりありません。しかし問題なのは、プログラマの中にも、これと同じようにステートを厳密に扱わない人が意外にいるのです。
たとえば、クレジットカード決済のみを受けつけ、顧客に請求書は送らないという Web ショップがあったとします。そして、そのショップの Order
クラスに次のようなメソッドがあったとします。
これでいいじゃないか、と思う人は多いでしょう。しかし、本当にそうでしょうか。もし仮に、メソッドが呼び出された時に実行されるだけで、あちこちにコピー&ペーストされるのでなかったとしても、こういうコードを書くべきではありません。このコードには問題があるからです。何が問題なのでしょうか。それは、決済処理が終わる前に、商品を発送することなどあり得ない、ということです。isPaid
が true
にならない限り、hasShipped
は true
にならないのです。要するに、このコードは冗長というわけです。コードを明解なものにするために、isComplete
メソッドはやはり必要でしょうが、その内容は次のようなものにすべきです。
仕事をしていると、必要なチェックが抜けているコード、冗長なチェックをしているコードは頻繁に目にします。上の例は大したものではありませんが、キャンセルや払い戻しが絡んでくると、もっと複雑になるでしょう。ステートを適切に扱う必要性がさらに高まるからです。上の例の場合、注文のステートは次の 3 つに明確に分かれることになります。
- 進行中:注文の追加、削除はできない。商品の発送もできない。
- 決済済み:注文の追加、削除はできない。商品の発送は可能。
- 発送済み:注文処理がすべて完了。変更は一切受けつけない。
ステートは非常に重要です。何か操作をしようとすれば、まず現在どのステートにいるかを確認する必要があります。その時々のステートによってできる操作とできない操作があるからです。しようとした操作が、そのステートではできないものであれば、ステートが変わるのを待つ必要があります。そうして、常にステートに合った操作だけが行われるようにすることで、オブジェクトを保護するわけです。
ステートに関しては、具体的にどういうことを注意すべきでしょうか。必要な処理を一つ一つ別のメソッドに分け、どれも必要な時だけ呼び出す、というのは、出発点としてはとても良いですが、あくまで出発点であり十分とは言えません。基本は、「ステートマシン」という考え方を理解することでしょう。そういえば学校でそういうことを習った覚えがあるけれど、もう何だか忘れてしまった、という人もいるでしょうが、そういう人もこれを機会に改めて勉強し直してください。さほど難しいものではありません。図を描くなどすれば、理解しやすくなるはずです。また、図があれば、他の人とステートマシンについて話をするのも簡単です。コードを書くときには、テスト駆動開発で個々の操作に適合するステート、適合しないステート、ステート間の遷移の適切さを確かめながら開発し、実行時に常にステートが正しく保たれるようにしましょう。State パターンについても学び、それが十分に理解できたら、「契約による設計(Design by Contract)」などについても学ぶといいでしょう。そうした知識があれば、入力データをチェックし、ステートがそれに合ったものになっているかを確認するのに役立ちます。さらに、public メソッドの処理開始時と終了時のオブジェクトのステートが妥当なものになっているかの確認などにも役立つはずです。
ステートが不適切になる時があるようなら、それはバグです。処理を中断しなければ、データが破壊されてしまう危険があります。ステートチェックのコードが多くて煩わしく感じられるなら、ツールを利用するか、コード生成、ウィービング、アスペクト技術の使用等について検討するなどして、それが隠れるようにすればいいでしょう。どのようなアプローチをとるにしても、ステートに注目して考えれば、コードをよりシンプルに、そして堅牢にすることにつながります。