JavaRush /Java Blog /Random-JA /Java でのリファクタリングの仕組み

Java でのリファクタリングの仕組み

Random-JA グループに公開済み
プログラミングを学ぶときは、コードを書くのに多くの時間がかかります。ほとんどの初心者開発者は、これが将来の活動になると信じています。これは部分的には真実ですが、プログラマーのタスクにはコードの保守とリファクタリングも含まれます。今日はリファクタリングについて話します。 Java でのリファクタリングの仕組み - 1

JavaRush コースのリファクタリング

JavaRush コースでは、リファクタリングのトピックを 2 回取り上げます。 大きなタスクのおかげで、実際のリファクタリングに慣れる機会があり、IDEA でのリファクタリングに関する講義は、作業を驚くほど簡単にする自動ツールを理解するのに役立ちます。

リファクタリングとは何ですか?

これは、機能を変更せずにコードの構造を変更することです。たとえば、2 つの数値を比較し、最初の数値の方が大きい場合はtrueを返し、そうでない場合はfalse を返すメソッドがあります。
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
結果は非常に面倒なコードになりました。初心者でもこのようなことを書くことはほとんどありませんが、そのようなリスクがあります。if-elseメソッドを 6 行短く記述できるのに、 なぜここにブロックがあるのか​​と思われるでしょう。
public boolean max(int a, int b) {
     return a>b;
}
このメソッドはシンプルでエレガントに見えますが、実行することは上記の例と同じです。これがリファクタリングの仕組みです。コードの本質に影響を与えることなく、コードの構造を変更します。リファクタリングの方法やテクニックは数多くありますが、それらについては後で詳しく説明します。

なぜリファクタリングが必要なのでしょうか?

理由はいくつかあります。たとえば、コードの単純さと簡潔さの追求です。この理論の支持者は、コードを理解するために数十行のコメントが必要な場合でも、コードは可能な限り簡潔であるべきだと考えています。他の開発者は、最小限のコメントで理解できるようにコードをリファクタリングする必要があると考えています。各チームは独自の立場を選択しますが、リファクタリングは削減ではないことを覚えておく必要があります。 その主な目的は、コードの構造を改善することです。 このグローバル目標には、いくつかの目標を含めることができます。
  1. リファクタリングにより、他の開発者が作成したコードの理解が深まります。
  2. エラーの発見と修正に役立ちます。
  3. ソフトウェア開発の速度を上げることができます。
  4. 全体的にソフトウェア構成が改善されました。
リファクタリングが長期間実行されない場合、開発に問題が発生し、作業が完全に停止する場合があります。

「コードが臭い」

コードにリファクタリングが必要な場合、「臭い」と言われます。もちろん、文字通りではありませんが、そのようなコードは実際にはあまり見栄えがよくありません。以下では、初期段階の主なリファクタリング手法を検討します。

不必要に大きな要素

サイズが巨大であるために、効果的に操作することが不可能な扱いにくいクラスやメソッドがあります。

ビッグクラス

このようなクラスには、膨大な数のコード行とさまざまなメソッドが含まれます。通常、開発者にとっては、新しいクラスを作成するよりも既存のクラスに機能を追加する方が簡単であり、それがクラスが成長する理由です。通常、このクラスの機能はオーバーロードされています。この場合、機能の一部を別のクラスに分離すると役に立ちます。これについては、リファクタリング手法のセクションで詳しく説明します。

ビッグメソッド

この「匂い」は、開発者がメソッドに新しい機能を追加するときに発生します。「ここに書けるのに、なぜパラメータのチェックを別のメソッドに入れなければならないのですか?」、「なぜ配列の最大要素を見つけるためのメソッドを別のメソッドに置く必要があるのですか?それはここに残しておきます。」こうすることでコードがより明確になります」などの誤解があります。 大規模なメソッドのリファクタリングには 2 つのルールがあります。
  1. メソッドを作成するときにコードにコメントを追加する場合は、この機能を別のメソッドに分割する必要があります。
  2. メソッドに 10 ~ 15 行を超えるコードが必要な場合は、メソッドが実行するタスクとサブタスクを特定し、サブタスクを別のメソッドに分割する必要があります。
大規模なメソッドを削除するには、いくつかの方法があります。
  • メソッドの機能の一部を別のメソッドに分離します。
  • ローカル変数を使用して機能の一部を抽出できない場合は、オブジェクト全体を別のメソッドに渡すことができます。

多くのプリミティブ データ型の使用

通常、この問題は、クラス内にデータを保存するフィールドの数が時間の経過とともに増加した場合に発生します。たとえば、小さなオブジェクトの代わりにプリミティブ型を使用してデータ (通貨、日付、電話番号など) を保存したり、定数を使用して情報をエンコードしたりする場合です。この場合、フィールドを論理的にグループ化し、別のクラスに配置する (クラスを選択する) ことをお勧めします。このデータを処理するメソッドをクラスに含めることもできます。

