継承はなんでダメ?

オブジェクト指向の継承を使うな」という主張が広まっているようです。なんでダメになったんでしょうか。

インターネットで見かけた「継承はダメ」という主張をいくつか眺めて、友人と議論しつつ、考えてみました。

「コードが読みにくくなる」

継承があると、メソッド呼び出しが実際にどのメソッド定義を呼び出すのか字面でわからない。 デバッガを使って、親クラスのメソッドに飛んだり、子クラスに飛んだりするのを追いかけないと行けない。 つらい。という主張。

めっちゃわかる。わかるんですが、これは「高度に共通化されたコードは読みにくい」という一般的な側面がかなり大きいような。 たとえば継承の代わりに高階関数を使うと、関数呼び出しがどのクロージャに飛ぶか字面でわからなくなる。 ひどいとコールバック地獄になって何が何やらになります。

継承がことさらにまずい理由を想像すると、すべてのメソッド呼び出しがポリモーフィックになりうるのは、読みにくさを増してるかも。 高階関数なら、高階関数の呼び出しだけ注意すればよいから。C++ の virtual キーワードは正しかったのだ(?)。

そういえば「インターフェイスの継承は良いが、実装の継承はダメ」という派閥も見かけますが、この問題はあまり変わらなそうです。 なんなら、飛び先の候補が「親子クラスだけ」から「インターフェイスを実装した全クラス」になるので、悪化する可能性も。

「すぐ神クラスになる」

基底クラスについつい便利メソッドを生やしてしまい、気づけば便利メソッドを寄せ集めた「神クラス」になっている。 その便利メソッドたちは、誰が使ってるかわからないので、変更できなくなってしまう。 つらい。という主張。

前半(寄せ集め)はややわかる。 コンポジション等で切り出そうと思っても、既存のインスタンス変数にアクセスする必要があったりすると、ついついインスタンスメソッドにしてしまったり。 インスタンスメソッドが暗黙的に this/self を受け取り、容易にインスタンス変数にアクセスできてしまうのは、継承が強力すぎるせいといえる気はします。

ただ、「神クラス」を作ってしまう者は、継承を禁止すると、すべてを詰め込んだ単一「神モジュール」を育成させたりしないのかな。

後半(変更できなくなる)は、コード共通化そのものの問題で、継承以外でも起きる気がします。 いろんなところから使われているコードを変更するのはどうしたってしんどい。 関数で切り出しても、mix-in にしても、コンポジション・委譲で切り出しても、それは本質的にはかわらない。

継承だと特別しんどくなる理由があるだろうか。あまり思いつかなかった。 それこそインスタンス変数のアクセス性確保のために、切り出しがむずかしい面はあるかなあ?

「変な継承をする人がいる」

コードを再利用するためだけに、親子関係とは言えない継承をしてしまうプログラマがいる。 愚かものが悪用してしまう継承は禁止すべきだ。という主張。

幸い、意図的にそんなことをするプログラマは身近にいないので、最初はピンときませんでした。 でも、そういう継承関係が生まれるシナリオは思いつきました。

  • もともと Clock というクラスでアナログ時計を実装していた。
  • デジタル時計も実装したくなった。
  • Clock を基底クラスにし、AnalogClock クラスと DigitalClock クラスを派生させるとよさそう。
  • と思ったが、すでにいろんな人が new Clock() している。全部 new AnalogClock() に書き換えてもらう? うーん……
  • 互換性を重視して、やむなく DigitalClock extends Clock で妥協する。
  • 「デジタル時計がアナログ時計を継承する」という結果だけ見た人は、書いた人をバカだと思ってしまう。

ということで、互換性の配慮などの事情で、奇妙な継承関係は確かに生じうる。というか、身に覚えもある。

ただ、これは継承以外でも普通に起きる。 アナログ時計を作る create_clock() という関数を公開しちゃうと、後から create_analog_clock() にするのは大変。 未来のユーザは「なんで create_clock() がアナログ時計なの、バカなの」って思うのかもしれません。

(時計の例がしっくりこない人は、モノクロテレビ・カラーテレビとか。レトロニム一覧から好きな例を選んで読み替えてください)

継承がことさらに問題だとしたら、後知恵だと良い設計が見えやすく、そうなっていないことが気になりすぎるのかも。 関数なら「はいはい」って流せるけど、オブジェクト指向はドグマになっていて、奇妙な設計を見ることに耐えられない。 でもそれで継承自体を禁止しようというのは、もはやオブジェクト指向への偏執的な愛情って感じがしますね……。

まとめ

「継承はダメ」という理由で、ある程度共感したのはいまのところ2つ。

  • すべてのメソッド呼び出しがポリモーフィックになりうるので、どこに飛ぶか常に注意が必要で、コード読解が大変(いまどきならエディタ支援でなんとかならんかな)
  • 暗黙的に this を受け取るインスタンスメソッドは便利すぎて悪用しやすく、寄せ集めの「神クラス」につながりがち(ただ、継承禁止で本当に解決するかはわからない)

「継承の問題はそんな些末なことじゃない! もっと大きい問題だ!」という人がいたら、ぜひやさしく教えて下さい。