2

これは主観的な質問だと思いますが、答えは見つかりませんでした。ただし、詳細を説明します。

非常に異なる設定で特定のアルゴリズムを実行できるように、多くの構成メソッドを持つクラスがあります。適切に構成した後、メソッド「generate()」を呼び出すだけで、塗りつぶされた2D配列が返されます。それはテストするのが難しい方法です。高さマップを返すので難しいです、ランダム性と大量のデータが含まれています。通常、分割しますが、実際には同じ理由でデータとアルゴリズムのステップが変更され、一方を他方から分離して使用することはないため、カプセル化が壊れます。ランダム性(実際に行っていること)をモックできることは知っていますが、どちらの方法でも、テストを作成するにはデータが多すぎます(たとえば、32 x 32のマップだけでは、1回のテストで1024個のアサーションが必要になります)。 。それに加えて、大きなマップの生成には最大3〜4秒かかる場合がありますが、これはテスト環境では明らかに良くありません。

また、最初にデータをアルゴリズムから分離し、アルゴリズムを純粋関数(データなし、引数のみ)にすることを試みました。これはテスト可能でしたが(アルゴリズムを分割したため)、これにより、多くの引数が渡され、かなり手続き型のコードになります。

ですから、ここでどのように進めるかはわかりません。クラスを分割すると、テストと理解が容易になりますが、カプセル化が非常に悪くなります。実用的であれ理論的であれ、このテーマについての考えをいただければ幸いです。

前もって感謝します。

編集:これは私が書いたコードであり、手続き型です。

JavaRandom、Attenuation、およびRandomNumberProcessorは、一緒に保持する必要があるクラスです。

これは、アルゴリズムで使用されるデータです。

package noise;

import noise.attenuation.DefaultAttenuation;

public final class DiamondSquareSettings {

    public static final class Builder {

            private static final double NEVER_ASSIGNED = 1.1;
            private static final long DEFAULT_RNG_SEED = 33609606l;
            private static final JavaRandom DEFAULT_RANDOM = new JavaRandom(DEFAULT_RNG_SEED);
            private static final double DEFAULT_RANGE = 1;
            private static final Attenuation defaultAttenuation = new DefaultAttenuation(0.7);

            private Random builderRandom = DEFAULT_RANDOM;
            private final double[] builderSeeds = {NEVER_ASSIGNED , NEVER_ASSIGNED , NEVER_ASSIGNED , NEVER_ASSIGNED};
            private double builderRange = DEFAULT_RANGE;
            private Attenuation builderAttenuation = defaultAttenuation;

            public Builder attenuation(final Attenuation attenuation) {
                    validateAttenuation(attenuation);
                    builderAttenuation = attenuation;
                    return this;
            }

            public DiamondSquareSettings build(final int side) {
                    validateSize(side);
                    return new DiamondSquareSettings(side, builderRandom, builderSeeds, builderRange, builderAttenuation);
            }

            public Builder random(final long seed) {
                    return random(new JavaRandom(seed));
            }

            public Builder randomRange(final double range) {
                    validateRange(range);
                    builderRange = range;
                    return this;
            }

            public Builder seed(final int seedPosition, final double seedValue) {
                    validateSeeds(seedPosition, seedValue);
                    builderSeeds[seedPosition] = seedValue;
                    return this;
            }

            Builder random(final Random random) {
                    validateRandom(random);
                    builderRandom = random;
                    return this;
            }

            private void validateAttenuation(final Attenuation attenuation) {
                    if (attenuation == null)
                            throw new IllegalArgumentException("attenuation == null!");
            }

            private void validateRandom(final Random random) {
                    if (random == null)
                            throw new IllegalArgumentException("random == null!");
            }

            private void validateRange(final double range) {
                    if (range < 0)
                            throw new IllegalArgumentException("range < 0" + "\nrange: " + range);
            }

            private void validateSeeds(final int seedPosition, final double seedValue) {
                    if (seedValue > 1 || seedValue < 0)
                            throw new IllegalArgumentException("Invalid seed" + "\nseedPosition: " + seedPosition + " seed: "
                                            + seedValue);
            }

            private void validateSize(final int side) {
                    final double doubleExpoent = Math.log(side - 1) / Math.log(2);
                    final int integerExpoent = (int) doubleExpoent; //TODO simplify more
                    if (doubleExpoent != integerExpoent)
                            throw new IllegalArgumentException("side is not a (power of two) plus one" + "\nside: " + side);
            }
    }

