26

神オブジェクトをリファクタリングする最良の方法を知っている人はいますか?

メソッドの結合が高いため、いくつかの小さなクラスに分割するほど単純ではありません。1つのメソッドを引き出すと、通常、他のすべてのメソッドを引き出すことになります。

4

4 に答える 4

38

ジェンガみたいです。あなたは忍耐と安定した手を必要とするでしょう、さもなければあなたは最初からすべてを作り直さなければなりません。それ自体は悪くありません。コードを破棄する必要がある場合もあります。

その他のアドバイス:

  • メソッドを引き出す前に考えてください。このメソッドはどのデータで機能しますか?それにはどのような責任がありますか?
  • 最初はgodクラスのインターフェースを維持し、新しく抽出されたクラスへの呼び出しを委任するようにしてください。結局、神のクラスは独自の論理のない純粋なファサードでなければなりません。次に、便宜上それを保持するか、破棄して新しいクラスのみを使用し始めることができます
  • 単体テストのヘルプ:メソッドを抽出する前に各メソッドのテストを記述して、機能が損なわれないようにします
于 2013-02-14T15:06:37.563 に答える
15

「神オブジェクト」は巨大なクラス(コード行で測定)を意味すると思います。

基本的な考え方は、その関数の一部を他のクラスに抽出することです。

あなたが探すことができるものを見つけるために

  • 一緒に使用されることが多いフィールド/パラメータ。彼らは一緒に新しいクラスに移動するかもしれません

  • クラス内のフィールドの小さなサブセットのみを使用するメソッド(またはメソッドの一部)は、それらのフィールドのみを含むクラスに移動する場合があります。

  • プリミティブ型(int、String、boolean)。多くの場合、それらは出てくる前に本当に価値のあるオブジェクトです。それらが値オブジェクトになると、多くの場合、メソッドを引き付けます。

  • 神オブジェクトの使い方を見てください。さまざまなクライアントによって使用されるさまざまな方法はありますか?それらは別々のインターフェースに入る可能性があります。これらのインターフェースには、別々の実装がある場合があります。

これらの変更を実際に行うには、次のようなインフラストラクチャとツールを使用する必要があります。

  • テスト:(おそらく生成された)徹底的なテストのセットを用意して、頻繁に実行できるようにします。テストなしで行う変更には細心の注意を払ってください。私はそれらを行いますが、単一のIDEアクションで完全に実行できるextractメソッドのようなものに制限します。

  • バージョン管理:実際に速度を落とすことなく、2分ごとにコミットできるバージョン管理が必要です。SVNは実際には機能しません。Gitはそうします。

  • ミカドメソッド:ミカドメソッドのアイデアは、変更を試みることです。それがうまくいくなら。何が壊れているかに注意しない場合は、最初に変更したものへの依存関係としてそれらを追加してください。変更をロールバックします。結果のグラフで、まだ依存関係がないノードでプロセスを繰り返します。http://mikadomethod.wordpress.com/book/

于 2013-03-18T11:22:23.973 に答える
2

LanzaとMarinescuによる「ObjectOrientedMetricsin Practice」という本によると、God Classの設計上の欠陥は、システムのインテリジェンスを集中化する傾向があるクラスを指します。神のクラスはそれ自体であまりにも多くの作業を実行し、些細な詳細だけを一連の些細なクラスに委任し、他のクラスからのデータを使用します。

神のクラスの検出は、3つの主要な特性に基づいています。

  1. それらは、直接またはアクセサメソッドを使用して、他のより単純なクラスのデータに頻繁にアクセスします。
  2. それらは大きくて複雑です
  3. それらは多くの非通信的振る舞いを持っています。つまり、そのクラスに属するメソッド間の凝集度は低いです。

神クラスのリファクタリングは複雑な作業です。この不調和は、メソッドレベルで発生する他の不調和の累積的な影響であることが多いためです。したがって、このようなリファクタリングを実行するには、クラスのメソッドについて、場合によってはその継承コンテキストについてさえも、さらに詳細な情報が必要になります。最初のアプローチは、互いに結び付けられているメソッドと属性のクラスターを識別し、これらのアイランドを別々のクラスに抽出することです。

