2021年6月更新 不知不觉已经过去快一年了,我来更新下这篇的后续。
首先我想分享给大家的是:这本书的纸质版已经由APress出版社正式出版了!
从原来的博客发布至今的近一年中,这本书经历了很多次的编辑和重构。事实上,在这本书有了初稿之后,我就曾请求同事Martin Fowler帮我review并提供一些早期的反馈,不过他当时还在忙于《重构》第二版的审校工作,所以只是放入了他的backlog里,直到去年7月才review完成。Martin的批注非常仔细,从目录结构,到遣词造句,再到实例与主题的关联度,以及参考文献等等方面,差不多有近10页。我根据这些批注重新组织了书的结构,并删减了差不多1/4的内容,使得结构和内容更聚焦。此外,Hannah又在此基础上进行了英文的编辑和润色,以及对一些代码实例的建议。
去年圣诞前后,APress的编辑找到我,表示他们有讲此书出版为纸质书并在他们平台上发售的意向。在经历了数个月的编辑,审校,勘误之后,这本书终于出版为纸质版本。在上周末我收到了出版社寄来的几本样书。看到自己的名字印刷在封面上是一种奇妙的感觉,开心当然是有的,不过随之而来的又是一些隐约的担忧:担心书中技术终会过时,担心有不好的comments等等。不过该来的终觉会来,这大约也是出版的一部分吧。
另外,感谢那些鼓励我的、默默提供勘误的、报告bug的人们,没有你们的帮助,这本书不会最终成为可能。
我的第一本英文技术书 太长不读 我写了一本关于React+TDD的英文书,你可以从这里免费获得(更新:和APress出版社的合同中明确要求需要从其他渠道将其下线)。在我写这篇文章的时候,读者大约是1000+。虽然读者不算多,但是通过一些渠道收集到的反馈还算不错,已经超出我自己预期很多了。如果你读完觉得还不错,而且愿意帮我宣传一下我会非常感激,如果你想要做一些修订甚至翻译的话那就更棒了(有一个俄罗斯小伙在帮我翻译俄文版,不过他貌似拖稿十分严重)。
缘起 实话实说,写这本书纯属意外之举,最开始的时候对于内容和形式是完全没有计划的,更不用说用另外一门语言作为载体了。应该是在2017年年末的时候,我在华为的无线部门做一个网管产品的Web化,人员能力提升显然是Web化的核心了 – 毕竟功能需要开发人员一行行去实现。当时该部门中的大部分开发同事的Web技能都比较薄弱,一些有经验的同时则每日被业务需求缠身无法顾及能力构建,因此引入外部的培训来填补这个空缺。
这个看起来是Web开发101的培训还有一些其他需求,比如用户故事拆分、自动化测试和TDD等等敏捷开发的基本内容。我为团队设计了一些循序渐进的例子作为培训的素材,并在后来的几轮迭代中进行了内容的删减和补充。培训的结果还是不错,有一半以上的同事对Web开发产生了兴趣,有人则开始意识到自动化测试(以及TDD)可以减少回归测试的工作量。
正如你所料,这个培训的一个副产品就是这本书的原型了。在培训结束之后我正好有了几周的beach时间,我用这些时间将内容变成一个教程:从一个简单的例子,逐步完善成一个前端应用。其中涉及了诸如Tasking,ATDD,用户故事等。
过程 初稿 原型是很容易的,素材是现成的,只需要将其以符合逻辑的方式连接起来即可。比如我想要描述通过TDD的方式开发一个应用的全过程,那么首先我需要讲清楚我们要做一个什么应用,此外我需要说清楚如何用TDD来完成这个应用。根据经验,人们喜欢循序渐进的方式来阅读,先做一个简单需求,然后逐步增强,并在过程中将学到的内容应用,然后推广到后续需求的开发中,并通过实现更加复杂需求习得我想要传递的知识/技巧。
TDD的101(通过例子来说明Tasking和测试驱动的过程) 介绍demo应用,搭建应用所需的环境 设计并实现一个简单需求 实现一个更复杂的需求 循序渐进…… 编辑初稿的过程中,正好有个掘金小册的编辑联系我有没有意向写个小册子。虽然最后由于篇幅和主题的选择方向没有合作,但是和编辑沟通的过程帮助我很好的梳理了草稿的结构。
英文版 在草稿写完之后,我找到几个同事做了快速的review并做了一些修改。到了2018年3月,我开始准备来澳洲的LTA,既然英语会是我接下来几年的主要语言,那么提升英文能力就成了优先级很高的事情。而学习语言的窍门就是:没有什么比实际使用一门语言更能提升语言能力的了。
于是我开始尝试把草稿翻译成英文版。开始的时候我还会保持双语版本的同步,到后来带宽不足的时候就只能把精力放到英文版上了。第一个完整的英文版发布于2018年5月,后面虽然陆续有些小的修改,主体部分没有太大的变化。
到了澳洲之后,读了很多英文原版的书籍/文章之后,发现了书中很多的语言错误,又在一个假期里集中修改过一轮,可读性有了一些提高。
重构 到了2019年的9月,经过了近1年2个纯React的项目后,我对与React及其生态的理解,以及在应用TDD(特别是和传统上认为的TDD很难在复杂/时间不允许的项目上实施)的认识上都有了新的发展。于是又将其中很多章节重写了,比如丢弃了pupeeteer改成cypress,采用react-testing-library而不是enzyme等等。另外,结构上也做了重新整理。
由于这些颠覆性的修改,我将其重新命名为Mastering Test Driven Development with React,并声明其为前一个版本的第二版。
到了2020年4月,由于covid-19在澳洲的肆虐,我们开始全面WFH。一方面突然有了很多时间,另一方面我意识到线上沟通时英文在有些时候还是会变成blocker,于是我又花费了一些时间来提升英文。于是又开始了一轮的re-wording。
在你今天阅读这本书的时候,你大约会惊叹于英文表达之地道,语法之准确以及用词之精准。这些都和我的英文水平没有太大关系。在5月的一天,一位澳洲同事Hannah Bourke写邮件给我,表示她通读了这本书,非常喜欢其中的讲述方式和实例以及节奏,她表示愿意帮我近一步润色。她通过PR的方式重新整理了本书的语言(目前已经完成了70%),由于Hannah本身就是前端Dev,所以很多表达方式也被重写为更容易被读者理解的方式。
一些收获 关于英文写作 语言的学习是个漫长而痛苦的过程,同样的内容,要从一门语言中的表述要翻译成另一门语言需要的更多的是重写而不是literal translation。从LTA开始我就开始刻意的用英文写作,还尝试把一些文章翻译并投稿给英文版洞见。
通过这些练习,我觉得我至少不再惧怕这件事情本身。英文表达当然有不地道的地方,但是读者也不是语言学家,大多数时候他们都可以准确无误的get到你的意思。即使有些复杂概念无法一次理解,通过评注或者提问等等,总是会搞清楚的。
关于自信构建 在这本书的写作过程中,我个人最大的收获应该是:当你制定了一个目标,不论这个目标开始开起来有多么的不切实际,一旦你开始细化这个目标并逐步实施,你就已经离这个目标不远了。当然,和每个任务一样,事情走到最后可能会和最开始的目标并不完全契合,但这大约是我们无法掌控的那部分了,就随他去吧。
心流 你可能有过这样的体验:在玩一个很有趣的游戏时,时间会飞快的流逝,等你终于通关之后才发现已经是凌晨,而你的午饭和晚饭还都没吃。虽然可能饿着肚子,但是你内心却有一种很兴奋,很神清气爽的感觉。而当你在做一些不得不完成的工作/作业时(比如写年终总结报告),时间又会过得很慢,你的心情也常常变得焦虑或者暴躁。
通常来说,人的心情总是会在各种情绪中起伏不定,不过毋庸置疑,我们都希望永远或者至少是尽可能多的保持第一个场景中的状态。
这种精神高度集中,通过自己的努力不断完成挑战,并常常会有忘记时间流逝,甚至忘记自身存在,只剩下“做事情”本身的状态,在心理学上被称之为心流(Flow)。人们虽然很早就发现了这种现象,但是直到1975年,心理学家米哈里·齐克森米哈里(Mihaly Csikszentmihalyi)才将其概念化并通过科学的方式来研究。
心流(英语:Flow),也有别名以化境(Zone)表示,亦有人翻译为神驰状态,定义是一种将个人精神力完全投注在某种活动上的感觉;心流产生时同时会有高度的兴奋及充实感。
进入心流之后会有很多特征:
愉悦 全身心投入 忘我,与做的事情融为一体 主观的时间感改变 心流被普遍认为是一种绝佳的精神体验。根据齐克森米哈里的理论,与心流对应的,还有一些其他的心理状态:
当自身能力很高,但是做的事情很简单的话,你做起来会比较无聊;而当能力不足,要做的事情很困难的话,你又会陷入焦虑。有意思的是,如果你技能不足,而做的事情又比较简单的话,并不会产生“心流”体验。恰恰相反,这种状态(apathy)是很消极的,做事情的过程中,你既没有运用任何技能,也并没有接受到任何的挑战。
如何进入心流状态 齐克森米哈里要进入心流状态,需要满足至少三点:
有清晰的目标 有明确且事实的反馈 能力和挑战的平衡(都处于比较高的状态) 比如,玩游戏的时候,目标是明确的,不论是简单的通过策略消灭对方,还是将三个同一颜色的宝石移动到同一行);反馈也是实时的,同色宝石连在一起是发出的声音以及屏幕上闪过的炫目的光芒,敌人在被你手中武器杀死时的惨叫,你自己的血槽等等;最后,游戏不能过于简单,如果太简单,你很快会觉得无聊,又不能太难,这样你会觉得挑战太大。
不过要在工作上进入心流状态,远远比玩游戏要复杂。比如不明确的目标,冗长的反馈周期,能力与挑战的不均衡等等。
基于反馈的开发 2014年底,我在ThoughtWorks组织3周3页面工作坊的时候,发现了一个很有意思的现象:通常公司内部的培训/工作坊都会出现这种现象:报名的人很多,前几次课会来很多人,慢慢的人数会减少,能坚持到最后的人很少,能完成作业的就更少了。而在3周3页面中,参加的人数越来越多,而且作业的完成率非常高(接近100%)。
回想起来,我在培训的最开始就设置了一些机制,保证学员可以有一个非常容易沉浸其中的环境:
通过watch、livereload等机制,保证每次修改的CSS/HTML都可以在1秒钟内在浏览器上自动刷新 通过对比mockup和自己实现的样式的差异,来调整自己的目标 将这些工具做成开箱即用的,这样经验不足者不至于被技术细节阻塞 做完之后,学员们的作品直接发布到github的pages上 事实上,这些实践恰好满足了上述的几个条件:
目标明确 快速且准确的反馈 技能与挑战的平衡 由于工作坊是在周内下班后(8点结束),我见到很多学员在课后(很晚的时候)还在写代码、调样式,完全沉浸其中,忘记时间。到最后,参加培训的学员们被要求通过设计原则自己实际一个Web Site,很多没有前段开发背景的同事也做出了非常有“设计感”的作品。
编程语言的壁垒 使用JavaScript或者Ruby这种解释型语言的开发人员,在第一次接触之后就会深深的爱上它,并再也无法回到编译型语言中去了。想一想要用Java打印一行Hello World你得费多大劲?
解释型语言中,你很容易可以采用REPL环境,写代码会变成一个做实验的过程:函数名写错了,参数传错了,某个函数没有定义等等错误/手误,都可以在1秒钟内得到反馈,然后你再根据反馈来修正方向。
举个小例子,写一个字符串处理函数:将字符串”qiu,juntao”转换成“Qiu Juntao”,你会怎么做?你大概知道有这样一些原生的API:
String.indexOf String.replace 大小写转换 正则表达式(可选) 如果用JavaScript来实现的话,你可以在Chrome的DevTools中完成大部分的工作:
注意这里的每次操作的反馈(特别是出错的情况),你可以在1秒钟内就知道明确的反馈,而不是等等待漫长的编译过程。DevTools里的console提供的REPL(read-eval-print-loop)可以帮助你建立流畅的编码体验。
如果用Java来做,你需要一大堆的准备工作,抛开JDK的安装,JAVA_HOME的设置等,你还需要编译代码,需要定义一个类,然后在类中定义main方法,然后可能要查一些API来完成函数的编写。而每一个额外的步骤都会延长反馈的时间。
测试驱动开发 那么在编译型语言中如何获得这种体验呢?又如何缩短反馈周期呢?答案是使用测试驱动开发(Test Driven Development)!
通常TDD会包含这样几个步骤:
根据用户故事做任务分解 根据分解后的Task设计测试 编写测试 编写可以通过测试的实现 重构 步骤3-5可能会不断重复,直到所有的Task都完成,用户故事也就完成了。如果仔细分析,这些步骤也恰好符合产生心流的条件:...
示例的需求描述 今天我们需要完成的需求是这样的:
对于一个给定的字符串,如果其中元音字母数目在整个字符串中的比例超过了30%,则将该元音字母替换成字符串mommy,额外的,在替换时,如果有连续的元音出现,则仅替换一次。
如果用实例化需求(Specification by Example)的方式来描述的话,需求可以转换成这样几条实例:
hmm经过处理之后,应该保持原状 she经过处理之后,应该被替换为shmommy hear经过处理之后,应该被替换为hmommyr 当然,也可以加入一些边界值的检测,比如包含数字,大小写混杂的场景来验证,不过我们暂时可以将这些场景抛开,而仅仅关注与TDD本身。
为什么选择这个奇怪的例子 我记得在学校的时候,最害怕看到的就是书上举的各种离生活很远的例子,比如国外的书籍经常举汽车的例子,有引擎,有面板,但是作为一个只是能看到街上跑的车的穷学生,实际无法理解其中的关联关系。
其实,另外一种令人不那么舒服的例子是那种纯粹为了示例而编写的例子,现实世界中可能永远都不可能见到这样的代码,比如我们今天用到的例子。
当然,这种纯粹的例子也有其存在的价值:在脱离开复杂的细节之后,尽量的让读者专注于某个方面,从而达到对某方面练习的目的。因为跟现实完全相关的例子往往会变得复杂,很容易让读者转而去考虑复杂性本身,而忽略了对实践/练习的思考。
TDD步骤 通常的描述中,TDD有三个步骤:
先编写一个测试,由于此时没有任何实现,因此测试会失败 编写实现,以最快,最简单的方式,此时测试会通过 查看实现/测试,有没有改进的余地,如果有的话就用重构的方式来优化,并在重构之后保证测试通过 它的好处显而易见:
时时关注于实现功能,这样不会跑偏 每个功能都有测试覆盖,一旦改错,就会有测试失败 重构时更有信心,不用怕破坏掉已有的功能 测试即文档,而且是不会过期的文档,因为一旦实现变化,相关测试就会失败 使用TDD,一个重要的实践是测试先行。其实在编写任何测试之前,更重要的一个步骤是任务分解(Tasking)。只有当任务分解到恰当的粒度,整个过程才可能变得比较顺畅。
回到我们的例子,我们在知道整个需求的前提下,如何进行任务分解呢?作为实现优先的程序员,很可能会考虑诸如空字符串,元音比例是否到达30%等功能。这当然没有孰是孰非的问题,不过当需求本身就很复杂的情况下,这种直接面向实现的方式可能会导致越走越偏,考虑的越来越复杂,而耗费了几个小时的设计之后发现没有任何的实际进度。
如果是采用TDD的方式,下面的方式是一种可能的任务分解:
输入一个非元音字符,并预期返回字符本身 输入一个元音,并预期返回mommy 输入一个元音超过30%的字符串,并预期元音被替换 输入一个元音超过30%,并且存在连续元音的字符串,并预期只被替换一次 当然,这个任务分解可能并不是最好的,但是是一个比较清晰的分解。
实践 第一个任务 在本文中,我们将使用JavaScript来完成该功能的编写,测试框架采用了Jasmine,这里有一个模板项目,使用它你可以快速的启动,并跟着本教程一起实践。
根据任务分解,我们编写的第一个测试是:
describe("mommify", function() { it("should return h when given h", function() { var expected = "h"; var result = mommify("h"); expect(result).toEqual(expected); }); }); 这个测试中有三行代码,这也是一般测试的标准写法,简称3A:
组织数据(Arrange) 执行需要被测的函数(Action) 验证结果(Assertion) 运行这个测试,此时由于还没有实现代码,因此Jasmine会报告失败。接下来我们用最快速的方法来编写实现,就目前来看,最简单的方式就是:...
In my last post, I mentioned to how to setup a intellij project by using gradle. and today we are going to talk about how to using intellij to write a demo java project from scratch(right after the gradle idea command).
To begin with TDD in java, we need some packages for that:
junit hamcrest Let’s all of those dependencies in build.gradle:
repositories { mavenCentral() } dependencies { testCompile 'junit:junit:4....