web-dev-qa-db-ja.com

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

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

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

16
tomsrobots

時間結合 に注意してください。ただし、これは常に問題になるとは限りません。

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

機能を一口サイズに分割することで、各部分が理解しやすくなり、個別にテストするのが間違いなく簡単になります。 巨大な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;
  }
}

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

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

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

24
user22815

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

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

10
Dave Nay

"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;

}
_

私は fluid interfaces を使用しました

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

(上記のように)気の利いた初期化を可能にします。

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

private void shift(Image i, ProcessConfiguration c)

それはshiftinganimageについてであり、詳細なパラメーターは何とか構成済み

または、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へのメソッド呼び出しは、そのようなパイプラインをインスタンス化し、何がどの順序で行われるかを透過的にします:shift回転

_public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}
_
5
Thomas Junk

なんらかのカレー化の使用を検討しましたか?クラス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を2つのクラスProcessor1およびProcessor2に置き換えることができます。

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つの引数を指定させる必要はありません。

3
Giorgio