「オブジェクト指向リエンジニアリングパターン」という本の神クラスの分割方法は、神クラスの責任を、協力するクラスまたは神クラスから引き出された新しいクラスのいずれかに段階的に再配分することを提案しています。

「レガシーコードを効果的に使用する」という本は、神のクラスのリファクタリングをサポートするために使用できるレガシーシステムをテストできるように、スプラウトメソッド、スプラウトクラス、ラップメソッドなどのいくつかの手法を紹介しています。

私がすることは、入力または出力と同じクラスプロパティを利用するGodクラスのメソッドをサブグループ化することです。その後、クラスをサブクラスに分割します。各サブクラスは、サブグループ内のメソッドと、これらのメソッドが使用するプロパティを保持します。

そうすることで、新しいクラスはそれぞれ小さくなり、一貫性が高まります(つまり、すべてのメソッドが同様のクラスプロパティで機能します)。さらに、生成した新しいクラスごとの依存関係が少なくなります。その後、コードをよりよく理解できるようになるため、これらの依存関係をさらに減らすことができます。

一般的に、当面の状況に応じて、いくつかの異なる方法があると思います。例として、ユーザー情報を検証し、ユーザーがオンラインユーザーリストに追加されるように「OnlineUserService」を更新し、ログイン固有のデータ(ようこそ画面や1回など)を返す「LoginManager」という名前の神のクラスがあるとします。オファー)クライアントに。

したがって、クラスは次のようになります。

import java.util.ArrayList;
import java.util.List;

public class LoginManager {

public void handleLogin(String hashedUserId, String hashedUserPassword){
    String userId = decryptHashedString(hashedUserId);
    String userPassword = decryptHashedString(hashedUserPassword);

    if(!validateUser(userId, userPassword)){ return; }

    updateOnlineUserService(userId);
    sendCustomizedLoginMessage(userId);
    sendOneTimeOffer(userId);
}

public String decryptHashedString(String hashedString){
    String userId = "";
    //TODO Decrypt hashed string for 150 lines of code...
    return userId;
}

public boolean validateUser(String userId, String userPassword){
    //validate for 100 lines of code...
    
    List<String> userIdList = getUserIdList();
    
    if(!isUserIdValid(userId,userIdList)){return false;}
    
    if(!isPasswordCorrect(userId,userPassword)){return false;}
    
    return true;
}

private List<String> getUserIdList() {
    List<String> userIdList = new ArrayList<>();
    //TODO: Add implementation details
    return userIdList;
}

private boolean isPasswordCorrect(String userId, String userPassword) {
    boolean isValidated = false;
    //TODO: Add implementation details
    return isValidated;
}

private boolean isUserIdValid(String userId, List<String> userIdList) {
    boolean isValidated = false;
    //TODO: Add implementation details
    return isValidated;
}

public void updateOnlineUserService(String userId){
    //TODO updateOnlineUserService for 100 lines of code...
}

public void sendCustomizedLoginMessage(String userId){
    //TODO sendCustomizedLoginMessage for 50 lines of code...

}

public void sendOneTimeOffer(String userId){
    //TODO sendOneTimeOffer for 100 lines of code...
}}

これで、このクラスが巨大で複雑になることがわかります。現在、クラスフィールドはメソッド間で一般的に使用されているため、本の定義ではまだ神のクラスではありません。しかし、議論のために、それを神のクラスとして扱い、リファクタリングを開始することができます。

解決策の1つは、メインクラスのメンバーとして使用される個別の小さなクラスを作成することです。追加できるもう1つのことは、さまざまなインターフェイスとそれぞれのクラスでさまざまな動作を分離することです。これらのメソッドを「プライベート」にすることで、クラスの実装の詳細を非表示にします。そして、メインクラスのこれらのインターフェースを使用して入札を行います。

したがって、最終的に、RefactoredLoginManagerは次のようになります。

public class RefactoredLoginManager {
    IDecryptHandler decryptHandler;
    IValidateHandler validateHandler;
    IOnlineUserServiceNotifier onlineUserServiceNotifier;
    IClientDataSender clientDataSender;

public void handleLogin(String hashedUserId, String hashedUserPassword){
        String userId = decryptHandler.decryptHashedString(hashedUserId);
        String userPassword = decryptHandler.decryptHashedString(hashedUserPassword);

        if(!validateHandler.validateUser(userId, userPassword)){ return; }

        onlineUserServiceNotifier.updateOnlineUserService(userId);

        clientDataSender.sendCustomizedLoginMessage(userId);
        clientDataSender.sendOneTimeOffer(userId);
    }
}