    private final int side;
    private final Random random;
    private final double[] seeds;
    private final double range;
    private final Attenuation attenuation;

    private DiamondSquareSettings(final int side, final Random random, final double[] seeds, final double range,
                    final Attenuation attenuation) {
            this.side = side;
            this.random = random;
            this.seeds = randomizeNeverAssignedSeeds(random, seeds, range);
            this.range = range;
            this.attenuation = attenuation;
    }

    public String getAttenuationInfo() {
            return attenuation.toString();
    }

    public String getRandomInfo() {
            return random.toString();
    }

    public double getRandomRange() {
            return range;
    }

    public double getSeed(final int seedPosition) {
            return seeds[seedPosition];
    }

    public int getSide() {
            return side;
    }

    Attenuation getAttenuation() {
            return attenuation;
    }

    Random getRandom() {
            return random;
    }

    private double[] randomizeNeverAssignedSeeds(final Random random, final double[] seeds, final double range) {
            final double[] properlyRandomizingSeeds = seeds;
            for (int i = 0 ; i < seeds.length ; i++)
                    if (seeds[i] == Builder.NEVER_ASSIGNED)
                            properlyRandomizingSeeds[i] = random.nextRandomValueInside(range);

            return properlyRandomizingSeeds;
    }

}

これはアルゴリズムの大部分です:

    package noise;

    public final class DiamondSquare {

    public static Noise generate(final DiamondSquareSettings settings) {
            validateSettings(settings);
            final DiamondSquareStep stepper = new DiamondSquareStep();
            double[][] noiseArray = new double[settings.getSide()][settings.getSide()];
            setupSeeds(settings, noiseArray);
            final RandomNumberProcessor processor =
                            new RandomNumberProcessor(settings.getRandomRange(), settings.getRandom(), settings.getAttenuation());
            noiseArray = processDiamondSquare(settings, stepper, noiseArray, processor);
            return new Noise(noiseArray);
    }

    private static double[][] processDiamondSquare(final DiamondSquareSettings settings,
                    final DiamondSquareStep stepper, double[][] noiseArray, final RandomNumberProcessor processor) {
            int stepSize = settings.getSide() - 1;
            while (stepSize >= 1) {
                    noiseArray = stepper.diamondSquare(stepSize, noiseArray, processor);
                    processor.nextStage();
                    stepSize /= 2;
            }
            return noiseArray;
    }

    private static void setupSeeds(final DiamondSquareSettings settings, final double[][] noiseArray) {
            noiseArray[0][0] = settings.getSeed(0);
            noiseArray[settings.getSide() - 1][0] = settings.getSeed(1);
            noiseArray[0][settings.getSide() - 1] = settings.getSeed(2);
            noiseArray[settings.getSide() - 1][settings.getSide() - 1] = settings.getSeed(3);
    }

    private static void validateSettings(final DiamondSquareSettings settings) {
            if (settings == null)
                    throw new IllegalArgumentException("settings == null!");
    }

}

これは、アルゴリズムの他のステップです。

package noise;


final class DiamondSquareResolver {

    private static final double OUT_OF_RANGE = 0; //TODO this code smells. It alters slightly the final result. Should be gone.
    private static final double DISAMBIGUATION = 0.00001;

    public double diamondStep(final int i, final int j, final int halfStep, final double[][] noise,
                    final RandomNumberProcessor processor) {
            final double left = initializeLeft(i - halfStep, j, noise);
            final double right = initializeRight(i + halfStep, j, noise);
            final double top = initializeTop(i, j - halfStep, noise);
            final double bot = initializeBot(i, j + halfStep, noise);
            final int divisor = processDivisor(left, right, top, bot);
            return (left + right + top + bot) / divisor + processor.generateNumber();
    }

    public double squareStep(final int i, final int j, final int halfStep, final double[][] noise,
                    final RandomNumberProcessor processor) {
            final double topLeft = noise[i - halfStep][j - halfStep];
            final double topRight = noise[i + halfStep][j - halfStep];
            final double bottomLeft = noise[i - halfStep][j + halfStep];
            final double bottomRight = noise[i + halfStep][j + halfStep];
            return (topLeft + topRight + bottomLeft + bottomRight) / 4 + processor.generateNumber();
    }

