atdd和tdd_TDD和转换优先权前提

atdd和tdd

上一次 ,我们研究了测试驱动开发 (TDD)的红色/绿色/重构阶段。 这次,我们将详细研究在Green阶段应用的转换。

转型优先前提

转型
你们中的大多数人都听说过我们在上一个TDD阶段中应用的重构,但是在Green阶段中也有相应的标准化代码更改。 鲍勃·马丁叔叔把它们命名为变换

转换优先级前提 (TPP)声称这些转换具有固有顺序,并且选择列表中较高的转换会导致更好的算法。

排序示例提供了轶事证据,其中违反顺序导致气泡排序,而正确顺序导致快速排序。

在根据其他人的帖子进行了一些修改之后,鲍勃叔叔得出了以下有序的转换列表

转型 描述
{} –>无 完全没有代码->使用nil的代码
nil->常数
常数->常数+ 一个简单的常量到一个更复杂的常量
常数->标量 用变量或参数替换常量
声明->声明 添加更多无条件语句
无条件->如果 分割执行路径
标量->数组
数组->容器 ??? 从来没有使用过,也没有解释过
语句->尾递归
如果->当
语句->递归
表达式->功能 用函数或算法替换表达式
变量->分配 替换变量的值
案件 将案例(或其他案例)添加到现有交换机,或者

将TPP应用于罗马数字Kata

罗马数字符号

阅读某些东西只会使您获得较浅的知识,因此让我们在一个小而熟悉的问题上尝试TPP:“ 罗马数字kata”
对于不熟悉它的人:目的是将数字转换为罗马字。 有关罗马符号及其值的概述,请参见左侧的表格。

与TDD中一样,我们从最简单的情况开始:

public class RomanNumeralsTest {

  @Test
  public void arabicToRoman() {
    Assert.assertEquals("i", "i", RomanNumerals.arabicToRoman(1));
  }

}

我们可以将其编译为:

public class RomanNumerals {

  public static String arabicToRoman(int arabic) {
    return null;
  }

}

请注意,我们已经在列表上应用了第一个转换: {}->nil 。 我们将第二个转换nil->constant应用于绿色:

public class RomanNumerals {

  public static String arabicToRoman(int arabic) {
    return "i";
  }

}

现在我们可以添加第二个测试:

public class RomanNumeralsTest {

  @Test
  public void arabicToRoman() {
    assertRoman("i", 1);
    assertRoman("ii", 2);
  }

  private void assertRoman(String roman, int arabic) {
    Assert.assertEquals(roman, roman, 
        RomanNumerals.arabicToRoman(arabic));
  }

}

使此测试通过的唯一方法是引入一些条件( unconditional->if ):

public static String arabicToRoman(int arabic) {
    if (arabic == 2) {
      return "ii";
    }
    return "i";
  }

但是,这导致数字2和返回的i数量重复。 因此,让我们尝试不同的转换序列。 警告:我现在要进入婴儿步骤模式。

首先,执行constant->scalar

public static String arabicToRoman(int arabic) {
  String result = "i";
  return result;
}

接下来, statement->statements

public static String arabicToRoman(int arabic) {
  StringBuilder result = new StringBuilder();
  result.append("i");
  return result.toString();
}

现在我们可以介绍if而不重复:

public static String arabicToRoman(int arabic) {
  StringBuilder result = new StringBuilder();
  if (arabic >= 1) {
    result.append("i");
  }
  return result.toString();
}

然后另一个statement->statements

public static String arabicToRoman(int arabic) {
  StringBuilder result = new StringBuilder();
  if (arabic >= 1) {
    result.append("i");
    arabic -= 1;
  }
  return result.toString();
}

最后我们执行if->while

public static String arabicToRoman(int arabic) {
  StringBuilder result = new StringBuilder();
  while (arabic >= 1) {
    result.append("i");
    arabic -= 1;
  }
  return result.toString();
}

现在我们的测试通过了。 顺便说一句,对3的测试也是如此。

戴着重构帽,我们发现了一些更细微的重复:在数字1和字符串i 。 它们都表达相同的概念(数字1),但是是不同的版本:一种阿拉伯语和一种罗马语。

我们应该引入一个类来捕获这个概念:

public class RomanNumerals {

  public static String arabicToRoman(int arabic) {
    StringBuilder result = new StringBuilder();
    RomanNumeral numeral = new RomanNumeral("i", 1);
    while (arabic >= numeral.getValue()) {
      result.append(numeral.getSymbol());
      arabic -= numeral.getValue();
    }
    return result.toString();
  }

}

public class RomanNumeral {

  private final String symbol;
  private final int value;

  public RomanNumeral(String symbol, int value) {
    this.symbol = symbol;
    this.value = value;
  }

  public int getValue() {
    return value;
  }

  public String getSymbol() {
    return symbol;
  }

}

现在事实证明,我们有一个特征嫉妒的案例。 我们可以通过提取一种方法使这一点更加明显:

