多くのソフトウェア開発者がなぜオープン/クローズド原則に違反するのですか?


74

多くのソフトウェア開発者が、アップグレード後にアプリケーションを破壊する関数の名前を変更するなど、多くのことを変更することにより、オープン/クローズの原則に違反するのはなぜですか?

この質問は、Reactライブラリの高速バージョンと継続バージョンの後に私の頭に飛びつきます。

短い期間ごとに、構文、コンポーネント名などに多くの変更があります。

Reactの今後のバージョンの例:

新しい非推奨の警告

最大の変更点は、React.PropTypesとReact.createClassを独自のパッケージに抽出したことです。どちらもメインのReactオブジェクトを介して引き続きアクセスできますが、いずれかを使用すると、開発モードのときに1回限りの非推奨警告がコンソールに記録されます。これにより、将来のコードサイズの最適化が可能になります。

これらの警告は、アプリケーションの動作には影響しません。ただし、特にconsole.errorを失敗として扱うテストフレームワークを使用している場合、フラストレーションが発生する可能性があることを認識しています。


  • これらの変更はその原則の違反と見なされますか?
  • Reactのようなものの初心者として、ライブラリ内のこれらの高速な変更でどのようにそれを学ぶのですか(とてもイライラします)?

6
これは明らかにそれを観察する例であり、「非常に多く」という主張は実証されていません。LuceneとRichFacesのプロジェクトは悪名高い例であり、Windows COMMポートAPIですが、他に手が届かないものは考えられません。そして、Reactは本当に「ビッグソフトウェア開発者」ですか?
-user207421

62
他の原則と同様に、OCPには価値があります。しかし、開発者には無限の先見性が必要です。現実の世界では、人々はしばしば最初の設計を間違えます。時間が経つにつれて、互換性のために古いミスを回避することを好む人もいれば、コンパクトで負担のないコードベースを持つために最終的にクリーンアップすることを好む人もいます。
セオドロスチャツィジアンナキス

1
オブジェクト指向言語を「当初意図したとおりに」最後に見たのはいつですか?コア原則は、システムのすべての部分が誰でも無限に拡張可能であることを意味するメッセージングシステムでした。それをあなたの典型的なOOPのような言語と比較しましょう-外部から既存のメソッドを拡張できるのはいくつですか?どれだけの数で便利になりますか?
ルアーン

レガシーは吸う。30年の経験から、常に完全にレガシーをダンプし、新たに始める必要があることが示されています。今日、誰もがいつでもどこでもつながりを持っているので、今日のレガシーはまったく無関係です。究極の例は「Windows対Mac」でした。マイクロソフトは伝統的に「レガシーをサポート」しようとしましたが、これは多くの点でわかります。Appleは常にレガシーユーザーに「F---あなた」と言ってきました。(これは、言語からデバイス、OSに至るまですべてに当てはまります。)実際、Appleは完全に正しく、MSFTは完全に間違っており、単純で単純です。
ファッティ

4
なぜなら、実際には100%の時間で機能する「原則」と「設計パターン」はまったく存在しないからです。
マッティバークネン

回答:


148

IMHO JacquesBの答えは、多くの真実を含んでいますが、OCPの根本的な誤解を示しています。公平を期すために、あなたの質問はすでにこの誤解も表しています-関数の名前を変更すると後方互換性が損なわれますが、OCPはそうではありません。互換性を破壊する必要があると思われる場合(または互換性を破壊しないように同じコンポーネントの2つのバージョンを維持する場合)、OCPは以前に破壊されていました!

イェルクWミッタークはすでに彼のコメントで述べたように、原則は「あなたは、コンポーネントの動作変更することはできません」と言っていません-それは言う、人はすべき試す方法で部品を設計するために、彼らは再利用beeingてのオープン(または拡張)されていますいくつかの方法で、変更の必要はありません。これは、適切な「拡張ポイント」を提供することによって、または@AntPで述べられているように、「クラス/関数構造をデフォルトですべての自然な拡張ポイントが存在するポイントに分解することによって」行うことができます。OCPに従うIMHOは、「下位互換性のために古いバージョンを変更せずに維持する」とは何の共通点もありません!または、以下の@DerekElkinのコメントを引用します。

OCPは、モジュールの作成方法に関するアドバイスです[...]。モジュールの変更を許可しない変更管理プロセスの実装に関するものではありません。

