饭饭TXT > 学习管理 > 《技术领导力:程序员如何才能带团队(出版书)》作者:周明耀【完结】 > 技术领导力:程序员如何才能带团队 (周明耀).txt

第3章 产品开发过程管理 .3

作者:周明耀 当前章节:15477 字 更新时间:2026-6-23 09:06

当你自己写代码的时候,别人给你提意见,你可能觉得很好,也可能觉得很烦。等有一天你去读一段晦涩难懂的代码的时候,你才能理解为什么有些细节会那么重要。其实这就是一个成长的过程。努力、用心、思考,这些都需要时间的积累。但如果不用心,时间花得再多,也不会有太大的进步。

总结项目经验教训的目的在于总结问题、分析原因,避免以后犯同样的错误,而不是追究谁的责任。

假设一个需求理解的缺陷,如果在需求阶段发现,修改一下可能只要一个小时,但是如果到了设计完成时发现这个缺陷,因为涉及的人员、文档增多,估计要一天时间,而如果等到代码都编写完成时才发现这个缺陷,可能需要十天八天了。如果缺陷没被发现,而是直接到了生产系统中呢?这就不是工作量的问题了,估计损失就难以估计了。在质量管理的理论中,缺陷越晚发现,修复的代价就要乘上十倍。

2.如何复盘

项目经理需要在产品开发过程中召开一个或者多个复盘会议,会议要对项目启动会的每一个环节进行回溯。例如项目启动会是否准备充分,启动会现场是否有提问没有回答上来,为什么没有回答准确;产品需求是否按期完成、评审是否通过、为什么存在产品经理不满意产品需求的情况;总体设计是否提前与产品需求完成;总体设计、概要设计评审是否存在高优先级缺陷,为什么会有这样的缺陷存在,是不是设计阶段没有考虑周全;单元测试是否完成,为什么单元测试效果不好;系统测试阶段发现了哪些高优先级缺陷,冒烟测试是否通过,为什么没有通过。

可以复盘的内容很多,一位严格、高水平的技术经理,这一环节应该是他花费大量时间的环节。复盘的参与人员不用很多,只需要该产品的开发人员参与就可以了。再重申一句,复盘会不要太严肃,它不是“批斗会”,而是为了总结经验,不断优化,不再犯同样的错误。

3.3

产品开发过程杂谈

3.3.1 理解业务的重要性

业务问题的本质是业务所服务对象的利益问题。根据业务对象的利益,可以整理出业务的生命周期和生命周期的拆分,再根据业务的概念和组织方式,可以分析出业务的核心生命周期。根据当前业务增长的方式,也可以反过来理解业务的生命周期拆分。通过对生命周期的分析,可以快速理解业务,并进行领域建模,为软件模拟业务做好准备。

为了能够让业务在软件中很好地运行起来,软件工程师必须理解业务的生命周期、拆分方式,以及业务服务对象的利益所在,即业务问题。业务的核心生命周期是什么?这些问题是如何拆分解决的?涉及了哪些概念?这些概念分别解决了哪些问题?针对这些问题,软件工程师经常需要根据自己的理解,创建一套概念体系来表述业务,或者用软件行业的术语来表述业务。

软件工程师还必须要考虑,用什么样的硬件把软件跑起来?怎样才能跑得好、跑得快,同时软件还能随着业务的流量逐渐地长大?

3.3.2 应对需求变更

互联网业务的一个主要特点是业务迭代非常快,每天有新需求,每周都有新发布,每年都有大重构,每一次变化都有可能导致各种状况发生。越是贴近用户的系统,受下游服务影响就越大。因此,在项目中发生需求变更是难免的,我们不能抵制变更,但是一定要用切实有效的办法控制变更。变更控制的目的并不是控制变更的发生,而是对变更进行管理,确保变更有序进行。

需求发生变更后一定要认真仔细修改相关的文件,如需求文档、项目计划、设计文档等,并通过书面方式通知团队成员,不能在会议上一笔带过,并且要确保团队成员都准确地知道了变更的内容以及下一步的计划。注意,再小的变更都会给项目带来影响,多次微小的变更可能引发连锁变化,所以不能因为变更影响小而疏忽怠慢。

此外,公司的变更管理流程可以帮助我们有效控制变更,通过流程我们可以规范变更,并帮助我们将变更内容准确地传递给每一位团队成员。变更管理流程不是操作上的累赘,而是帮助我们控制项目范围、避免麻烦产生的利器。

3.3.3 开发进度管理

“向进度落后的项目中增加人手,只会使进度更加落后”,这句话摘自《人月神话 》。

