プログラマが知るべき97のこと/ステートに注目する

「ステート(状態)」というものについて、世間の人は面白い感覚を持っているようです。 今朝、私はコーヒーを淹れるのに必要なものを買うために、近所の店に寄って買い物をしました。 私はカフェインがないと一日たりとも仕事はできないのです。 私が好きなのはカフェラテなので、牛乳を買おうと思ったのですが、牛乳がどこにも見当たりません。 そこで店員に尋ねました。すると、

「すみません。牛乳は完全に売り切れてます」という答えが返ってきたのです。

プログラマからすれば、これは変な話です。売り切れは売り切れであり、それ以外ではあり得ません。 「少し売り切れ」、「かなり売り切れ」などということはないのです。 もしかすると店ではもう一週間も牛乳が品切れで、店員の言葉にはそれが込められていたのかもしれませんが、品切れが一週間だろうが1日だろうが、私にとって結果は同じです。カフェラテでなくエスプレッソで1日過ごすしかないということです。

もちろん、普段の生活では、ステートの扱いが厳密でなくて困ることはあまりありません。 しかし問題なのは、プログラマの中にも、これと同じようにステートを厳密に扱わない人が意外にいるのです。

たとえば、クレジットカード決済のみを受けつけ、顧客に請求書は送らないというWebショップがあったとします。 そして、そのショップの Order クラスに次のようなメソッドがあったとします。

public boolean isComplete() {
  return isPaid() && hasShipped();
}

これでいいじゃないか、と思う人は多いでしょう。しかし、本当にそうでしょうか。 もし仮に、メソッドが呼び出された時に実行されるだけで、あちこちにコピー&ペーストされるのでなかったとしても、 こういうコードを書くべきではありません。 このコードには問題があるからです。 何が問題なのでしょうか。それは、決済処理が終わる前に、商品を発送することなどあり得ない、ということです。 ​isPaid​​true​にならない限り、​hasShipped​​true​にならないのです。 要するに、このコードは冗長というわけです。 コードを明解なものにするために、​isComplete​メソッドはやはり必要でしょうが、その内容は次のようなものにすべきです。

public boolean isComplete() {
    return hasShipped();
}

仕事をしていると、必要なチェックが抜けているコード、冗長なチェックをしているコードは頻繁に目にします。上の例は大したものではありませんが、キャンセルや払い戻しが絡んでくると、もっと複雑になるでしょう。ステートを適切に扱う必要性がさらに高まるからです。上の例の場合、注文のステートは次の3つに明確に分かれることになります。

  • 進行中 : 注文の追加、削除はできない。商品の発送もできない。
  • 決済済み : 注文の追加、削除はできないっ商品の発送は可能。
  • 発送済み : 注文処理がすべて完了。変更は一切受けつけない。

ステートは非常に重要です。何か操作をしようとすれば、まず現在どのステートにいるかを確認する必要があります。その時々のステートによってできる操作とできない操作があるからです。しようとした操作が、そのステートではできないものであれば、ステートが変わるのを待つ必要があります。そうして、常にステートに合った操作だけが行われるようにすることで、オブジェクトを保護するわけです。

ステートに関しては、具体的にどういうことを注意すべきでしょうかの必要な処理を一つ一つ別のメソッドに分け、どれも必要な時だけ呼び出す、というのは、出発点としてはとても良いですが、あくまで出発点であり十分とは言えません。基本は、「ステートマシン」という考え方を理解することでしょう。そういえば学校でそういうことを習った覚えがあるけれど、もう何だか忘れてしまった、という人もいるでしょうが、そういう人もこれを機会に改めて勉強し直してください。さほど難しいものではありません。図を描くなどすれば、理解しやすくなるはずです。また、図があれば、他の人とステートマシンについて話をするのも簡単です。コードを書くときには、テスト駆動開発で個々の操作に適合するステート、適合しないステート、ステート間の遷移の適切さを確かめながら開発し、実行時に常にステートが正しく保たれるようにしましょう。Stateパターンについても学び、それが十分に理解できたら、「契約による設計 (Designby Contract)」などについても学ぶといいでしょう。そうした知識があれば、入力データをチェックし、ステートがそれに合ったものになっているかを確認するのに役立ちます。さらに、publicメソッドの処理開始時と終了時のオブジェクトのステートが妥当なものになっているかの確認などにも役立つはずです。

ステートが不適切になる時があるようなら、それはバグです。処理を中断しなければ、データが破壊されてしまう危険があります。ステートチェックのコードが多くて煩わしく感じられるなら、ツールを利用するか、コード生成、ウィーピング、アスペクト技術の使用等について検討するなどして、それが隠れるようにすればいいでしょう。どのようなアプローチをとるにしても、ステートに注目して考えれば、コードをよりシンプルに、そして堅牢にすることにつながります。