TL; DR
- (主にClassBaseの)OOPは、メンバ変数を"インスタンス単位のグローバル変数"として振る舞うよ
- 一方で、一定のメンバを集めた範囲を対象にしたオペレーションには抽象化として高い意義があるよ
- メンバ変数のGlobal変数性を断ち切るためには、オブジェクトがImmutableであれば良いよ
- ObjectのImmutable性を前提にしたOOPをSLOOP(Side effect Less OOP)と名付けて区別してみてはどうだろう。
- またImmutable性を必ずしも前提にはしないが、Mutableであるメソッドを陽に区別する立場をASLOOP(Auxiliary SLOOP)とするのはどうだろうか。
グローバル変数の何が問題だったのか
本質的には以下のようなコードが問題になるのだった。(詳しくはいろいろな記事があるので確認してほしいです。)
(架空の言語のコードなので雰囲気でお願いします)
globalStr = "abc"
f() { ... }
g() { ... }
main() {
f(); // ここでglobalStrが書き換わって
g(); // ここの挙動を変えてしまうかもしれない
// したがって
// 1. f, gのコードを個別に読むだけでは全体の挙動がわからない(リーダビリティがない)
// 2. fとgの単体テストではf, gの挙動を明らかにできない/切り離すことができない(テスタビリティがない)
// 3. fの繰り返しの利用に対して問題が生じやすい(リエントラントでない)
// 4. fの利用とgの利用に順序の固定が発生する
// etc, etc...
}
インスタンス単位の"グローバル変数"
実は(クラスベースの)オブジェクト指向では、しばしばこのグローバル変数が復活したのと変わらない状況がインスタンス単位で発生する。
(架空の言語のコードなので雰囲気でお願いします)
class X {
private memberStr = "abc;
f() { ... }
g() { ... }
}
main() {
x = new X();
x.f(); // ここでmemberStrが書き換わって
x.g(); // ここの挙動を変えてしまうかもしれない
// したがって
// 1. f, gのコードを個別に読むだけでは全体の挙動がわからない(リーダビリティがない)
// 2. fとgの単体テストではf, gの挙動を明らかにできない/切り離すことができない(テスタビリティがない)
// 3. fの繰り返しの利用に対して問題が生じやすい(リエントラントでない)
// 4. fの利用とgの利用に順序の固定が発生する
// etc, etc...
}
グローバル変数とそう変わらないわけですね。
もちろんありとあらゆるモジュールから利用される可能性があるグローバル変数と比べるとずっとマシではあります。
でも本質的な課題はここでは解決していないわけです。
OOPの価値は対象物とメソッドという語彙による抽象化
ではなぜクラスが大事かというと、対象物を中心とした抽象化を行うことが、プラクティカルな面で有効であることがわかっているからです。
(このことも様々な記事があると思うのでそちらをご参照ください)
※関数の"第ゼロ引数"という以上の意味はあまりない、という立場はありえる。
関数型言語の一部はそういう立場を支持しているように思われる。ところが結局Elixirの |>
演算子に見て取れるように、 Object -> Method(Arguments) という語順を保つための手法がしばしば登場してしまう。
小さなことに思えるかもしれないが、つまりはプラクティカルな面でのOOPを支援しているのであって、
人間の頭がそういう抽象化手法を求めているのだと思われる。(Chainingがしやすいといった実利的側面もある)
また各関数の第一引数が何であるか慣例としてプログラマが(非常に)気にしてケアしている。OOPはこれを強制できる。
ローカルなグローバル変数をローカルな定数にしてもOOPの価値は実現できる
しかしこういった価値には別に副作用を伴わせる必要はない。
Immutableなオブジェクトだとしても別にmethod chainingはできる。
(架空の言語のコードなので雰囲気でお願いします)
class X {
private const memberStr = "abc";
f() { ... }
g() { ... }
}
main() {
x = new X();
x.f(); // ここでmemberStrが書き換わって...しまうことはない。
x.g(); // ここの挙動を変えてしまう...ことはない。
// したがって問題はなにもない
}
SLOOPと呼んではどうか。
こういうImmutable性を前提にしたOOPは、DDDのValueObjectなど、しばしば登場する。
しかしImmutableObjectという以上に名前がない。
特に「ありとあらゆるところでImmutable Objectを前提にしたオブジェクトを用いる立場」を、しばしば人は「関数型」と呼びがちである。
しかしそれは関数型という言葉以上の意味を含んでしまっている。
そこで新たな用語を導入することを提案してみたい。
名付けて SLOOP(Side effect Less Object Oriented Programming) だ。
補助付きSLOOP
ただしDDDでいうところのEntityオブジェクト(同値性を保ちながら変遷を辿るオブジェクト)のようなものはやはり現実としてよく登場する。
そこでOOPでは以前から"LocalなGlobal変数問題"への対処としてCommandQuery分離原則という原則が敷かれている。
そういう立場では、C++のconstメソッドや、Rustの&self, &mut selfを陽に区別する立場でメソッドを整理するということが有効であると考えられる。
そこでSLOOPよりは少し弱いが、プラクティカルな立場として、
「CommandQuery分離をコード上に表明する立場」のことを ASLOOP(Auxiliary SLOOP) と呼んで区別してみるのはどうだろうか。