リファクタリングとオープン/クローズの原則


12

最近、きれいなコード開発に関するWebサイトを読んでいます(英語ではないため、ここにはリンクを入れません)。

このサイトで宣伝されている原則の1つは、オープンクローズド原則です。各ソフトウェアコンポーネントは、拡張用にオープンにし、変更用にクローズする必要があります。たとえば、クラスを実装してテストした場合、バグを修正するか、新しい機能を追加する(たとえば、既存のメソッドに影響を与えない新しいメソッド)ためにクラスを変更するだけです。既存の機能と実装は変更しないでください。

通常、インターフェイスIと対応する実装クラスを定義することにより、この原則を適用しますA。クラスAが安定(実装およびテスト)になったとき、通常はあまり変更しません(おそらく、まったく変更しません)。

  1. コードに大きな変更を必要とする新しい要件(パフォーマンス、インターフェイスのまったく新しい実装など)が到着した場合、新しい実装を作成し、成熟していない限りB使用Aし続けBます。B成熟したときに必要なのは、Iインスタンス化の方法を変更することだけです。
  2. 新しい要件がインターフェースの変更も示唆している場合、新しいインターフェースI'と新しい実装を定義しますA'。ですからIA凍結しており、残る実装限りとして生産システムのためI'およびA'それらを交換するのに十分安定していません。

そのため、これらの観察を考慮して、Webページが複雑なリファクタリングの使用を提案していることに少し驚いていました。

オープン/クローズド原則の実施と、ベストプラクティスとしての複雑なリファクタリングの使用の提案との間に矛盾や矛盾はありませんか?または、ここでのアイデアは、クラスの開発中に複雑なリファクタリングを使用できるということですAが、そのクラスが正常にテストされたら、凍結する必要がありますか?

回答:


9

私は、オープンクローズの原則を設計目標と考えています。違反することになった場合、それは最初の設計が失敗したことを意味します。これは確かに可能であり、可能性があります。

リファクタリングとは、機能を変更せずにデザインを変更することを意味します。問題があるため、デザインを変更している可能性があります。おそらく問題は、既存のコードに変更を加える際にオープンクローズの原則に従うことが難しく、それを修正しようとしていることです。

OCPに違反することなく次の機能を実装できるようにするために、リファクタリングを行っている可能性があります。


あなたは確かに設計目標としての原則と考えてはいけません。それらはツールです-あなたはソフトウェアを内部できれいで理論的に正しいものにするのではなく、クライアントのために価値を生み出そうとしています。これはガイドラインであり、それ以上のものではありません。
T.サー

@ T.Sar原則は、あなたが目指しているガイドラインであり、保守性と拡張性を重視しています。それは設計目標のように思えます。設計パターンまたはフレームワークをツールとして見る方法では、原則をツールとして見ることはできません。
Tulainsコルドバ

@TulainsCórdova保守性、パフォーマンス、正確性、スケーラビリティ-これらは目標です。オープンクローズの原則は、それらに対する手段です -多くの1つにすぎません。適用できない場合、またはプロジェクトの実際の目標を損なう可能性がある場合、オープンクローズの原則に向かって何かをプッシュする必要はありません。クライアントに「オープンクローズドネス」を販売しません。単なるガイドラインとして、より読みやすく明確な方法で物事を行う方法を見つけた場合、破棄できる経験則に勝るものはありません。結局のところ、ガイドラインはツールです。
T. Sar

@ T.Sarクライアントに売ることができないものがたくさんあります...一方、プロジェクトの目標を損なうようなことをしてはならないという点であなたに同意します。
Tulainsコルドバ

9

Open-Closed原則は、ソフトウェアがどれだけうまく設計されているかを示す指標です。文字通り従う原則ではありません。また、誤って既存のインターフェイス(呼び出したクラスとメソッド、およびそれらがどのように動作することを期待するか)を誤って変更しないようにするのに役立つ原則です。

目標は、高品質のソフトウェアを作成することです。これらの特性の1つは拡張性です。これは、コードを簡単に追加、削除、変更できることを意味します。これらの変更は、実用的な既存のクラスに限定される傾向があります。新しいコードを追加することは、既存のコードを変更するよりもリスクが少ないため、この点に関しては、Open-Closedを実行することをお勧めします。しかし、私たちは正確にどのコードについて話しているのでしょうか?既存のメソッドを変更する必要なく、クラスに新しいメソッドを追加できれば、OCに違反するという犯罪ははるかに少なくなります。

OCはフラクタルです。それはあなたのデザインのすべての深さでリンゴになります。誰もがクラスレベルでのみ適用されると想定しています。ただし、メソッドレベルでもアセンブリレベルでも等しく適用できます。

適切なレベルでのOCの頻繁な違反は、おそらくリファクタリングの時が来ていることを示唆しています。「適切なレベル」とは、全体的な設計に関係するすべての判断の呼び出しです。

Open-Closedに続くことは、クラスの数が爆発的に増えることを意味します。不必要に(大文字の「I」)インターフェイスを作成します。最終的には、クラス全体に機能のビットが散在することになり、それをすべて一緒に配線するためにより多くのコードを書く必要があります。ある時点で、元のクラスを変更する方が良いと思います。


