极限编程是一个轻量级的、灵巧的软件开发方法;同时它也是一个非常严谨和周密的方法。它的基础和价值观是交流、朴素、反馈和勇气;即,任何一个软件项目都可以从四个方面入手进行改善:加强交流;从简单做起;寻求反馈;勇于实事求是。--百度百科

工程实践的重要性

在之前的文章中,我们聊过了什么是敏捷,也介绍了scrum框架,今天要聊的话题是极限编程(ExtremeProgramming,简称XP),从名称上来说我们能够发现,XP的关注点和scrum是有明显区别的,XP更加强调工程实践,也是早期众多的敏捷方法中特别强调在软件开发领域应用的实践,当然,笔者认为,xp中的很多方法和实践虽然是讨论的软件开发,但是一些思维和技巧也同样值得其他行业借鉴,敏捷的源头就是关注在软件开发领域的,这个在敏捷宣言中也有明确的体现,“We are uncovering better ways of developing software by doing it and helping others do it.”,而且很多敏捷领域的资深大佬,也多次在强调要注重工程实践,只靠一些流程和管理模式的改变并不足以确保团队走上正确的敏捷之路。所以我们今天就来看一看XP到底能给我们提供些什么?

极限编程推荐的实践

打开网易新闻 查看更多图片

如图所示,XP给出了13个非常经典的实践方法,我们先来看一下最里层,分别是重构、简单设计、结对编程和测试驱动开发,这部分的四个实践比较偏重个体这个层次,重构强调不断优化设计、简单设计提倡“足够”的设计以避免浪费,结对编程强调成员结对进行开发,以优化产品质量,而测试驱动开发以一种新的开发思路确保开发的“有法可依”;

再向外一层是侧重团队实践的,包括了:建立团队共同遵守的编码标准,希望团队可以保持可持续的开发速度(40小时),通过持续集成避免大量集成的错误堆积到研发后期,集体所有制建立公开透明的团队氛围,而最难以理解的是隐喻(Metaphor),隐喻更像是为了便于设计和理解,把大家比较容易理解和共识的内容加入到系统设计当中,比如电商网站的购物车就是一个很好的隐喻的例子,看到这个名字相信任何人都能很快的理解他的作用,它应该具备什么功能?设计成什么样子?很快就达成一致了。

最外层涉及的范围就更广了一些,这些实践需要产品、业务负责人以及其他的关键干系人的参与,包括:计划游戏、小版本发布、客户测试和完整的团队,篇幅限制,我们不对每一种实践进行详细的阐述,接下来我会挑选几个典型实践和大家分享我的理解和观点。

重构

重构是指在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构,也就是说系统最终提供的功能是不会改变的,我们要做的是内部优化,通过这样的方式为后续的技术跑道铺平道路,重构并不难理解,但是在实际的工作环境中很多公司到没有重构的文化,而具体原因可以归纳为如下的几个方面:

没时间 这个说起来可能有些推卸责任的味道,但是却也是不争的事实,在很多组织中,研发人员对要面临996的考验,每天疲于应付严重过量的需求,至于重构这种“费力不讨好”的事,是没有时间也没有心情做的;

风险高 重构意味着你要修改现有的代码逻辑,也就意味着你需要充分的了解原有逻辑和设计,如果因为重构而导致了不必要的问题,那研发人员只能郁闷又无处发泄,尤其是团队成员水平层次不齐又存在大量老旧代码的情况下,重构的风险更加难以预测;

不重视 很多领导都认可重构是一项好的实践和做法,但是当重构这件事与业务交付、合规要求、产品创新等一系列需求放在一起时,重构往往是排在列表最下方的位置,认可重构,但是并不着急是常见的观点,但所谓“工欲善其事必先利其器”,重构能给你的是一个好的基础和前提,在存在问题的设计基础之上不断扩展和更新只会让问题更加严重和难以修复,这个工程积累下来的就是所谓的技术债务,技术债务也同样有利息,如果能够按期偿还,就能够达到业务交付与技术债务的平衡,如果不能按期偿还,拖欠的越久就越难以承担,最终会面临破产;

重构不值钱 在当今的软件开发和互联网领域,跳槽变的越加频繁,现在流行的做法是在一个企业中混几年经验就跳到下一家公司,工资会大涨甚至翻倍,作为一个研发人员,我的目标是尽快跳到下一家企业,说不准哪一天我就跟跟领导提离职申请了,我花时间和精力去搞重构干嘛?重构不会涨工资,做不好还要担责任,我干嘛重构。

