明らかな抽象化のないコード複製


14

コードの行を見たときに、ロジックにおけるその役割を忠実に説明するテーマの抽象化に適合できないコード重複のケースに遭遇したことはありますか?それに対処するために何をしましたか?

これはコードの複製であるため、理想的には、たとえば独自の機能を作成するなどの屈折処理を行う必要があります。しかし、コードはそれを記述するための優れた抽象化を持たないため、結果は奇妙な関数になり、良い名前を見つけることすらできず、ロジックの役割はそれを見ただけでは明らかではありません。それは、私にとって、コードの明快さを傷つけます。明快さを保持し、そのままにしておくことはできますが、保守性が損なわれます。

このようなものに対処する最良の方法は何だと思いますか?

回答:


18

コードの重複は、「しゃれ」の結果である場合があります。2つのことは同じに見えますが、同じではありません。

過剰な抽象化は、システムの真のモジュール性を損なう可能性があります。モジュール方式の下では、「何が変わる可能性があるのか​​」を判断する必要があります。そして「何が安定しているの?」。安定しているものはすべてインターフェースに入れられ、不安定なものはモジュールの実装にカプセル化されます。その後、物事が変わると、必要な変更はそのモジュールに分離されます。

リファクタリングは、安定していると思われるもの(たとえば、このAPI呼び出しは常に2つの引数を取る)を変更する必要がある場合に必要です。

したがって、これら2つの重複したコードフラグメントについて、一方に必要な変更は、必然的に他方も変更する必要があることを意味しますか?

その質問にどのように答えるかによって、優れた抽象化が何であるかについてのより良い洞察が得られるかもしれません。

デザインパターンも便利なツールです。おそらく、重複したコードが何らかの形式のトラバーサルを行っているため、反復子パターンを適用する必要があります。

複製されたコードに複数の戻り値がある場合(そして、それが単純な抽出メソッドを実行できない理由です)、おそらく戻り値を保持するクラスを作成する必要があります。クラスは、2つのコードフラグメント間で異なる各ポイントに対して抽象メソッドを呼び出すことができます。次に、クラスの具体的な実装を2つ作成します。各フラグメントに1つです。[これは事実上、テンプレートメソッドのデザインパターンであり、C ++のテンプレートの概念と混同しないでください。あるいは、あなたが見ているものは、Strategyパターンでより良く解決されるかもしれません。]

それについて考える別の自然で便利な方法は、高階関数を使用することです。たとえば、ラムダを作成するか、コードが抽象化に渡す匿名内部クラスを使用します。一般に、重複を削除することはできますが、実際にそれらの間に関係がない限り(一方が変更され、他方が変更される必要がある場合)、モジュール性を損なう可能性があります。


4

このような状況に遭遇した場合、「非伝統的な」抽象化について考えるのが最善です。関数内に多くの重複があり、あまりにも多くの変数を渡す必要があるため、単純な古い関数の因数分解はうまく適合しないかもしれません。ここでは、D / Pythonスタイルのネストされた関数(外部スコープへのアクセスを含む)がうまく機能します。(はい、そのすべての状態を保持するクラスを作成できますが、2つの関数でのみ使用している場合、これはネストされた関数を持たないためのugい冗長な回避策です。) mixinはうまく機能します。たぶんあなたが本当に必要なのはマクロです。たぶん、テンプレートのメタプログラミングやリフレクション/イントロスペクション、あるいは生成的プログラミングを考慮する必要があります。

もちろん、実際的な観点からは、これらは言語がサポートしておらず、言語内にそれらをきれいに実装するのに十分なメタプログラミング機能がない場合、不可能ではないにしても困難です。この場合、「より良い言語を取得する」以外は何を伝えればよいかわかりません。また、多くの抽象化機能(Ruby、Python、Lisp、Dなど)を備えた高水準言語を学習すると、一部の手法はまだ使用可能であるがあまり明確ではない低水準言語でより適切にプログラミングできる場合があります。


狭いスペースで圧縮された多くの優れた技術に対して+1。(まあ、説明された技術に対しても+1だったでしょう。)
マクニール

3

個人的に私はそれを無視して先に進みます。奇妙な場合、複製する方が良い場合があります。リファクタリングに何年も費やすことができ、次の開発者が一見して変更を取り消すことができます!


2

コードサンプルがなければ、コードに簡単に識別できる抽象化がない理由を言うのは困難です。この警告を使用して、いくつかのアイデアを示します。

  • 共通のコードを保持するために1つの新しい関数を作成する代わりに、機能をいくつかの異なる部分に分割します。
  • 共通のデータ型または抽象的な動作に基づいて小さな断片をグループ化します。
  • 新しい断片を指定して複製コードを書き換えます。
  • 新しいコードがまだ明確な抽象化に反している場合は、同様にそれを小さく分割し、プロセスを繰り返します。

この演習の最大の難点は、特定の抽象化レベルで無関係な動作が関数に組み込まれている可能性が高く、それらのいくつかをより低いレベルで処理する必要があることです。明快さはコードを維持する鍵であると正しく推測しますが、コードの動作(現在の状態)を明確にすることは、コードの意図を明確にすることとは大きく異なります。

関数のシグネチャで何を識別するかによって小さなコードの断片を抽象化し、大きな断片を分類しやすくする必要があります。

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