優秀なプログラマーは、「正しい」拡張ポイントを念頭に置いてコンポーネントを設計するために経験を使用します(または、さらに良いことに、人為的な拡張ポイントは不要です)。ただし、不必要なオーバーエンジニアリングを行わずにこれを正しく行うには、コンポーネントの将来のユースケースがどのようになるかを事前に知る必要があります。経験豊富なプログラマーでさえ、将来を調べて、今後のすべての要件を事前に知ることはできません。そのため、下位互換性に違反する必要がある場合があります-コンポーネントにある拡張ポイントの数や、特定のタイプの要件に関してOCPにどれだけ従うかに関係なく、変更せずに簡単に実装できない要件が常にありますコンポーネント。


14
IMO OCPを「違反」する最大の理由は、適切に準拠するために多大な努力を要することです。Eric Lippertが、多くの.NETフレームワーククラスがOCPに違反しているように見える理由に関する優れたブログ投稿をしています。
BJマイヤーズ

2
@BJMyers:リンクをありがとう。Jon Skeetは、保護されたバリエーションの概念に非常に似ているため、OCPについて優れた投稿をしています。
Doc Brown

8
この!OCPは、触れずに変更できるコードを書くべきだと言っています!どうして?そのため、テスト、レビュー、コンパイルは一度だけで済みます。新しい動作は新しいコードから来るはずです。古い実績のあるコードをねじ込むことではありません。リファクタリングはどうですか?リファクタリングはOCPの明らかな違反です!前提が変更された場合にリファクタリングするだけだと考えるコードを書くのは罪です。番号!それぞれの仮定を小さな箱に入れてください。間違っている場合は、ボックスを修正しないでください。新しいものを書いてください。どうして?古いものに戻る必要があるかもしれないからです。あなたがそれをするとき、それがまだうまくいけばそれは素晴らしいだろう。
candied_orange

7
@CandiedOrange:コメントありがとうございます。リファクタリングとOCPは、あなたがそれを説明しているように逆に見えません。OCPに従うコンポーネントを作成するには、多くの場合、リファクタリングサイクルがいくつか必要です。目標は、要件の「ファミリ」全体を解決するために変更を必要としないコンポーネントでなければなりません。それでも、「万が一に備えて」コンポーネントに任意の拡張ポイントを追加しないでください。これにより、簡単にオーバーエンジニアリングが発生します。リファクタリングの可能性に依存することは、多くの場合、これに代わるより良い代替手段となります。
Doc Brown

4
この答えは、(現在の)トップアンサーのエラーを呼び出すのに良い仕事をします-オープン/クローズで成功するための重要なことは、「拡張ポイント」の観点から考えるのをやめ、あなたの分解について考えることですすべての自然な拡張ポイントがデフォルトで存在するポイントへのクラス/関数構造。プログラミングは「外で」すべてのシナリオはあなたの現在のメソッド/関数はデコレータのための自然な拡張ポイントを形成し、外部インターフェース、アダプタなどに押し出されるために食料調達する、これを達成するための非常に良い方法です
AntのP

67

オープン/クローズの原則には利点がありますが、重大な欠点もいくつかあります。

理論的には、この原則は「拡張は開いているが変更は閉じている」コードを作成することにより、後方互換性の問題を解決します。クラスに新しい要件がある場合、クラス自体のソースコードを変更するのではなく、動作を変更するために必要な適切なメンバーだけをオーバーライドするサブクラスを作成します。したがって、クラスの元のバージョンに対して記述されたすべてのコードは影響を受けないため、変更によって既存のコードが破壊されなかったと確信できます。

実際には、コードが肥大化し、混乱した古いクラスが混乱してしまいます。拡張機能を使用してコンポーネントの一部の動作を変更できない場合は、コンポーネントの新しいバリアントに目的の動作を提供し、下位互換性のために古いバージョンを変更しないでください。

多くのクラスが継承する基本クラスの基本的な設計上の欠陥を発見したとします。エラーは、プライベートフィールドのタイプが間違っているためであるとします。メンバーをオーバーライドしてこれを修正することはできません。基本的には、クラス全体をオーバーライドする必要があります。つまり、拡張Objectして代替の基本クラスを提供することになります。また、すべてのサブクラスに代替を提供する必要があります。 。ただし、欠陥のある階層を削除することはできません(コードの削除は変更であるため)。将来のすべてのクライアントは両方の階層に公開されます。

現在、この問題に対する理論的な答えは「最初に正しく設計するだけ」です。コードが完全に分解され、欠陥やミスがなく、将来のすべての要件変更に備えて拡張ポイントが設計されていれば、混乱を回避できます。しかし、実際には誰もが間違いを犯し、誰も未来を完全に予測することはできません。

