2020-10-22

継承禁止するべき

キチガイ刃物ゴミプログラマ継承危険ものは取り上げるべきだ。

オブジェクト指向プログラミングにおける継承は強力な手法であるが、これを正しく使えるプログラマは残念なことに極めて少ない。たいていの場合継承を使うことで却ってプログラム保守を困難にしてしまう。継承アンチパターンの最たるものは、単なるメソッドやメンバ変数の共有のために継承を使うパターンだ。これを行うとデータが密結合になってバグの原因になり、プログラムを把握することも極めて困難になる。

そもそも熟達したプログラマ感覚では、業務で書くアプリケーション実装継承を使うべき局面などほとんど無い。ライブラリ等のより低レベルな処理で仕様が確定しているものについては、継承効果的となる場合もあるが、複雑なアプリケーションロジック継承を使うのはほとんどの場合、時期尚早な抽象化となる。

また、凡庸プログラマ継承で実現したいと思うことは、ほとんどの言語でより適切な手段存在する。

継承を使う資格があるか、一発で判定できる質問

継承を誤って用いるプログラマが多いにも関わらず、実は継承の使い時ははっきりしている。以下は、一定水準のプログラマならば、誰でも答えられる質問である。これに答えられないプログラマ不勉強を恥じるべきである

問題
継承を使うべき/使ってもよいのは、どのようなときですか。
正解
リスコフの置換原則を満たすときリスコフの置換原則 - Wikipedia

答えられない人、自分の答えが正解の内容と一致しているか即座に判断できない人は、継承を使うべきではない。医学知識ゼロ素人外科手術をするようなものであり、非常識まりない。

リスコフの置換原則は、オブジェクト指向文脈で言えば、以下のようになる。

Baseを基底クラス、DerivedをBase任意派生クラスとするときBase型として生成されたオブジェクトをDerived型のオブジェクトに置き換えても問題なく振る舞うようにしなければならない」

問題なく振る舞う」というのは、以下のことを意味する。


継承を用いないやり方

ゴミプログラマ継承を使いたがる理由の99%は、以下である

こんなもん、何も難しいこと考えずとも、以下のどちらかの方法解決する。