2
「既存のメソッドを変更する代わりにクラスに新しいメソッドを追加できると、OCに違反する犯罪ははるかに少なくなります。」:私の知る限り、新しいメソッドを追加してもOCの原則(拡張可能)に違反することはありません。問題は、明確に定義されたインターフェイスを実装し、したがって明確に定義されたセマンティクスを持つ既存のメソッドを変更することです(変更のために閉じられています)。原則として、リファクタリングはセマンティクスを変更しないので、私が見ることができる唯一のリスクは、すでに安定した十分にテストされたコードにバグを導入することです。
ジョルジオ

1
これは、拡張機能の公開を示すCodeReviewの回答です。そのクラス設計は拡張可能です。対照的に、メソッドを追加すると、クラスが変更されます。
レーダーボブ16

新しいメソッドを追加すると、OCPではなくLSPに違反します。
Tulainsコルドバ

1
新しいメソッドを追加しても、LSPに違反することはありません。あなたがメソッドを追加する場合は、導入しました新しいインターフェイスを@TulainsCórdova
RubberDuck

6

Open-Closed原則は、TDDが普及する前に登場した原則のようです。何かを壊す可能性があるため、コードをリファクタリングするのは危険であるという考えです。そのため、既存のコードをそのまま残して単純に追加する方が安全です。テストがない場合、これは理にかなっています。このアプローチの欠点は、コードの萎縮です。クラスをリファクタリングするのではなく拡張するたびに、余分なレイヤーが作成されます。単にコードをボルトで締めるだけです。コードを追加するたびに、複製の機会が増えます。想像してみてください。コードベースに使用したいサービスがありますが、必要なものがないため、新しいクラスを作成して拡張し、新しい機能を追加します。別の開発者が後から登場し、同じサービスを使用したいと考えています。残念ながら、彼らはしません 私の拡張バージョンが存在することに気づきました。元の実装に対してコーディングしますが、私がコーディングした機能の1つも必要です。私のバージョンを使用する代わりに、実装を拡張し、新しい機能を追加するようになりました。これで、元のクラスと、機能が重複している2つの新しいバージョンの3つのクラスができました。オープン/クローズドの原則に従ってください。この重複はプロジェクトの存続期間にわたって蓄積され続け、不必要に複雑なコードベースにつながります。

十分にテストされたシステムでは、このコードの萎縮に苦しむ必要はありません。コードを安全にリファクタリングして、新しいコードを継続的に追加する必要なく、設計で新しい要件を取り入れることができます。この開発スタイルは緊急設計と呼ばれ、コードのベースになります。これにより、不要なデータを徐々に収集するのではなく、ライフタイム全体にわたって良好な状態を維持できます。


1
私は(私がそれらを発明しなかったという意味で)オープンクローズド原則やTDDの支持者ではありません。私が驚いたのは、誰かがオープンクローズの原則を提案し、同時にリファクタリングとTDDの使用を提案したことです。これは私には矛盾しているように思えたので、これらすべてのガイドラインを一貫したプロセスにまとめる方法を見つけようとしていました。
ジョルジオ

「アイデアは、何かを壊す可能性があるため、コードをリファクタリングするのは危険であるため、既存のコードをそのままにして追加する方が安全です。」:実際には、このようには見えません。アイデアはむしろ、交換または拡張できる小さな自己完結型ユニットを使用することです(したがって、ソフトウェアを進化させることができます)が、完全にテストされたら各ユニットに触れないでください。
ジョルジオ

クラスはコードベースで使用されるだけではないことを考える必要があります。作成したライブラリは、別のプロジェクトで使用できます。したがって、OCPは重要です。また、必要な機能を備えた拡張クラスを知らない新しいプログラマーは、設計上の問題ではなく、通信/文書化の問題です。
Tulainsコルドバ

アプリケーションコードの@TulainsCórdovaは関係ありません。ライブラリコードの場合、セマンティックバージョニングは、重大な変更を伝えるのに適していると主張します。
opsb

1
ライブラリコードAPIを使用した@TulainsCórdovaは、クライアントコードをテストすることができないため、はるかに重要です。アプリケーションコードを使用すると、テストカバレッジにより破損がすぐに通知されます。別の言い方をすれば、アプリケーションコードはリスクなしで
重大

6

素人の言葉で:

A. O / Cの原則とは、特殊なニーズに対応するためにクラスを変更するのではなく、拡張によって特殊化を行う必要があることを意味します。

B.欠落している(特殊化されていない)機能を追加することは、設計が完成していないことを意味し、基本契約に明らかに違反することなく、基本クラスに追加する必要があります。これは原則に違反していないと思います。

C.リファクタリングは原則に違反しません。

設計が成熟するとき、たとえば、生産のしばらく後に:

  • そうする理由はほとんどなく(ポイントB)、時間とともにゼロになる傾向があります。
  • (ポイントC)より頻繁ではありませんが、常に可能です。
  • すべての新しい機能は特殊化であると想定されています。つまり、クラスを拡張(継承)する必要があります(ポイントA)。

