以前从来没有想到自己写出这么丑的代码还有脸拿出来分享

33 views
Skip to first unread message

武可

unread,
Dec 23, 2015, 10:01:33 PM12/23/15
to agiles...@googlegroups.com
可能现在脸皮变厚了吧。:)

最近做了一个算24的练习。

过程很纠结,观看过程中如有不适,我非常抱歉。
结果代码也仅仅是能工作而已,远远谈不上清晰优雅。
之所以拿出来分享,是想说说这次练习我的一些体会。

1. TDD和预先设计的关系。
基本上我的理解是TDD要避免过度设计,但是什么是“过度”却是非常难把握的。
以前我的尝试中,经常因为事先完全没有设计,在TDD过程中发觉走偏了。因为觉得似乎从头再写更简单一点,就中途放弃了。
在这次的过程中,至少有3次我都感到走入歧途了。不过这次硬着头皮写,竟然最终也写出了一个能工作的版本。深深感觉到今年以来做kata是对TDD技术的提高。
当然这样开发是否有效率是另外一个问题了。

2. 有时候要重构要先写烂代码。
以前我总是尽量深思熟虑后再写代码,力求一次写对。因为怕代码写乱以后,就很难再改回来了。虽然做练习的时候第一个Test可能会hard code一个结果来通过,但随着深入进行到代码比较复杂,一处修改牵涉多条逻辑的时候,就越来越不敢简单粗暴的通过测试了。这样往往因为想不清楚就卡在某处无法推进了,这也是前面说有时候觉得走入死胡同放弃的一个原因。
通过这段时间TDD和重构的练习。现在对烂代码有了一定的容忍度。可以捏着鼻子先写一句很傻,而且只在特定情况正确的代码。因为我已经有信心可以从Test中获得足够多的反馈。而且也相信只要写足够多的傻代码,我会在后续的重构中找到比较聪明的写法。
最重要的体会是,很傻、只是部分正确的代码,也比完全没代码要好一点。

3. TDD与所产生的代码设计的关系。
到这个练习告一段落的时候,我感觉明显代码距离理想的结果还很远,估计还需要相当多次的重构。
在这个点上我有种很熟悉的感觉,代码非常像实际工作中碰到的情况:有一定的逻辑,但是又不充分体现业务逻辑。代码中有明显的重复,却没有一眼看得到的方案去整合这些重复。
经过TDD给代码带来了相当数量的测试用例,但是这些测试还没有清晰的描绘出代码块直接的关系,以及代码执行的路径。另外因为测试用例没有清晰表达出所要的覆盖目的,导致代码中至少还埋着一个bug。
我的感觉是,TDD的过程是通过分解逐步深入理解问题的过程,如果最终对问题的理解还不清晰,代码并不会因为做了TDD而变得清晰。

不知道这个题目用 London School 方式来做会不会更容易些。准备下次尝试一下。

武可

unread,
Dec 30, 2015, 4:41:49 AM12/30/15
to agiles...@googlegroups.com
第一个版本经过重构后的最终结果。

重构过程比较纠结,感觉兜了一大圈终于修正了前期所走的岔路

重构的主要对象是resolver类,前后对比如下图
Inline image 1

Joseph Yao

unread,
Dec 30, 2015, 11:10:19 PM12/30/15
to agiles...@googlegroups.com
@武可,感谢你分享自己的 Kata 练习和对 TDD 的理解。赞一个

我记得几年前的一次代码道场里,有人问我如何 TDD 一个算24点的程序出来,我当时无法回答。没想到,当我看到你的这个邮件时,突然灵光一现有了思路。:) 我打算用 TDD 做一遍这个 Kata 并分享出来,应该会和你的做法不太一样吧(我看了你那个 Kata 的大约前 30 步)。

你下面分享的三点体会,我都蛮认同的,顺便附加一些我自己的体会在下面。我已经把你的 Kata 加到 Kata 接力里面了(第十八棒)。https://www.evernote.com/l/ALxVummdBfdLqpAyFpL_8oytwI31kOgXU_4

