申导发个kata接力,Tennis

25 views
Skip to first unread message

Yuan Mai

unread,
Nov 13, 2014, 7:10:56 PM11/13/14
to agiles...@googlegroups.com
申导录了个网球计分的视频,尝试把kata接力继续下去,大家一起来切磋,欢迎指正。

http://www.tudou.com/programs/view/e24zKWezmSQ/

麦宇安

申健Jacky

unread,
Nov 13, 2014, 7:17:24 PM11/13/14
to agiles...@googlegroups.com
献丑了,欢迎指正。

Br,
申健Jacky Shen
个人网站:www.JackyShen.com         新浪微博:@申导
| 敏捷教练 | Agile Coach | Scrum培训 | CSP | ATDD | XP | 管理3.0 | Facilitation | Kanban | 软件匠艺 |
============================================================================

--
您收到此邮件是因为您订阅了Google网上论坛中的“agileshanghai”论坛。
要退订此论坛并停止接收此论坛的电子邮件,请发送电子邮件到agileshangha...@googlegroups.com
要查看更多选项,请访问https://groups.google.com/d/optout

武可

unread,
Nov 17, 2014, 10:31:40 PM11/17/14
to agiles...@googlegroups.com
Tennis.score(3,2);
Tennis.score(4,1);

Joseph Yao

unread,
Nov 20, 2014, 12:13:55 PM11/20/14
to agiles...@googlegroups.com
我的反馈如下: 

编码操作相关:
+ 用键盘和快捷键来编程不错
- 可以考虑用vi来编程
- 可以考虑设置快捷键来切换测试和代码

TDD相关:
+ 第一个TDD cycle (love all) 做的不错
+ 第二个TDD cycle (fifteen all) 做的不错
+ 第三个TDD cycle (thirty all) 中应该要做重构了, 因为love, fifteen, thirty 与 0, 1, 2的重复还是蛮明显的
+ 第四个TDD cycle (forty all) 整体不错, 重构做的很清晰. 虽然可以考虑步子再小一点(比如数组先只有love, 而不是都加上), 但是我想那时你应该已经把重复代码看得很清楚了
  • - translation数组可以考虑是 static final, 并且初始化时可以不写 new String[], 而只写 {“love” …}
  • - translation这个变量名可以考虑改成 scoreText 之类的
  • - 需求理解错了吧, 3比3时应该是 “deuce” 而不是 “forty all”
- 第五个TDD cycle (fifteen love) 中其他不错, 不过应该要重构代码. “fifteen”和”love”明显和数组中的元素重复, 应该被替换成 translation[1] 和 translation[0]
+ 第六个TDD cycle (thirty love) 做的不错
  • - 不过, 我觉得给if加个 {} 没有什么意思, {} 在这里属于多余的代码
+ 第七个TDD cycle (player one win) 做的不错
- 第八个TDD cycle (player one advantage) 让测试通过做的有点快了, 我可能会选择另加一个新的 if (playerOne == 4 && playerTwo == 3) 来实现. 把advantage的逻辑 playerOne - playerTwo == 1在这里就总结出来感觉有点早
- 第九个TDD cycle (deuce) 和第八个有类似的问题
  • - 我感觉第九个TDD cycle 应该再写一个 advantage 的测试, 比如 5比4. 一方面, advantage这个问题并没有解决完. 另一方面, 从 Transformation Priority Premise (TPP) 的角度来说, deuce 对应的 transformation 是 unconditional -> if, 而再写一个 advantage 的测试对应的 transformation 应该是 statement -> statements (playerOne == 4 -> playerOne == 4 || playerOne == 5). 后者的优先级更高一些
  • - 前面提到了, deuce 不应该是 4比4, 而是 3比3
+ 第十个TDD cycle (player two win) 中最后重构好的代码还是很精彩的, 去掉了那些 if的重复代码, 很简洁.
  • - 最后的代码中我还建议做一个重构, 就是把 playerTwo 领先和获胜的代码变成和 playerOne 领先和获胜的代码并列, 而不是嵌套. 修改后的代码大致如下:

if (playerOne >= 4 || playerTwo >= 4)
     if (playerOne >= playerTwo)
          return winnable(playerOne, playerTwo, “playerOne”);
     else
          return winnable(playerTwo, playerOne, “playerTwo”);

private static String winnable(…) {
     return String.format(...)
}

原先的代码其实只会递归一次, 这种情况使用递归会比较奇怪, 因为一般的递归不会只执行一次, 容易让人产生误解. 另外 winnable 这个函数名修改成 scoreForDueceAndBeyond 之类的名字会好一些

和武可一样, 除了duece, 我也发现了那两个Bug
  • 代码没有处理类似 2比1 的情况, 也就说如果 playerTwo 的得分不为0, 且不是 duece, advantage 和 win的情况, 都没有处理
  • win的情况并没有都处理, 比如 4比1的测试会让代码失败

最后, 建议把最终代码也分享出来, 方便其他人在其基础上修改. :)

谢谢,
Joseph

申健Jacky

unread,
Nov 20, 2014, 10:47:00 PM11/20/14
to agiles...@googlegroups.com
感谢joseph详细的评论。

先说bug, 4-1/3-2就是没完全列出的需求,故意留下一个不完整:)最终代码第25行已经明显不能满足4-1这种情况了。
而4-4的确应该是deuce,这个是我理解错了。