オープン/クローズド原則は非常に誤解されています。あなたのポイントAとBがまさにそれを実現します。
gnasher729

1

私にとって、オープンクローズド原則はガイドラインであり、厳格な規則ではありません。

原則のオープン部分に関して、Javaの最終クラスとプライベートとして宣言されたすべてのコンストラクタを持つC ++のクラスは、オープンクローズ原則のオープン部分に違反します。最終クラスには、優れたソリッドユースケース(注:ソリッドではなくソリッド)があります。拡張性を考慮した設計が重要です。ただし、これにはかなりの先見性と努力が必要であり、YAGNIに違反する必要はありません(必要ではありません)。主要なソフトウェアコンポーネントを拡張用に開く必要がありますか?はい。すべて?いいえ。それ自体は投機的な一般性です。

クローズドパーツに関しては、一部の製品のバージョン2.0から2.1から2.2から2.3に移行する場合、動作を変更しないことをお勧めします。ユーザーは、マイナーリリースごとにコードが壊れると、本当に気に入らなくなります。ただし、その過程で、バージョン2.0の初期実装が根本的に壊れていたり、初期設計を制限した外部制約が適用されなくなったりすることがよくあります。にやにや笑って、そのデザインをリリース3.0で維持しますか、それとも3.0を下位互換性のないものにしますか?後方互換性は大きな制約になる可能性があります。メジャーリリースの境界は、後方互換性を壊すことが許容される場所です。これを行うと、ユーザーが動揺する可能性があることに注意する必要があります。過去とのこのブレークがなぜ必要なのかについて、良いケースがなければなりません。


0

定義により、リファクタリングは動作を変更せずにコード構造を変更します。したがって、リファクタリングするとき、新しい機能を追加しません。

Open Close原則の例としてあなたがしたことはOKに聞こえます。この原則は、既存のコードを新しい機能で拡張することです。

ただし、この答えを間違えないでください。機能のみを実行するか、大量のデータのリファクタリングのみを実行する必要があることを意味しません。プログラミングの最も一般的な方法は、すぐにリファクタリングを少し行うよりも少し機能を実行することです(もちろん、動作を変更していないことを確認するためのテストと組み合わせて)。複雑なリファクタリングは「大きな」リファクタリングを意味するものではなく、複雑で熟考されたリファクタリング手法を適用することを意味します。

ソリッド原則について。これらはソフトウェア開発のための本当に良いガイドラインですが、盲目的に従うべき宗教的なルールではありません。場合によっては、2番目、3番目、n番目の機能を追加した後、最初の設計がOpen-Closeを尊重していても、他の原則やソフトウェア要件を尊重しないことがよくあります。より複雑な変更を行う必要がある場合、設計とソフトウェアの進化にはポイントがあります。全体のポイントは、これらの問題をできるだけ早く見つけて実現し、リファクタリング技術を可能な限り適用することです。

完璧なデザインというものはありません。既存のすべての原則やパターンを尊重できる、またそうすべきデザインはありません。それはコーディングのユートピアです。

この答えがあなたのジレンマに役立つことを願っています。必要に応じて、説明を求めてください。


1
「つまり、リファクタリングするとき、新しい機能は追加しません」
ジョルジオ

「時々、2番目と3番目とn番目の機能を追加した後、最初の設計は、Open-Closeを尊重していても、他の原則やソフトウェア要件を尊重しないことに気付くことがあります。」新しい実装の作成を開始Bし、準備ができたら、古い実装Aを新しい実装に置き換えますB(これはインターフェイスの1つの使用です)。Aのコードはのコードの基礎として機能し、開発中BBコードのリファクタリングを使用できますが、既にテストされたAコードはフリーズしたままにする必要があると思います。
ジョルジオ

@Giorgioリファクタリングを行うと、バグが発生する可能性があります。そのため、テストを作成します(または、TDDを実行した方がよいでしょう)。リファクタリングする最も安全な方法は、コードが機能していることがわかったときにコードを変更することです。合格している一連のテストを行うことでこれを知っています。実動コードを変更した後も、テストに合格する必要があるため、バグを導入していないことがわかります。また、テストは本番コードと同じくらい重要であるため、本番コードと同じルールをテストに適用し、テストをクリーンに保ち、定期的かつ頻繁にリファクタリングします。
パトコスチャバ

@Giorgioの進化としてコード上にコードBが構築さAれているA場合、Bリリースされたときに、A削除して二度と使用しないでください。クライアントは、以前使用したAだけで使用するBインタフェースがあるため、変更について知らなくてもI(?リスコフの置換原則の多分少しここに...変更されませんでしたL SOLIDから)
PatkosはCsaba

はい、これは私が念頭に置いていたものです。有効な(十分にテストされた)置き換えを行うまで、動作するコードを捨てないでください。
ジョルジオ

-1

私の理解によると-既存のクラスに新しいメソッドを追加しても、OCPが壊れることはありません。ただし、クラスに新しい変数を追加すると混同されます。ただし、既存のメソッドと既存のメソッドのパラメーターを変更すると、確実にOCPが壊れます。コードが既にテストされており、メソッドを[要件が変更されるとき]意図的に変更すると問題が発生するためです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.