DecryptHandler:

public class DecryptHandler implements IDecryptHandler {

    public String decryptHashedString(String hashedString){
        String userId = "";
        //TODO Decrypt hashed string for 150 lines of code...
        return userId;
    }

}
 
public interface IDecryptHandler {

    String decryptHashedString(String hashedString);

}

ValidateHandler:

public class ValidateHandler implements IValidateHandler {
    public boolean validateUser(String userId, String userPassword){
        //validate for 100 lines of code...

        List<String> userIdList = getUserIdList();

        if(!isUserIdValid(userId,userIdList)){return false;}

        if(!isPasswordCorrect(userId,userPassword)){return false;}

        return true;
    }

    private List<String> getUserIdList() {
        List<String> userIdList = new ArrayList<>();
        //TODO: Add implementation details
        return userIdList;
    }

    private boolean isPasswordCorrect(String userId, String userPassword) 
    {
        boolean isValidated = false;
        //TODO: Add implementation details
        return isValidated;
    }

    private boolean isUserIdValid(String userId, List<String> userIdList) 
    {
        boolean isValidated = false;
        //TODO: Add implementation details
        return isValidated;
    }

}

ここで注意すべき重要なことは、インターフェイス()には他のクラスで使用されるメソッドのみを含める必要があるということです。したがって、IValidateHandlerは次のように単純に見えます。

public interface IValidateHandler {

    boolean validateUser(String userId, String userPassword);

}

OnlineUserServiceNotifier:

public class OnlineUserServiceNotifier implements 
    IOnlineUserServiceNotifier {

    public void updateOnlineUserService(String userId){
        //TODO updateOnlineUserService for 100 lines of code...
    }

}
 
public interface IOnlineUserServiceNotifier {
    void updateOnlineUserService(String userId);
}

ClientDataSender:

public class ClientDataSender implements IClientDataSender {

    public void sendCustomizedLoginMessage(String userId){
        //TODO sendCustomizedLoginMessage for 50 lines of code...

    }

    public void sendOneTimeOffer(String userId){
        //TODO sendOneTimeOffer for 100 lines of code...
    }
}

LoginHandlerで両方のメソッドにアクセスするため、インターフェースには両方のメソッドを含める必要があります。

public interface IClientDataSender {
    void sendCustomizedLoginMessage(String userId);

    void sendOneTimeOffer(String userId);
}
于 2021-01-03T16:26:52.363 に答える
1

ここには本当に2つのトピックがあります。

  • 神のクラスを考えると、そのメンバーはどのように合理的にサブセットに分割されますか?基本的な考え方は、概念的な一貫性(多くの場合、クライアントモジュールでの頻繁な併用によって示される)と強制的な依存関係によって要素をグループ化することです。明らかに、これの詳細はリファクタリングされているシステムに固有です。結果は、神のクラス要素の望ましいパーティション(グループのセット)です。

  • 必要なパーティションを指定して、実際に変更を加えます。コードベースに規模がある場合、これは困難です。これを手動で行うと、そのアクセサーを変更して、代わりにパーティションから形成された新しいクラスを呼び出す間、Godクラスを保持することをほぼ強制されます。もちろん、これらの変更を手動で行うと間違いを犯しやすいため、テスト、テスト、テストを行う必要があります。神のクラスへのすべてのアクセスがなくなったら、最終的にそれを削除できます。これは理論的には素晴らしいように聞こえますが、何千ものコンパイルユニットに直面している場合、実際には長い時間がかかります。これを行う間、チームメンバーにGodインターフェイスへのアクセスの追加を停止させる必要があります。ただし、自動リファクタリングツールを適用してこれを実装することはできます。このようなツールを使用すると、ツールのパーティションを指定して、信頼できる方法でコードベースを変更できます。C ++ Godクラスのリファクタリングであり、3,000のコンパイルユニットを備えたシステム間でこのような変更を行うために使用されています。

于 2019-05-10T16:46:15.890 に答える