.NETフレームワークのようなものを取ります-ジェネリックが10年以上前に導入される前に設計されたコレクションクラスのセットを引き続き使用します。これは確かに後方互換性の恩恵です(何も書き直さずにフレームワークをアップグレードできます)が、フレームワークを肥大化し、多くが単に廃止された多くのオプションを開発者に提供します。

どうやらReactの開発者は、オープン/クローズの原則に厳密に従うことは複雑さとコードの肥大化のコストに見合う価値がないと感じているようです。

オープン/クローズの実用的な代替手段は、非推奨の制御です。単一のリリースで後方互換性を壊すのではなく、古いコンポーネントはリリースサイクルの間保持されますが、クライアントはコンパイラ警告を通じて、古いアプローチが後のリリースで削除されることを通知されます。これにより、クライアントはコードを変更する時間ができます。これは、この場合のReactのアプローチのようです。

(この原則の私の解釈は、ロバートC.マーティンによるオープンクローズド原則に基づいています)


37
「原則として、コンポーネントの動作を変更することはできません。代わりに、コンポーネントの新しいバリアントに目的の動作を提供し、下位互換性のために古いバージョンを変更しないでください。」–私はこれに同意しません。原則として、コンポーネントを設計して、動作を変更する必要がないように設計する必要があります。これは、必要に応じてコンポーネントを拡張できるためです。問題は、特に現在広く使用されている言語では、その方法をまだ理解していないことです。式の問題の一部である...
イェルクWミッターク

8
…それは、例えば。JavaもC♯も式の解決策を持ちません。HaskellとScalaはそうですが、彼らのユーザーベースはずっと小さいです。
ヨルグWミットタグ

1
@Giorgio:Haskellでは、ソリューションは型クラスです。Scalaでは、解決策は暗黙的およびオブジェクトです。申し訳ありませんが、現在、手元にリンクがありません。はい、マルチメソッド(実際には、「マルチ」である必要はありません。むしろ、必要なのはLispメソッドの「オープン」な性質です)も解決策です。通常の論文は著者がすべて、現在、既存のソリューションが無効になっているという事実につながる表現の問題に制限を加えるように書かれているので、表現の問題の複数のフレージングがあることに注意してください、そして、どのように自分自身を示して...
ヨルグWミットタグ

1
…言語はこの「より難しい」バージョンを解決することさえできます。たとえば、Wadlerは元々、式の問題をモジュラー拡張だけでなく、静的に安全なモジュラー拡張についても表現しています。Common Lispのマルチメソッドは、しかし、あるではない、彼らは動的にのみ安全である、静的安全。Oderskyは、モジュールを静的に安全にすべきであると言うことでこれをさらに強化しました。つまり、拡張モジュールを見るだけでプログラム全体を見ずに安全を静的にチェックできるようにします。これは実際にはHaskell型クラスではできませんが、Scalaではできます。そして、…
ヨルグWミットタグ

2
@Giorgio:そのとおりです。Common LispマルチメソッドにEPを解決させるのは、実際には複数のディスパッチではありません。メソッドが開いているという事実です。典型的なFP(または手続き型プログラミング)では、型判別は関数に関連付けられています。典型的なオブジェクト指向では、メソッドは型に関連付けられています。一般的なLispメソッドは公開されており、事後および別のモジュールでクラスに追加できます。それがEPを解決するためにそれらを使用可能にする機能です。たとえば、Clojureのプロトコルは単一のディスパッチですが、EPも解決します(静的な安全性を主張しない限り)。
ヨルグWミットタグ

20

私は、オープン/クローズド原則を理想と呼びます。すべての理想と同様に、ソフトウェア開発の現実をほとんど考慮していません。また、すべての理想と同様に、実際にそれを達成することは不可能です。人は、できる限り最善を尽くそうとするだけです。

物語の反対側は、黄金の手錠として知られています。黄金の手錠は、オープン/クローズの原則に固執しすぎると手に入るものです。黄金の手錠は、過去のミスが多すぎて後方互換性を損なうことのない製品が成長できないときに発生するものです。

この有名な例は、Windows 95メモリマネージャーにあります。Windows 95のマーケティングの一環として、すべてのWindows 3.1アプリケーションがWindows 95で動作すると述べられました。Microsoftは実際にWindows 95でテストするために数千のプログラムのライセンスを取得しました。Sim Cityには、実際には未割り当てのメモリに書き込むバグがありました。Windows 3.1では、「適切な」メモリマネージャがないため、これは軽微な問題でした。ただし、Windows 95では、メモリマネージャーがこれをキャッチし、セグメンテーションエラーが発生します。ソリューション?Windows 95では、アプリケーション名がのsimcity.exe場合、OSはメモリマネージャーの制約を実際に緩和して、セグメンテーションフォールトを防ぎます!

