CodeGym /Java Blog /ランダム /Java でのリファクタリングの仕組み
John Squirrels
レベル 41
San Francisco

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

ランダム グループに公開済み
プログラミングの方法を学ぶときは、コードの作成に多くの時間を費やします。ほとんどの初心者開発者は、これが将来自分がやることになると信じています。これは部分的には真実ですが、プログラマーの仕事にはコードの保守とリファクタリングも含まれます。今日はリファクタリングについてお話します。 Java でのリファクタリングの仕組み - 1

CodeGym でのリファクタリング

リファクタリングは、CodeGym コースで 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-else6 行のメソッドをより簡潔に記述できるのに、 なぜブロックを使用するのでしょうか?

 public boolean max(int a, int b) {
      return a > b;
 }
これで、上記の例と同じ操作を実行するシンプルで洗練されたメソッドが完成しました。これがリファクタリングの仕組みです。コードの本質に影響を与えることなく、コードの構造を変更します。リファクタリングの方法やテクニックは数多くありますが、それらについて詳しく見ていきます。

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

理由はいくつかあります。たとえば、コードの単純さと簡潔さを実現するためです。この理論の支持者は、コードを理解するために数十行のコメントが必要な場合でも、コードは可能な限り簡潔であるべきだと考えています。他の開発者は、最小限のコメントで理解できるようにコードをリファクタリングする必要があると確信しています。各チームは独自の立場を採用していますが、リファクタリングは削減を意味するものではないことに注意してください。 その主な目的は、コードの構造を改善することです。 この全体的な目的には、いくつかのタスクを含めることができます。
  1. リファクタリングにより、他の開発者が作成したコードの理解が深まります。
  2. バグの発見と修正に役立ちます。
  3. ソフトウェア開発のスピードを加速できます。
  4. 全体として、ソフトウェア設計が改善されます。
リファクタリングを長期間行わないと、開発が完全に停止するなど、開発に支障が出る可能性があります。

「コードが臭い」

コードにリファクタリングが必要な場合、コードには「臭い」があると言われます。もちろん、文字通りではありませんが、そのようなコードは実際にはあまり魅力的に見えません。以下では、初期段階の基本的なリファクタリング手法について説明します。

不当に大きいクラスとメソッド

クラスやメソッドは、そのサイズが非常に大きいため、扱いにくく、効果的に操作することが不可能になる場合があります。

大人数クラス

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

ロングメソッド

この「臭い」は、開発者がメソッドに新しい機能を追加するときに発生します。「コードをここに記述できるのに、なぜパラメータ チェックを別のメソッドに追加する必要があるのか​​?」、「最大値を見つけるために別の検索メソッドが必要なのはなぜですか?」 「配列に要素が含まれているのですか? ここに保持しましょう。この方がコードが明確になります」などの誤解があります。

長いメソッドをリファクタリングするには、次の 2 つのルールがあります。

  1. メソッドを作成するときにコメントを追加したい場合は、その機能を別のメソッドに配置する必要があります。
  2. メソッドに 10 ~ 15 行を超えるコードが必要な場合は、そのメソッドが実行するタスクとサブタスクを特定し、サブタスクを別のメソッドに入れるようにする必要があります。

長いメソッドを削除するには、いくつかの方法があります。

  • メソッドの機能の一部を別のメソッドに移動する
  • ローカル変数により機能の一部を移動できない場合は、オブジェクト全体を別のメソッドに移動できます。

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

この問題は通常、クラス内のフィールドの数が時間の経過とともに増加した場合に発生します。たとえば、すべて (通貨、日付、電話番号など) を小さなオブジェクトではなくプリミティブ型または定数に保存する場合です。この場合、フィールドの論理グループを別のクラス (抽出クラス) に移動することをお勧めします。データを処理するメソッドをクラスに追加することもできます。

パラメータが多すぎます

これは、特に長いメソッドと組み合わせた場合によくある間違いです。通常、この問題はメソッドの機能が多すぎる場合、またはメソッドに複数のアルゴリズムが実装されている場合に発生します。パラメーターの長いリストは理解するのが非常に難しく、そのようなリストを含むメソッドを使用するのは不便です。結果として、オブジェクト全体を渡す方が良いでしょう。オブジェクトに十分なデータがない場合は、より一般的なオブジェクトを使用するか、メソッドの機能を分割して、各メソッドが論理的に関連するデータを処理するようにする必要があります。

データのグループ

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

OOP 原則に違反するソリューション

これらの「臭い」は、開発者が適切な OOP 設計に違反した場合に発生します。これは、ユーザーが OOP 機能を完全に理解しておらず、それらを完全にまたは適切に使用できない場合に発生します。