オプションの長いリスト

特に大規模なメソッドと組み合わせた場合によくある間違いです。通常、メソッドの機能が過負荷になっている場合、またはメソッドが複数のアルゴリズムを組み合わせている場合に発生します。パラメーターの長いリストは理解するのが非常に難しく、そのようなメソッドは使用するのが不便です。したがって、オブジェクト全体を転送することをお勧めします。オブジェクトに十分なデータがない場合は、より一般的なオブジェクトを使用するか、論理的に関連するデータを処理できるようにメソッドの機能を分割する価値があります。

データグループ

論理的に関連したデータのグループがコードに現れることがよくあります。たとえば、データベースへの接続パラメータ (URL、ユーザー名、パスワード、スキーマ名など)。要素のリストから単一のフィールドを削除できない場合、そのリストは別のクラスに配置する必要があるデータのグループになります (クラス選択)。

OOPの概念を台無しにするソリューション

このタイプの「臭い」は、開発者が OOP 設計に違反した場合に発生します。これは、彼がこのパラダイムの機能を完全に理解していない場合、またはそれらを不完全または間違って使用した場合に発生します。

相続の拒否

サブクラスが親クラスの機能の最小限の部分を使用する場合、階層が正しくないように感じられます。通常、この場合、不要なメソッドは単純にオーバーライドされないか、例外がスローされます。クラスが別のクラスから継承されている場合、これはその機能がほぼ完全に使用されていることを意味します。正しい階層の例: Java でのリファクタリングの仕組み - 2 間違った階層の例: Java でのリファクタリングの仕組み - 3

switch ステートメント

オペレーターに何が問題があるのでしょうかswitch? 設計が非常に複雑な場合は問題です。これには、多くのネストされたブロックも含まれますif

異なるインターフェースを持つ代替クラス

いくつかのクラスは実際には同じことを行いますが、それらのメソッドの名前は異なります。

一時フィールド

クラスに、オブジェクトが値で埋められるときだけ必要とする一時フィールドが含まれており、それ以外の時間は空であるか、あるいは、神が禁じているのであれば、コードは「null臭い」となり、そのような設計は疑わしいものになります。決断。

改造を困難にする臭気

これらの「臭い」はより深刻です。残りの部分は主にコードの理解を妨げますが、コードを変更することはできません。何らかの機能を導入すると、開発者の半分は辞め、半分は夢中になります。

並列継承階層

クラスのサブクラスを作成するときは、別のクラスの別のサブクラスを作成する必要があります。

均一な依存関係の分布

変更を実行するときは、このクラスのすべての依存関係 (使用) を検索し、多くの小さな変更を加える必要があります。1 つの変更 - 多くのクラスで編集します。

複雑な変更ツリー

この匂いは前の匂いとは逆です。変更は同じクラスの多数のメソッドに影響を与えます。一般に、このようなコードの依存関係はカスケード的です。1 つのメソッドを変更すると、別のメソッドで何かを修正する必要があり、さらに 3 番目のメソッドでも修正する必要がある、というようになります。1 つのクラスに多くの変更が加えられます。

「生ゴミが臭い」

頭痛を引き起こすかなり不快な部類の臭気。役に立たない、不要な、古いコード。幸いなことに、最新の IDE とリンターは、そのような臭いについて警告することを学習しました。

メソッド内の大量のコメント

このメソッドには、ほぼすべての行に多くの説明コメントがあります。これは通常、複雑なアルゴリズムに関連付けられているため、コードをいくつかの小さなメソッドに分割し、それらに意味のある名前を付けることをお勧めします。

コードの重複

異なるクラスまたはメソッドは同じコード ブロックを使用します。

怠惰なクラス

このクラスが担う機能はほとんどありませんが、その多くは計画されていました。

未使用のコード

クラス、メソッド、または変数はコード内で使用されておらず、「デッドウェイト」です。

過剰結合

このカテゴリの匂いは、コード内の多数の不必要な接続によって特徴付けられます。

サードパーティの方法

メソッドは、独自のデータを使用するよりも、別のオブジェクトのデータをはるかに頻繁に使用します。

不適切な親密さ

クラスは別のクラスのサービス フィールドとメソッドを使用します。

長時間の授業通話

1 つのクラスが別のクラスを呼び出し、3 番目のクラスにデータを要求し、4 番目のクラスにデータを要求するというようになります。このような長い呼び出しチェーンは、現在のクラス構造への高いレベルの依存性を意味します。

クラスタスクディーラー

クラスは、タスクを別のクラスに渡すためにのみ必要です。たぶんそれは削除されるべきでしょうか?

リファクタリング手法

以下では、ここで説明したコードの臭いを取り除くのに役立つ初期のリファクタリング手法について説明します。

クラス選択