我编写的test case顺序就是要展示给观众考虑到先从一个维度做,再做另一个维度的问题。总而言之,TDD之前的TODO list/测试用例还是应该详细规划一下的。

关于第八个cycle,playerOne - playerTwo == 1,我觉得这个逻辑不完全是推出来的,而是需求规定好的,所以可以直接写出来。如此第九个cycle也就不必写两个advantage的case了。如果必须要写第二个测试,的确是遵从TPP为妙。

关于第十个,采用递归是为了进一步去除重复,推向(可选的)极致。这里受了麦大仙的影响:P


本次的代码我已经删了,也必须删:)。这样才可以找个时间再录一遍,同时把代码放出来。我也再考虑如何能让观众把视频每一步看得更清楚些,或许需要一些后期编辑。

欢迎继续交流。

Br,
申健Jacky Shen
个人网站:www.JackyShen.com         新浪微博:@申导
| 敏捷教练 | Agile Coach | Scrum培训 | CSP | ATDD | XP | 管理3.0 | Facilitation | Kanban | 软件匠艺 |
============================================================================

Joseph Yao

unread,
Nov 21, 2014, 1:08:29 AM11/21/14
to agiles...@googlegroups.com, Ronald Mai
感谢你的回复, Jacky.

你说测试的演进是按照”维度”来的, 这个我很认同 (我也喜欢用”维度”这个词). 我觉得在TDD之前列出一些想到的情况是好的 (仅限于写在纸上, 不写任何代码), 不过我不会有什么”详细的规划”再去TDD. 我的做法是, 从最简单的测试开始, 逐步演进, 不断归纳现有测试已解决的问题 (维度), 根据现有代码和测试找出下一个失败的测试.

关于那个递归, 我觉得我们两个的解决方案中的代码量是一样的, 如下所示. 用递归谈不上去除重复. 既然你说受到了宇安的影响, 不知道他怎么想? :)

if (playerOne >= 4 || playerTwo >= 4)
     return winnable(playerOne, playerTwo, “playerOne”);

private static String winnable(…) {
    if (playerOne < playerTwo)
return winnable(playerTwo, playerOne, “playerTwo”);

     return String.format(...)
}

———————————————— 

if (playerOne >= 4 || playerTwo >= 4)
     if (playerOne >= playerTwo)
          return winnable(playerOne, playerTwo, “playerOne”);
     else
          return winnable(playerTwo, playerOne, “playerTwo”);

private static String winnable(…) {
     return String.format(...)
}

武可

unread,
Nov 21, 2014, 6:24:42 AM11/21/14
to agiles...@googlegroups.com
学习Scala中
录屏还没尝试过,而且写的过程比较慢,就不献丑了
发个最终结果截图 ==> code

Joseph Yao

unread,
Nov 27, 2014, 9:31:14 AM11/27/14
to agiles...@googlegroups.com
我把代码贴出来了, 再加上了一些反馈. 因为只是看到了最终代码, 没有看到过程, 有些反馈可能并不合理了. :)

测试:
  • 每个测试组的名字归纳的不错
  • 如果是TDD做的这个Kata, 并且测试代码最后的顺序就是TDD中添加测试的顺序的话, 我很奇怪为什么在 “give score names by score point” 之后选择 “give winner’s name” 而不是 “be score all if the before Forty”. 感觉后者的步子比较小一点

代码:
  • Scala match case 结构在这里写的不错, 感觉很合适
  • 我觉得 advantageName 这个 val 有点多余, 为什么不在下面的 case 中直接使用 playerName这个方法呢? 出于什么考虑呢? 我会这样重构playerName函数
def playerName(p1: Int, p2: Int) = (p1 - p2 > 0) ? name1 : name2

  • scoreName 这个函数有点多余了, 不如直接用 names 数组了. 当然可以考虑把数组名字改成 scoreNames 之类的

武可

unread,
Dec 3, 2014, 12:00:21 PM12/3/14
to agiles...@googlegroups.com
多谢你的点评。
test case代码的顺序大致上就是添加测试的顺序。除了 Winner和Deuce case里的最后一行是在做完Advantage之后增加的。现在想来还是应该新写一个case比较好。
之所以先写winner后写score all,是因为我感觉score all是个nice to have的特性。显示成Thirty,Thirty虽然与需求不同,但是逻辑上也不算是错。反正kata没有PO,我就自己把它排在后面了。后来我还尝试了几次,都是最后才加入这个case的。

关于scoreName你说的很对。应该是我重构时候漏掉这个点了。

advantageName提取出来主要是给下面拼字符串用。因为使用的是Scala的String Interpolation特性。可以看到
    s"$player wins" 
要远比 player + " wins" 
或者 String.format("%s wins", player)
清晰简洁。
由于语法关系,变量和语句稍有不同
    变量:   s"$advantageName wins"
    语句:   s"${playerName(p1,p2)} wins"
我比较偏爱没有大括号的样子,所以就多抽了一个val。

Scala没三目运算符,所以这种if赋值的代码是少见的写起来比java罗嗦一点的情况 :)。
能稍作改善的地方可能就是写在一行吧。
    def playName(p1:Int,p2:Int) = if (p1 - p2 > 0) then name1 else name2
Reply all
Reply to author
Forward
0 new messages