7个你需要知道的结对礼仪 结对编程远远不是两个程序员坐在一起写代码那么简单。 — 鲁迅(没有说过)
结对编程 结对编程可能算是比测试驱动开发更具有争议的敏捷实践了,事实上,仅有很少的团队可以很好的实施它并从中受益,对于更多的团队来说,即使在践行敏捷的团队中,也常常会分为旗帜鲜明的两个阵营。提倡者往往会强调结对编程在“传递领域知识”,“减少潜在缺陷”,“降低信息孤岛的形成”等方面的作用;而反对者则认为结对编程在很多时候都是在浪费时间:开发者在实践中很多时候都难以聚焦,容易产生分歧,另外个人的产出往往也难以度量。
这篇文章不打算讨论结对编程对效率的影响,也不讨论要不要进行结对编程,当然也不会涉及以何种力度来执行结对。这里的假设是团队决定采用结对编程,但是对于如何实施存在一些疑虑;或者已经在采用结对编程的团队里,发现很多时候结对编程并没有很好的发挥作用,需要一些更有实践意义的指导。
结对编程远远不是两个开发者坐在一起写代码那么简单。作为一种科学且充满趣味性(才不是)的工程实践,事实上它是有一些基本原则的,团队里的开发者需要正确的实施这些原则,结对编程才有可能为团队带来实际的受益。
假设你找到了另一个程序员,并且已经准备好一起来编码实现一个具体的业务需求。在开始着手开始结对之前,你首先需要setup环境。
硬件设置 工欲善其事,必先利其器。首先你和你的peer需要一个大的外接显示器和有一个可以调节高度的桌子(当然也可以用纸箱子DIY一个低配版)。这一点往往被初学者忽视,而事实上它可以直接决定结对能不能作为一个可持续的团队工程实践。如果你的颈椎长时间处于不舒服的状态,你的注意力很快会退散,精神会变得难以集中,而一个处于病态的身体是无法支撑长时间的编码工作的。
在开始之前,请将屏幕调整到舒适的高度,以不仰头不低头为度(身高不一样的两个人可以通过调整椅子的高度达到基本一致)。此外还要注意看屏幕的角度,如果你需要抻着脖子才能看清楚屏幕,那么当时间稍长一点时,你的颈椎也会非常吃力。因此,如果条件允许的话,你和你的peer可以使用双屏来进行结对。
当然,还有一些常见的其他关于硬件准备的细节,比如:
VGA/HDMI转接头 充足的电源 键盘/鼠标转接头 便签和笔 纸和笔永远是你(们)的好朋友,在实际动手写代码之前,请拿出纸笔来将要做的事情划分成更细粒度,可以验证的任务列表,贴在显示器的下边缘。最后,另外记得将手机调成震动模式,利人利己。
软件设置 硬件准备好之后就可以进行软件的配置了。软件问题远比硬件复杂,因为大部分开发者都有自己钟爱的工具集,小到curl/wget,大到Vim/Emacs,不要期望在这个问题上和peer达成一致,这是可遇而不可求,可望而不可即的。
根据我自己的经验而言,高级一些的IDE比如JetBrains出品的Intellij,WebStorm等都可以随意切换快捷键集合(keymap)。如果你和peer就快捷键的使用上无法达成一致,在轮到你输入代码的时候,你可以切换到自己熟悉的Keymap,反之亦然。
在Intellij/WebStorm里,你可以通过 ctrl+`` 来切换各种设置,比如选择3`就可以切换不同的Keymap,然后在下一步的窗口中按照自己的喜好进行切换预设的Keymap。某项目的王晓峰(Vim党)和张哲(Emacs党)尤其擅长此技,他们两人结对时,可以做到在抢键盘的瞬间将keymap切换到自己的挚爱而对方无察觉的地步。
这种技法事实上仅仅对于结对双方都有很高超的键盘操作能力的前提下,也唯有这种场景下,双发可以互不妥协。对于另一种场景(可能在实际中更为常见),比如结对一方键盘技巧和操作效率低下,影响结对流畅程度的场景,则需要效率低下的一方自己摒弃陋习(使用鼠标而不是键盘),快速赶上。
和别人结对之前,你需要至少熟悉一个IDE/编辑器,比如通过纯键盘的操作完成
按照名称查文件 按照内容搜索 定位到指定文件的指定行/指定函数 选中变量,表达式,语句等 可以快速执行测试(可以在命令行,也可以在IDE中) 熟常基本Shell技能和常用命令行工具的使用,可以完成诸如
文件搜索 网络访问 正则表达式的应用 查找替换文件中的内容 等操作,这样在结对时可以大大提高效率。这些都是稍加练习就可以掌握的技能,并没有多少技术含量在内,而且学会了可以收益很久。
当知识不对等时 好了,铺垫了这么多,终于到了正题部分。最理想的结对状态是,双方的技能水平相当,知识储备基本类似,可以非常流畅的进行交流,在结对过程中可以完全专注在需要解决的问题本身上,讨论时思想激烈碰撞,编码时键动如飞,不知日之将夕。这种场景下完全不需要任何技巧,随心所欲,自由发挥即可。与此对应的另一种场景是双方都没有任何储备,技能也无法胜任,这种情况我们需要在项目上完全避免。
这两种极端的情况之外,就是不对等的场景了,这也是现实中最为常见的case:很多时候,结对双方会有一个人比较有经验,而另一个人则在某方面需要catchup。比如一个老手带一个新手,或者一个擅长业务的开发和另一个该领域的新人结对等。
一般而言,需要双方有一个人来做主导,另一个人来观察,并在过程中交互,答疑解惑,共同完成任务。与传统的教与学不同的是:结对需要的是两个智慧头脑的碰撞,而不是单方面的灌输。因此观察者不是单方向的被动接受,主导者也并非完全讲述。事实上结对是一个会有激烈交互的过程。
主导者 对于主导者来说,千万不要太投入,而无视peer的感受。这种场景非常常见,我自己有时候也会不自觉的忽略掉peer,自顾自的写代码,很多时候把peer当成了小黄鸭。这时候你的peer会有强烈的挫败感,也很难跟上你的节奏,从而影响结对的效果。作为主导者,需要更耐心一些,不断的和自己的peer交互。
另一个极端是,主导者太热心的coach,而忽视了给新人实际锻炼的机会。这时候需要主导者给peer更多的实践机会:比如在带着新人编写了一个小的TDD循环(红绿重构)之后,可以抑制住自己接着写的冲动(我知道这个非常困难),然后将键盘交给你的peer,让他模仿你刚才的做法来完成下一个。
有时候,当你看到peer正在用一个不好的做法来完成任务时,你可以即使让他停下来,并通过问问题的方式来启发他:
还有更好的做法吗? 如果peer仍然在迟疑的话,你可以进一步提示:
你觉得XXX会不会更好? 一个实际的例子是,你们在写一段JS代码来迭代一个列表,你的peer正在用for循环来操作一个数组,而你可以提议使用Array.map。有些时候,你的peer会给你一些惊喜的回答!他的回答甚至比你预想中的更加出色,你也可以通过这种方式来向他学习。
观察者 另一方面,作为观察者而言,结对毋庸置疑是一种特别好的学习机会,你应该抓住一切可能的机会来向你的peer学习。包括快捷键的使用,命令行工具参数的应用,良好的编程习惯等等。保持你的专注力和好奇心,比如你看到peer神器的通过快捷键删除了花括号(block)中的所有代码,或者将curl的返回值以prettify过的样式打印到控制台,或者通过命令行merge了一个PR等等。
在实践的时候,可以采取Ping-Pang的方式来互换主导者和观察者的角色。比如,A写一个测试,B来写实现,A来重构,然后换B来写测试,A来实现,B来做重构等等。开始时,可能会由一个人来主导,随着合作越来越顺畅,你们可以提高交换的频次。
保持专注 在选定了要两人一起解决的问题之后,你们需要一起完成任务划分。这样可以确保你们可以永远关注在单一任务上,避免任务切换带来的损耗。
在做完一项任务后,用mark笔轻轻将其从纸上划掉(或者打钩)。千万不要小看这个小动作的威力,它既可以将你们的工作进度很好的表述出来,也可以在任何时候帮助你们回到正在做的事情上(特别是在吃完午饭之后),另外这个微小的具有仪式感的动作是对大脑的一个正向反馈,促进多巴胺的分泌(代码写的这么开心,还要什么女朋友?)。
很多时候,我们需要暂时搁置争议,保持前行。
无法统一的意见 如果你遇到了一个固执己见的同事,而不凑巧的是你也是一个难以被说服的人,那么如何处理那些无法避免的争论呢?特别是那些没有对错之分的技术问题。比如那种编程语言更适合Web开发,比如如何践行TDD(比如自顶向下的TDD和自底向上的TDD)等等。...
QA的职责 我们先讨论一下传统的瀑布模型下QA是如何工作的,其中最主要的问题是什么;然后作为对比,我们来看看在敏捷团队里QA又是如何工作的,工作重点又是什么;最后,我们详细看一看在新的职责下,QA应该如何做。
瀑布开发模型 即使在今天,在很多企业中,瀑布模型仍然是主流。每一个需求都需要经过分析,设计,开发,测试,上线部署,运维等阶段。虽然一些企业已经在实施敏捷开发,比如项目/产品以迭代的方式运作,也有诸如每日站会,代码检视等敏捷实践,但是如果仔细审视,你会发现其实开发模式骨子里还是瀑布:按照软件组件划分的部门结构(详见康威定律),按照职能划分的团队(开发和测试分属不同部门),过长的反馈周期,永远无法摆脱的集成难题等等。
随着软件变得越来越复杂,团队里没有任何一个人可以说出系统是如何运作的,也不知道最终用户是谁,以及最终用户会以何种方式来使用最终的软件。
更糟糕的是,按照职能划分的团队在物理上都是隔离的,比如独立的测试部门,独立的运维部门,整日忙碌而难以预约到档期的业务人员,当然还有经常疲于交付,无处吐槽的苦逼开发。由于这些隔离,信息的反馈周期会非常长,一个本来很容易修复的缺陷可能在4周之后才可能被另一个部门的测试发现,然后通过复杂的工作流(比如某种形式的缺陷追踪系统)流到开发那里,而开发可能还在拼命的完成早就应该交付的功能,从而形成恶性循环。
瀑布模式中的QA 在这样的环境中,QA们能做的事情非常有限。在需求开始时会他们参加需求澄清的会议,制定一些测试计划,然后进行测试用例的设计。有的企业会用诸如Excel之类的工具来记录这些用例。这些写在Excel里的,死的用例用处非常有限。而最大的问题在于:它们无法自动化执行。另外,在实际软件开发中,需求总是会经常发生变化,需求的优先级也会有调整,然后这些记录在Excel中的死的用例会很快过期,变得无人问津。
除此之外,QA中的有些成员会使用工具来录制一些UI测试的场景,然后在每个新版本出来之后进行回放即可。然而,当UI发生一点变化之后,这些自动化的用例就会失效:比如HTML片段中元素位置的调整,JavaScript的异步调用超时等等。
显然,这种单纯以黑盒的形式来检查功能点的测试方式是不工作的,要真正有效的提升软件质量,仅仅通过事后检查是远远不够的,软件的质量也应该内建于软件之中。QA的工作也应该是一个贯穿软件生命周期的活动,从商业想法,到真实上线,这其中的所有环节,都应该有QA的参与。
系统思考 如果不从一个系统的角度来思考软件质量,就无法真正构建出健壮的、让业务和团队都有信心的软件系统。质量从来都不只是QA的职责,而是整个团队的职责。
关于软件质量,一个根深蒂固的误解是:缺陷在开发过程中被引入,然后在测试阶段被发现,最后在QA和开发的来来回回的撕扯中被解决(或者数量被大规模降低),最后在生产环境中,就只会有很少的,优先级很低的缺陷。
然而事实上,很多需求就没有仔细分析,业务价值不很确定,验收条件模糊,流入开发后又会引入一些代码级别的错误,以及业务规则上的缺陷,测试阶段会漏掉一些功能点,上线之后更是问题百出(网络故障,缓存失效,黑客攻击,操作系统补丁,甚至内存溢出,log文件将磁盘写满等等)。
在一个敏捷团队中,每个个人都应该对质量负责,而QA则以自己的丰富经验和独特视角来发掘系统中可能的质量隐患,并帮助团队将这些隐患消除。
我在ThoughtWorks的同事Anand Bagmar在他的演讲What is Agile testing- How does automation help?中详细讨论过这部分内容。
QA到底应该干什么? 本质上来说,任何软件项目的目标都应该是:更快地将高质量的软件从想法变成产品。
将这个大目标细分一下,会得到这样几个子项,即企业需要:
更多的商业回报(发掘业务价值) 更快的上线时间(做最简单,直接的版本) 更好的软件质量(质量内嵌) 更少的资源投入(减少浪费) 其实就是传说中的多、快、好、省。如果说这是每一个软件项目的目标的话,那么团队里的每一个个人都应该向着这个目标而努力,任何其他形式的工作都可以归类为浪费。用Excel记录那些经常会失效,而且无法自动执行的测试用例是浪费,会因为页面布局变化而大面积失效的UI测试也是浪费,一个容易修复的缺陷要等到数周之后才被发现也是浪费。
在这个大前提下,我们再来思考QA在团队里应该做什么以及怎么做。
QA的职责 Lisa Crispin在《敏捷软件测试》中提到过一个很著名的模型:敏捷测试四象限。这个模型是QA制定测试策略时的一个重要参考:
如果按照纵向划分的话,图中的活动,越向上越面向业务;越向下越面向技术。横向划分的话,往左是支撑团队;往右是评价产品。
其实简化一下,QA在团队里的工作,可以分为两大类:
确保我们在正确的交付产品 确保我们交付了正确的产品 根据这个四象限的划分,大部分团队可能都会从Q2起步:QA会和BA,甚至UX一起,从需求分析入手,进行需求分析,业务场景梳理,这时候没有具体的可以被测试的软件代码。不过这并不妨碍测试活动,比如一些纸上原型的设计(感谢刘海生供图):
通过这一阶段之后,我们已经有了用户故事,这时候QA需要和开发一起编写用户故事的自动化验收测试。当开发交付一部分功能之后,QA就可以做常规的用户故事测试,几个迭代之后,QA开始进行跨功能需求测试和探索性测试等。根据探索性测试的结果,QA可能会调整测试策略,调整测试优先级,完善测试用例等等。
根据项目的不同,团队可以从不同的象限开始测试策略的制定。事实上,Q1-Q4仅仅是一个编号,与时间、阶段并无关系,Lisa Crispin还专门撰文解释过。
关于QA如何在软件分析的上游就介入,然后通过BDD的方式与业务分析师一起产出软件的各种规格描述,并通过实例的方式来帮助整个团队对需求的理解,ThoughtWorks的林冰玉有一篇文章很好的介绍了BDD的正确做法。如果将QA的外延扩展到在线的生产环境,制定合理的测量指标,调整测试策略,强烈推荐林冰玉写的另一篇文章产品环境中的QA。
其他职责 事实上,软件生命周期中有很多的活动,有很多处于灰色地段。既可以说是应该开发做,又可以说应该QA做,甚至可以推给其他角色(比如OPs)。不过我们知道,一旦涉及角色,人们就再也不会按照全局优化的思路来应对问题了。这种灰色的活动包括:
持续集成的搭建 测试环境的创建于维护 UAT上的数据准备 代码中的测试代码的维护 测试代码的重构 在团队实践中,这些活动我们通常会让QA和开发或者OPs同事一起结对来完成。一方面避免知识孤岛的形成,另一方面在跨角色的工作中,也可以激发出更多不同的思路。
万能的QA? 虽然在这些活动中,QA都会参与,但是并不是说团队里只要有一个QA就可以了。QA在参与这些活动时,侧重点还是有很大不同的。
比如需求分析阶段,如果有QA的加入,一些从QA角度可以发现的有明显缺陷的场景,则可以在分析阶段就得到很好的处理。另一方面,尽早介入可以设计出更合理的测试计划(比如哪些功能的优先级比较高,用户更会频繁使用,那么对应的测试比重也会更高)。在Story分析与书写阶段,QA可以帮助写出更加合理的验收条件,既满足业务需求,又可以很好的指导开发。
在和开发一起编写澄清需求时,主要是编写自动化验收测试,而不是实际编写业务逻辑的实现(虽然QA应该参与Code Reivew环节,学习并分享自己的观点);甚至在上线运维阶段,QA还需要和OPs一起来设计用户数据的采集指标(比如用户访问的关键路径,浏览器版本,地区的区分等),从而制定出新的测试策略。
扩展阅读 What is Agile testing - How does automation help?...
说实话,这是一篇软文,为我的新书《轻量级Web应用开发》写的软文。如果你不想接着往下读,可以直接去这里买一本来看,:)
之过去的几年中,我参加过好多次Hackday活动。每次看到在为期两天的时间里,2-3个人将一个想法变成现实,都会有一种强烈的成就感。而且这个Hack的过程中,会重拾编程的乐趣,大家的积极性都非常高,用着各种有趣的技术(大数据,开源硬件,Node.js,GIS系统),逐步的将模糊的想法,变成现实,并最终为客户带来价值。最新的一次在这里
通过这些Hackday的经历,以及在众多项目中的经验,我总结了一些轻量级的方法/实践。这些方法/实践非常容易落地,并且久经验证。在很多项目中已经在不断的使用。它们可以帮你更好的将一个想法变成现实,并且在随后的开发中还可以继续发挥作用而不至失效(测试,构建脚本,自动化部署等等)。我希望你可以在自己的项目中尝试这些方法/实践,也希望这些方法/实践可以真正的帮助你和你的项目取得成功。
细化你的“点子” 根据一个已有的产品来参考,演绎,并形成自己的产品并非难事,而创新则是一件非常困难的事情,因为你需要“无中生有”。在ThoughtWorks,我们有这样一些步骤可以帮助客户来梳理信息,并最终交付产品,简而言之,可以归结为这样几个步骤:
Discovery(用户研究,探索) Define(归纳洞见,发现) Design(原型设计,验证) Delivery(制定计划,实施) 上图是一个“点子”的原型(一个交换技能的应用,用户可以教别人自己擅长的技能,作为交换,也可以从别人那里学习心得技能),原型事实上是第三步的产物。我们通过一些调查(口头采访,或者问卷调查)得到一些基本的信息,然后归纳这些信息,并和真实用户再次确认,得到一个概念。有了概念,再来设计一个基本的原型,这个原型还可以迭代数次,然后进入下一个阶段。
前三个阶段更多的是用户体验设计师,以及客户的业务人员参与的。在前三步完成之后,进入实施的时候,软件工程师开始投入。这篇文章更关注**第四步(Delivery)**中的各种实践,通过这些实践,我们可以很好的完成交付计划,使得我们的好想法最终可以变成为用户提供服务的产品。
实现“点子”的方法 在软件领域,将一个想法变成实际的产品需要经历若干个阶段。按照传统的软件开发方式,会有前期的调研,需求分析,概要设计,详细设计,编码实现,测试,发布等一系列的流程。这种方式对每个阶段的定义都非常明确,而且每个阶段需要依赖前一个阶段的输出,因此往往被称为“瀑布模型”。后来慢慢发现,这个模型的反馈周期太长,一个软件从调研到发布往往需要数年,当发布之后,可能市场早已经沧海桑田。人们后来发明了更加符合现代市场需求的“敏捷开发”,在敏捷中,更加强调短平快的将需求变为产品。
简而言之,敏捷开发更强调:
快速发布 渐进增强 小步迭代 而在敏捷开发的继承者精益中,这几点理念也被更进一步的深化。由于没有办法预见未来,我们只能用一种边做边看的方式来验证想法。简而言之,就是先根据经验和调查,做出一个合理的推断,然后定义好范围,构想出一个最小可行产品(MVP),这个MVP的功能非常内聚,非常紧凑,我们需要尽可能快的让其上线,并被真是的用户使用,测试。根据这些用户的反馈,我们会做一些调整,比如去掉那些很少人使用的功能,聚焦在用户喜欢的功能上;从用户的实际使用中,调整界面元素的位置,子功能的入口等等。这个过程会持续多轮,最后的结果会是一个有真是用户使用,并且比较贴近真实需求的产品。当然这还不够,我们需要不断的打磨,渐进式的增强产品的功能,逐步完善功能等。
有一个非常形象的图,可以看出瀑布模型和敏捷开发两种方法的对比:
敏捷开发通过逐步细化,迭代前进的方式,分阶段的将需求实现,在整个过程中,更容易做到快速调整。
所有的这些过程,都非常依赖“快速”这个关键点。如果MVP花了3周就产生了,但是为了让其上线,你花费了1个月,那么很可能这个MVP已经过时了;如果你确实快速的将MVP发布了,在得到了用户的很多反馈之后,花费1个月来实现这些反馈,又会让你落在竞争对手之后;如果快速的发布了多次,并且幸运的是,你的用户量变多了,如果花费很长时间来调整架构,则可能失去当前的市场窗口。也就是说,你需要非常快速地对变化做出反应!
轻量级的开发方式 开发中的三个重点 在工程实践中,我认为有三个特别需要注意重要的点,这三点可以极大程度的改善项目现状,提高效率,并使得产品的高质量交付成为可能,它们分别是:
自动化(自动化一切) 质量内嵌(defect的数量,是否真正满足了需求) 代码本身的质量(可读性,可维护性,可扩展性) 自动化包括,自动provision,自动部署,自动化测试,自动打包等等。这是提高团队开发效率的必要工具。比如书中提到的grunt/gulp脚本,jasmine/rspec/capybara测试,部署脚本,vagrant/Chef等,都是关于如何将日常开发中的任务尽可能的自动化。
软件没有Bug当然是所有人都追求的,我们有很多中方式来保证代码质量。而在编写产品代码的同时,写大量的自动化测试,是投入产出比最高的一种了。通过单元测试,集成测试,以及一些有限但是关键的UI测试,我们可以覆盖很多的需求,而将这些测试自动化起来之后,可以节省大量的开发/测试成本,并减少回归测试的代价。
要支撑快速的发布,我们需要一系列的技术实践。这些技术包括环境的搭建,框架的使用,代码的编写,产品的发布;而且包括后台的数据库设计,业务代码,同样还有前端的展现等。
何为轻量级? 在《轻量级Web应用开发》中,我介绍了一系列的实践/工具,这些实践/工具贯穿整个软件开发的生命周期,使得敏捷开发/精益的开发方式变得可以“落地”。比如如何使用轻量级的开发框架来搭建API原型,如何将应用发布在免费的云平台上,如何通过虚拟化技术快速搭建开发环境,从而节省环境配置的投入,如何快速平滑的发布,如何使用测试先行的方式来保证代码质量,如何做高效的自动化UI测试等等。
轻量级Web框架 前端开发流程 构建工具 环境自动化(开发环境的搭建,CI服务器的搭建) 自动化部署 UI测试 实例驱动(书中有很多的实例,也有很多从项目中总结出来的实践) 这是一本主要关注开发实践的书,书中通过很多实际的例子来帮助读者建立一套完整,高效,轻量级的开发方式,这些方式可以直接在你的下一个项目中使用。甚至如果项目的技术栈变成了另外一种语言,你也可以迅速找到同类的替代品。比如rake之于gradle,sinatra至于spring-mvc等等。
每个组件都是可以替换掉的,比如ORM,如果你觉得DataMapper无法满足实际需要,那么可以换成ActiveRecord。如果Rails太重,使用Sinatra或者Grape或许是一个更好的选择。AngularJS包含了太多东西,Backbone.js或许适合你的场景,而也未尝不可以用Riot.js来替换掉Backbone中的view层。
在本地,可以将应用部署到一个vagrant+chef来provision的环境中,而通过部署自动化,这个动作可以很容易的在AWS的云上实现。轻量级的开发方式,帮助你用最小的代价来替换系统中的任意一个组件,因为每个组件在一开始都是按照可替换原则选用的。
另一方面,轻量级的另一个意思是:现发布静态的版本,然后再将内容替换为动态版本。发布一个静态的页面非常容易,具体细节可以参考这篇文章。当需要动态内容是,免费版的Heroku是一个触手可得的选择,AWS则是一个更加专业的选择(各种服务都配置完善,你只需要关注自己的应用部署即可)。