7个你需要知道的结对礼仪

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)等等。...

December 7, 2018 1 min

实时数据可视化

通常来说,可视化的报表会以更高效率的方式将数据背后隐藏的信息传递给我们。通过一个简单的`BarChart`,我们就很容易对比某商品在第二季度中的销量差异;而通过一条简单的`LineChart`,则很容易看出员工平均工作时间在某个月份的分布。这些报表都或多或少与时间相关:随着时间的流逝,某项指标会因为各种各样的因素而产生变化。

June 17, 2018 3 min

团队里的两类程序员

程序员的分类 最近几年,我在多个不同类型的项目上,以不同的角色工作过:有时候会为项目前期做一些预研、然后为后续的交付估算工作量;有时候则在项目中期加入团队,做本职的交付工作(就是写业务代码);而另外有些时候则会帮助客户的团队进行能力建设等等。 由于要在不同的场景和不同的上下文中频繁切换,我和很多不同水平、有着不同变成习惯和思维方式的程序员一起工作过。我觉得对于程序员这样一种角色,还可以再细化成两大类。虽然都是程序员,但是其职责,技能要求,甚至思维方式都可能有很大的不同。 这两大类程序员分别为: 原型类程序员(prototyper) 产品类程序员(engineer) 原型类程序员 如果通过敏捷的方式来运作一个项目,在项目开始的时候,开发往往需要客户/用户,BA(业务分析师)等角色一起协作。在有一个对需求的粗略梳理之后,通过技术手段快速实现应用程序的原型,用以快速验证业务场景。由于对需求的梳理是较为粗略的,其中有很多待验证的假设,如果按照传统的方式来运作,则会有很多不必要的浪费 设计师通过Photoshop作出页面 开发根据PS文件来开发 开发的同时,业务分析可能有了新的发现 设计师对Photoshop作出修改 开发根据新的PS来做修改 这种重量级的,瀑布方式的工作体验非常糟糕,不论是设计师,还是开发,总觉得很多事情都在变化,自己花费了很大力气做的像素级别的精准调整可能在第二天就要被推翻。不过现实世界就是这样:需求的变化是不可避免的,我们唯一能做的是积极拥抱变化,而不是在最开始就尝试设计、实现完美的效果。 与其在一开始就试图设计一个完美的、确定的系统,不如放弃幻想,每次只做简陋而可以示意的原型,然后不断去迭代,最后达到可用的系统。 比如,如果设计师完全不使用Photoshop,也不产出任何的PS文件,而是和开发坐在一起,开发一个简陋,但是易于修改(比如HTML+CSS)的页面,然后向业务分析师或者客户来确认,然后根据反馈来快速调整。在迭代过程中,每次确认之前,只做非常少量的工作,这样既可以避免返工,又可以让将来的调整更容易。 如果用这种方式来运作,我们队程序员的能力要求是这样的: 手速快(可以在很短时间内作出一个可以工作的原型) 技术广博(前后端) 可以与不同角色一起工作(最好是通过结对的方式) 比如通过python -m http.server 8080来运行一个Web服务器,而不是去考虑负载均衡或者HTTP缓存等,也无需考虑在为前端代码加入动态路由。 很有可能,一个在<head>里通过CDN引入外部依赖,然后将所有代码都写入main.js就可以让我们的想法变成触手可及的应用,在建立起快速反馈的机制之后,就可以和我们的BA,甚至客户一起来协作,并快速打磨原型,使之和客户的期望契合。 原型驱动 去年我参加了一个非常有意思的项目,在我加入的时候,客户只有一个非常粗糙的想法:在网络(不是计算机网络)中,如果某个实体被攻击了,那么对整个系统的影响是什么?比如某个变电站由于气温太高爆炸了(6·18西安变电站爆炸事故),那么哪些地铁站会受到影响?以及收到多大影响?又或者某个运营商的网络中断了,那么它对该城市的各大银行的转账业务有多大影响? 客户希望有一个比较直观的方式来展现网络中的节点,以及当故障发生时会有什么样的影响。我们从原型开始,没有设计师的参加,也没有专门的业务分析师,一开始只有有一个简单的文档来描述,然后我根据文档画了一个草图: 和客户确认了,我用bootstrap+leaflet做了一个简单的页面: 客户表示大致是这么个意思。这时候我的代码是这样的: 一个HTML文件 从CDN来获取jQuery,bootstrap,leaflet inline的script中写了一点微小的代码 inline的css 确定了这就是客户想要的效果之后,我做了一些简单的调整: 将第三方依赖下载下来,放到本地的一个目录中 将inline的JavaScript和CSS抽取到文件 写了一个简单的shell script用来启停本地的HttpServer 几周之后,客户对右侧的两个panel有一些新的需求,要求有一棵树,可以点击,然后要做各种同步: 这时候,我的代码还是几个微小的jQuery写的文件,通过简单的事件机制来完成交互。很快,客户有了一个新的需求:中间区域的图中的非叶子节点要可点击,点击之后右侧的一个panel显示该节点下的所有叶子,同样,右侧panel中的节点也需要可点击。 也就是说,中间的树需要两个不同的视图,而背后的数据只有一份(这样点击时的展开和收起才会是同步的),这时候用事件机制就很难做到了,而且代码的复杂度直线上升,而且可维护性基本没有。 基本满足客户的需求之后,团队里加入了两位同事,这时候我做了一个比较大胆的决定,用vue.js将整个应用重写,将大部分功能都抽象为组件,方便以后的扩展。完成的时候,界面看起来是这样子的: 如果整理一下思路,原型类开发的流程是这样的: 用CDN+胶水代码迅速实现 及时与客户沟通,并根据反馈修改 在需求相对稳定,方向确定之后,再做实际的技术选型 重构 — 甚至重写(使用靠谱的技术站进行) 比如上面这个项目,如果现在让我再来做一次技术选型,我可能会这样选择: 前端框架:React + Redux + React-router 界面原型:AntDesign 地图:Leaflet 可视化:D3 产品类程序员 另一方面,当idea已经经过验证,我们需要通过严谨的工程实践将其产品化,那么对开发的要求却又有不同:...