この理想の背後にある本当の問題は、製品とサービスの概念の比較です。誰も実際にどちらもしません。すべてが2つの間の灰色の領域のどこかに並んでいます。製品指向のアプローチから考えると、オープン/クローズは素晴らしい理想のように思えます。あなたの製品は信頼できます。ただし、サービスに関しては、話は変わります。古い機能をクリーンアップできないため、オープン/クローズの原則では、チームがサポートする必要のある機能の量が漸近的に無限に近づく必要があることを示すのは簡単です。つまり、開発チームは毎年、より多くのコードをサポートする必要があります。最終的にはブレークポイントに到達します。

今日のほとんどのソフトウェア、特にオープンソースは、オープン/クローズド原則の一般的な緩和バージョンに従っています。マイナーリリースではオープン/クローズが従順に表示されるのが非常に一般的ですが、メジャーリリースでは放棄されます。たとえば、Python 2.7には、Python 2.0および2.1時代の多くの「悪い選択」が含まれていますが、Python 3.0はそれらすべてを一掃しました。(また、彼らはWindows 2000のリリースされたWindows NTのコードベースにWindows 95のコードベースからのシフトは、物事のすべての種類を破ったが、それはなかった私たちが行動を決定するために、アプリケーション名を確認するメモリマネージャに対処する必要はありません意味します!)


これはSimCityについての非常に素晴らしい話です。ソースはありますか?
BJマイヤーズ

5
@BJMyersこれは古い話です、Joel Spolekyはこの記事の終わり近くでそれについて言及しています。私はもともと、数年前にビデオゲームの開発に関する本の一部としてそれを読みました。
コートアンモン

1
@BJMyers:私は彼らが何十もの人気のあるアプリケーションに対して同様の互換性「ハッキング」を持っていると確信しています。
Doc Brown

3
@BJMyersにはこのようなものがたくさんあります。もしよければ、Raymond ChenのThe Old New Thingブログにアクセスして、Historyタグを参照するか、「互換性」を検索してください。前述のSimCityのケースに顕著に近いものを含む、多くの物語の回想があります-補遺:チェンは非難するために名前を呼ぶのが好きではありません。
-Theraot

2
95-> NTの移行でも、ほとんど問題は発生しませんでした。オリジナルのWindows用SimCityは、Windows 10(32ビット)でも引き続き機能します。DOSゲームであっても、サウンドを無効にするか、VDMSoundなどを使用してコンソールサブシステムがオーディオを適切に処理できるようになっていれば、問題なく動作します。マイクロソフトは後方互換性を非常に真剣に考えており、「仮想マシンに入れよう」というショートカットも受け入れていません。回避策が必要な場合もありますが、特に相対的な点では、それでもかなり印象的です。
ルアーン

11

Doc Brownの答えは最も正確であり、他の答えはOpen Closed Principleの誤解を示しています。

明示的に誤解を明確にするために、OCPはあなたが後方互換性のない変更を加えるべきではないことを意味することに信念があるように思われる(偶数または任意の変更またはこれらの線に沿って何かを。)あなたはしないように、OCPは、設計部品についてです必要にそれらの変更が後方互換性があるかどうかに関係なく、機能を拡張するためにそれらを変更します。機能の追加以外にも、下位互換性(リファクタリングや最適化など)または下位互換性(機能の廃止や削除など)に関係なく、コンポーネントに変更を加える多くの理由があります。これらの変更を行うことができるということは、コンポーネントがOCPに違反したこと意味するわけではありません OCPに違反しています)。

本当に、それはソースコードに関するものではありません。OCPのより抽象的な関連する声明は、「コンポーネントは、その抽象化の境界に違反することなく拡張を許可する必要がある」です。さらに進んで、より現代的な表現は「コンポーネントはその抽象化境界を強制するが、拡張を許可する必要がある」と言います。Bob MartinによるOCPに関する記事でも、「ソースコードは違反している」と「修正に近づいている」と説明していますが、後でソースコードの修正とは関係なく、抽象化に関係するカプセル化について話し始めます。境界。

したがって、質問の誤った前提は、OCPが(意図されている)コードベースの進化に関するガイドラインであるということです。OCPは通常、「コンポーネントは拡張機能に対して開かれ、消費者による変更に対して閉じられる必要がある」とスローガン化されています。基本的に、コンポーネントのコンシューマーがコンポーネント機能を追加する場合、古いコンポーネントを追加機能を備えた新しいコンポーネントに拡張できますが、古いコンポーネントを変更することはできません。