最后,祝大家元旦快乐,愿大家多多练习。:)

谢谢,
Joseph

On Dec 24, 2015, at 11:01 AM, 武可 <madc...@gmail.com> wrote:

可能现在脸皮变厚了吧。:)

最近做了一个算24的练习。

过程很纠结,观看过程中如有不适,我非常抱歉。
结果代码也仅仅是能工作而已,远远谈不上清晰优雅。
之所以拿出来分享,是想说说这次练习我的一些体会。

1. TDD和预先设计的关系。
基本上我的理解是TDD要避免过度设计,但是什么是“过度”却是非常难把握的。
以前我的尝试中,经常因为事先完全没有设计,在TDD过程中发觉走偏了。因为觉得似乎从头再写更简单一点,就中途放弃了。
在这次的过程中,至少有3次我都感到走入歧途了。不过这次硬着头皮写,竟然最终也写出了一个能工作的版本。深深感觉到今年以来做kata是对TDD技术的提高。
当然这样开发是否有效率是另外一个问题了。

首先,我觉得算 24 点属于 Complex(复杂)的问题。他需要考虑算法,而且通过测试来穷举所有的情况是不现实的。对于这类复杂问题,我觉得没有一个大致的设计和算法思路就开始 TDD 并不会有效,有时甚至无法进行下去。如果我来解这类问题,我会先想想大概的思路是什么再开始 TDD。到什么程度算是有了一个可以开始的大致思路呢?这个因人而异,我还没找到什么标准来衡量。之前在今年的成都敏捷之旅和GDCR组织者聚会时,我和马逸清做过一次基于一定算法的 TDD 尝试,实践下来我的感受是,并不需要对算法思路有 100 %的了解就可以开始 TDD 了。只要在 TDD 的过程中慢慢朝那个算法的方向演进就可以了,同时 UT 在这个过程中起了关键的保护作用。

以算 24 点为例,我想象中的大致算法是对某个输入(如1,2,3,4这个组合)穷举所有的可能性并计算结果,如果算出来等于 24 就是一个解了。一个可能的解会包含 3个计算符(加减乘除的任意组合,一共是 4 的 3 次方种可能性),以及4个数字(1 - 13,一共是 4*3*2*1 种可能性),两者相乘一共是 1536 种可能性。在 TDD 中,我会朝这个算法方向不断靠拢,比如一开始应该是只有加法和4个数字,之后不断增加操作符就可以了。另外,我觉得输出解的格式(如 6+6+6+6)是一个独立于算法之外的问题,因为我打算用前缀表达式来做计算,避免考虑括号和运算优先级。还有就是去掉那些重复的解(如 4*3*2*1 和 1*2*3*4)也是独立于算法之外的另一部分代码。


2. 有时候要重构要先写烂代码。
以前我总是尽量深思熟虑后再写代码,力求一次写对。因为怕代码写乱以后,就很难再改回来了。虽然做练习的时候第一个Test可能会hard code一个结果来通过,但随着深入进行到代码比较复杂,一处修改牵涉多条逻辑的时候,就越来越不敢简单粗暴的通过测试了。这样往往因为想不清楚就卡在某处无法推进了,这也是前面说有时候觉得走入死胡同放弃的一个原因。
通过这段时间TDD和重构的练习。现在对烂代码有了一定的容忍度。可以捏着鼻子先写一句很傻,而且只在特定情况正确的代码。因为我已经有信心可以从Test中获得足够多的反馈。而且也相信只要写足够多的傻代码,我会在后续的重构中找到比较聪明的写法。
最重要的体会是,很傻、只是部分正确的代码,也比完全没代码要好一点。