伊格尔森定律(Eagleson’s Law):“自己写的代码如果有半年时间没有看过,就跟别人写的代码没什么区别了。”这句话的意思是,代码看起来会很混乱、难以理解,并且同样无法通过进度估计来预测项目成功完成的时间。

在《华尔街日报》的一次采访中,微软CEO纳德拉透露自己每月会有一个周五和公司最高领导层开一次8小时的会,在另外的3周里开4小时的会,讨论的内容从财务表现到产品使用率的实时数据,覆盖面相当广。他这样做的目的是为了让公司的高级管理层能够保持统一战线。

为了有效控制进度,开发经理需要请各个小组每天召开“晨会”以检查昨天工作进展,并且布置当天工作;另外,每周五下午召开周例会,项目组全体成员都要参加。

1.晨会

晨会的检查基准是各个小组的底层计划,以检查和确认为目的,不展开讨论。晨会规定在15分钟以内,每个小组的成员站在白板前面完成。步骤如下:

第一步,检查状态。成员逐个说明昨天任务的完成情况,今天计划的工作任务,以及遇到的问题。如果确认任务的完成情况不涉及执行细节,则仅需要回答:“完成”或者“未完成”。开发经理进行记录,一般任务按时完成可用打钩表示,未按时完成需要特别标记出来。检查完成后,将任务的完成情况分成三类:“按期完成”“延迟完成”和“延迟中”,并进行汇总统计。

第二步,调整计划。根据昨天任务的完成情况和个人任务的调整,小组一起对“底层计划”进行适当调整,确定当天每个人的工作任务。

第三步,解决问题。首先审核昨天列出的问题的状态。然后,将今天每个提出的问题记录到白板上。除非可以当场解决的简单问题,否则不对问题展开讨论,只记录到白板的问题栏。会后由相关成员讨论问题的解决方案,或另行安排时间组织专题讨论。

2.周例会

周例会检查和调整项目计划,关注的问题是:任务完成了吗?没完成的原因是什么?怎么调整?先确认了状态,再讨论该如何调整工作或计划,并一定要落实到具体的行动方案上。

只有提前建立工作结果的验收标准,才能确定如何才算是完成任务了。

项目计划要想落地,需要细化分解到每个人的工作任务中去。个人工作计划可以通过白板的方式管理,它可以实时反映进展情况。

计划进行调整和更新是常态,因为“计划本身没有用,不断地进行计划才有用”。底层计划的管理使用简单的白板就可以发挥巨大的作用,可见软件开发中“人”才是最重要的,没有“人”的主观能动性,再高级的管理工具也难以发挥作用。

3.3.4 评审的重要性

对于软件设计来说,评审与其技术设计方法本身是一样重要的,评审对于研发项目的成功而言是绝对必要的。对设计进行评审是为了尽早发现软件的欠缺,尽可能把这些缺陷在进入下一阶段工作之前予以纠正,从而避免后期付出更多的代价。

IT系统在能够真正运行前,需要很多前期的分析、设计工作,这些工作决定了系统最终能否正常运行。但由于这些工作大多落在书面上,验证其正确性不够严谨,所以更多依靠一些有经验的专家通过书面审核的方式来确认工作是否达到要求,这就是我们常说的评审。

评审过程中涉及的角色主要有4种,包括责任人、主审人、评审专家、记录员。

3.3.5 软件版本管理

因为迭代的存在,软件会产生不同的版本。每个版本的软件都是一个完整的开发生命周期。软件的诞生可以认为是第一个版本,而版本升级的过程也就是软件不断长大的过程。

由于软件开发是一个独立的生命周期,可以与软件生命周期并行,因此自然就可以做到多个版本并行开发,以并行的方式来提高生产力。一旦同一个软件的不同版本生命周期并行地被推进,就需要把代码的生命周期单独切分出来,以确保不同版本软件工程师之间的代码在空间和时间上不会产生冲突。因为软件工程师编写代码是软件开发的核心生命周期,所以要确保他们的工作能够按照各自的时间顺序来执行编码生命周期活动。

多版本并发又会产生上线冲突的问题。线上版本的运行是软件生命周期的核心,不管有多少个开发生命周期并行,线上版本都只有一个,必须确保版本上线按顺序发生。另一方面,软件开发生命周期是软件生命周期中的非核心生命周期,不可以损害核心生命周期,所以必须要排队上线,并且要保证后续的版本要包含的前一个线上版本的所有内容,确保其连续性,这就形成了发布生命周期。

我刚开始工作的时候,我们使用的源代码版本控制软件是CVS ,后来开始使用SVN,再后来出现了Git,我们来看看它们之间的差别。

