多くのパラメーターを持つ単一のメソッドと、順番に呼び出す必要のある多くのメソッド


16

私は多くのこと(シフト、回転、特定の軸に沿ったスケーリング、最終位置への回転)を行う必要がある生データをいくつか持っていますが、コードの可読性を維持するためにこれを行う最善の方法はわかりません。一方では、必要なことを行うために多くのパラメーター(10以上)を備えた単一のメソッドを作成できますが、これは悪夢のようなコードです。一方、それぞれ1〜3個のパラメーターを持つ複数のメソッドを作成することもできますが、正しい結果を得るには、これらのメソッドを非常に特定の順序で呼び出す必要があります。メソッドが1つのことを行い、それをうまく行うことが最善であると読んでいますが、順番に呼び出す必要のある多くのメソッドがあると、見つけにくいバグのコードが開かれるようです。

バグを最小限に抑え、コードを読みやすくするために使用できるプログラミングパラダイムはありますか?


3
最大の問題は「それらを順番に呼び出さない」ことではなく、あなた(またはより正確には将来のプログラマー)が順番に呼び出す必要があることを「知らない」ことです。メンテナンスプログラマが詳細を知っていることを確認してください(これは、要件、設計、仕様を文書化する方法に大きく依存します)。ユニットテスト、コメントを使用し、すべてのパラメーターを取得して他のパラメーターを呼び出すヘルパー関数を提供します
-mattnz

ちょっとした発言のように、流interfaceなインターフェイスコマンドパターンが役立つ場合があります。ただし、最適な設計を決定するのは、(所有者として)あなたとライブラリのユーザー(顧客)の責任です。他の人が指摘しているように、操作は非可換である(実行の順序に敏感である)ことをユーザーに伝える必要があります。
ルワン

非可換演算の例:画像変換(回転、スケーリング、クロッピング)、行列乗算など
rwong

カレーを使用できる場合があります。これにより、メソッド/関数を間違った順序で適用することができなくなります。
ジョルジオ

ここで取り組んでいるメソッドのセットは何ですか?つまり、標準は、変換オブジェクト(Javaの2D向けのAffine Transformなど)を、それを適用するメソッドに渡すことだと思います。変換の内容は、設計上、最初の操作を呼び出す順序によって異なります(したがって、「必要な順序で呼び出す」ではなく、「必要な順序で呼び出す」)。
時計仕掛けのミューズ

回答:


24

気づく 一時的な結合に。ただし、これは常に問題とは限りません。

ステップを順番に実行する必要がある場合、ステップ1はステップ2に必要なオブジェクト(ファイルストリームやその他のデータ構造など)を生成します。これだけでも、2番目の機能が必要です。しなければならない、間違った順序でそれらを呼び出すことも可能ではありません、最初の後に呼び出されます。

機能を一口サイズに分割することで、各部分が理解しやすくなり、単独でテストするのが間違いなく簡単になります。あなたが持っている場合、巨大な100ライン機能途中休憩中や何かを、どのようにあなたの失敗したテストが間違っている何を言うのでしょうか?5行のメソッドのうち1つが壊れた場合、失敗した単体テストにより、注意が必要な1つのコードにすぐに導かれます。

これは、複雑なコードがどのようにあるべきで検索します。

public List<Widget> process(File file) throws IOException {
  try (BufferedReader in = new BufferedReader(new FileReader(file))) {
    List<Widget> widgets = new LinkedList<>();
    String line;
    while ((line = in.readLine()) != null) {
      if (isApplicable(line)) { // Filter blank lines, comments, etc.
        Ore o = preprocess(line);
        Ingot i = smelt(o);
        Alloy a = combine(i, new Nonmetal('C'));
        Widget w = smith(a);
        widgets.add(w);
      }
    }
    return widgets;
  }
}

生データを完成したウィジェットに変換するプロセスのどの時点でも、各関数はプロセスの次のステップで必要なものを返します。スラグから合金を形成することはできません。最初にスメル(精製)する必要があります。入力として適切な許可(スチールなど)なしでウィジェットを作成することはできません。

各ステップの具体的な詳細は、テスト可能な個々の機能に含まれています。岩石の採掘とウィジェットの作成プロセス全体のユニットテストではなく、各特定のステップをテストします。これで、「ウィジェットの作成」プロセスが失敗した場合に、特定の理由を絞り込むことができるようにする簡単な方法ができました。

テストと正確性の証明の利点は別として、この方法でコードを記述する方がはるかに読みやすくなります。誰もが巨大なパラメータリストを理解できません。それを小さな断片に分解し、各断片が何を意味するのかを示してください


2
おかげで、これは問題に取り組む良い方法だと思います。オブジェクトの数は増えますが(不必要に感じるかもしれませんが)、読みやすさを維持しながら順序を強制します。
トムロボット

10