这不是一个研发人员该有的心态,我们常说工匠精神,跳槽可能让你达到短期目标,但是从长远出发,好的工匠精神,对技术和设计的精益求精才是走的更远的基础,笔者也是做研发出身的,即使现在已经多年不做研发工作,但是早期对技术的钻研和探索,到现在都始终能给自己带来帮助,这个帮助不只是与研发人员沟通能够更加顺畅,更重要的是曾经对技术的热情和追求让自己养成了对待工作真的的态度,以及精益求精的原则;重构固然好,那在什么样的时机下进行重构呢?在Martin Fowler(敏捷宣言的起草者之一)《重构:改善既有代码的设计》一书中,给出了一些重构的好时机,我把其中的几个拿出来和大家分享:

  • 三次法则 所谓再一再二不再三,一件事情可以犯两次错误但不要犯第三次,犯错一定要积累经验,代码设计也一样,如果一个功能或方法,在一个地方写了一次,在另一个地方又一写了一次,第三次的时候你还要写一次类似的逻辑吗?这个时候请考虑重构,抽象出一个可以共用的方法;

  • 添加功能 当你向现有的功能中添加方法时,意味着原有的功能一定是不能满足要求的,也就是说这是一个不错的机会来改善原有的设计,同时也方便后续的功能添加能够更加容易,也许后续添加功能的人后续不是你了,但当你想到后续的人修改时对原有设计的认可和赞赏,这就是一个工匠的快乐源泉;

  • 修补错误 如果你的代码出现了错误,那请我你自己为什么原有的设计不容易发现这个错误,是不是我的设计还可以优化,简化到你能够看出来有这样的问题存在,这个也许夸张,但这是你需要努力的方向;

  • 复审评审 代码评审是一个非常有效的质量改进活动,但是如果你希望你的搭档能够快速的理解你交付的功能,如果你希望评审的过程更加高效,那请考虑重构,如果代码评审总能够发现代码中存在问题,那么问题修改时加入重构的动作!

结对编程

结对编程进行起来也比较简单,一个人写代码一个人看,但是不是简单的看,是要思考、要能够发现问题、要互相讨论,而且这个过程要进行角色互换,笔者觉得结对编程的时间不宜过长,我们可以选择在每天的工作时间中选择几个小时进行结对,如果全天都做结对编程,这个是比较难以开展的,而结对编程能够带来的好处可以概括为如下的几个方面:

工作备份 结对编程的形式,可以让两个人对工作都有充分的理解,如果一个人休假或离职,另外一个人可以完美对的接替工作,如果遇到任何问题也可以互相讨论,加速问题的排查和处理;

持续评审 代码评审的重要性不言而喻,而结对编程是把评审工作做到极致的一种体现,每写一段代码都有人帮你做检查,有问题就可以互相讨论并进行优化,这样的效果一定是好的,但如果您的团队暂时无法做到结对编程,那退而求其次在线的结对评审或许可以尝试一下。

知识共享 结对编程不仅仅是保证质量的手段,同时也是知识共享的好机会,两个人结对的过程可以学习对方的编程思路和设计模式,通过讨论也能够加深对设计的理解,长此以往在能力提升的角度也会产生很好的效果;

互相监督 或许从敏捷的角度来说我们并不提倡监督,以为敏捷强调信任和尊重,但是不得不说,这样的实践确实也起到了监督的效果,想象一下旁边一直坐着一个人你还哪里好意思偷懒呢,但是毕竟这样会让人觉得被监视,会产生疲劳,所以像开头我们说的,推荐的做法是每天结对一段时间即可,没必要长期结对。

但是结对编程其实是有前提的,通过上面的描述大家一定会有一个疑问,这样的方式领导会认可吗?确实在实际的工作当中很多组织都不太认可这种方式,因为领导的直观感受是结对是浪费资源,两个人做一个人的事效率太低了,所以结对编程的第一个大前提就是领导要认可。

而第二个非常关键的前提是结对双方的能力差距要小,这个其实很好理解,一个大神写代码菜鸟认真的看可能也会看不懂,而菜鸟写代码大神看到后可能会抓狂,最合理的是结对的双方能力可以接近一些,避免上述尴尬情况的产生,想象一下你考驾照的时候教练为什么骂你骂的那么凶?当然,也有一种特殊情况,就是老人带新人,当公司有新人加入时,结对编程是让新人快速了解的好手段,这个时候就可以忽略能力差距这个事情了,不过为了新人的成长大佬要有足够的耐心,否则几天之后新人被你骂走了又得不偿失。

测试驱动开发

“什么!还没开发就要测试?”

打开网易新闻 查看更多图片

想象一下,工人在砌墙的时候,是要先拉一根绳的,作为参考可以确保砌的墙是直的,而我们的研发过程呢,是研发人员先花费大量的精力写代码,写好之后丢个测试,让测试量一量墙是不是直的,为什么研发过程不能先给出标准再按照标准进行开发呢,测试驱动开发就希望能够构建这样一种模式。

测试驱动开发意味着你要先写一个单元测试,然后编写恰好够用的代码,让它通过这个测试,接着对代码进行重构,主要是提高它的可读性和消除重复。整理一下,然后继续。这个步骤中有两点是需要注意的:

恰好够用的代码是指我们写的代码要足够精简,冗余的代码即为浪费;