public static String arabicToRoman(int arabic) {
  StringBuilder result = new StringBuilder();
  RomanNumeral numeral = new RomanNumeral("i", 1);
  arabic = append(arabic, result, numeral);
  return result.toString();
}

private static int append(int arabic, StringBuilder builder,
    RomanNumeral numeral) {
  while (arabic >= numeral.getValue()) {
    builder.append(numeral.getSymbol());
    arabic -= numeral.getValue();
  }
 return arabic;
}

现在我们可以将append()方法移动到RomanNumeral

public class RomanNumerals {

  public static String arabicToRoman(int arabic) {
    StringBuilder result = new StringBuilder();
    RomanNumeral numeral = new RomanNumeral("i", 1);
    arabic = numeral.append(arabic, result);
    return result.toString();
  }

}

public class RomanNumeral {

  private final String symbol;
  private final int value;

  public RomanNumeral(String symbol, int value) {
    this.symbol = symbol;
    this.value = value;
  }

  public int getValue() {
    return value;
  }

  public String getSymbol() {
    return symbol;
  }

  public int append(int arabic, StringBuilder builder) {
    while (arabic >= getValue()) {
      builder.append(getSymbol());
      arabic -= getValue();
    }
    return arabic;
  }

}

我们可以通过内联现在仅在RomanNumeral类中使用的getter来进一步清理:

public class RomanNumeral {

  private final String symbol;
  private final int value;

  public RomanNumeral(String symbol, int value) {
    this.symbol = symbol;
    this.value = value;
  }

  public int append(int arabic, StringBuilder builder) {
    while (arabic >= value) {
      builder.append(symbol);
      arabic -= value;
    }
    return arabic;
  }

}

此代码还有另一个问题:我们将arabicbuilder作为两个单独的参数传递,但它们不是独立的。 前者代表尚未处理的阿拉伯数字部分,而后者代表已处理的部分。 因此,我们应该引入另一个类来捕获共享概念:

public class RomanNumerals {

  public static String arabicToRoman(int arabic) {
    ArabicToRomanConversion conversion
        = new ArabicToRomanConversion(arabic);
    RomanNumeral numeral = new RomanNumeral("i", 1);
    numeral.append(conversion);
    return conversion.getResult();
  }

}

public class RomanNumeral {

  private final String symbol;
  private final int value;

  public RomanNumeral(String symbol, int value) {
    this.symbol = symbol;
    this.value = value;
  }

  public void append(ArabicToRomanConversion conversion) {
    while (conversion.getRemainder() >= value) {
      conversion.append(symbol, value);
    }
  }

}

public class ArabicToRomanConversion {

  private int remainder;
  private final StringBuilder result;

  public ArabicToRomanConversion(int arabic) {
    this.remainder = arabic;
    this.result = new StringBuilder();
  }

  public String getResult() {
    return result.toString();
  }

  public int getRemainder() {
    return remainder;
  }

  public void append(String symbol, int value) {
    result.append(symbol);
    remainder -= value;
  }

}

不幸的是,我们现在在RomanNumeral有一个RomanNumeral羡慕的案例功能。 我们两次使用conversion ,而我们自己的成员使用三次,所以这还不错,但是让我们考虑一下。

让罗马数字知道从阿拉伯到罗马的转换过程是否有意义? 我认为不是,所以让我们将代码移到正确的位置:

public class RomanNumerals {

  public static String arabicToRoman(int arabic) {
    ArabicToRomanConversion conversion
        = new ArabicToRomanConversion(arabic);
    RomanNumeral numeral = new RomanNumeral("i", 1);
    conversion.process(numeral);
    return conversion.getResult();
  }

}

public class RomanNumeral {

  private final String symbol;
  private final int value;

  public RomanNumeral(String symbol, int value) {
    this.symbol = symbol;
    this.value = value;
  }

  public String getSymbol() {
    return symbol;
  }

  public int getValue() {
    return value;
  }

}

public class ArabicToRomanConversion {

  private int remainder;
  private final StringBuilder result;

  public ArabicToRomanConversion(int arabic) {
    this.remainder = arabic;
    this.result = new StringBuilder();
  }

  public String getResult() {
    return result.toString();
  }

  public void process(RomanNumeral numeral) {
    while (remainder >= numeral.getValue()) {
      append(numeral.getSymbol(), numeral.getValue());
    }
  }

  private void append(String symbol, int value) {
    result.append(symbol);
    remainder -= value;
  }

}

我们必须为RomanNumeral的字段重新引入吸气剂,以便RomanNumeral进行编译。 我们可以通过首先引入ArabicToRomanConversion类来避免这种返工。 嗯, 也许重构也有一个固有的顺序

好,继续下一个测试:4.我们可以通过另一系列的转换使它通过。 首先, scalar->array

public static String arabicToRoman(int arabic) {
  ArabicToRomanConversion conversion
      = new ArabicToRomanConversion(arabic);
  RomanNumeral[] numerals = new RomanNumeral[] {
    new RomanNumeral("i", 1)
  };
  conversion.process(numerals[0]);
  return conversion.getResult();
}