    private double initializeBot(final int i, final int bottomCoordinate, final double[][] noise) {
            final int height = noise[0].length;
            if (! (bottomCoordinate >= height))
                    return validatedPosition(noise[i][bottomCoordinate]);
            else
                    return OUT_OF_RANGE;
    }

    private double initializeLeft(final int leftCoordinate, final int j, final double[][] noise) {
            if (! (leftCoordinate < 0))
                    return validatedPosition(noise[leftCoordinate][j]);
            else
                    return OUT_OF_RANGE;
    }

    private double initializeRight(final int rightCoordinate, final int j, final double[][] noise) {
            final int width = noise.length;
            if (! (rightCoordinate >= width))
                    return validatedPosition(noise[rightCoordinate][j]);
            else
                    return OUT_OF_RANGE;
    }

    private double initializeTop(final int i, final int topCoordinate, final double[][] noise) {
            if (! (topCoordinate < 0))
                    return validatedPosition(noise[i][topCoordinate]);
            else
                    return OUT_OF_RANGE;
    }

    private int processDivisor(final double ... sides) { //TODO remove varagrs argument, as it is not proper.
            int divisor = 4;
            for (final double side : sides)
                    if (side == OUT_OF_RANGE)
                            divisor--;
            return divisor;
    }

    private double validatedPosition(final double value) {
            return value != OUT_OF_RANGE ? value : value + DISAMBIGUATION;
    }

}

final class DiamondSquareStep {

    public double[][] diamondSquare(final int step, final double[][] noise, final RandomNumberProcessor processor) {
            final double[][] steppedNoise = copyNoise(noise);
            final DiamondSquareResolver solver = new DiamondSquareResolver();
            final int halfStep = step / 2;
            performSquareSteps(step, steppedNoise, processor, solver, halfStep);
            performDiamondSteps(step, steppedNoise, processor, solver, halfStep);
            return steppedNoise;
    }

    private double[][] copyNoise(final double[][] noise) {
            final double[][] steppedNoise = new double[noise.length][noise[0].length];
            for (int i = 0 ; i < noise.length ; i++)
                    for (int j = 0 ; j < noise[0].length ; j++)
                            steppedNoise[i][j] = noise[i][j];
            return steppedNoise;
    }

    private void performDiamondSteps(final int step, final double[][] noise, final RandomNumberProcessor processor,
                    final DiamondSquareResolver solver, final int halfStep) {
            for (int i = 0 ; i < noise.length ; i += step)
                    for (int j = 0 ; j < noise[0].length ; j += step) {
                            if (i + halfStep < noise.length)
                                    noise[i + halfStep][j] = solver.diamondStep(i + halfStep, j, halfStep, noise, processor);
                            if (j + halfStep < noise[0].length)
                                    noise[i][j + halfStep] = solver.diamondStep(i, j + halfStep, halfStep, noise, processor);

                    }
    }

    private void performSquareSteps(final int step, final double[][] noise, final RandomNumberProcessor processor,
                    final DiamondSquareResolver solver, final int halfStep) {
            for (int i = halfStep ; i < noise.length ; i += step)
                    for (int j = halfStep ; j < noise[0].length ; j += step)
                            noise[i][j] = solver.squareStep(i, j, halfStep, noise, processor);
    }

}

4

1 に答える 1

5

単一責任の原則を満たすためにクラスを分割することは、まったく問題ありません。この原則に違反しているかどうかのテストは、次のように要約できます。

クラスが何をするのかを一文で説明してください。説明が長文であったり、「and」が含まれている場合は、SRP に違反している可能性があります。

原則を満たすことの大きな利点の 1 つは、一般に、テストする必要のあるユニットが少なくなり、テストが容易になることです。しかし、他にも理由があります。単一の責任を持つクラスは、1 つの理由でのみ変更されるため、保守性と安定性が大幅に向上します。

ただし、その方法も重要です。IDE がアルゴリズム的に分割を行っていない限り、リファクタリングでバグが発生する可能性があります。そのため、リファクタリングする前にクラスをテストし、リファクタリングを行った後 (そしてすべてがまだパスしている場合) に、それに応じてテストを分割することをお勧めします。

于 2012-10-20T21:38:20.700 に答える