Subversion(SVN)作为CVS的重写版和改进版,其目标就是作为一个更好的版本来控制软件,取代目前流行的CVS。Subversion的主要开发人员都是业界知名的CVS专家。Subversion支持绝大部分的CVS功能/命令;命令风格和界面也与CVS非常接近。当然,不同的地方正是对CVS的改进。

从技术的角度来说,在Subversion中,“文件foo.c的第5版本”这个说法是错误的,正确的说法应该是:“文件foo.c在版本库被修改了5次,即执行5次commit后是什么样子”。显然,在Subversion中,版本库被修改5次后foo.c的内容和被修改了6次后foo.c的内容很可能完全一样,因为版本库的第6次修改很可能只修改了版本库的其他部分,而并没有对foo.c进行修改。相反,在CVS中,文件foo.c的第1.1版本和第1.2版本总是不同的。

CVS只能对文件进行版本控制,不能对目录进行版本控制,因此CVS没有任何关于文件“移动”(move)操作的概念。当人为进行文件移动操作时,CVS只能注意到一个文件在一个位置被删除了,而在一个新位置创建了另外一个文件。由于它不会连接两个操作,因此也很容易使文件历史轨迹丢失。设置CVS存储库时,必须非常谨慎地为每个文件选择准确的位置,因为在设置之后,几乎就要一直使用这个位置了。

Subversion将目录作为一类特殊的文件来处理(事实上,从文件系统的角度来看,目录确实是一类特殊的文件,当目录中的子目录/文件被删除、重命名、或新的子目录/文件被创建时,目录的内容将发生改变)。因此,Subversion像记录普通文件的修改历史一样记录对目录的修改历史,当发生文件/目录的移动、重命名或拷贝操作时,Subversion能够准确记录操作前后的历史联系。同样,像对文件的不同历史版本进行比较一样,Subversion支持对目录的不同历史版本的比较,可以清晰展现目录的变化历史。

与上面两者相比较,VSS适合小团队使用,基本的配置管理功能都有。VSS最大的特点就是部署比较简单,上手比较快。VSS最大的缺点就是安全性问题、目录共享、文件方式存储等。当然VSS还只能在Windows下使用。

3.3.6 优先级安排

设置优先级的关键是,划去清单中那些既不紧急也不重要的事项,找到那些既紧急又重要的事项,并进行优先处理,然后再去做剩下的那些或是紧急或是重要的事项,这时候就需要你来安排合适的优先级了,优先处理既紧急又重要的事项是关键。我们常常把紧急的事情看作最重要的,最后反而耽误了解决重要的事情。

根据时间管理的经验,安排工作的原则顺序为:重要/紧急工作>重要/不紧急>不重要/紧急>不重要/不紧急。

记住,在资源和进度都有严格限制的情况下,需要考虑集中优势资源优先完成高优先级的需求。

3.3.7 项目管理重要性

项目管理是核心能力之一。其他方面能力的积累也往往需要依靠项目管理能力的转化。譬如核心技术的积累,首先是基于最佳实践,而最佳实践只能依靠强有力的项目管理能力才能脱颖而出。而把最佳实践转化成解决方案,也是靠项目管理能力的支撑。最后再通过项目管理,把解决方案转化成核心技术产品或平台。

很多开发经理会看轻项目管理能力,其实这是错误的。项目管理是一种软实力,只有逻辑能力强、对于业务和技术都非常了解的人才能既做好开发经理,也做好项目经理,其实也只有两者都管理得当,我们的开发项目才能够真正发布成功。

项目管理的重要性,大致包含以下几条:

支撑公司收入:无论是产品型公司,还是外包型公司,最终都在通过一个个项目的形式落地,你可以把一个产品从咨询开始到客户付款的整个过程当成一个项目。这时候,大家明白了吧,没有项目的支撑,公司哪来的收入?哪来钱养活技术人员?公司的注册资金不是用来发工资的,大家的工资是通过客户付款产生的。

项目进度把控:技术人员很容易犯的错误是技术优先,对项目的进展不太关注,而项目管理的目标就是要让这一点不出现问题,也只有做好项目管理才能真正解决进度把控问题,你不能单纯地认为只需要给开发人员添加KPI就行了,如果真的可以,为什么会有OKR的出现?为什么日本的软件企业越来越不行了,要知道,他们可是KPI的发明人和强有力的执行者。

人员工作安排:缺少强有力项目管理能力的技术领导,很容易让团队的部分成员陷入迷茫。提出的命题很大,但是落实到每位技术人员身上,一部分人会迷茫,不知道每天应该干点什么,所以最好的方式是对工作进行拆分,每天的工作要能说清楚它的具体目标、要求标准等,这样才能让大家有存在感和参与感。

3.3.8 产品质量的重要性

乔布斯说过,苹果的产品从不做市场调查,因为如果问用户要什么,用户只会在现有产品上提改进要求,不会提出创新的产品要求来。说白了,他们的理念是不问用户想要什么,问自己内心,问自己内心想要什么就好了。苹果要为那些疯狂到想要改变世界的人造工具,所以诞生了Mac、iPad、iPhone等经典产品,这确实是很牛的。他们的设计大师Ive说过,为什么用户喜欢苹果?因为用户在使用中会感受到他们打造极致体验的良苦用心。这种良苦用心确实是用户更喜闻乐见的。iPhone在2009年进入日本市场前,日本的手机是世界上独一份的存在,市场主流的Sony和夏普的手机,能看电视、能上网、能当电子钱包、能写邮件,这些属性和世界其他地区的手机都不一样。当时即使是如日中天的Nokia,也没有敲开日本市场的大门,但是iPhone一进入市场,短短一两年用户们就发现,不论是上网、写邮件、玩游戏,苹果手机都能给予更好的体验,随之日本手机市场的游戏规则发生改变,到了2010年后,日本的手机市场就再也不是原来的模样了。可见有些质量,比用户讲出来的质量更重要。直面自己内心,打造极致体验,也许是更高的境界。

刚刚工作的时候,我对于产品质量真的没什么概念。甚至,我对于什么是产品没有任何概念。因为那时候,我只是在做软件里面的一个小模块。完全不了解完整的产品是什么,更别提去想象客户会怎么用这个产品了。

质量是一种管理,是一种控制,是一种预防。只有成熟的组织,成熟的团队,才能做出高水平的产品来。

质量就是你的最高水平,以及你对用户理解的最高水平。你只有发挥出自己的最高水平,并且全身心投入,从根本上解决问题,才能带来更好的用户体验,更好的质量。

3.3.9 测试过程的区别

通用的测试过程包括计划、设计、实现、执行、完成几个步骤。

测试计划:需要确定这次测试目标和策略,估计测试用例、测试实现的工作量,确定所需的人力资源和测试环境资源。这些内容都写在测试计划中,测试计划通过评审就可以执行了。

测试设计:需要确定测试需求,设计测试用例,并对测试用例进行评审等。

测试实现:任务包括搭建测试环境、编写测试脚本、编写驱动程序和准备测试数据。根据需要尝试测试部分程序,然后修改测试用例和驱动程序等。

测试执行:根据计划将测试任务分配给测试的执行人员,测试执行人员根据测试用例输入测试数据、记录测试结果。发现问题后记录和跟踪缺陷,缺陷修改完成后进行验证。执行中还要对测试环境进行管理和监控。

测试完成:主要工作完成以后需要对测试的情况进行分析、总结,确认目标是否达成,并给出测试结论或建议。具体的工作包括评估测试活动、分析测试结果、编写测试报告,最后对测试的整体情况进行评审并形成结论。

测试工作最容易被压缩,一旦进度紧张,开发经理就会本能地选择压缩测试时间。要知道,测试是保障交付质量最常用也是最有效的手段,V模型中的每种测试都有自己的目的和针对性,不能相互替代。这好比在系统前拦上了一道道不同的网,只有每道网尽量多拦截缺陷,才能保证最终交付的质量,如果每道都放行,最后问题就会集中爆发。

单元测试与集成测试相比,测试对象有以下几点区别:

·集成测试的被测对象是单元间的组合,不同模块往往是分配给不同的人员开发。集成测试主要关注不同单元模块之间的接口和配合度。

·单元测试的测试对象是这些模块下实现具体功能的单元,一般是对应详细设计中所描述的设计内容。单元测试主要关注每个具体单元模块内部的逻辑结构和功能是否正确。

·单元测试与系统测试相比,其侧重点在于发现程序设计或实现的逻辑错误,基本属于白盒测试的范畴。

·单元测试使问题及早暴露,也便于问题的定位解决,单元测试属于早期测试,因而错误发现后就能明确知道是由哪一单元产生的。

·单元测试允许多个被测单元的测试工作同时开展。

单元、集成、系统测试比较如下:

3.3.10 测试驱动开发

测试驱动开发(Test-Driven Development,TDD )是一种不同于传统软件开发流程的新型开发方法。它要求在编写某个功能的代码之前先编写测试代码,然后只编写测试能通过的功能代码,从而推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。

TDD的基本思路就是通过测试来推动整个开发的进行。而测试驱动开发技术并不只是单纯的测试工作。

需求向来就是软件开发过程中感觉最不好明确描述且易变的东西。这里说的需求不只是指用户的需求,还包括对代码的使用需求。很多开发人员最害怕的就是后期还要修改某个类或者修改、扩展函数的接口,为什么会发生这样的事情?就是因为这部分代码的使用需求没有很好的描述。测试驱动开发就是通过编写测试用例,先考虑代码的使用需求(包括功能、过程、接口等),而且这个描述是无二义的,可执行验证的。

通过编写这部分代码的测试用例,对其功能的分解、使用过程、接口都进行了设计。而且这种从使用角度对代码的设计通常更符合后期开发的需求。可测试的要求,对代码内聚性的提高和复用都非常有益,因此测试驱动开发也属于一种代码设计的过程。

开发人员通常对编写文档非常厌烦,但要使用、理解别人的代码时通常又希望能有文档进行指导。而测试驱动开发过程中产生的测试用例代码就是对代码的最好解释。

1.TDD模型

TDD最重要的功能在于保障代码的正确性,能够迅速发现、定位Bug。而迅速发现、定位Bug是很多开发人员的梦想。针对关键代码的测试集,以及不断完善的测试用例,这也为迅速发现、定位Bug提供了条件。

TDD的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先要思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码来满足这些测试用例,最后循环进行添加其他功能,直到完成全部功能的开发。

我们这里把这个技术的应用范围从代码编写扩展到整个开发过程。对整个开发过程的各个阶段进行测试驱动,首先需要思考如何对每一个阶段进行测试、验证、考核,并编写相关的测试文档,然后开始下一步工作,最后再验证相关的工作。比较流行的测试模型是V测试模型。

在开发的各个阶段,包括需求分析、概要设计、详细设计、编码过程中都应该考虑相对应的测试工作,完成相关测试用例的设计、测试方案、测试计划的编写。这里提到的开发阶段只是举例,需要根据实际的开发活动进行调整。相关的测试文档也不一定是非常详细复杂的文档或者什么形式,但应该养成测试驱动的习惯。

关于测试模型,还有X测试模型。这个测试模型,我认为,是对详细阶段和编码阶段进行建模,应该说更详细地描述了详细设计和编码阶段的开发行为,即针对某个功能进行对应的测试驱动开发。

2.TDD优势

相对于传统的结构化开发过程方法,它具有以下优势:

1)TDD根据客户需求编写测试用例,对功能的过程和接口都进行了设计,而且这种从使用者角度对代码进行的设计通常更符合后期开发的需求。因为关注用户反馈,可以及时响应需求变更,同时因为是从使用者角度出发的简单设计,也可以更快地适应变化。

2)出于易测试和测试独立性的要求,这将促使我们实现松耦合的设计,并更多地依赖于接口而非具体的类,最终提高系统的可扩展性和抗变性。而且TDD明显缩短了设计决策的反馈循环,使我们在几秒或几分钟之内就能获得反馈。

3)将测试工作提到编码之前,并频繁地运行所有测试,这样可以尽量地避免和尽早发现错误,极大地降低后续测试及修复的成本,也提高了代码的质量。在测试的保护下,不断重构代码,以消除重复设计,优化设计结构,提高代码的重用性,从而提高软件产品的质量。

4)TDD提供了持续的回归测试,使我们拥有重构的勇气,因为若代码的改动导致系统其他部分产生任何异常,测试都会立刻通知我们。完整的测试会帮助我们持续地跟踪整个系统的运行状态,因此我们就不需要担心会产生什么不可预知的副作用了。

5)TDD所产生的单元测试代码就是最完美的开发者文档,它们展示了所有的API该如何使用以及是如何运作的,而且它们与工作代码保持同步,永远是最新的。

6)TDD可以减轻压力、降低忧虑,提高我们对代码的信心,使我们拥有重构的勇气,这些都是快乐工作的重要前提。

7)提高了开发效率。

3.TDD的重要性

(1)反映真实需求

这里存在先写测试和后写测试的区别。

先说后写测试。根据很多经验,在直接写产品实现代码时,需要考虑需求与实现的细节,往往很容易因一时疏忽做不到两者兼顾。

有人会说我可以通过后写测试来保证。第一经验是很多人都不会在实现完成后补充测试,因为还有更多的工作和需求需要实现。第二是开发人员很容易在后补的测试中只试图测试已有的实现,而不是需求本身,很容易遗漏掉一些边界检查之类,在测试时,已有的实现细节会在脑子里面先入为主,即使实现存在问题或漏洞,也很难在后补的测试中测出来。我阅读过很多面试者的测试代码,很明显都是后补的,因为一些很明显的问题没有测出来,甚至已经实现的逻辑也是只测试了一部分。而这些面试者也都承认。这样的结果就是,一旦代码嵌入开始集成之后就会暴露出问题,而后补的测试起不到对实现代码的保障需求作用,开发者不得不借助调试工具,单步跟踪实现代码的每一行去寻找问题发生的原因。

再说先写测试。按照TDD的流程,先写失败的测试,再写恰好让测试成功的实现代码,最后重构,如此往复。这样的好处在于:每先写一个测试都是在试图用自己对问题和需求的理解来定义实现代码的框架。从测试的不同角度,范围、边界、大小、功能等来定义实现代码将来会是个什么样子。一个测试接着一个测试,利用TDD的过程,恰好不多不少,正好驱动出解决这个需求和问题的实现代码来。

这里的重点在于先写测试可以让开发者把重点放在理解需求和实现需求上,而不是一开始就陷入实现的细节中,掉入两者都兼顾不好的境地。先写的测试代码,作为副产品,可以作为验证需求的得力保障。

(2)设计在其中

先写测试对于设计的好处在于,先写测试会先定义新的类,以及定义类与类之间的关系,就是定义类与类之间如何交互,每个类如何暴露自己的接口,类和类之间的引用关系。这时,开发者会认真考虑如何分解类与类之间的耦合关系,这样产生的实现代码利用了IoC和DIP的模式,会更容易实现面向接口编程。

先写测试的好处还在于,代码的可测试性很高,在加入更多的测试代码和新类的时候,同样借力于已有类的面向接口和依赖反转所带来的可测试性,从而达到新实现代码的面向接口和可测试性,形成良性循环。而这对于整体的代码和设计将十分有利。换句话说,测试即设计。

回头看后写测试的情况,因为从一开始开发者就把重心放在实现的细节和功能需求的往复上,对于代码设计、类的关系和定义很容易疏于考虑,造成耦合紧,可测试性差的情况。

(3)增强信心

在我看来,软件开发周期、软件交付最大的问题在于交付后的运行和维护阶段,这两个阶段才是软件在持续交付价值的时期。软件的可维护性,在这个阶段凸显价值。在软件交付后,包括软件开发周期期间,不可避免的就是开发者再根据新的需求,逐渐添加新的功能代码,或者修复一些已知的缺陷。

很多经验表明,在开发者按照需求添加一些新的代码进入系统,或者试图修复已有缺陷时,很容易导致既有功能出错,也就是新引入的代码打破了既有代码的逻辑,从而导致回归问题的出现。因为软件系统的可测试性差,无法做到快速、频繁、自动的回归测试,带来的可维护性自然也很差。

而作为TDD的副产品之一——可以快速频繁自动运行的测试代码,可以在开发者新引入代码之际,给予开发者足够的信心,每次添加一点新代码,一个方法,一个类,都需频繁运行已有相关的测试代码,来确保新引入代码不会打破已有的功能。

在持续集成中,这些测试代码可以帮助验证每次嵌入的代码都不会破坏已有的功能。这也是《重构》里面反复提到的在每个重构小步骤后都要运行所有的测试代码的原因所在。

(4)粒度和进度

按照TDD的原则,先写测试,可以让开发者在同一时间只关注功能需求的一小部分,把功能需求细分到一定小的粒度,用测试代码去表示这样的需求,用实现代码让测试通过,实现需求,然后重构。

这样的好处在于开发者自己的注意力和重心不用在整个功能需求内的小需求点之间犹豫,每次注重解决单个小问题,解决完一个再进入下一个小问题的解决。在保证小粒度实现的同时,保证进度即使被打断时,已经做完的内容是完整可以运行的,即至少是实现了部分需求的代码。

而后写测试的代价是实现代码很有可能包含设计上的问题,甚至含有缺陷,在完美的测试代码完成之前(事实上这是不可能的)可以交付的是可能存在严重缺陷,甚至是曲解了功能需求的实现代码。

4.TDD总结

TDD能够带来的好处:

·减少开发周期中的反馈;

·提高代码质量;

·保证设计质量;

·集中精力开发一个功能。

TDD能改善和验证设计:

·以客户端的视角编写测试代码;

·为客户端提供示例代码;

·更注重接口的设计;

·为了便于测试,需要实现松耦合;

·更少的调试时间。

3.3.11 自动化部署工具介绍

随着敏捷开发和DevOps的流行,自动化部署成为软件企业的关键能力,决定了企业的生产力水平。在网站、公共服务和云计算领域,部署是把开发人员的交付件安装到测试环境、类生产环境和生产环境等,是DevOps中的重要组成部分,决定了软件的交付效率和质量水平。手工部署费时、低效也无法保障质量,在对外提供多节点集群服务的互联网公司,手工部署变成了噩梦。而自动化部署的目标就是要取消手工操作,将全部步骤流程化、标准化、规范化,从而提高质量和效率。

下面介绍几种常用的自动化部署工具。

1.Chef

Chef是一款自动化服务器配置管理工具,可以对所管理的对象实行自动化配置,如系统管理,软件安装,开发语言是Ruby。Chef由Chef Server、Chef Workstation和Chef Node组成。Chef Node是安装了Chef-client并注册了的被管理节点,Chef-client连到Chef Server取得最新的配置指令(Cookbook)并按照指令配置自己。Chef的优点是有丰富的模块和配置脚本,缺点是需要熟悉Ruby,学习曲线比较陡峭。

2.Puppet

Puppet是一个开源的软件自动化配置和部署工具,开发语言是Ruby,支持Linux、UNIX、windows平台,使用自有的Puppet描述语言,可管理配置文件、用户、cron任务、软件包、系统服务等(Puppet把这些系统实体称之为资源)。Puppet的设计目标是简化对这些资源的管理以及妥善处理资源间的依赖关系。

Puppet采用C/S星状的结构,所有的客户端和一个或多个服务器进行交互。每个客户端周期(默认半个小时)向服务器发送请求,获得其最新的配置信息,并且保证和该配置信息同步。每个Puppet客户端每半小时(可以设置)连接一次服务器端,下载最新的配置文件,并且可以严格按照配置文件来配置客户端。配置完成以后,Puppet客户端可以反馈给服务器端一条消息。如果出错,也会给服务器端反馈一条消息。

Puppet的优点是社区支持好,有成熟的接口,能够支持每一种操作系统,安装配置简单,并且有很强的报表能力。它的缺点是你仍然需要熟悉Ruby和命令行CLI。

3.SaltStack

SaltStack是一个服务器基础架构集中化管理平台,允许管理员对多个操作系统创建一个一致的管理系统,包括VMware vSphere环境。SaltStack也属于主从(C/S)结构,由主控端(master)和被控端(minion)基于证书认证。SaltStack与特定的命令结合使用可以在一个或多个minion执行,它具备配置管理、远程执行、监控等功能。SaltStack基于Python语言实现,结合轻量级消息队列(ZeroMQ)与Python第三方模块(Pyzmq、PyCrypto、Pyjinjia2、python-msgpack和PyYAML等)一起构建。

SaltStack的优点是社区活跃,输入输出和配置采用YAML且格式清晰一致,扩展性和弹性很好。它的缺点是新手安装上手不易,Web界面没有竞争力,非Linux系统支持不是很好。

4.Ansible

Ansible是新出现的自动化运维工具,基于Python开发,集合了众多运维工具(Puppet、Cfengine、Chef、Func、Fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。Ansible基于Python开发,具有众多的模块,可以提供幂等性能操作。Ansible有以下优点:

1)相比其他自动化集群管理和运维工具Puppet、Chef,Ansible显得极其简单并且轻量级,也可以和Puppet一样进行模块扩展。

2)轻量级的好处是学习门槛低、问题少、安装快、执行快。操作完全依赖SSH而不需要安装Agent。这样的好处是不再需要维护Agent的状态,不用担心Agent挂掉。而SSH是每台服务器必备的服务。

3.3.12 编写高质量代码的难度

任何程序员都能写出机器可以阅读的代码,但只有好的程序员才能写出人可以阅读的代码。这句话道出了要写出容易阅读的代码的难度。

以程序命名为例。我们在程序代码中,往往看到很多类似icount、var、num这样名字变量,还有很多叫作manager、controllor的类,这些都是因为我们想不到应该如何命名导致的。除了名词匮乏,我们的动词往往也很匮乏,证明就是:我们的很多函数名都叫Process()、Run()、Poll()、Loop()诸如此类。如果我们把程序源代码看成是一篇文章,那么这篇文章的词汇,就是各种变量和函数的名字。如果我们在命名上困难重重,那么这篇文章也一定晦涩难懂。

命名上的困难,除了因为我们英语词汇量太小以外,另外一个原因是我们对于业务领域的不了解。在接到需求后,我们往往就急着开始所谓的设计和开发。如果我们把程序仅仅看成一些数据和算法组合,那么我们的命名必然也只是局限于这些数据结构、算法的概念上,比如我们常见到xxMap的结构体,还有xxCallback的函数指针。用这样一堆名字构建起来的程序,就好像摩斯电码一样难以理解。尽管在这些看起来都差不多的字符背后,实现的是一个个鲜活而独特的业务需求,但是光看字面是完全无法想象出来的。这个问题实际上也很好解决,就是我们在写程序之前,多去了解这个程序所在的应用领域,看看这个应用领域里面到底是有些什么样的词汇。

比如一个商业应用中,就会有Bill、Invoice、Deal等专用词汇,在游戏应用中,有Player、NPC、Monster这些概念……我们可以在几乎任何时候,都能从这些业务领域中攫取大量的词汇,来替换掉计算机领域中少得可怜的几个词。如果我们真正把代码中的命名,变成应用领域的词汇,那么这样的代码片段,就是一个描述某种业务领域的文章,如此,可读性就能大大加强。

命名本身并不影响程序的运行,但是我们也没必要直接写出好像被扰码器处理过一样的代码。如果我们的命名词汇既准确又生动,那么我们的代码一定也是非常容易读懂的。特别是,我们阅读代码的目的常常不是要评估代码的算法,而是找到某段业务逻辑的位置来进行修改,一个和业务逻辑有关联的命名,能让我们快速跳过大量不相干的代码,直接定位到需要修改的地方,这对代码维护是非常有利的。

我们知道,面向对象编程,需要以对象类型来对业务代码进行建模,而由于汉语名词的匮乏,我们常常在表达一个对象时,找不到一个专有名词来表达,而是用“做什么什么的东西”来表达这个对象,这对我们代码的设计会造成极大的困扰。因为行为的特征在对象上往往是不够稳定的,一旦我们以行为作对象的名字,而这个对象在后续的迭代中屡次被修改,这样就很容易出现名不符实的情况。

因此我们在设计面向对象代码的时候,不能仅仅以汉语的习惯去设计,而是要多找找有没有专门表达这个对象的英语名词。

如果我们想写出如同自然语言一样易读的软件代码,那么就一定要用自然语言来写文章的结构。但是很可惜的是,自然语言的文章以传情达意为目的,而软件代码主要是控制电脑工作的任务列表。这两者之间一个重要的差异就在于“语句”的存在形式上。

我们知道,代码是一行行执行的,而电脑对于数据的处理,往往存在很多类似的、重复的处理步骤。此时,就用到了“封装”:我们把类似的、重复的代码封装成子函数;用继承的方法来构建相似的数据对象。如果我们还能用恰如其分的名字来命名这些子函数和子类型,那么我们就能避免长篇累牍的重复代码,从而让代码更容易理解。也许,这种封装是一种“额外”的劳动,因为CPU不管这些,如果你只是想算出结果,那么完全可以用一个函数从头写到尾。但是如果你有意识地做一些有具体业务含义的封装,你会得到另外一个好处就是代码能更方便被重用。代码重用的首要条件是代码可理解,而封装正是对复杂的实现过程进行屏蔽,让人可以快速理解。而业务领域的重复逻辑是非常常见的,如果代码刚好是一些典型的业务流程,那么这些对应流程的代码,就一定能被重用到大量的类似业务流程的处理环节,这样的好处不言而喻。

3.3.13 业务代码对技术的作用

真正的技术和业务驱动的差距在于业务看的是今天,或者是短暂的明天,而技术放眼的是未来。但这并不是说学习算法和基础没有用,这是为业务提供理论基础,只有将技术应用到业务中,才能充分发挥它的作用。

大家要明白,技术只有支撑了业务,你才有未来,不能解决当前业务问题的技术人员,不要谈理想,因为只有能够解决问题的人,才配谈技术理想。当业务出现问题的时候,技术人员应该立即站出来,去了解、理解业务,用自己的技术手段结合业务来解决问题,而不是觉得学习技术没有用,这种想法本质上是错误的。

3.3.14 迭代的意义

软件无法一次性把所有的业务都模拟出来,必须要分步骤将一个一个阶段做出来,通过用户的反馈,一点点地升级。这就是所谓的迭代,每一个小的迭代都是瀑布式的推进,每一个迭代对下一个迭代也是瀑布式的推进,整体仍然是瀑布式的推进。所以瀑布式的推进,也就是活动按时间顺序发生,本身是没有问题的。

迭代应该如何进行呢?迭代的前提是必须要先确定优先级,而理解清楚业务的核心生命周期是最高优先级。首先要实现业务的核心生命周期,一旦业务的核心生命周期模拟出来之后,业务人员就可以操作软件了,进而可以对软件进行反馈。形成反馈环后,软件就可以通过一次一次的迭代,丰富核心生命周期的功能,或者进行架构拆分,形成新的非核心生命周期。

目录
设置
设置
阅读主题
字体风格
雅黑 宋体 楷书 卡通
字体大小
适中 偏大 超大
保存设置
恢复默认
手机
手机阅读
扫码获取链接,使用浏览器打开
书架同步,随时随地,手机阅读
首 页 < 上一章 章节列表 下一章 > 尾 页