このクラスが実行する関数が多すぎるため、一部の関数を別のクラスに移動する必要があります。たとえば、Human居住地の住所も含むクラスと、完全な住所を提供するメソッドがあります。
class Human {
   private String name;
   private String age;
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}
アドレス情報とメソッド (データ処理動作) を別のクラスに配置することをお勧めします。
class Human {
   private String name;
   private String age;
   private Address address;

   private String getFullAddress() {
       return address.getFullAddress();
   }
}
class Address {
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}

方法の選択

メソッド内の機能をグループ化できる場合は、別のメソッドに配置する必要があります。たとえば、二次方程式の根を計算するメソッドは次のとおりです。
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
    else if (D == 0) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
    else {
        System.out.println("Equation has no roots");
    }
}
3 つの可能なオプションすべての計算を別のメソッドに移動しましょう。
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        dGreaterThanZero(a, b, D);
    }
    else if (D == 0) {
        dEqualsZero(a, b);
    }
    else {
        dLessThanZero();
    }
}

public void dGreaterThanZero(double a, double b, double D) {
    double x1, x2;
    x1 = (-b - Math.sqrt(D)) / (2 * a);
    x2 = (-b + Math.sqrt(D)) / (2 * a);
    System.out.println("x1 = " + x1 + ", x2 = " + x2);
}

public void dEqualsZero(double a, double b) {
    double x;
    x = -b / (2 * a);
    System.out.println("x = " + x);
}

public void dLessThanZero() {
    System.out.println("Equation has no roots");
}
各メソッドのコードは大幅に短く、明確になりました。

オブジェクト全体を転送する

パラメータを指定してメソッドを呼び出すと、次のようなコードが表示されることがあります。
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double yearlySalary = employee.getYearlySalary();
    double awards = employee.getAwards();
    double monthlySalary = getMonthlySalary(yearlySalary, awards);
    // Продолжение обработки
}

public double getMonthlySalary(double yearlySalary, double awards) {
     return (yearlySalary + awards)/12;
}
このメソッドでは、employeeMethod値を取得してプリミティブ変数に格納するために、最大 2 行が割り当てられます。このようなデザインには最大 10 行かかる場合があります。オブジェクト自体をメソッドに渡す方がはるかに簡単で、そこから必要なデータを抽出できます。
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}
シンプル、短く、簡潔。

フィールドを論理的にグループ化し、別のクラスに配置する

上記の例は非常に単純であり、これを見ると多くの人が「実際にこれを行うのは誰ですか?」という疑問を抱くかもしれませんが、多くの開発者は、不注意やコードのリファクタリングに消極的なため、または単純に「それで済むだろう」と考えて、同様の構造上のエラー。

なぜリファクタリングが有効なのか

適切なリファクタリングの結果、コードが読みやすく、プログラム ロジックの変更が脅威にならず、新機能の導入がコード解析地獄にならず、数日間は快適なアクティビティとなるプログラムが得られます。 。プログラムを最初から書き直す方が簡単な場合は、リファクタリングを使用しないでください。たとえば、チームは、コードの解析、分析、リファクタリングにかかる​​人件費は、同じ機能を最初から実装する場合よりも高いと見積もっています。または、リファクタリングが必要なコードにデバッグが難しいエラーが多数含まれている場合。コードの構造を改善する方法を知ることは、プログラマーの仕事において必須です。Java プログラミングは、実践に重点を置いたオンライン コースである JavaRush で学ぶ方が良いでしょう。即時検証付きの 1,200 以上のタスク、約 20 のミニプロジェクト、ゲーム タスク - これらすべてがコーディングに自信をもつのに役立ちます。始めるのに最適な時期は今です:) Java でのリファクタリングの仕組み - 4

リファクタリングをさらに深く掘り下げるためのリソース

リファクタリングに関する最も有名な本は、『リファクタリング』です。「既存のコードの設計の改善」、Martin Fowler 著。Joshua Kiriewski による以前の本「パターンによるリファクタリング」に基づいて書かれた、リファクタリングに関する興味深い出版物もあります。テンプレといえば。リファクタリングを行う場合、基本的なアプリケーション設計パターンを知っていると常に非常に役立ちます。これらの優れた書籍は、これに役立ちます。
  1. 「デザイン パターン」 - エリック フリーマン、エリザベス フリーマン、キャシー シエラ、バート ベイツ著、Head First シリーズより。
  2. 「可読コード、または芸術としてのプログラミング」 - ダスティン・ボズウェル、トレバー・ファウチャー。
  3. Steve McConnell 著の「Perfect Code」は、美しくエレガントなコードの原則を概説しています。
さて、リファクタリングに関するいくつかの記事:
  1. 大変な作業です。レガシー コードのリファクタリングを始めましょう
  2. リファクタリング;
  3. 誰でも使えるリファクタリング
    コメント
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION