我知道这是一个主观问题,但我找不到答案。不过我会详细说明:
我有一个具有许多配置方法的类,因此您可以使用非常不同的设置运行特定算法。正确配置后,只需调用方法“generate()”,它就会返回一个填充的2d数组。这是难以测试的方法。这很难,因为它返回heightmap,其中包含随机性和大量数据。一般来说,我会拆分它,但这会打破封装,因为在实践中数据和算法的步骤也会因同样的原因而改变,没有人会使用另一个的separeted。我知道我可以模拟随机性(我实际上正在做),但无论哪种方式,编写测试都是太多的数据(例如,仅32到32的地图在单个测试中需要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);
}
}
答案 0 :(得分:5)
将课程划分为满足Single Responsibility Principle是完全可以接受的。您是否违反此原则的测试可归纳为:
在一个句子中描述课程的作用。如果解释是一个连续的句子或包括“和”,你可能违反了SRP。
满足该原则的一个巨大好处是,一般来说,您的单位将需要进行测试,使测试更容易。但还有其他原因。具有单一责任的班级只会因为一个原因而改变,因此它更易于维护和稳定。
但是,你采取的方式也很重要。除非IDE在算法上为您进行拆分,否则您可能会在重构中引入错误。因此,在重构之前让测试类通常是一个好主意,然后在完成重构之后(并且一切仍然通过)相应地划分测试。