97-ステートに注目する

プログラマが知るべき97のこと」の97個目のエピソードは、ステート(状態)に関する話です。学習を怠っていないプログラマであれば、「ステート」という言葉を聞いて「ステートマシン(図)」と「ステートパターン」などを連想すると思います。このエピソードにも書かれているように、プログラムの中では「ステート(状態)」は重要な要素で、注意深く扱わなければなりません。

仕事をしていると、必要なチェックが抜けているコード、冗長なチェックをしているコードは頻繁に目にします。

自分もこれはよく目にしました。ヒアリングなどをしてみると、そもそもステートについて意識をしていない事がほとんどなのが現実です。ステートについては意識しているものの、プログラムをシーケンシャルな処理としてしか捉えることができない為に、おかしな事になっていることもあります。ステートを正しく管理し扱うためには「ステートマシン」を使う事は大切です。
しかし、どうしてステートに着目する必要があるのでしょうか?それは、プログラミングというよりも、システムテストなどのテスティングに起因します。このエピソードで紹介されている例では、「支払い済」と「発想済み」を別のステータスとして管理している設計でないかと思われます。しかし、本文に書かれているように、本来は3つのステート(進行中・決済済み・発送済み)で構成・設計されている事が自然な姿です。設計と実装が異なるという事は充分にありえますが、少なくともテスティングにおいては、「支払い済」と「発送済み」を別のステータスとして管理していると考えてしまうと、それだけで多くのテストケースが発生します。また、それらのテストケースの中には無効な状態(支払い前で発送済み)も含まれることになります。この1点だけであればそれほどのインパクトはないでしょう。しかし、テスティングの事を考慮するといかにシンプルに設計し、テストパターンを少なくするかは品質に大きく影響する事なのです。つまり、プログラミングの中でステートを意識する事が重要なのは確かですが、それ以上にテスティングの中では重要な事になります。
実装の都合としてステートを2つ以上のフラグなどで管理する事はありますが、そのような時はステートパターンを使う事も検討したいところです。しかし、単純なステートの場合はデザインパターンの適用で、より解りにくくなってしまうこともあります。例えば、サーバのステートは「起動前・起動中・停止」の3つしかなく、一方にしか遷移しないとした場合、ステートパターンの適用は大げさすぎます。単純にフラグを持たせれば充分でしょう。このような場合は、Javaであればassert 構文が有効です。このエピソードであれば、次のように記述することで、冗長性がなくなります。

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

テストの時などには、assert文は有効にするVMの起動オプション-ea を使用します。すると、万が一に不具合があってisPaidがfalseを返してしまうと、AssertionErrorがthrowされるのです。しかし、本番などではコメントと同様に実行に影響を与えません。このようにステートパターンでは少し大げさだなと思いつつも、コメントとして前提条件などを書く代わりにassert文を書くことは有効な事です。
ステートは、シーケンスな処理とは異なる軸でソフトウェアに影響を与えます。しかし、プログラムは原則としてはシーケンシャルにしか書くことが出来ないため、どうしても意識が薄くなってしまいます。だからこそ、ステートを意識して、ステートを考慮した設計と実装を行う必要があるのです。そして、そのステートが正しく設計・管理されていることはテスティングを通じて、品質に大きな影響を与えます。

プログラマが知るべき97のこと

プログラマが知るべき97のこと