1. 序章

ほとんどのプログラマーは、優れたソフトウェアの作成に努めています。 しかし、残念ながら、それを達成することは、私たちが最初に考えていたよりも複雑です。

このチュートリアルでは、ソフトウェアの品質と、コードの臭いとの関係について詳しく説明します。

2. ソフトウェア要件

優れたソフトウェアを作成することは困難な作業です。 同時に多くの要件を満たす必要があります。

  • 便利な機能を提供する
  • バグを含めるべきではありません
  • ユーザーフレンドリーなインターフェースを持つ
  • 妥当な時間枠でアクションに応答する
  • あまり多くのリソースを消費しないでください
  • 操作(インストール、ホストなど)と保守(機能の追加、バグの修正)が簡単(かつ安価)である必要があります

これらの特性の多くは矛盾しています。 したがって、これらの品質の許容可能なバランスを追求する必要があります。

ただし、これらのいくつかは他のものより重要です。 たとえば、MichRaveraは次のように述べています。

それが機能しない場合、それがどれほど速く機能しないかは問題ではありません。

このリストのように、保守性は他の品質の背後にあることがよくあります。 ただし、ほとんどの場合、保守可能なコードには本質的にバグが少なく、パフォーマンス特性が優れており、操作が簡単です。 したがって、コードを保守可能にすると、他の特性も改善される可能性が高くなります。

3. コード品質

コードの品質など、多くの要因が保守性に影響を与えます。 しかし、コードの品質自体も複雑なトピックです。 通常、コードを良くするものよりも悪くするものを定義する方が簡単です。

悪いコードを扱うとき、私たちは通常、それが悪いと感じることがあります。 場合によっては、問題を簡単に見つけることができます。 しかし、多くの場合、それはそれほど明白ではありません。 そのような場合、それは正しくないと感じます。 何かがおかしい。 問題を特定できなくても、コードの臭いを感じます。

これらの問題にはさまざまな形があります。 コードが何をするのか、どのように行うのかを理解するのが難しい場合があります。 その他の場合、要件が変更されたとき、またはバグを修正する必要があるときに、動作を変更するのは困難です。 同様に、新しい機能を追加するのは難しいかもしれません。

一般的なのは、問題が一緒に発生する傾向があるということです。心理的な理由があります。 これらを理解するために、コードがどのように進化するかを見てみましょう。

3.1. コードベースの進化

低品質のコードベースが表示されていると考えてみましょう。 それをどのように変えますか? これまでに作成した中で最も美しいコードを追加しますか? おそらくそうではありません。 すでに臭いコードベースであるため、既存のパイルにさらにゴミを追加します。 もう少しは関係ありません。

確かに、1つの臭い部分は関係ありません。 しかし、このように振る舞うと、ソフトウェアに触れるたびに臭いがします。 最終的に、それは腐敗します。 そして、誰も腐敗している何かで働きたいとは思わない。 そこで、「前回は1週間デバッグしなければならなかったので、このモジュールには触れたくない」や「最初から書き直すのが最善だ」などのフレーズを言い始めます。 通常、プロジェクトをダンプして再起動する機会はありません。 だから私たちは私たちが嫌うこの忌まわしさで立ち往生するでしょう。

すべてを最初から書き直すことは必ずしも良い決断ではないことに注意してください。 必要な労力は通常過小評価されています。 よりうまく機能するのは、いくつかの部分を識別して分離し、他の部分に触れずにそれらを書き直すことです。 完了したら、次のモジュールに進むことができます。 この反復アプローチは、ほとんどの場合、より適切に機能します。

結局、品質の観点から、ソフトウェアを繰り返し書き直すか、一度に書き直すかは問題ではありません。 重要なのは、私たちの行動が変わったのか、それとも以前と同じように働いているのかということです。 常に新しいコードを周囲に適応させると、そのプロセスは別の壊滅的なコードベースにつながります。 理由がわかりません。 以前の間違いはしませんでした。 しかし、私たちは他のものを作りました。

3.2. 良いコードの書き方

解決策は、私たちの行動に注意することです。 悪い環境に適応しないでください。 問題を修正するのではなく、防止する必要があります。 何があっても、常に高品質でクリーンなコードを追加する必要があります。 その結果、時間の経過とともに、全体的なコード品質が向上します。