OCPは、コンポーネントの作成者が機能を変更または削除することについては何も述べていません。OCPは、バグの互換性を永遠に維持することを主張していません。作成者であるあなたは、コンポーネントを変更したり削除したりすることでOCPに違反していません。消費者がコンポーネントに機能を追加できる唯一の方法が、たとえばモンキーパッチを適用することによって、OCPに違反している場合、あなたまたはあなたが書いたコンポーネントはOCPに違反していますまたは、ソースコードにアクセスして再コンパイルします。多くの場合、これらのどちらもコンシューマー向けのオプションではありません。つまり、コンポーネントが「拡張のために開かれていない」場合、それらは不運です。彼らは単にあなたのコンポーネントを彼らのニーズに使うことができません。OCPは、少なくとも特定の「拡張機能」のクラスに関しては、ライブラリの消費者をこの立場にしないと主張しています。ソースコードやソースコードのプライマリコピーに変更を加えることできる場合でも、多くのマイナスの結果が生じる可能性があるため、変更できないことを「ふり」することをお勧めします。

あなたの質問に答えるために:いいえ、これらはOCPの違反ではありません。OCPは変更の割合ではないため、作成者が行う変更はOCPの違反にはなりません。変更は、しかし、でき作成 OCPの違反を、それらがコードベースの以前のバージョンでOCPの障害によって動機付けすることができます。OCPは特定のコードのプロパティであり、コードベースの進化の歴史ではありません。

対照的に、後方互換性はコードの変更の特性です。コードの一部に下位互換性があるかどうかは、意味がありません。いくつかのコードいくつかの古いコードとの後方互換性について話をするのは理にかなっています。したがって、一部のコードの最初のカットが後方互換性があるかどうかについて話すことは意味がありません。コードの最初のカットはOCPを満たすか、または満たさない場合があり、一般に、コードの履歴バージョンを参照せずに、一部のコードがOCPを満たすかどうかを判断できます。

最後の質問については、StackExchangeは一般に主に意見に基づいているため、ほぼ間違いのないトピックですが、ここ数年で記述している現象がJavaScript疲労と呼ばれている技術、特にJavaScriptを歓迎します。(グーグルに気軽に他のさまざまな記事を見つけてください。風刺的な記事もあります。これについては複数の観点から話しています。)


3
「あなたは、作成者として、コンポーネントを変更したり削除したりすることでOCPに違反していません。」-これについてのリファレンスを提供できますか?私が見た原則の定義はどれも、「作成者」(それが何を意味するにせよ)が原則から免除されているとは述べていません。公開されたコンポーネントの削除は、明らかに重大な変更です。
ジャックB

1
@JacquesB人々やコードの変更はOCPに違反しませんが、コンポーネント(つまり実際のコード)は違反します。(そして、完全に明確にするため、コンポーネントは他のコンポーネントのOCPに違反しているのではなく、OCP自体に対応できていないことを意味します。) 、破損またはその他。コンポーネントは、どちらかの拡張子に開いており、修正に閉じられ、またはそれは方法がかもしれ同じように、ではないのですprivateか。著者が行う場合privateの方法をpublic後で、それは、彼らがアクセス制御に違反してきました(1/2)を意味するものではありません
デレク・エルキンズ

2
...また、メソッドが実際にprivate以前ではなかったことを意味しません。「公開されたコンポーネントを削除することは、明らかに重大な変更です」と非機密です。新しいバージョンのコンポーネントがOCPを満たしているか、満たしていない場合、これを判断するためにコードベースの履歴は必要ありません。あなたのロジックでは、OCPを満たすコードを書くことはできませんでした。コードの変更のプロパティである後方互換性と、コードのプロパティであるOCPを統合しています。あなたのコメントは、クイックソートには後方互換性がないと言っているのと同じくらい理にかなっています。(2/2)
デレクエルキンズ

3
@JacquesBまず、これがOCPに準拠するモジュールについて話していることに再び注意してください。OCPは、ソースコードを変更できないという制約がある場合でも、モジュールを拡張できるようにモジュールを記述する方法に関するアドバイスです。本書の前半で、彼は、決して変更できないモジュールの設計について話し、モジュールを決して変更できない変更管理プロセスの実装について話しました。回答の編集については、モジュールのコードを変更して「OCPを破る」ことはありません。代わりに、モジュールを「拡張」するためにソースコードを変更する必要がある場合、(1/3)
デレクエルキンズ

2
「OCPは特定のコードのプロパティであり、コードベースの進化の歴史ではありません。」- 優れた!
Doc Brown
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.