僕は、「メソッドを、アクセッサとミューテータに分けろ」と言いました (ほんとは、もっと細かく分類したほうがいいのだけど、その話はいずれドコ カでするでしょう)。これに対して、「あー、なるほど」と言う人もいるけれ ど、「なんでじゃい?」と思う人もいるでしょう。それでここでは、特にアク セッサという概念に焦点を当てて追加説明をします。
例えばスタックで、メソッドpop()がスタックトップの値を返すと同時に、副 作用としてポップ操作を行う例は多いですね。そうすると、pop()の宣言は次 のようになります。
// スタックにはdoubleの値が積まれるとする public double pop() throws StackException;
こう宣言されたpop()は副作用があるのでアクセッサではないし、値を返すの でミューテータでもない。だけど、こっち(値付きのpop)のほうが便利でしょ う、という意見もありますね。
正直言って、このての意見や質問に答えるのはうっとおしいのだけど、疑問 を感じる方もいるでしょうから……。一言でお答えしますと、副作用(状態遷 移)も値もあるメソッドが欲しいなら、いくらでも定義してけっこうです。
もともと、アクセッサとミューテータに分けるのは、プログラムの証明やテ スト(合わせて検証と呼びましょう)を行うためです。検証に使う論理式を書 き下すとき、副作用と値の両方を持つメソッドはとても扱いにくいのです。
しかし、定義したすべてのメソッドを検証する必要がいつでもあるわけでは ないので、検証対象のメソッドだけをアクセッサとミューテータに分けてもら えればOK。仮に、検証対象となるメソッドを基本メソッドと呼ぶと、僕の提案 は、「基本メソッドは、アクセッサとミューテータに分けよう」というもので す。基本メソッドをもとに作った便利メソッドが、副作用と値を両方持っても、 そこまではトヤカク言いませんて。
先のスタックの例でいえば、基本メソッドgetTop()とdoPop()が十分に検証さ れていれば、次のようにして書いた便利メソッドpop()も、たぶん“正しく” 動くでしょう。
public double pop() throws StackException {
double t = getTop();
doPop();
return t;
}
そもそも、切実に検証しなくちゃいけないと思うクラス/メソッドなんて、 全体の1割もないのじゃないのかな。そして、1割でも検証すれば、ソフトウェ ア全体の品質は格段に上がると思いますよ。
アクサッサは、副作用を持たずに値を返すメソッドです。純関数と似てるけ ど、引数だけでなくオブジェクトの状態に依存してもよい点が関数ではなくて メソッドと呼ぶ由縁です。それと、これは気持ちだけの問題だけど、アクセッ サは、オブジェクトの内部状態についてなにがしかの情報をもたらすと期待さ れています(*注1)。
たとえば、次のようなメソッドは内部状態と何の関係もないけど、アクセッ サに分類されます。
public int three() {
return 3;
}
さて、仕様を書く人間は、「これはアクセッサとする」と指定することがで きます。実装者は、アクセッサ(の実装)を書くとき、副作用がないように注 意しなくてはなりませんね。あるいはまた、実装者がテスト担当者に「これは アクセッサだ」と告げることもできます。
いずれの場合でも、「これはアクセッサだ」という言明は、要求(あるいは 希望)であったり、自信の表現であったりして、客観的な事実というよりは、 人間の信頼関係に基礎を置いているように思えます。
例えば、実装者が言った「これはアクセッサだ」という言葉を、テスト担当 者は、言った彼への信頼や不信からではなくて、もっと客観的な事実から判定 できないでしょうか。それができないとすると、「アクセッサ」という概念は、 お約束のための符丁に過ぎない、となるかもしれません(それでも、無意味に はなりませんけど)。
実は、外部からの実験観測では、「内部状態を変更してない」ことを確認す ることはできません。これは当たり前です。だって、外部から正確に内部状態 を知る手段はないのですよ。そうであるなら、「内部状態を変更してない」こ とを知るスベなんてあるわけないじゃないの!
たとえば、次のようなカウンタ実装を考えてみましょう。
public class Counter {
private int count;
private boolean flag;
public Counter() {
count = 0;
flag = false;
}
public int getCount() {
if (flag) {
flag = false;
} else {
flag = true;
}
return count;
}
// ...
}
ここで、メソッドgetCountは、なぜか(意味もなく)フラグをトグルしてい ます。つまり、呼び出されるごとに内部状態を変更するメソッドです。が、こ の状態遷移は、外部の観測者に知られることは絶対にありません。ですから、 getCountは“事実上アクセッサ”と言ってよいでしょう。
「内部状態を変更してない」かどうかは実装者しか知りえないし、その実装 者も勘違いするかも知りません(副作用を入れないつもりがついウッカリとか)。 つまり、真実は闇のなかです。ですから、問題になるのは真実ではなくて、 “事実上アクセッサ”とみなしてよいかどうか、なのです。
あるメソッドが「アクセッサである」ことを、「事実上アクセッサとみなし てよい」と解釈するなら、定義することはできます。しかし、特定のメソッド を1つだけ取り出して「このメソッドはアクセッサである」と主張することは (特殊な例外ケースを除いて)できません。そうではなくて、アクセッサセッ ト(メソッドの集合)が全体として定義可能なのです。
いま、話を簡単にするために、int a()とint b()という2つのメソッドを考え て、{a(), b()}というセットがアクセッサセットだとはどういうことか考えて みましょう。a()もb()も戻り値を持つのは宣言から明らかなので、「副作用が ない」という点が問題になります。「副作用がない」をもっと正確に言えば、 内部で副作用があっても、それは観測にかからないことです。
ところが、観測行為とはアクセッサを呼ぶことなので、「観測にかからない」 とは、a()、b()の戻り値がa()、b()の呼び出しによって影響されないことにな ります -- これは、ちょっとややこしいですね。もう少し噛み砕く必要があり ます。
まず、a()の戻り値が特定の値nであったとしましょう。等式で書くなら、 a() == n です。これに引き続きもう一度a()を呼ぶと、戻り値はどうなるでしょ うか。a()がアクセッサなら、もちろん、またnです。これをホーア論理式 (表明)で書けば次のとおり。
a() == n ⇒ a() == n;
つまり、事前条件が a() == n ならば、何も実行しなかった後の事後条件 (どうも日本語としては奇妙ですが)として a() == n を満たします。メソッ ドb()に関しても同様で、次が成立します。
b() == m ⇒ b() == m;
次に、a() == n が成立している状況から再びa()を呼び出す間に、他のアク セッサ呼び出しに割り込まれたときを考えてみます。問題を正確に述べると: a() == nであった。引き続いて、a()とb()を適当に混ぜ合わせて呼び出した後で、 a() == n が成立するか? となります。a()、b()が共に観測できる副作用を持 たないなら、この答えはYESのはずです。ですから、次のようなホーア論理式 が成立します。
a() == n ⇒ {a()} a() == n;
a() == n ⇒ {b()} a() == n;
a() == n ⇒ {a();a()} a() == n;
a() == n ⇒ {a();b()} a() == n;
a() == n ⇒ {b();a()} a() == n;
a() == n ⇒ {b();b()} a() == n;
...
もともとのホーア論理式のかたちは、「論理式1 ⇒{実行文} 論理式2;」であ り、実行文はミューテータ呼び出しの列でした。上では、実行文にアクセッサ 呼び出しを書いてますが、呼び出しを行うだけで戻り値を無視すると解釈して ください。
一般的な書き方をすれば、次が成立します。
a() == n ⇒ {a()とb()を任意に混ぜ合わせた呼び出し} a() == n;
b()についても同様です。
今までは、a(), b()という2つのメソッドの組を例にしてきたのですが、より 一般に{a1(), a2(), ..., aN()}というN個のメソッドのセットがアクセッ サセットであることは、次のように定義できます。
さらに、a1(), a2(), ..., aN()を任意に混ぜ合わせた呼び出しをしら みつぶしに調べる必要はなくて、基本となるのは、次のケースです。
つまり、同じアクセッサを2度続けて呼び出しても値が変わらず、また、途中 に1個のアクセッサ呼び出しで割り込まれても値が変わらなければいいのです。 (よく考えると、割り込みはi ≠ jのケースだけでも十分です。)アクセッサ が引数を持つときも、引数を固定して、引数なしアクセッサとみなして同様な 条件を書き下すことができます。
アクセッサの個数やアクセッサ戻り値の種類は、事実上無限になることもあ るので、現実的な作業として、上のような条件をしらみつぶしに確認すること (全件テスト)は困難かもしれません。ですが、少なくとも原理上は、 内部構造に一切言及することなく、一群のメソッドがアクセッサセットである かどうかが判定可能だということは確認できました。
アクセッサという概念はとても単純ですが、つきつめて考えると色々と面白いこと があります。アクセッサ(あるいはミューテータ)の定式化のなかに、意外に も、観測行為やシステムの因果律の本質がかいまみえるのです。このことは、 来月にでも :-)