接下来, constant->scalar

public static String arabicToRoman(int arabic) {
  ArabicToRomanConversion conversion
      = new ArabicToRomanConversion(arabic);
  RomanNumeral[] numerals = new RomanNumeral[] {
    new RomanNumeral("i", 1)
  };
  int index = 0;
  conversion.process(numerals[index]);
  return conversion.getResult();
}

现在我们需要一个if

public static String arabicToRoman(int arabic) {
  ArabicToRomanConversion conversion
      = new ArabicToRomanConversion(arabic);
  RomanNumeral[] numerals = new RomanNumeral[] {
    new RomanNumeral("i", 1)
  };
  int index = 0;
  if (index < 1) {
    conversion.process(numerals[index]);
  }
  return conversion.getResult();
}

还有另一个constant->scalar

public static String arabicToRoman(int arabic) {
  ArabicToRomanConversion conversion
      = new ArabicToRomanConversion(arabic);
  RomanNumeral[] numerals = new RomanNumeral[] {
    new RomanNumeral("i", 1)
  };
  int index = 0;
  if (index < numerals.length) {
    conversion.process(numerals[index]);
  }
  return conversion.getResult();
}

您可能会看到前进的方向。 接下来是statement->statements

public static String arabicToRoman(int arabic) {
  ArabicToRomanConversion conversion
      = new ArabicToRomanConversion(arabic);
  RomanNumeral[] numerals = new RomanNumeral[] {
    new RomanNumeral("i", 1)
  };
  int index = 0;
  if (index < numerals.length) {
    conversion.process(numerals[index]);
    index++;
  }
  return conversion.getResult();
}

然后if->while

public static String arabicToRoman(int arabic) {
  ArabicToRomanConversion conversion
      = new ArabicToRomanConversion(arabic);
  RomanNumeral[] numerals = new RomanNumeral[] {
    new RomanNumeral("i", 1)
  };
  for (RomanNumeral numeral : numerals) {
    conversion.process(numeral);
  }
  return conversion.getResult();
}

最后是constant->constant+

public static String arabicToRoman(int arabic) {
  ArabicToRomanConversion conversion
      = new ArabicToRomanConversion(arabic);
  RomanNumeral[] numerals = new RomanNumeral[] {
    new RomanNumeral("iv", 4),
    new RomanNumeral("i", 1)
  };
  for (RomanNumeral numeral : numerals) {
    conversion.process(numeral);
  }
  return conversion.getResult();
}

现在我们已经完成了算法,我们要做的就是将其添加到numerals数组中。 顺便说一句,这应该是一个常量:

public class RomanNumerals {

  private static final RomanNumeral[] ROMAN_NUMERALS 
      = new RomanNumeral[] {
    new RomanNumeral("iv", 4),
    new RomanNumeral("i", 1)
  };

  public static String arabicToRoman(int arabic) {
    ArabicToRomanConversion conversion
        = new ArabicToRomanConversion(arabic);
    for (RomanNumeral romanNumeral : ROMAN_NUMERALS) {
      conversion.process(romanNumeral);
    }
    return conversion.getResult();
  }

}

同样,这里似乎还有另一种令人羡慕的功能,我们可以通过以下方式解决:

public class RomanNumerals {

  public static String arabicToRoman(int arabic) {
    return new ArabicToRomanConversion(arabic).getResult();
  }

}

public class ArabicToRomanConversion {

  private static final RomanNumeral[] ROMAN_NUMERALS 
      = new RomanNumeral[] {
    new RomanNumeral("iv", 4),
    new RomanNumeral("i", 1)
  };

  private int remainder;
  private final StringBuilder result;

  public ArabicToRomanConversion(int arabic) {
    this.remainder = arabic;
    this.result = new StringBuilder();
  }

  public String getResult() {
    for (RomanNumeral romanNumeral : ROMAN_NUMERALS) {
      process(romanNumeral);
    }
    return result.toString();
  }

  private void process(RomanNumeral numeral) {
    while (remainder >= numeral.getValue()) {
      append(numeral.getSymbol(), numeral.getValue());
    }
  }

  private void append(String symbol, int value) {
    result.append(symbol);
    remainder -= value;
  }

}

回顾性

我注意到的第一件事是,遵循TPP之后,我发现该基本算法的速度比我在此kata上的一些较早尝试中要快得多。 下一个有趣的事情是,转换和重构之间似乎存在相互作用。 您既可以执行转换,然后进行重构清理,也可以仅使用不引入重复的转换来防止重构的需要。 后者的执行效率更高,而且似乎可以加快发现所需算法的速度。 当然值得深思。 似乎需要进行更多的实验。

参考: TDD和我们的JCG合作伙伴 Remon Sinnema 的“转换优先权前提”,来自“ 安全软件开发”博客。

翻译自: https://www.javacodegeeks.com/2013/01/tdd-and-the-transformation-priority-premise.html

atdd和tdd

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值