January 14, 2018 1 min

反馈拯救世界

心流 你可能有过这样的体验:在玩一个很有趣的游戏时,时间会飞快的流逝,等你终于通关之后才发现已经是凌晨,而你的午饭和晚饭还都没吃。虽然可能饿着肚子,但是你内心却有一种很兴奋,很神清气爽的感觉。而当你在做一些不得不完成的工作/作业时(比如写年终总结报告),时间又会过得很慢,你的心情也常常变得焦虑或者暴躁。 通常来说,人的心情总是会在各种情绪中起伏不定,不过毋庸置疑,我们都希望永远或者至少是尽可能多的保持第一个场景中的状态。 这种精神高度集中,通过自己的努力不断完成挑战,并常常会有忘记时间流逝,甚至忘记自身存在,只剩下“做事情”本身的状态,在心理学上被称之为心流(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都完成,用户故事也就完成了。如果仔细分析,这些步骤也恰好符合产生心流的条件:...

January 13, 2018 1 min

从三明治到六边形

本文首先发表于ThoughtWorks洞见。 软件项目的套路 如果你平时的工作是做各种项目(而不是产品),而且你工作的时间足够长,那么自然见识过很多不同类型的项目。在切换过多次上下文之后,作为程序员的你,自然而然的会感到一定程度的重复:稍加抽象,你会发现所有的业务系统都几乎做着同样的事情: 从某种渠道与用户交互,从而接受输入(Native App,Mobile Site,Web Site,桌面应用等等) 将用户输入的数据按照一定规则进行转换,然后保存起来(通常是关系型数据库) 将业务数据以某种形式展现(列表,卡片,地图上的Marker,时间线等) 稍加简化,你会发现大部分业务系统其实就是对某种形式的资源进行管理。所谓管理,也无非是增删查改(CRUD)操作。比如知乎是对“问题”这种资源的管理,LinkedIn是对“Profile”的管理,Jenkins对构建任务的管理等等,粗略的看起来都是这一个套路(当然,每个系统管理的资源类型可能不止一种,比如知乎还有时间线,Live,动态等等资源的管理)。 这些情况甚至会给开发者一种错觉:世界上所有的信息管理系统都是一样的,不同的仅仅是技术栈和操作的业务对象而已。如果写好一个模板,几乎都可以将开发过程自动化起来。事实上,有一些工具已经支持通过配置文件(比如yaml或者json/XML)的描述来生成对应的代码的功能。 如果真是这样的话,软件开发就简单多了,只需要知道客户业务的资源,然后写写配置文件,最后执行了一个命令来生成应用程序就好了。不过如果你和我一样生活在现实世界的话,还是趁早放弃这种完全自动化的想法吧。 复杂的业务 现实世界的软件开发是复杂的,复杂性并不体现在具体的技术栈上。如Java,Spring,Docker,MySQL等等具体的技术是可以学习很快就熟练掌握的。软件真正复杂的部分,往往是业务本身,比如航空公司的超售策略,在超售之后Remove乘客的策略等;比如亚马逊的打折策略,物流策略等。 用软件模型如何优雅而合理的反应复杂的业务(以便在未来业务发生变化时可以更快速,更低错误的作出响应)本身也是复杂的。要将复杂的业务规则转换成软件模型是软件活动中非常重要的一环,也是信息传递往往会失真的一环。业务人员说的A可能被软件开发者理解成Z,反过来也一样。 举个例子,我给租来的房子买了1年的联通宽带。可是不多过了6个月后,房东想要卖房子把我赶了出来,在搬家之后,我需要通知联通公司帮我做移机服务。 如果纯粹从开发者的角度出发,写出来的代码可能看起来是这样的: public class Customer { private String address; public void setAddress(String address) { this.address = address; } public String getAddress() { return this.address; } } 中规中矩,一个简单的值对象。作为对比,通过与领域专家的交流之后,写出来的代码会是这样: public class Customer { private String address; public void movingHome(String address) { this.address = address; } } 通过引入业务场景中的概念movingHome,代码就变得有了业务含义,除了可读性变强之外,这样的代码也便于和领域专家进行交流和讨论。Eric在领域驱动设计(Domain Drvien Design)中将统一语言视为实施DDD的先决条件。 层次架构(三明治) All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections....

August 21, 2017 2 min