AからBの機能を用いる場合は前者を、Aを変更できないがBと同じインタフェースを持たせたい場合後者を使えばいい。

  • いわゆるBridgeとAdaptorパターン

    • ブリッジパターンよくわからんから、説明してくれ。アダプタパターンは「継承することで、継承元をラップする」ということはわかったので。

      • よくわからんも何もごく当たり前のオブジェクト指向の使い方だが メンバ変数のオブジェクトに処理を委譲する ポリモーフィズムで具体的なオブジェクトごとに処理を切り替える ...

        • あー、なるほど、ってならないが、元増田は多分わかっているんやろね。ところで本は何使って勉強しましたか?

  • AにBを持たせる class A { private B b; // 実装} AをラップしてBと共通のインタフェースを実装する interface IB { // Bのメソッド}class A implements IB { private A a; // 実装}class B implements IB { ...

  • よし、お前の熱い気持ちしかと受け止めた。バズるのは時間帯が悪かったと思うので、次に投稿するときには夜ご飯ぐらいがいいよ。それでは添削してやろう。 継承はだめ、という主...

    • リスコフの置換原則は、継承したメソッドは上下で型の変化を無くせ、というだけだろ? 全然違う件

      • 入れ替えても「他と同じように」動くクラスを作れって話だよな。 もしかすると型の変化を無くせという一節にその意味が込められているのかもしれないがどう考えても伝わってない。

        • 「型」という言葉が、たとえばCoqのような形式証明や、よくあるHaskellの純粋性などを解説する際に用いられるような 「周辺環境すべての情報を含むもの」 という意味でしたら、「型を...

        • そのつもりで書いたが、分かりにくいか... 次から気をつける。

      • リスコフの置換原則は、もとのクラスと継承したクラスとで、引数かレシーバーの型が T だとすると、継承したクラスはもとのクラスと同じ型を返さないといけないという話だよね?違...

  • 要は、継承より委譲しろ、ってことだな?

  • クラス型オブジェクト指向言語の機能は何でも出来すぎるという意味でgoto文に近いと思う。 gotoが機能的に制限された構造化言語構文に進化したように、デザインパターンをもっと整理...

    • シェルスクリプトとLispに回帰すべきなんですよ。 あらゆるオブジェクトは、リスト、マップ、ラムダ式のような汎用的なデータ構造で表される。 プログラムのすべての機能は互い...

      • 上は Ruby が実現している気がする。下はユニゲージってやつか。どうみてもユニゲージは無理があるが。

        • Erlangがそれに近い Erlang https://www.erlang.org/docs https://www.ymotongpoo.com/works/lyse-ja/ Elixir およびErlangをラップした言語であるElixir https://elixir-lang.org/docs.html https://elixirschool.com/ja/

      • プログラムのすべての機能は互いに独立した(仮想的な)プロセスとして並行に実行され、プロセス間のデータのやり取りにはメッセージングとパターンマッチを用いる。

  • pythonにはinterfaceの概念がないので継承を使わざるをえない場面があるので極力関数定義にしてるわ。 ステートレスなデータを扱う場合は状態を表すデータ型用意して関数でこねくり回す...

    • えーっとね そもそもPythonはダックタイピングだからinterfaceは必要ない

      • そんなんわかってるよ。だからinterfaceの概念ないっていってるじゃん。 イディオムとしてABC使ったとしても継承しか振る舞いの定義方法のやり方がないから何も間違ったこと言ってない...

    • Ruby も同じ。

  • ゴミプログラマは2段を超える階層構造を一般に理解できない

  • 継承で馬鹿ルールを思い出した 必ず共通空クラスを継承することっていうルール SIer の大好物ルール オレにとっては未だに意味不明 class Base() {なんか実装してある} class Empty() extends Bas...

    • printデバッグとかに使うんすかね 知らんけど

      • そういうことなんか。勉強になった。 Ruby なんかでも、BasicObject が生まれた契機は、こういう「バグとかチェックするのに便利」なやつなんだろうか?

  • Qiitaのポエムより、何か言ってる感がある分ひでぇ

  • 保守困難個所を作れるなら、つまり自分の有利じゃん その重要な部分が周知されていないのも、明らかに意図されているだろう 今後もそうなんだろうな

  • 多くの場合、後者の方法が、もっと言えば 「AをラップしたクラスA'と、BをラップしたクラスB'を作り、A'とB'に共通のインタフェースを実装する」 のがベストプラクティスではないか。

  • コピペで作れるように使い方わかってる奴がサンプル作りゃええやん 待ってるで

  • MixinとかTraitとか、継承によらずに機能を共通化するための仕組みが各言語に用意されてるのに、誰も言及していない…

  • こういう過激派がいるプロジェクトは、プログラム的には正しくても事業的には成功しない印象。

  • 完全に正しい多段継承でテンプレートパターン使いすぎて使い勝手最悪のライブラリを見たことがあるのでリスコフが全てだとは思わないかな。 継承より委譲には賛成。 > Aをラップし...

    • なんかわかっている人がきた感じ。 イテレータパターンとオブサーバーパターンは言語に組み込まれた感じがする。Ruby とか見てると。

      • いつの話ししてるんだよお前

        • うーん、2005年ぐらいか? Scala とか出てきたけど、デザインパターンが言語レベルで組み込まれたのは、これぐらいじゃない?Java や Javascript も機能に組み込んだけど。

          • ScalaのTraitは何のためにあるのか教えてくれ

            • さぁ?オレはScala嫌いだから知らんよ。 まぁ、でも多分こんな目的では?という予想はあるよ。 ・もともとは C++ は多重継承ができて、カオスになった。 ・Java が単一継承となり、振...

  • リスコフの置換原則を満たしていても共通化の仕方が雑だからひどいことになっている継承、というのはいっぱいあるように思う コンポジションのが責務の分割がうまくいくのでコンポ...

    • コンポジット・パターンのことかと思ったけど、委譲のことを言いたいのですね?ぐぐったら、コンポジションを委譲と書いてあったので。

記事への反応(ブックマークコメント)

ようこそ ゲスト さん