結局、古くて臭い部分が私たちの邪魔をし始めます。 それらは残りのコードから際立っています。 したがって、最後に行うことは、それらを識別することです。

幸いなことに、私たちは何度も間違いを繰り返す傾向があります。 賢い人々はパターンを認識し、収集し、整理しました。 これらのパターンをコードの臭いと呼びます。

4. コードの臭いの種類

コードの臭いにはカタログがあります。 このカタログでは、共通の特性に基づいてグループ化されています。 このセクションでは、これらのグループを網羅することなく見ていきます。

4.1. ブローター

Bloaters は、コード内の構成(クラス、メソッドなど)であり、大きすぎるため、効果的に処理できません。 ほとんどの場合、ソフトウェアに機能を追加するときに、時間の経過とともに表示されます。 ここに行を追加し、そこにメソッドを追加します。ブーム、2000行のクラスがあります。

あまり目立たない膨らみの匂いは、データクランプという名前です。

class DateUtil {
    boolean isAfter(int year1, int month1, int day1, int year2, int month2, int day2) {
        // implementation
    }
  
    int differenceInDays(int year1, int month1, int day1, int year2, int month2, int day2) {
        // implementation
    }
  
    // other date methods
}

上記のすべての方法は日付で機能します。 したがって、それらはすべて、年、月、日という3つの整数引数を受け取ります。 それらをDateクラスにグループ化すると、コードが読みやすくなります。

class Date {
    int year;
    int month;
    int day;
}

class DateUtil {
    boolean isAfter(Date date1, Date date2) {
        // implementation
    }
  
    int differenceInDays(Date date1, Date date2) {
        // implementation
    }
  
    // other date methods
}

さらに、これらのメソッドを Date クラスに移動して、操作でデータをカプセル化する必要があります。 しかし、それは別の話です。

4.2. オブジェクト指向の悪用者

良いオブジェクト指向コードを書くのは難しい場合があります。 原則に従わないと、これらの臭いの1つに遭遇する可能性があります。

たとえば、 switch-caseステートメントは、OOのコードの臭いと見なされます。 この例を考えてみましょう:

class Animal {
    String type;
  
    String makeSound() {
        switch (type) {
            case "cat":
                return "meow";
            case "dog":
                return "woof";
            default:
                throw new IllegalStateException();
        }
    }
}

switch-caseの代わりに、ポリモーフィズムを使用する必要があります。

abstract class Animal {
    abstract String makeSound();
}

class Cat extends Animal {
    @Override
    String makeSound() {
        return "meow";
    }
}

class Dog extends Animal {
    @Override
    String makeSound() {
        return "woof";
    }
}

switch-caseステートメントを削除しただけではありません。 その上、私たちのクラスはもう違法な状態になることはできません。

4.3. 変更防止策

変更防止策は単一責任の原則に違反しています。

たとえば、ショットガン手術は、動作を変更するためにコードの複数の部分に触れる必要があることを意味します。

ある観点からすると、発散変化は反対です。 これは、複数の動作の変更がコードの同じ部分に影響を与えることを意味します。

必ずしも貧弱なコードを示すとは限らないコードの臭いがあります。たとえば、抽象ファクトリデザインパターンを使用する場合、意図的に並列継承階層を実装します。 他の場合では、それは多くの頭痛の種を引き起こす間違ったデザインです。

4.4. 必需品

Dispensables は、コードにノイズを導入します。 それらがなければ、コードははるかにクリーンになる可能性があります。

たとえば、次のスニペットについて考えてみます。

// amount
double a = order.getAmount();
// discount factor
double b = 1;
if (a > 10) {
    b = 0.9;
}
// discounted price
double c = product.getPrice() * b;
// order sum price
double d = a * c;

適切な変数名を使用すると、コメントを取り除くことができます。

double amount = order.getAmount();
double discountFactor = 1;
if (amount > 10) {
    discountFactor = 0.9;
}
double discountedPrice = product.getPrice() * discountFactor;
double orderSumPrice = amount * discountedPrice;

すべてのコメントがコードの臭いであるとは限らないことに注意してください。 彼らがコードの機能や動作を説明している場合、彼らは私たちのコードが十分に読めないことを示しています。 しかし、なぜ何かが必要なのかを述べれば、貴重な情報を提供します。 たとえば、特別なビジネス要件のために奇妙なエッジケースを処理する必要がある場合です。