你说的这点我很认同。我觉得 TDD 对程序员能力提升的一个方面就是学会控制自己的过度设计,使我们不会在没有 Working code 之前就想着如何写出 Good code。每当我或者我的Pair说“如果将来需求变成。。。那么代码设计成这样比较好”时,我会说“将来的事情将来再说吧,还是过了眼前的失败测试先”。实际上,TDD 中只有通过测试(Green)这个阶段是不应该做设计的,设计都是在写失败测试(Red)和重构(Refactor)这两个阶段发生的。


3. TDD与所产生的代码设计的关系。
到这个练习告一段落的时候,我感觉明显代码距离理想的结果还很远,估计还需要相当多次的重构。
在这个点上我有种很熟悉的感觉,代码非常像实际工作中碰到的情况:有一定的逻辑,但是又不充分体现业务逻辑。代码中有明显的重复,却没有一眼看得到的方案去整合这些重复。
经过TDD给代码带来了相当数量的测试用例,但是这些测试还没有清晰的描绘出代码块直接的关系,以及代码执行的路径。另外因为测试用例没有清晰表达出所要的覆盖目的,导致代码中至少还埋着一个bug。
我的感觉是,TDD的过程是通过分解逐步深入理解问题的过程,如果最终对问题的理解还不清晰,代码并不会因为做了TDD而变得清晰。

不知道这个题目用 London School 方式来做会不会更容易些。准备下次尝试一下。

欢迎你用 GOOS 方法来试一下。:) 根据我上面提到的思路,我觉得 GOOS 方法在解决这个问题上面应该可以应用的。我觉得,不论是 GOOS(ATDD + TDD),TDD 还是 DDD(领域驱动开发),有一点是相同的。就是尽早把你的设计想法变成测试和代码,并通过不断的代码迭代(几分钟到几十分钟)来演进设计。在开发时,我通常的做法是把自己思考或者讨论设计(不写代码)的时间控制在5分钟之内,超过这个时间限制就必须开始写代码了。


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

czc1009

unread,
Dec 31, 2015, 11:11:33 AM12/31/15
to joseph.ya...@gmail.com, agiles...@googlegroups.com
祝大家元旦快乐!



发自我的小米手机
在 Joseph Yao <joseph.ya...@gmail.com>,2015年12月31日 下午12:20写道:

武可

unread,
Jan 27, 2016, 12:59:35 AM1/27/16
to agiles...@googlegroups.com
@Joseph,多谢点评。关于你说输出格式是一个独立的问题我深有体会。第一遍做的时候有一段一头扎进去重构输出的代码。终于完成的时候回顾了一下才发现算法方面的问题还停在原地。

第二次GOOS方式的时候,最初写使用表达式的代码时直接mock对象相等,前期实现表达式的时候用对象equals判断,最终只要有限的一组测试就可以完善字符串输出了。很明显划分了类的职责之后进行测试和演化都更加清晰容易了。

使用GOOS方法重做一遍。最大的感受是,比第一次从容了很多。由于是平时抽空做,经常写写停停,几乎没有感到过焦虑怕在某个点停止后下次会跟不上思路。而第一次做的时候,虽然TDD对代码进行了保护,也能说明当前代码的状态,然后在比较长的重构过程中往往有测试通过了但程序处于不一致的情况,还脑子里有若干步骤去消除坏味道。这是一种超出TDD循环的结构,在重构完成之前,无法明晰的在代码里表达出来。在中间停止的话,下次会需要相当一段时间重新进入上回的思路。

还有一点是大概进行到三分之二的时候,我已经感觉到这个问题已经被解决了。尽管实现代码根本还没写。这是Detroit方式下根本不可能发生的。

有些不确定的是两次练习的差别,究竟是TDD方法不同占的比重更大,还是第二次对解决方案有了思路占的比重更大。也许下次kata应该换个顺序练习试试。
另外不确定GOOS方法如果最初在类之间接口划分发生了重大偏差的话,后续纠正起来会是什么感觉。

以下是GOOS练习的记录
Reply all
Reply to author
Forward
0 new messages