重构 代码测试通过之后就要进行重构,是因为最开始的目的只是为了让测试能够快速的通过,而并没有过多的考量设计,重构的动作就是为了优化设计,这样想的话上面说的重构的时机是不是又可以增加一条了?

经过上面的描述,我们初步认识了什么是TDD,那为了加深大家的理解我们通过下图的总结来对比下传统模式和TDD的区别:

  • 传统方式是:编写方法→编写测试案例→执行测试案例→修复缺陷;

  • 而TDD的模式是:编写测试案例→运行测试案例(注定失败)→编写方法以满足测试案例→执行测试案例→如果失败则修复缺陷→成功则进行重构。

所以我们上述的TDD更确切的叫法应该是UTDD。那既然有UTDD还有没有别的TDD呢?当然有,还有ATDD(验收测试驱动开发),验收测试驱动开发相比UTDD来说更加强调客户合作,既然是验收测试那一定是客户给出的验收条件才具有说服力,我们需要通过与客户协作确定验收标准,这个标准具体来说要体现在用户故事的验收条件中,而关于ATDD的进一步延伸就不得不提到BDD(行为驱动开发)【读者现在应该已经表情失控:还有完没完了?】;

行为驱动开发帮助我们把验收条件标准化,要求遵循GWT模型(Given-When-Then),并且现在已经有一些工具可以支持BDD开发,结合工具的使用我们可以实现按照规定的格式用自然语言描述需求,工具帮我们转化为代码,在此基础上我们可以完善测试用例和功能实现,做到了需求与测试案例和功能的一致性,虽然听起来是美好的,但是业务和产品真的能按照你的工具要求描述需求吗?这也是一个需要深入讨论的问题。

持续集成

持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。--Martin Fowler

从狭义的角度来说,持续集成是让研发人员之间的代码能够持续的集成在一起,以便能够快速发现问题,从广义上来说,持续集成也代表了研发、测试和运维各个团队之间工作的集成,如下图所示

研发人员从仓库获取最新的代码版本后进行编码,完成后应该在本地进行测试验证,没有问题后要拉取仓库中最新代码进行二次验证,完成上述步骤后可以将代码提交到版本库当中,根据提前制定的触发机制,持续集成服务器会进行代码构建,除此之外这个过程中可能还包括了代码质量扫描、自动化单元测试和一定量的验收测试任务,待任务执行完成后将结果通知给研发人员,这个结果不管是成功的还是失败的,通知的动作都不可忽略,而当构建失败之时,团队需将修复失败的构建作为第一优先级的任务,也就是精益生产中安灯拉绳的概念,一切要停下来,待问题解决后才可以继续,避免在存在问题的代码基础上持续更新,造成问题的积累,而之所以叫持续集成肯定是希望这个集成的周期越短越好,短的集成周期可以带来如下好处:

  • 提高产品质量

  • 缩短交付周期

  • 建立团队信心

  • 为持续部署打好基础

以上的好处都是很容易理解的,我们就不做深入讨论了,既然我们提到了持续部署,就简单说一下持续集成、持续交付和持续部署的区别。

持续交付是在持续集成的基础上可以实现将交付的功能部署到开发环境、测试环境、预生产环境之上,更多的是证明我们已经具备了持续交付对的能力,而持续部署更加强调我们能够做到将功能自动化的部署到生产环境,反馈更加快速,关注的侧重点从具备交付能力转变为快速的满足客户要求,而不管做到什么程度,各个环节都强调一个反馈的问题,也就是集成、构建、部署的结果必须及时通知给相关负责人,这个过程可以结合很多高效的实践,以做到信息的及时性,比如:将构建结果通过报警等进行展示,通过邮件通知负责人,通过即时通讯工具发送即时消息等都是可以考虑的做法。

笔者的观点来看,很多传统企业中(比如金融)持续部署还比较理想化,毕竟在生产发布的环境要受到很多制约因素的影响,比如企业中常见的审批流、合规要求、安全要求等等,同时还要考虑灰度发布、蓝绿部署等机制以确保生产发布的风险可控,总之,理想是要有的,万一实现了呢?

从上面的内容我们也能发现,整个过程的管理除了刚刚提到的内容之外还有很多需要完善的基础性工作,比如自动化测试、完善对的配置管理、环境管理、版本管理等,这不仅仅是工具的问题也是机制建立的问题。而结合近些年很火的DevOps理念,还要考虑到文化、流程、思维等各个方面的转变。

关于XP中的实践我们就先简单介绍这几种,所以最后还是回归主题,极限编程是怎样极限的?

1、如果做代码评审有用,那就随时评审--结对编程;

2、如果测试有用,那就多做、早做--自动化测试、TDD、用户验收测试;

3、如果做设计有用,那就定期做—重构;

4、如果集成重要,那就持续做--持续集成;

所以,极限编程你值得拥有!

快过HugeCTR:用OneFlow轻松实现大型推荐系统引擎

2021-02-24

经典神经网络 | 从Inception v1到Inception v4全解析

2021-02-23

Dubbo的设计理念原来就藏在这三张图中

2021-02-03