web-dev-qa-db-ja.com

静的メソッドを悪用していますか?

数か月前、私は新しいプロジェクトで作業を開始しました。コードを実行すると、使用されている静的メソッドの量が増えました。 collectionToCsvString(Collection<E> elements)としてのユーティリティメソッドだけでなく、多くのビジネスロジックも保持されています。

この背後にある理論的根拠の責任者に尋ねたところ、彼はそれが春の専制政治から逃れる方法であると述べました。これは、この思考プロセスに関連するものです。顧客レシートの作成方法を実装するために、サービスを

@Service
public class CustomerReceiptCreationService {

    public CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

さて、その男は、基本的にクライアントクラスはSpring Beanでなければならないという制限を課しているため、Springが不必要にクラスを管理することを嫌うと述べました。最終的にはすべてをSpringで管理することになり、ステートレスオブジェクトを手続き型の方法で操作することになります。ここで述べられていることの多かれ少なかれ https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

したがって、上記のコードの代わりに、彼は

public class CustomerReceiptCreator {

    public static CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Springが可能な限りクラスを管理しないようにすることもできますが、すべてを静的にする利点はわかりません。これらの静的メソッドもステートレスなので、あまりオブジェクト指向ではありません。私は何かでより快適に感じるでしょう

new CustomerReceiptCreator().createReceipt()

静的メソッドにはいくつかの追加の利点があると彼は主張します。つまり:

  • 読みやすい。静的メソッドをインポートすると、アクションを気にするだけで済み、どのクラスがそれを実行しているかはわかりません。
  • 明らかにDB呼び出しのないメソッドなので、パフォーマンスの点で安価です。そして、それを明確にすることは良いことです。そのため、潜在的なクライアントはコードに行き、それをチェックする必要があります。
  • テストを書きやすくする。

しかし、私はこれには完全に正しくないことがあると感じているので、これについての熟練した開発者の考えを聞きたいです。

だから私の質問は、このプログラミング方法の潜在的な落とし穴は何ですか?

13
user3748908

new CustomerReceiptCreator().createReceipt()CustomerReceiptCreator.createReceipt()の違いは何ですか?ほとんどありません。唯一の重要な違いは、最初のケースの構文がかなり厄介であることです。静的メソッドを回避するとコードが適切にOOになると信じて最初の方法を実行すると、重大な間違いが生じます。単一のメソッドを呼び出すだけのオブジェクトの作成は、鈍い構文による静的メソッドです。

状況が異なるのは、CustomerReceiptCreatoringする代わりにnewを注入する場合です。例を考えてみましょう:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

    void processOrder(Order order) {
        ...
        CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
        ...
    }
}

これを静的メソッドのバージョンと比較してみましょう。

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
    ...
}

静的バージョンの利点は、システムの他の部分とどのように相互作用するかを簡単に説明できることです。そうではありません。グローバル変数を使用していない場合、システムの残りの部分は何ら変更されていないことがわかります。システムの他の部分がレシートに影響を与えていない可能性があることは知っています。不変オブジェクトを使用した場合、順序は変更されておらず、createReceiptは純粋な関数です。その場合、他の場所でのランダムな予測不可能な影響を心配することなく、この呼び出しを自由に移動/削除/などできます。

CustomerReceiptCreatorを注入した場合、同じ保証はできません。それは呼び出しによって変更される内部状態を持っているかもしれません、私は他の状態の影響を受けるか変更するかもしれません。関数のステートメント間に予測できない関係があり、順序を変更すると意外なバグが発生する可能性があります。

一方、CustomerReceiptCreatorが突然新しい依存関係を必要とする場合はどうなりますか?機能フラグをチェックする必要があるとしましょう。注入する場合、次のようなことができます。

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

    public CustomerReceipt createReceipt(Order order) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

呼び出しコードにはCustomerReceiptCreatorが自動的に注入され、FeatureFlagsが自動的に注入されるため、これで完了です。

静的メソッドを使用している場合はどうなりますか?

public class CustomerReceiptCreator {
    public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

ちょっと待って!呼び出しコードも更新する必要があります:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
    ...
}

もちろん、これはprocessOrderFeatureFlagsをどこから取得するかという問題を依然として残しています。運が良ければ、トレイルはここで終わります。そうでなければ、FeatureFlagsを通過する必要性がスタックのさらに上に押し上げられます。

ここにはトレードオフがあります。静的メソッドは依存関係を明示的に渡す必要があるため、より多くの作業が必要になります。注入されたメソッドは作業を減らしますが、依存関係を暗黙的にして隠し、コードを推論しにくくします。

23
Winston Ewert