4.5. カプラー

カプラーは、クラスを個別に変更できないようにします。

たとえば、不適切な親密さは、他のクラスのプライベート部分にアクセスすることによってデータを隠すことに違反します。

興味深いことに、2つの匂いが互いに正反対である場合があります。

メッセージチェーンについて考えてみます。ここで、メソッド呼び出しをチェーンします。

class Repository {
    Entity findById(long id) {
        // implementation
    }
}

class Service {
    Repository repository;

    Repository getRepository() {
        return repository;
    }
}

class Context {
    Service service;

    void useCase() {
        // the following is a message chain
        Entity entity = service.getRepository().findById(1);
        // using entity
    }
}

解決策は、ServiceRepositoryを呼び出すメソッドを導入することです。

class Service {
    Repository repository;

    Entity findById(long id) {
        return repository.findById(id);
    }
}

class Context {
    Service service;

    void useCase() {
        Entity entity = service.findById(1);
        // using entity
    }
}

ただし、この例では、メソッド Service.findById()ミドルマンと呼ばれ、これは別の匂いです。 そしてそれを取り除くために、私たちはそれを元のコードに書き直す必要があります。

勝てないということですか? もちろん違います。 これは、正しい解決策がそれを何に使用するかによって異なることを意味します。

たとえば、データは本質的に階層的である可能性があります。 したがって、異なる粒度でデータにアクセスしたいので、メッセージチェーンは問題を意味しません。

上記の例では、 Service.getRepository()を使用すると、このコードはデータに関するものではないため、デメテルの法則に違反しています。 これは動作に関するものであり、Serviceクラスの内部構造を公開しています。

または、 Service を削除して、Contextから直接Repositoryを使用することもできます。

class Context {
    Repository repository;

    void useCase() {
        Entity entity = repository.findById(1);
        // using entity
    }
}

このように、私たちはもはや仲介者やメッセージチェーンを持っていません。 ただし、状況によっては他の問題が発生する場合があります。 たとえば、レイヤーまたはモジュールの境界に違反する可能性があります。

また、これらの例では、Serviceクラスに別のコードの臭いが発生することに注意してください。 その匂いが何であるかを知ることは興味深い運動です。 最後のセクションで答えを述べました。

4.6. その他の問題

コードの臭いを特定して修正することは特効薬ではないことに注意してください。問題を表す場合があります。 他の場合には、そうではありません。 抽象ファクトリの例のように。

他の場合では、潜在的なコードの臭いの修正は別のものです、例えば、中間の人対。 メッセージチェーンの場合。 しかし、繰り返しになりますが、適切な決定を下せるようにするには、コードの目的を理解する必要があります。

また、コードの臭いは、コードのすべての問題を特定することにはほど遠いです。 代わりに、通常は簡単であるが修正が明らかではないいくつかの一般的な問題を特定することのみを目的としています。

5. コードの臭いを取り除く方法

コードベースの品質にもかかわらず、クリーンなコードを書くことでコードの臭いを防ぐ必要があると話しました。

しかし、時には、匂いがすでに存在しています。 次に、コードの臭いを探すことで、問題のあるコードを特定できます。 しかし、どうすればこれらの問題を取り除くことができますか? 外部から観察可能な動作を変更せずに、コードの構造を変更する必要があります。 リファクタリングはまさにそれを行う手法です。 すでにトピックをカバーしているので、詳細には立ち入りませんここ

定期的にリファクタリングを行うと、コードの品質が常に向上します。 このプロセスは、臭いコードを導入するのと似ていますが、反対の効果があります。 一度に1つずつ、ゆっくりとコードを改善します。 そして最終的に、私たちのコードベースは素晴らしいものになります。

6. 結論

保守可能なコードは、ソフトウェアの他の欠陥を防ぐことができます。 または、少なくとも、対処が容易になります。

コードの臭いはコードの品質の問題です。 それらは一般的で、コードベースによく現れる繰り返しパターンです。 それらを特定することで、コード品質を的を絞って改善することができます。

このチュートリアルでは、コードベースの品質が低下するのを防ぐ方法について説明しました。 次に、コードの臭いの例をいくつか見ました。 最後に、リファクタリングはそれらを取り除くための頼りになるテクニックであると述べました。

最後に、約束どおり、Serviceは前の例のlazyクラスです。