在20世纪60年代末,大家已经非常清楚,培养再多的程序员也不会缓解软件危机。唯一的解决方式是提高程序员的生产力——也就是说,让现有的程序员能够编写更多的代码——这就是软件工程领域的起源。因此,学习软件工程的一个很好的起点,就是了解什么是生产力。
尽管生产力这个词通常被认为是软件工程学的基础,但是令人惊讶的是,很多人对它有一种曲解。如果你去问任何一个程序员关于生产力的问题,你肯定会听到“代码行数”“功能点”“复杂性度量指标”等词。事实上,在软件项目中,生产力的概念并没有什么神奇或者神秘的地方。我们可以将生产力定义为:
在一定时间内或者在一定成本下完成的单位任务的数量。
这个定义的问题在于如何定义一个单位任务。一个常见的单位任务可能指的是一个项目,但是不同的项目在规模和复杂性方面差别很大。程序员A在指定的时间内可以完成三个项目,而程序员B只能完成一个大型项目的一小部分,这样我们并不知道这两个程序员相对的生产力如何。由于这个原因,单位任务通常比整个项目要小得多。通常,它更像是一个函数、一行代码,或者某个项目中一些更小的组件。只要不同项目之间的单位任务是一样的,并且一个程序员在任何项目上完成一个单位任务花费的时间都是一样的,那么就不需要精准的度量指标。一般来说,如果我们说程序员A的效率是程序员B的 n 倍,那么指的是程序员A可以在程序员B完成一个项目的时间内,完成 n 倍的(同等)项目。
1968年,Sackman、Erikson和Grant发表了一篇令人大开眼界的文章,声称程序员之间的生产力相差10~20倍 。后来的研究和文章把这一差距推向了高倍区。这意味着某些程序员编写的代码是能力较差的程序员的20倍(或者更多倍)。一些公司甚至声称其组织中不同软件团队之间的生产力存在两个数量级的差距。这种差距令人震惊!如果一些程序员[所谓的大师级程序员(Grand Master Programmer,GMP)]的生产力比其他程序员高20倍,那么我们是否可以使用一些技巧或方法,来提高一个普通(或者低生产力)程序员的生产力呢?
因为我们不太可能通过培训的方式,将每个程序员的水平都提高到GMP级别,所以大多数软件工程方法论会采用其他技巧来提高大型团队的生产力,例如更好的管理过程。本系列图书采取了另一种方法:不是试图提高整个团队的生产力,而是教每个程序员如何提高他们自己的生产力,并朝着GMP的方向努力。
尽管单个程序员的生产力对项目的交付进度有最大的影响,但是实际上管理者更关心项目成本,即项目需要多长时间完成,以及完成项目需要多少成本,而不是关心单个程序员的生产力。因此,除小型项目之外,团队的生产力会优于团队成员的生产力。
团队生产力不仅仅是所有成员生产力的平均值,也包含了团队成员之间各种复杂的互动。会议、交流、个人之间的互动和其他活动都可能降低团队成员的生产力,但是也可以让新成员或者知识较少的团队成员,跟上项目进度和重新编写现有的代码(没有这些活动的开销,是程序员在处理小型项目时,比在处理中型项目或大型项目时效率更高的主要原因)。团队可以通过增加沟通和培训来提高其生产力,抑制重写现有代码的冲动(除非真的有必要),以及通过项目管理,让程序员第一次就能够正确地编写代码(减少重写代码的需要)。
前面给出的定义提供了两种度量生产力的方法:一种基于时间(生产力是指单位时间内完成单位任务的数量);另一种基于成本(生产力是指在给定成本下完成单位任务的数量)。有时候成本比时间重要,有时候时间比成本重要。为了衡量成本和时间,我们可以分别使用工时和实际时间。
从公司的角度来看,在项目成本中与程序员生产力相关的部分,与工时或者每个团队成员在项目上花费的小时数直接成正比。一个人天大约是8个工时,一个人月大约是176个工时,一个人年大约是2000个工时。项目的总成本就是花费在该项目上的总工时乘以团队成员的平均小时工资。
实际时间(也称为日历时间或者时钟时间)指的就是在项目期间具体时间的推移。项目时间进度表和产品的最终交付时间,通常都是基于实际时间的。
工时是实际时间乘以同时从事某个项目的团队成员数量的结果,但是优化其中一个变量并不一定也能够优化另一个变量。例如,假设你正在开发一个市政选举的应用程序。在这种情况下,最关键的变量是实际时间,无论成本如何,该软件都必须在选举日期前完成开发和部署。相比之下,正在开发下一款杀手级手机应用的“初级程序员”,可以在项目上投入更多的时间,从而延长实际的交付时间,但是却没有足够的钱来聘请多余的人手来更快地完成应用开发。
在大型项目中,项目经理会犯的最大错误之一,就是混淆了工时和实际时间。如果两个程序员可以在2000个工时(以及1000个实际小时)内完成一个项目,那么你可能会得出这样的结论:4个程序员可以在500个实际小时内完成项目。换句话说,通过在项目上增加1倍的人员,你可以用一半的时间按时完成项目。但是在现实中,这并不总是有效的(就像添加第二个烤箱,不会让烤蛋糕变得更快一样)。
通过增加人员的数量来增加每个实际小时的工时数,这种方式通常用在大型项目上比用在小型项目和中型项目上更加成功。小型项目的工作范围是有限的,一个程序员就可以跟踪所有与项目相关的细节,他不需要咨询、协调或者培训其他人来完成项目。一般来说,在小型项目中增加程序员只会消除这些优势,并且在无法显著提高交付进度的情况下,反而会显著增加成本。在中型项目中,这种平衡是微妙的,两个程序员可能比三个程序员的生产力更高 ,但是增加更多的程序员可以帮助人员不足的项目更快地完成(尽管这可能需要更高的成本)。在大型软件项目中,扩大团队规模会相应地加快项目的进度,但是一旦团队的规模超过了某个点,可能就必须增加两到三个人来完成通常由一个人完成的工作量。
随着项目变得越来越复杂 ,程序员的生产力会随之降低,因为更复杂的项目需要更深入(更长时间)的思考来理解正在发生的事情。此外,随着项目复杂性的增加,软件工程师更有可能将缺陷引入系统中,并且在系统早期引入的缺陷可能直到后期才会被发现,此时纠正这些缺陷的成本要高得多。
复杂性有两种形式。让我们考虑下面两种对复杂性的定义:
1.有复杂的、错综关联的或者相互交织在一起的部分,从而导致系统难以理解。
2.由许多相互关联的部分组成。
我们可以称第一种定义为概念复杂性。例如,在高级语言(HLL)如C/C++中,一个算术表达式可能包含复杂的函数调用、几个具有不同优先级的算术/逻辑操作符,以及大量让表达式难以理解的括号。概念复杂性可能出现在任何软件项目中。
我们可以称第二种定义为范围复杂性,即人类大脑难以消化太多信息的情况。即使某个项目的单个组成部分很简单,但是庞大的规模也会使一个人不可能理解整个项目。范围复杂性经常出现在中型项目和大型项目中(实际上,正是这种复杂性将小型项目与其他项目区分开来)。
概念复杂性会以两种方式影响程序员的生产力。首先,复杂的构想比简单的构想需要更多的思考(因此也需要更多的时间)。其次,复杂的构想会更有可能包含后续需要修正的缺陷,从而降低相应的生产力。
范围复杂性会带来不同的问题。当项目达到一定规模时,项目中的程序员可能完全不知道项目的其他部分正在发生什么,并且可能会重复开发系统中已经存在的代码。显然,这降低了程序员的生产力,因为他们浪费了编写代码的时间 。由于范围复杂性的存在,对系统资源的使用效率也会降低。当面对系统的某一部分时,一个小团队的工程师可能会测试他们各自的部分,但是他们看不到与系统其他部分的交互(甚至可能还没有准备好)。因此,系统资源使用的问题(例如,CPU周期或者内存问题)可能直到项目后期才会被发现。
通过良好的软件工程实践,有可能会降低这种复杂性。但是通常结果是相同的:随着系统变得愈加复杂,人们必须花费更多的时间来考虑它们,并且缺陷会急剧增加。最终结果就是降低了生产力。
生产力是一种可以被度量和尝试预测的项目属性。当一个项目完成时,如果团队准确地记录了项目开发期间完成的任务,那么就很容易确定这个团队(及其成员)的生产力。尽管过去项目的成功或失败不能保证未来项目的成功或失败,但是过去的生产力是预测软件团队未来生产力的最佳指标。如果你想要改进软件开发过程,那么就需要同时跟踪那些有效的技术和无效的技术,这样才知道在未来的项目中应该做什么(或者不应该做什么)。为了跟踪这些信息,程序员及其支持人员必须记录所有的软件开发活动。在软件工程中引入纯开销的一个很好的例子就是文档,虽然它对当前项目顺利进行或者提高项目质量几乎没有任何帮助,但是它能够帮助在未来的项目中预测(和提高)生产力。
Watts S.Humphrey编写的 A Discipline for Software Engineering (由Addison-Wesley Professional出版,1994年),是一本很好的介绍如何跟踪程序员生产力的读物。Humphrey教授的是一套由各种表单、指导原则和软件开发流程组成的系统,他称之为个人软件流程(Personal Software Process,PSP)。虽然PSP是针对个人的,但是它对了解软件开发流程中与程序员有关的问题非常有价值。反过来,它可以极大地帮助程序员决定如何开展他们的下一个重点项目。
通过观察团队或个人过去在类似项目中的表现来预测他们的生产力,这种方法存在的问题在于,它只适用于同类项目。如果一个新的项目与团队过去的项目有很大的不同,那么过去的表现可能无法作为一个很好的指标。因为项目在规模大小上有很大的不同,无法提供足够的信息来衡量整个项目的生产力,也就无法预测未来的生产力。因此,为了更好地评估团队和团队成员,需要使用比整个项目粒度级别更小的度量系统(度量指标)。一个理想的度量指标是独立于项目的(包括项目的团队成员、所选择的编程语言、所使用的工具,以及其他相关的活动和组件),它必须能够跨多个项目使用,以便在项目之间进行比较。虽然目前确实有一些度量指标,但是没有一个是完美的——甚至没有一个非常好的指标。然而,一个糟糕的度量指标总比没有指标要好,因此软件工程师仍在继续使用它们,直到出现更好的度量指标。在本节中,我将讨论几个常见的度量指标,以及每个指标的优缺点。
一个用来确定软件系统复杂性的简单指标,就是最终可执行文件的大小 。这里假设,越复杂的项目产生的可执行文件越大。
这个指标的优点是:
● 计算方法很简单(通常,你只需要查看一个目录下的文件列表,并且计算一个或多个可执行文件大小的总和)。
● 它不需要访问原始的源代码。
遗憾的是,可执行文件大小度量指标也有不足之处,使得大多数项目不能使用它:
● 可执行文件通常包含未初始化的数据,虽然它们增加了文件大小,但是与系统的复杂性几乎没有或者根本没有关系。
● 库函数会增加可执行文件的大小,但是它们实际上降低了项目的复杂性 。
● 可执行文件大小度量指标不是与语言无关的。例如,汇编语言程序往往比HLL的可执行程序紧凑得多,但是大多数人认为汇编程序比同等的HLL程序复杂得多。
● 可执行文件大小度量指标是与CPU相关的。例如,80x86 CPU的可执行文件通常比为ARM(或者其他RISC指令集)架构的CPU编译的相同程序要小。
可执行文件大小度量指标的一个主要缺陷是,某些可执行文件格式包含了未初始化的静态变量空间,这意味着对源文件的微小更改可能会极大地改变可执行文件的大小。解决这个问题的一种方法是,只计算某个源文件中的机器指令(可以是以字节为单位的机器指令大小,也可以是机器指令的总数)。虽然这个指标解决了未初始化的静态数组的问题,但是它仍然存在可执行文件大小度量指标的所有其他问题:它依赖于CPU,它会计算不是由程序员编写的代码(例如,第三方库代码),它是与语言相关的。
代码行数[LOC,或者KLOC(千行代码)]指标是目前使用的最常见的软件度量指标。顾名思义,它是一个项目中源代码行数的总数。这个度量指标有一些明显的优缺点。
简单地计算源代码行数似乎是使用LOC度量指标的最常见形式。编写一个代码行计数程序相当简单,Linux等操作系统上已有的大多数字数统计程序,都可以用来统计代码行数。
以下是关于LOC度量指标的一些常见优点:
● 无论使用何种编程语言,编写一行源代码所花费的时间都是相同的。
● LOC度量指标不受项目中使用的库(或者其他重用的代码)影响(当然,假设你不会统计依赖库的源代码行数)。
● LOC度量指标与CPU无关。
不过,LOC度量指标确实也有一些缺点:
● 它不能很好地说明程序员已经完成了多少工作。一个VHLL程序中的100行代码,通常比100行汇编代码要完成的功能更多。
● 它会假设每一行源代码的成本都是相同的。然而,事实并非如此。空行的开销很小,简单的数据声明的概念复杂性较低,而复杂的布尔表达式语句的概念复杂性很高。
语句数量度量指标是对某个源文件中编程语言的语句数量进行统计。它不会统计空行或者注释,也不会统计跨多行编写的单条语句。因此,在统计程序员的工作量方面,它比LOC的效果更好。
尽管语句数量度量指标比代码行数指标能够更好地反映程序的复杂性,但是它也存在许多相似的问题。它衡量的是工作量而不是完成的工作,它不像我们希望的那样与语言无关,并且它会假设程序中每条语句需要的工作量都是相同的。
功能点分析(Function Point Analysis,FPA)最初被设计成一种在编写任何源代码之前,预测该项目所需工作量的机制。它的基本思想是考虑程序需要的输入数量、产生的输出数量,以及程序必须要执行的基本计算,然后使用这些信息来决定项目的时间进度 。
与代码行数或者语句数量等简单的度量指标相比,FPA有几个优点。它是真正独立于语言和系统的,只取决于软件的功能定义,而非功能实现。
不过,FPA也有一些严重的缺点。首先,与代码行数或者语句数量度量指标不同,它不能直接计算出程序中“功能点”的数量。而且分析过程是主观的,即必须由分析程序的人来决定每个功能的相对复杂性。其次,FPA从未能够成功地实现自动化。这使得我们无法分析一个程序在哪里算作结束,而另一个程序又从哪里算作开始,以及如何对每个功能点评估不同的复杂性值(这又是一项主观任务)。由于这种人工分析相当耗时且代价昂贵,所以FPA不像其他度量指标那样流行。在很大程度上,FPA是在项目完成时而不是在开发期间应用的一个事后分析(项目结束时)工具。
正如前面所提到的,LOC和语句数量度量指标的一个基础性错误在于,它们假设每条语句都具有相同的复杂度。FPA的情况稍好一些,但需要分析师为每条语句评估一个复杂程度。遗憾的是,这些度量指标都不能准确地反映出完成被度量工作所要付出的努力程度,因此都不能用来完整地评估程序员的生产力。
Thomas McCabe开发了一种称为圈复杂度的软件度量指标,通过计算源代码的路径数量来度量代码的复杂性。它以程序的一个流程图开始,流程图中的节点对应于程序中的语句,节点之间的边对应于程序中的非顺序控制流。通过统计流程图中涉及的节点数量、边数量和连接组件的数量,从而评估出整个代码的圈复杂度。假设有一个1000行的printf程序(没有其他内容),那么它的圈复杂度应该是1,因为程序只有一条路径。现在假设有第二个例子,它混合了大量的控制结构和其他语句,那么它的圈复杂度会高得多。
圈复杂度度量指标很有用,因为它是一个客观的度量指标,而且可以通过编写程序来计算这个值。它的缺点是与程序的多少没有关系,也就是说,它对待一条printf语句就像对待一行1000条printf语句一样,尽管第二个版本显然需要更多的工作(即使额外的工作只是一堆剪切、粘贴操作)。
我们还可以设计出其他一些指标用来度量程序员的生产力。一个常见的度量指标就是统计程序中操作符的数量。这个度量指标能够反映出这样一个事实:有些语句(包括那些不涉及控制路径的语句)会比其他语句更加复杂,需要花费更多的时间来编写、测试和调试。另一个度量指标是统计程序中令牌(例如,标识符、保留字、操作符、常量和标点符号)的数量。不过,不管使用什么样的度量指标,它都有缺点。
许多人尝试使用多个度量指标的组合(例如,使用代码行数乘以圈复杂度和操作符的数量)来创建更加“多维”的指标,以便在代码量较少时更好地评估工作量。遗憾的是,随着度量指标越来越复杂,其会变得越来越难以在指定的项目中应用。LOC度量指标之所以成功,是因为你可以使用UNIX wc(单词统计)工具(它也可以用来统计行数)来快速了解某个程序的大小。而统计其他度量指标通常需要一个专门的、依赖于某种编程语言的应用程序(假设该指标是可自动化的)。出于这个原因,尽管人们提出了大量的度量指标,但很少有像LOC那样广泛流行的。
通过大致测量一个项目的源代码量的度量指标,可以推测出在该项目上要花费的时间,前提是假设每一行代码或者每一条语句都需要一些平均时间来编写,但是代码行数(或者语句)和要完成的工作之间并没有绝对的关系。遗憾的是,虽然度量指标可以用于测量程序的一些物理属性,但是很少能测量出我们真正想知道的答案,即编写代码所需的脑力劳动究竟是多少。
几乎所有度量指标的另一个失效原因是,它们都假定更多的工作会产生更多(或者更复杂)的代码。但是这并不总是正确的。例如,优秀的程序员通常会花费精力来重构他们的代码,使其更加精简。在这种情况下,更多的工作实际上会产生更少的代码(以及更简单的代码)。
度量指标也没有考虑到与代码相关的环境问题。例如,为裸机嵌入式设备编写的10行代码,是否等同于为SQL数据库应用程序编写的10行代码?
所有这些度量指标都没有考虑到某些项目的学习曲线。Windows设备驱动程序的10行代码,是否可以等价于web applet中的10行Java代码?其实这两个项目的LOC值是不可比较的。
最终,大多数度量指标都失效了,因为它们测量的是错误的东西。它们测量的是程序员编写的代码量,而不是程序员对整个项目的总体贡献(即生产力)。例如,一个程序员可以使用一条语句来完成一个任务(比如一个标准库调用),而另一个程序员可以编写几百行代码来完成相同的任务。大多数度量指标都会认为这两个程序员中后者的生产力更高。
由于这些原因,即使是目前最复杂的软件度量指标也有根本的缺陷,这使得它们无法完全有效反映出程序员的生产力。因此,选择一个“更好的”度量指标通常不会比使用一个“有缺陷的”度量指标有更好的结果。这也是LOC度量指标一直如此流行的另一个原因(也是本书使用它的原因)。虽然LOC是一个非常糟糕的度量指标,但是它并不比许多其他现有的度量指标差很多,而且不需要编写特殊的软件就可以很容易地统计它。
早期的软件工程文献声称,一个程序员平均每天可以产生10行代码。在1977年的一篇文章中,Walston和Felix认为每个开发人员每月大约会产生274行代码 。这两个数字都描述了在产品生命周期内调试和记录代码的生产情况(即LOC除以所有程序员在产品第一次发布到最后一次发布期间所花费的时间),而不是简单地日复一日编写代码。即便如此,这些数字似乎也还是很低。为什么?
在项目开始时,程序员可能会每天快速编写1000行代码,然后逐渐放慢速度,研究项目某个部分的解决方案、测试代码、修复bug、重写一半代码,然后记录他们的工作。到该产品第一次发布时,生产效率已经下降到最初一两天的1/10:从每天1000行代码下降到不到100行代码。一旦发布了第一个版本,通常就会开始开发第二个版本,然后是第三个版本,依此类推。在产品生命周期中,可能会有几个不同的开发人员开发代码。等到项目发布最后一版时,它可能已经被重写了多次(极大地降低了工作效率),而且有些程序员花费了大量宝贵的时间来学习代码是如何运行的(这也会降低他们的工作效率)。因此,在产品的整个生命周期中,程序员的平均生产力会下降到每天10行代码。
软件工程生产力研究最重要的一个结果是,提高生产力的最好方式不是通过发明一些方法,让程序员在单位时间内编写两倍的代码,而是减少在调试、测试、记录文档和重写代码,以及给新程序员讲解代码(一旦第一个版本已经存在)上所浪费的时间。为了减少这些浪费,改进程序员在项目中使用的流程,要比培训他们在单位时间内编写两倍的代码容易得多。软件工程总是在关注这个问题,并试图通过减少所有程序员花费的时间来提高生产力。个人软件工程的目标是减少个体程序员在项目上所花费的时间。
正如前面所提到的,虽然生产力对于管理层来说是发放奖金、加薪或口头表扬的重要因素,但是跟踪生产力的真正目的是为了预测未来项目的开发时间。过去的结果并不能保证未来的生产力,所以你还需要知道如何估计项目进度(或者至少是项目中你的那部分进度)。作为个体软件工程师而言,你通常没有足够的背景、教育经历或经验来确定时间进度,所以你应该与项目经理进行沟通,向他们解释时间进度表中需要考虑的事项(不仅仅是编写代码所需的时间),然后构建一个估计时间的方法。尽管如何正确估计项目所有细节的内容,已经超出了本书的范围(请见2.11节“获取更多信息”),但是我们应该知道,如何估计开发时间取决于你所参与的项目的规模,比如是一个小型项目、中型项目还是一个大型项目,或者仅仅是一个项目的某一部分。
根据定义,一个小型项目是一个软件工程师可以单独完成的项目。对项目进度的主要影响是这个软件工程师的能力和生产力。
估计小型项目的开发时间比估计大型项目要容易得多,也更加准确。小型项目不会涉及并行开发,并且在进度表中只需要考虑单个开发人员的生产力。
毫无疑问,估计小型项目的开发时间,第一步是识别和理解需要完成的所有工作。如果此时项目的某些部分还没有被清晰定义,那么在进度表中就会引入相当大的错误,因为这些未定义的组件,不可避免地要花费比你想象的多得多的时间。
在估计某个项目的完成时间时,设计文档是项目中最重要的部分。如果没有详细的设计,那么就不可能知道项目由哪些子任务组成,以及每个子任务将花费多少时间来完成。一旦你将项目分解成适当大小的子任务(一个合适的大小,就是清楚地知道完成它需要多少时间),你需要做的就是将所有子任务的时间汇总起来,从而产生一个合理的初步估计。
然而,人们在估计小型项目的进度时最常犯的一个最大的错误是,他们会把子任务的时间加到进度表中,而忘记了会议、电话、电子邮件和其他管理任务的时间。他们还容易忘记增加测试时间,以及发现和修复缺陷(和重新测试)的时间。因为很难估计软件中存在多少缺陷,以及解决这些缺陷需要多少时间,所以大多数管理人员会将进度表中第一次估计的值扩大2~4倍。假设程序员(或团队)在项目上能够保持合理的生产力,那么这个公式可以用来很好地估计小型项目的开发时间。
从概念上讲,中型项目和大型项目是由许多小型项目(分配给各个团队成员)组成的,它们结合起来形成最终的结果。因此,第一种估计大型项目进度的方法,是将其分解为一堆较小的项目,然后估计每个子项目的开发进度,再合并(汇总)起来进行总体估计。这是估计小型项目进度的放大版本。遗憾的是,在现实情况中,这种估计方式会带来很多问题。
第一个问题是,中型项目和大型项目会存在小型项目中不存在的问题。一个小型项目通常只有一个工程师,并且正如前面所提到的,对小型项目的时间安排完全取决于这个工程师的生产力和可投入时间。在一个较大的项目中,许多人(包括工程师以外的人)都会影响到对进度的估计。一个拥有关键知识的软件工程师可能在休假或者生病几天,耽误了另一个工程师获取所需信息来开展工作。从事大型项目开发的工程师通常每周都会有几次会议(这些会议在大多数日程安排中都没有提到),这些会议会让他们持续下线几个小时,也就是说,在这段时间内他们没有在编程。在大型项目中,团队组成结构也可能会发生变化,例如,一些有经验的程序员离开了,而另一些人不得不继续学习其他子任务,而且新加入项目的程序员需要时间来跟上进度。有时候,即使是为新员工配备一个计算机工作站,也要花上几周的时间(例如,在一家非常官僚的大公司的IT部门)。等待购买软件工具、开发硬件以及来自组织其他部分的支持,也会造成进度安排失效的问题。这样的例子不胜枚举。很少有进度估计能够准确地预测出这些会消耗多少时间。
最终,估计中型项目和大型项目的进度会包括4个任务,即把项目分解成多个较小的项目,估计这些小项目的进度,增加集成测试和调试的时间(也就是让各个小项目结合到一起并且正常工作的时间),然后通过一个乘数因子得到最终的合计结果。这种方式并不精确,但是它到今天依然有效。
因为项目进度估计涉及对开发团队未来能力的预测,所以很少有人相信计划的进度是完全准确的。然而,常见的软件开发进度预测尤其糟糕,其中有以下一些原因:
它们是在研发项目。 研发项目包括做一些你以前从未做过的事情。它们需要一个研究阶段,在此期间开发团队需要分析问题并试图确定解决方案。通常,没有办法预测研究阶段需要多长时间。
管理层已经预先制定了时间表。 通常,市场部门决定其想要在某个日期之前销售产品,而管理部门则通过从该日期向前倒推时间来制订项目计划。在要求开发团队估计子任务的时间之前,管理层已经对每个任务应该花费的时间有了一些先入为主的想法。
这个团队以前做过类似的事情。 管理层通常会认为,如果你以前做过某件事情,那么第二次做起来会更容易(因此会花费更少的时间)。在某些情况下,这是有道理的。如果团队做的是同一个研发项目,那么第二次做就会更容易,因为他们只需要做开发,可以跳过(至少大部分)研究阶段。但是,认为项目第二次做总是更容易的假设很少是正确的。
没有足够的时间和资金。 在很多情况下,管理人员会在项目必须完成的前提下,设置某种资金或时间限制,否则该项目就会被取消。对于那些薪水跟项目进展挂钩的人来说,这是错误的。如果让他们在说“是的,我们能满足计划”和找一份新工作之间做出选择,大多数人即使知道机会很渺茫,也都会选择前者。
程序员会夸大他们的效率。 有时候,当软件工程师被问到他们能否在一定时间内完成一个项目时,他们不会谎称需要多长时间,而是对他们的表现做出乐观的估计,但是实际上在工作中很少会站得住脚。当被问及他们可以产生多少有效工作时,大多数软件工程师会给出一个图表,展示其有史以来在一个较短时间内的最大产出(例如,在“危机模式”下每周可工作60~70个小时),但是他们很少会考虑意料之外的困难(例如,出现一个很严重的系统缺陷)。
进度取决于额外的时间。 管理层(和某些工程师)经常会认为,当进度开始延后时,程序员总是可以多投入“几个小时”来赶上进度。结果是,进度往往比他们期望的会更加延后(因为他们忽略了工程师大量加班所带来的负面影响)。
工程师就像搭积木一样。 在项目进度安排中一个常见的问题是,管理层认为可以通过向项目中增加程序员人数来提前发布软件。然而,正如前面所提到的,这并不一定是正确的。你不能通过在一个项目中增加或者减少工程师人数,就期望项目进度能产生相应的变化。
对子项目的估计是不准确的。 实际的项目进度安排是以自上而下的方式制订的。整个项目被分成几个较小的子项目,然后这些子项目又被分成几个子项目,依此类推,直到子项目的规模非常小,有人可以准确地预测每个子项目所需的时间。但是,这种方法会面临三个挑战:
● 是否愿意尽力以这种方式来安排进度(即对项目提供正确、准确的自上而下的分析)。
● 是否能够对小的子项目进行准确的估计(特别是软件工程师,他们可能没有经过适当的管理培训,所以不清楚哪些因素是在估计进度时需要考虑的)。
● 是否愿意接受进度预估的结果。
尽管参与项目的每个人都有良好的意图,但是许多项目明显落后于计划,所以管理层必须要求加快开发进度,以满足一些重要的里程碑节点。为了能在最后期限之前完成任务,工程师通常每周会投入更多的时间来缩短(实际的)交付日期。当这种情况发生时,就说该项目处于“危机模式”。
危机模式可以在短时间内产生效果,使工作在最后期限之前被(迅速地)完成,但是总的来说,危机模式并不总是有效的,并且会导致较低的生产力,因为大多数人需要照顾工作以外的事情,需要时间去休息、减压,需要让他们的大脑花时间来思考已知的所有问题。当你疲劳工作时会导致犯错,而错误通常需要更多的时间来纠正。从长远来看,放弃使用危机模式,坚持每周工作40个小时会更有效率。
使用危机模式的最佳方法是,在整个项目中添加里程碑,从而产生一系列“小危机”,而不是在最后生成一个大危机。每个月多花一天或几天的时间,比在项目快结束时连续干几周要好得多。为了赶在最后期限之前完成工作,有一天或两天多工作几个小时,不会对你的生活质量产生负面影响,也不会让你感到疲惫不堪。
除健康和生产力的问题以外,在危机模式下工作还可能会带来进度安排、道德和法律方面的问题:
● 糟糕的进度安排也会影响未来的项目。如果你每周工作60个小时,那么管理层会认为未来的项目也可以在相同的(实际的)时间内完成,期望你在未来也按照这样的速度完成,而不需要任何额外的补偿。
● 在危机模式下长时间运行的项目,技术人员的流动率很高,进一步降低了团队的生产力。
● 危机模式也会导致一个法律问题,即超时工作却没有加班费。视频游戏行业的几起备受瞩目的诉讼表明,工程师有权获得加班费(他们不是无薪员工)。即使你的公司能够挺过这些官司,对汇报时间、管理开销和工作计划的要求也会变得更加严格,从而导致生产力的下降。
同样,如果运作得当,危机模式也可以帮助你在特定的期限内完成任务。但最好的解决办法是制订更好的进度计划,完全避免使用危机模式。
本章花费了大量的时间来定义生产力和测量它的度量指标。但是我们并没有花很多时间来描述一个程序员如何提高其生产力,从而成为一个卓越的程序员。关于这个主题可以写(而且已经写了)一整本书。本节会介绍一些可以用来提高个人和团队工作效率的技术。
作为一个软件开发人员,你将大部分时间花费在使用软件开发工具上,并且工具的质量对生产力有巨大的影响。遗憾的是,选择开发工具的主要标准似乎是对工具的熟悉程度,而不是工具对当前项目的适用程度。
请记住,当你在项目开始时选择工具的时候,你可能需要在整个项目期间(甚至更长时间)都必须使用这些工具。例如,一旦你开始使用缺陷跟踪系统,由于数据库文件格式不兼容,你可能就很难切换到其他系统,源代码控制系统也是如此。幸运的是,软件开发工具(尤其是IDE)现在已经相对成熟,而且许多工具之间都是可互操作的,因此你不太会做出错误的选择。尽管如此,但是在项目开始时仔细考虑如何选择工具,可以为你省去很多后续的烦恼。
对于一个软件开发项目来说,最重要的是选择使用哪种编程语言,以及使用哪种编译器/解释器/转换器。选择最佳的语言是一个很难的问题。你很容易证明一些编程语言是正确的,因为你熟悉它们,你不需要再去学习它们;然而,未来新的工程师在学习编程语言的同时还要维护代码,他们的工作效率可能会低得多。此外,选择某些语言可以简化开发过程,充分提高生产力,以弥补学习语言所损失的时间。正如前面所提到的,选择一种糟糕的语言可能会浪费很多开发时间,直到你发现它不适合这个项目,于是不得不重新开始。
编译器性能(每秒可以处理一个普通源文件的多少行代码)会对你的生产效率产生巨大的影响。如果编译器平均编译一个源文件只需要2秒钟而不是2分钟,那么使用更快的编译器可能会提高效率(尽管更快的编译器可能会缺少一些特性,从而在其他方面降低了效率)。工具处理代码的时间越少,留给设计、测试、调试和优化代码的时间就越多。
使用一系列能够很好地协同工作的工具也很重要。今天,我们认为使用集成开发环境(IDE)是理所当然的,它将编辑器、编译器、调试器、源代码浏览器和其他工具集成到一个单独的程序中。这使得我们可以在屏幕的同一个窗口内,快速地在编辑器中进行更改,重新编译源代码模块,并在调试器中运行结果,极大地提高了工作效率。
然而,你经常不得不在IDE之外处理项目的某些工作。例如,有些IDE不支持源代码控制或者缺陷跟踪(尽管许多IDE都支持)。大多数IDE没有提供用于编写文档的文字处理程序,也没有提供简单的数据库或者电子表格功能来维护需求列表、设计文档或者用户文档。最有可能的是,你不得不使用一些IDE之外的程序,例如文字处理工具、电子表格、绘图/图形工具、Web设计工具和数据库程序等,来完成项目所需的所有工作。
在IDE之外运行程序不是问题,只要确保你选择的应用程序,与你的开发过程和IDE生成的文件是兼容的即可(反之亦然)。如果在IDE和外部应用程序之间移动文件,你必须不断运行一个转换程序,那么你的生产效率将会降低。
我能为你推荐一些工具吗?不能。因为项目的需求多种多样,所以我在这里无法给出这种建议。我的建议是在项目开始时就注意到这些问题。
但是我可以给出一个建议,就是在选择开发工具时避免有“为什么我们不尝试这种新技术”的想法。在使用了一个开发工具6个月之后(并基于它来编写源代码),如果你发现它不能够完成工作,那么后果可能是灾难性的。除了考虑产品开发,你还要认真评估这些工具,只有在确信新工具确实有用之后,才能选择使用它们。苹果公司的Swift编程语言就是一个典型的例子。在Swift v5.0发布之前(大约在Swift首次发布的4年之后),使用Swift语言一直是令人沮丧的。每年苹果公司都会发布一个与之前版本代码不兼容的新版本,迫使你不得不修改旧的程序。此外,该语言的早期版本中缺少许多功能,并且一些功能并不完善。直到5.0版本(在编写本书时发布)以后,Swift语言才变得相对稳定。然而,那些早期迎合这一“潮流”的可怜的人,为该语言的不成熟发展付出了代价 。
遗憾的是,在许多项目中,你自己无法选择开发工具。这个决定来自上级的命令,或者你沿用产品以前的工具。抱怨它不仅会浪费时间和精力,还会降低你的工作效率。相反,你应当充分利用你所拥有的工具集,并成为使用它的专家。
对于任何项目,我们都可以将工作分为两种类型:与项目直接相关的工作(例如,为项目编写代码或者文档)和与项目间接相关的工作。间接的活动包括会议、阅读和回复电子邮件、填写考勤卡和更新日程安排。这些都是日常开销活动,它们增加了项目的时间和成本,但是并不直接有助于完成工作。
通过遵循Watts S.Humphrey在 Personal Software Engineering (《个人软件工程》)中介绍的方法,你可以跟踪在项目期间将时间都花费在了何处,并且很容易地看到直接花费在项目上的时间,以及花费在其他活动上的时间。如果你的其他活动时间超过总时间的10%,那么你应当重新考虑日常活动。你应当试着减少或者整合这些活动,来降低它们对你的工作效率的影响。如果你没有跟踪项目之外花费的时间,那么就会错过通过减少管理开销来提高生产力的机会。
如果不是最后期限迫在眉睫,人们往往会放慢工作节奏,当最后期限临近时,他们又会进入“超级模式”,这是人类的天性。如果没有目标,那么人们就很难高效地完成工作。如果没有最后期限,那么人们就很难有动力及时去实现这些目标。
因此,为了提高你的工作效率,一定要有明确的目标和子目标,并将其附加到里程碑上。
从项目管理的观点来看,里程碑是项目中的一个标记点,它代表了工作的进展程度。一个好的管理者总是会在项目进度中设定目标和里程碑。然而,很少有时间进度计划会为单个程序员提供有用的目标。这就是个人软件工程需要发挥作用的地方。要想成为一个超级高效的程序员,你需要对自己项目中的目标和里程碑进行微管理。一些简单的目标,例如,“我要在吃午饭之前完成这个功能”或者“我要在今天回家之前找到这个错误的根源”,可以让你集中注意力。而另一些更大的目标,例如,“下周二我将完成这个模块的测试”或者“今天我将运行至少20个测试程序”,可以帮助你评估生产力,并确定你是否实现了自己的目标。
能否提高工作效率取决于你的态度。虽然别人可以帮助你更好地管理时间,或者在你陷入困境时帮助你,但是最重要的是你必须主动改善自己。你需要时刻注意自己的节奏,不断努力提高自己的表现。通过跟踪自己的目标、努力和进步,你会知道什么时候需要“让自己振作起来”,通过更努力地工作来提高工作效率。
缺乏动力可能是提高工作效率的最大障碍之一。如果你的态度是“啊,我今天还要做这件事”,那么你完成这个任务所花费的时间可能比你的态度是“哇!这是最棒的部分!这将会很有趣!”更多。
当然,你做的每一个任务并不都是有趣的,这是个人软件工程会接触的一个场景。如果你想要保持高于平均水平的生产力,那么当一个项目让你感到“缺乏动力”时,你需要有足够的自我激励。试着创造一些理由让这份工作更有吸引力。例如,为自己创造一些小挑战,并在完成后奖励自己。一个高效的软件工程师会经常练习自我激励:你对一个项目保持动力的时间越长,你的工作效率就越高。
专注于一个任务并消除干扰,是另一种能够显著提高生产力的方法。你应当能够“进入状态”。通过这种方式工作的软件工程师比那些一心多用的人更有效率。为了提高工作效率,你应尽可能长时间地专注于某一个任务。
在没有任何视觉刺激(除了显示屏)的安静环境中,专注于一个任务是最容易的。有时候,工作环境并不利于让你专注。在这种情况下,戴上耳机,播放背景音乐可能有助于消除干扰。如果音乐太让人分心,则可以试着听听白噪声,网络上有一些白噪声的应用程序。
无论什么时候你在工作中被打断了,你都需要时间恢复状态。事实上,你可能需要半个小时才能完全集中精力工作。当你需要集中精力完成一个任务时,可以贴一个告示说只有紧急的事情才能打断你,或者在你的工作台附近贴上“办公时间”,即你可以被打断的时间,例如,你可以允许别人打断你5分钟。回答同事们自己能想明白的问题,可以节省其10分钟的时间,但是这可能会浪费你半个小时。你必须作为团队的一部分工作,成为一个好队友,然而,同样重要的是,需要确保过度的团队互动不会降低你(和其他人)的生产力。
在一个典型的工作日中,会有许多已经预定的工作中断时间,例如用餐时间、休息时间、会议、行政管理(如处理电子邮件和时间核算)等。如果可能的话,你可以试着在这些事件周围安排其他活动。例如,关闭任何邮件提醒,因为在几秒钟内回复邮件很少是必需的,而且如果有紧急情况,别人会亲自找到你或者打电话给你。如果别人确实希望你能快速回复,那么你可以设置一个闹钟,提醒自己在固定时间查收邮件(对待短信和其他干扰也是如此)。当你接到很多非紧急电话时,你可以考虑把手机调成静音,在休息时每隔1小时左右查看一下短信。如何做取决于你的个人和职业生活,但是你受到的干扰越少,你的工作效率就越高。
有时候,无论你多么有动力,你都会对自己的工作感到无聊,也很难集中注意力,你的工作效率会大幅下降。如果你不能进入状态,无法将注意力集中在任务上,那么就休息一下,做一些其他事情。不要以无聊为借口,在一个又一个任务之间来回奔波,却又完成不了多少工作。但是,当你真的遇到障碍,无法前进时,不妨试着换一些你可以做得更有成效的事情。
你应该尽力尝试处理所有分配给你的任务。虽然这不会提高你的工作效率,但是如果你不断地向其他工程师寻求帮助,则可能会降低他们的生产力(记住,他们也需要保持专注,避免干扰)。
如果你正在做一个需要更多知识的任务,而你又不想经常打断其他工程师,那么你可以有以下几种选择:
● 花点时间自学,这样你就能完成任务了。虽然这可能会影响到你短期的工作效率,但是你所获得的知识将帮助你完成未来类似的任务。
● 去见你的经理,解释你遇到的问题。讨论一下是否可能把任务重新分配给更有经验的人,然后给你分配一个你能够更好地处理的任务。
● 与你的经理安排一次会议,在不太影响其他工程师工作效率的时间(例如,在工作日的早上)寻求帮助。
有时候,你的自立态度可能会有点过头。你可能在一个问题上花费了太多的时间,而你的队友只需要几分钟就能解决这个问题。成为一个卓越程序员的一个方面是,认识到自己陷入困境,需要帮助才能继续前进。当你被困住的时候,最好的方法是,设置一个定时闹钟——在被困在这个问题上几分钟、几小时甚至几天之后,寻求帮助。如果你知道该向谁寻求帮助,那么就直接寻求帮助。如果你不确定,那么就和你的经理谈谈。最有可能的情况是,你的经理会指引你找到合适的人,这样你就不会打扰到那些无论如何也帮不了你的人。
团队会议(每天或每周)是向团队成员寻求帮助的好地方。如果你手头上有好几个任务要做,而你又被困在一个特定的任务上,那么可以把它放在一边,先做其他任务(如果可能的话),然后把你的问题留到团队会议上来问。如果你在会议之前把工作做完了,则可以让你的经理分配一些其他工作,这样你就不必打扰别人了。此外,在处理其他任务时,你可能也会找到解决方案。
没有什么比团队成员士气低落能更快地扼杀一个项目了。这里有一些建议可以帮助你克服士气低落:
● 了解项目的商业价值。通过了解或提醒自己项目的实际用途,你将对项目投入更多的热情,也更感兴趣。
● 对项目(你的部分)负责。当你对这个项目负责时,你的骄傲和荣誉就与它绑在一起了。不管发生什么事情,请确保你总是可以谈论自己对项目所做的贡献。
● 避免在你无法控制的项目问题上投入精力。例如,如果管理层做出了一些影响项目进度或者设计的糟糕决定,那么就在这些限制范围内尽最大努力工作。当你可以努力去解决问题时,不要只是坐在那里抱怨那些管理决定。
● 如果你的个性给你的士气带来了问题,请与你的经理和其他受影响的人员讨论一下。沟通是关键。任由问题继续下去,只会导致更大的士气问题。
● 时刻警惕可能会降低士气的情况和态度。一旦项目团队的士气开始下降,通常就很难恢复。你越早处理士气问题,就越容易解决它们。
有时候,财务、资源或者个人问题都会降低项目参与者的士气。作为一个卓越的程序员,你的工作就是躬身入局,战胜挑战,继续编写卓越的代码,并且鼓励项目中的其他人也这么做。这样做并不总是容易的,但是没有人说过成为一个卓越的程序员是容易的。
Gene Bellinger在Systems Thinking上发表的 Project Systems ,2004年。
Robert Heller和Tim Hindle编写的 Essential Managers:Managing Meetings ,DK Publishing,1998年。
Watts S.Humphrey编写的 A Discipline for Software Engineering ,Addison-Wesley Professional,1994年。
Harold Kerzner编写的 Project Management:A Systems Approach to Planning,Scheduling,and Controlling ,Wiley,2003年。
Patrick Lencioni编写的 Death by Meeting:A Leadership Fable … About Solving the Most Painful Problem in Business ,Jossey-Bass,2004年。
Robert E.Levasseur编写的 Breakthrough Business Meetings:Shared Leadership in Action ,iUniverse.com,2000年。
James P.Lewis编写的 Project Planning,Scheduling,and Control ,McGraw-Hill,2000年。
Steve McConnell编写的 Software Project Survival Guide ,Microsoft Press,1997年。
Tom Mochal在TechRepublic网站上发表的 Get Creative to Motivate Project Teams When Morale Is Low ,2001年9月21日。
Robert K.Wysocki和Rudd McGary编写的 Effective Project Management ,Wiley,2003年。