ほとんどすべてのコードを正しい順序で実行する必要があるため、「順番に実行する必要があります」引数は重要ではありません。結局のところ、ファイルに書き込むことはできませんし、それを開いてから閉じることはできますか?

コードを最も保守しやすいものにすることに集中する必要があります。これは通常、小さくて簡単に理解できる関数を書くことを意味します。各機能には1つの目的があり、予期しない副作用が発生しないようにする必要があります。


5

» ImageProcesssor «(またはプロジェクトに合った名前)と、必要なすべてのパラメーターを保持する構成オブジェクトProcessConfigurationを作成します。

 ImageProcessor p = new ImageProcessor();

 ProcessConfiguration config = new processConfiguration().setTranslateX(100)
                                                         .setTranslateY(100)
                                                         .setRotationAngle(45);
 p.process(image, config);

画像プロセッサ内で、1つの方法でプロセス全体をカプセル化します process()

public class ImageProcessor {

    public Image process(Image i, ProcessConfiguration c){
        Image processedImage=i.getCopy();
        shift(processedImage, c);
        rotate(processedImage, c);
        return processedImage;
    }

    private void rotate(Image i, ProcessConfiguration c) {
        //rotate
    }

    private void shift(Image i, ProcessConfiguration c) {
        //shift
    }
}

このメソッドは、正しい順序shift()で変換メソッドを呼び出しますrotate()。各メソッドは、渡されたProcessConfigurationから適切なパラメーターを取得します。

public class ProcessConfiguration {

    private int translateX;

    private int rotationAngle;

    public int getRotationAngle() {
        return rotationAngle;
    }

    public ProcessConfiguration setRotationAngle(int rotationAngle){
        this.rotationAngle=rotationAngle;
        return this;
    }

    public int getTranslateY() {
        return translateY;
    }

    public ProcessConfiguration setTranslateY(int translateY) {
        this.translateY = translateY;
        return this;
    }

    public int getTranslateX() {
        return translateX;
    }

    public ProcessConfiguration setTranslateX(int translateX) {
        this.translateX = translateX;
        return this;
    }

    private int translateY;

}

流体インターフェースを使用しました

public ProcessConfiguration setRotationAngle(int rotationAngle){
    this.rotationAngle=rotationAngle;
    return this;
}

(上記のように)気の利いた初期化が可能です。

1つのオブジェクトに必要なパラメーターをカプセル化する明らかな利点。メソッドシグネチャが読み取り可能になります。

private void shift(Image i, ProcessConfiguration c)

それは画像シフトすることであり、詳細なパラメータは何らかの形で設定されます

または、ProcessingPipelineを作成できます。

public class ProcessingPipeLine {

    Image i;

    public ProcessingPipeLine(Image i){
        this.i=i;
    };

    public ProcessingPipeLine shift(Coordinates c){
        shiftImage(c);
        return this;
    }

    public ProcessingPipeLine rotate(int a){
        rotateImage(a);
        return this;
    }

    public Image getResultingImage(){
        return i;
    }

    private void rotateImage(int angle) {
        //shift
    }

    private void shiftImage(Coordinates c) {
        //shift
    }

}

メソッドのメソッド呼び出しは、processImageそのようなパイプラインをインスタンス化し、何がどの順序で行われるかを透過的にします:shiftrotate

public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}

3

何らかのカレーの使用を検討したことがありますか?クラスProcesseeとクラスがあると想像してくださいProcessor

class Processor
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T1 a1, T2 a2)
    {
        // Process using a1
        // then process using a2
    }
}

今、あなたは、クラスを置き換えることができProcessor、二つのクラスでProcessor1Processor2

class Processor1
{
    private final Processee _processee;

    public Processor1(Processee p)
    {
        _processee = p;
    }

    public Processor2 process(T1 a1)
    {
        // Process using argument a1

        return new Processor2(_processee);
    }
}

class Processor2
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T2 a2)
    {
        // Process using argument a2
    }
}

次に、以下を使用して正しい順序で操作を呼び出すことができます。

new Processor1(processee).process(a1).process(a2);

3つ以上のパラメーターがある場合、このパターンを複数回適用できます。必要に応じて引数をグループ化することもできます。つまり、各processメソッドに引数を1つだけ持たせる必要はありません。


ほぼ同じ考えでした;)唯一の違いは、Pipelineが厳密な処理順序を強制することです。
トーマスジャンク

@ThomasJunk:私の知る限り、これは要件です:「正しい結果を得るには、これらのメソッドを非常に特定の順序で呼び出す必要があります」。厳密な実行順序を持つことは、関数構成に非常によく似ています。
ジョルジオ

そして、私もそうしました。しかし、処理順序を変更する場合は、多くのリファクタリングを行う必要があります;)
トーマスジャンク

@ThomasJunk:はい。本当にアプリケーションに依存します。処理ステップを非常に頻繁に交換できる場合は、おそらくあなたのアプローチの方が優れています。
ジョルジオ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.