継承の使用の失敗

サブクラスが親クラスの関数の小さなサブセットのみを使用する場合、階層が間違っている可能性があります。これが発生した場合、通常、余分なメソッドは単純にオーバーライドされないか、例外がスローされます。あるクラスが別のクラスを継承するということは、子クラスが親クラスのほぼすべての機能を使用することを意味します。正しい階層の例: Java でのリファクタリングの仕組み - 2間違った階層の例: Java でのリファクタリングの仕組み - 3

switch ステートメント

ステートメントの何が間違っているのでしょうかswitch? 非常に複雑になるとダメです。関連する問題は、多数のネストされたifステートメントです。

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

複数のクラスは同じことを行いますが、メソッドの名前は異なります。

一時フィールド

null値が設定されているときのみオブジェクトが必要とする一時フィールドがクラスにあり、それ以外の時間は空である場合、または、禁じられていますが、コードは臭います。これは疑わしい設計上の決定です。

改造を困難にする臭い

これらの臭いはより深刻です。他の匂いは主にコードを理解しにくくしますが、コードを変更することはできません。新しい機能を導入しようとすると、開発者の半分は辞めてしまい、半分は気が狂ってしまいます。

並列継承階層

この問題は、クラスをサブクラス化する際に、別のクラス用に別のサブクラスを作成する必要がある場合に現れます。

均一に分散された依存関係

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

複雑な変更ツリー

この匂いは前の匂いとは逆です。変更は 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) {
     // Some actions
     double yearlySalary = employee.getYearlySalary();
     double awards = employee.getAwards();
     double monthlySalary = getMonthlySalary(yearlySalary, awards);
     // Continue processing
 }
 
 public double getMonthlySalary(double yearlySalary, double awards) {
      return (yearlySalary + awards)/12;
 }
には、employeeMethod値の受け取りとプリミティブ変数への格納に特化した 2 行があります。このような構成には最大 10 行かかる場合があります。オブジェクト自体を渡し、それを使用して必要なデータを抽出する方がはるかに簡単です。

 public void employeeMethod(Employee employee) {
     // Some actions
     double monthlySalary = getMonthlySalary(employee);
     // Continue processing
 }
 
 public double getMonthlySalary(Employee employee) {
     return (employee.getYearlySalary() + employee.getAwards())/12;
 }

シンプル、簡潔、簡潔。

フィールドを論理的にグループ化し、それらを別のフィールドに移動するというclassDespite事実は、上記の例が非常に単純であるという事実であり、それらを見ると、多くの人が「誰がこんなことをするのか?」と疑問に思うかもしれませんが、多くの開発者は不注意のためにこのような構造上の間違いを犯します。コードをリファクタリングする気がない、あるいは単に「それで十分だ」という態度です。

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

適切なリファクタリングの結果、プログラムは読みやすいコードになり、ロジックが変更される可能性も怖くなくなり、新しい機能の導入はコード分析地獄にならず、数日間は楽しい経験になります。 。プログラムを最初から作成する方が簡単な場合は、リファクタリングをすべきではありません。たとえば、コードの理解、分析、リファクタリングに必要な労力は、同じ機能を最初から実装するよりも多くなる、とチームが見積もっているとします。または、リファクタリングするコードにデバッグが難しい問題が多数ある場合。コードの構造を改善する方法を知ることは、プログラマーの仕事において不可欠です。また、Java でのプログラミングの学習には、実践に重点を置いたオンライン コースである CodeGym が最適です。即時検証を伴う 1200 以上のタスク、約 20 のミニプロジェクト、ゲームのタスク - これらすべてがコーディングに自信をもつのに役立ちます。始めるのに最適な時期は今です:)

リファクタリングにさらに没頭するためのリソース

リファクタリングに関する最も有名な本は、Martin Fowler 著の「Refactoring. Improving the Design of Existing Code」です。以前の本に基づいた、リファクタリングに関する興味深い出版物もあります: Joshua Kerievsky 著「パターンを使用したリファクタリング」。パターンと言えば… リファクタリングをするとき、基本的なデザイン パターンを知っていると常に非常に役立ちます。これらの優れた本は、これに役立ちます。 パターンと言えば... リファクタリングを行うとき、基本的な設計パターンを知っていることは常に非常に役立ちます。これらの優れた書籍は、これに役立ちます。
  1. 「デザインパターン」エリック・フリーマン、エリザベス・ロブソン、キャシー・シエラ、バート・ベイツ著、Head First シリーズより
  2. 「The Art of Readable Code」Dustin Boswell と Trevor Foucher 著
  3. Steve McConnell 著の「Code Complete」では、美しくエレガントなコードの原則が説明されています。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION