Peripateticism

Yuens' blog

View the Project on GitHub

header

《现代软件工程-构建之法》读书记录

年前看到项目中出现不少问题,比方有些是因为很多不规范的操作导致的重复无意义的工作,忽然想起之前借来的一本《现代软件工程-构建之法》书没有看完,年后在看完嘉信公司发展史那本书后,打算再看看这本,找找共鸣。

下面是读书的记录,里面有自己的一点感想或者对原书的直接抄录改写。小结如下:[toc]

1. 软件工程师的成长

1.1 个人能力的衡量

两个人完成各自的三个任务,让他们对这样的任务进行时间上的估计,以及实际用时的比较。

虽然可能第一个人在前两项工作中完成的很快,但是在他的实际总时长上,可能反而跟第二个人差不多,第二个人的方差,就是说他实际用时在三个项目上,任务上所花费的时间是差不多的,反而比第一个人更稳定,这样的人在交付的时候是衡量能力的一个重要方面

虽然说软件项目需要创新,需要意外和惊喜,但更多的是可成可重复和常规的人物,所以如果能降低任务交付的标准方差按时交付工作,那么内部和外部的客户也会对你的工作有信心,也更喜欢,更愿意与你合作。

通过标准方差来衡量工作的能力这也是六西格玛方法的核心概念。下面简介来自百科对六西格玛的介绍:

六西格玛是一种改善企业质量流程管理的技术,以“零缺陷”的完美商业追求,带动质量成本的大幅度降低,最终实现财务成效的提升与企业竞争力的突破。

1.2 技能的反面

作者以玩魔方这一例子,从拥有口诀从而达到精通玩魔方的水平,但是他不理解为什么口诀是这么写的,离开了口诀,自己就只能拼出一面。

那这还是货真价实的技能嘛?如果说要知道什么是技能,那也要知道什么是技能的反面。

计算机人机交互领域的科学家比尔·巴克斯顿,在他1995年的一篇文章中,提到了技能的反面,他认为技能的反面就是解决问题,其实对于我们大多数人来讲,可能解决问题的这种能力,反而是一种在新环境中你的适应和学习能力。

解决问题能力的高低,反而代表着我们在这样的环境中的适应力,拥有着很好的解决问题能力,反而我们认为这是很厉害的人。

但实际上呢,并不是这样,就拿作者举的例子来说,面试官让被面试者写一段程序,然而面试者可能忘了开头怎么写,或者数组怎么定义。你发现他把时间都花在了解决低层次问题上,面试官真正想考察的算法技能,或者程序设计技能,都因为这些低级问题,反而无暇顾及。要避免这样的问题,提高技能,答案很简单,只有不断练习才能解决低层次的问题,编程不经大脑的自动操作,从而才有时间和脑力解决高层次问题

另外就是,如果一个人他没有学过这方面,而去做这方面的项目或者工作,那么必然会有学习的成本,从这一点来说,在我的理解看,这也叫做技能的反面,但是我们也需要承认的是,这是在一个新环境的场景下。其中的适应和解决问题能力,既可以说是技能的反面,也可以说是学习能力。毕竟,对于程序员来说,新事物不断涌现,学习能力是必不可少的。

2. 两人合作

2.1 代码风格规范

代码的风格原则当然是简明易读,没有二义性。下面记录其中一些重要的点:

缩进

用四个空格最好,不仅是说它的可读性,而且在大部分的编辑工具中默认情况下Tab的长度可能不同,用4个空格键可以避免在项目合作过程中因为代码不同带来的差异。

行宽限制

以前规定80字符行宽,是因为以前的打字机显示行宽为80,但现在时代不同,可以定为100。

断行与空白的{}行

虽然有时候把最后的一个括号写到if判断条件之后似乎看起来可以节省几行,但是不同的语句放在一行中,程序调试不方便,要一步步观察,判断的条件中各变量的变化情况,导致单步执行困难。所以我们还是选择让括号单独占一行。

分行

不要把多条语句放在一行上,更严格的说,不要把多个变量定义在一行,否则不方便调试错误。

命名

命名应该体现出其含义。用单个字母给有复杂语境的实体命名是不可取的。一个经过检验的方法叫做匈牙利命名法。

好的命名目的是一眼看出变量的类型。因为早期的计算机语言不做类型检查。

即使刚刚我们列举了匈牙利命名法的例子,但一些强类型的语言如C#,对类型有严格要求,前缀就不是很必要。

下划线

用来分隔变量,名字中的作用域标注和变量的语义。例如一个类型成员变量,通常m_表示,或者简单的用一个下划线来做前缀。

大小写

用大小写可以区分多个单词组成的变量名。

一个通用的做法是所有类/类型/函数名都用Pascal形式,所有的变量都用Camel形式。

注释

注释是为了解释程序做什么what,为什么这样做why,以及特别注意的地方。

不要把程序是怎么工作的进行注释,比方说下面写了一个for循环的代码,然后你的注释里写到:下面是一个循环从多少到多少。

复杂的注释放在函数开头。因为很多函数开始的主持都用来解释参数的类型,如果程序中我们已经说明参数类型,可以不重复。

注释也要随着程序的修改而更新,一个误导的注释,往往比没有主持更糟糕。

注释包括所有源代码,应该只用ASCII字符,不要用中文或其他特殊字符,否则会影响可移植性。

此外,现代编程环境中,程序编辑器可以设置各种美观得体的字体,可以用不同显示风格来表示程序的不同部分。

有些程序设计语言的教科书,对于基本的语法有详细的注释,但仅是为了教学的目的,并不适合正式项目。

2.2 代码设计规范

函数。只做一件事,并且做好。

goto。函数最好由单一出口,为了达到这一目的,可以使用goto。只要有助于程序逻辑清晰,体现什么方法都可使用。

错误处理。腾讯主要功能实现后,一些程序员会乐观估计,认为只需要另外20%的时间,给代码加一些错误处理就大功告成,但是这20%的工作,往往需要全部项目80%的时间。

参数处理。在Debug版本中,所有参数都要验证的正确性,而在正式版本中,对于外部比方用户或别的模块传递过来的参数,也要验证其正确性。

断言。当你觉得某件事肯定如何的时候,就可以用断言Assert。当然,如果你认为某事可能会发生,就要写代码来处理可能发生的错误情况。

2.3 如何处理C++中的类

注意,除了关于异常(Exception)的部分,大部分其他原则对C#也适用。

2.3.1 类

  1. 使用类来封装面向对象的概念和多态(Polymorphism)。
  2. 避免传递类型实体的值,应该用指针传递。换句话,对于简单的数据类型,没有必要用类来实现。
  3. 对于有显式的构造和析构函数的类,不要建立全局的实体,因为你不知道它们在何时创建和消除。
  4. 仅在必要时,才使用“类”。

2.3.2 class vs. struct

如果只是数据的封装,用struct即可。

2.3.3 公共/保护/私有成员(public、private和protected)

按照这一的次序来说明类中的成员:public、protected、private。

2.3.4 数据成员

  1. 数据类型的成员用m_name说明。
  2. 不要使用公共的数据成员,要用inline访问函数。这样可兼顾封装与效率。

2.3.5 虚函数(Virtual Function)

  1. 使用虚函数来实现多态(Polymorphism)。
  2. 仅在很有必要时,才使用虚函数。
  3. 如果一个类型要实现多态,在基类(Base Class)中的析构函数应该是虚函数。

2.3.6 构造函数(Construction)

  1. 不要在构造函数中做复杂的操作,简单的初始化所有数据成员即可。
  2. 构造函数不应该返回错误(事实上也无法返回)。把可能出错的操作放到HrInit()或FInit()中。

下面是一个例子;

class Foo
{
    public:
        Foo(int cLines) { m_hwnd = NULL; m_cLines = cLines}
        virtual ~Foo();
        HRESULT HrInit()l
        void DoSomething();
    private:
        HWND m_hwnd;
        int m_cLines;
};

2.3.7 析构函数(Destructor)

  1. 把所有的清理工作都放在析构函数中。如果有些资源在析构函数之前就释放了,记住要重置这些成员为0或NULL。
  2. 析构函数也不应该出错。

2.3.8 new和delete

  1. 如果可能,实现自己的new/delete,这样可以方便的加上自己的跟踪和管理机制。自己的new/delete可以包装系统提供的new/delete。
  2. 检查new的返回值。new不一定都成功。
  3. 释放指针时不用检查NULL。

2.3.9 运算符(Operators)

  1. 在理想状态下,我们定义的类不需要自定义操作符。只有当操作符的确需要时,才需要定义操作符。
  2. 运算符不要做标准语义之外的任何动作。例如,“==“的判断不能改变被比较实体的状态。
  3. 运算符的实现必须非常有效率,如果有复杂操作,应定义一个单独的函数。
  4. 当你拿不定主意的时候,用成员函数,不要用运算符。

2.3.10 异常(Exception)

  1. 异常是在“异乎寻常”的情况下出现的,它的设置和处理都要花费“异乎寻常”的开销。所以不要用异常作为逻辑控制来处理程序的主要流程。
  2. 了解异常及处理异常的花销,在C++语言中,这是不可忽视的开销。
  3. 当使用异常时,要注意在什么地方清理数据。
  4. 异常不能跨过DLL或进程的边界来传递信息,所以异常不是万能的。

2.3.11 类型继承(Class Inheritance)

  1. 仅在必要时,才使用类型继承。
  2. 用const标注只读的参数(参数指向的数据是只读的,而不是参数本身)。
  3. 用const标注不改变数据的函数。

2.4 结对编程

在结对编程中,有两个重要角色,驾驶员控制键盘输入领航员起到领航提醒的作用。结对编程有如下好处:

  1. 开发层次,提供更好的设计数量和代码质量,两人合作解决问题能力提高。
  2. 开发人员自身,带来更多信心,高质量的产出,带来更高的满足感。
  3. 企业管理层次,有效的交流学习,传递经验,分享知识,更好的进行人员流动。运用得当,可以得到更高的投入产出比。

但是有一点需要注意,不能让一个人长时间担任领航员,而应该每隔一个固定时间,互相交替休息

2.5 正确给予反馈

评论人的三种层次:

  1. 最外层,行为和后果。
  2. 中间层,习惯和动机。
  3. 最内层,本质和固有属性。

举个例子,如果一个人写缩进的时候,说好是四个空格,但他比方使用了两个空格,大家在阅读修改代码时,会有很多不便。

在行为层面上,我们会指出他的错误。然而在中间层,我们可能会攻击他的习惯和动机,被攻击的一方就比较难表白,并且澄清动机。比方我们会对他说,你这都是第几次缩进弄错了,上次我们已经达成共识,你怎么还这么做,是对大家的决议不满吗?那也不能偷偷搞破坏。

最内层本质和固有属性,我们可能会对他说,你太没有脑子了,我们开会提到过边玩风格,都没有进你的脑瓜,看来你都只想着自己风格和方便,团队精神都丢掉了,像你这样xxx学院出来的学生,怎么都是这样自私啊。

这一层面已经攻击深入到核心,被攻击方已经无法回应攻击的目标——xxx学院的学生,这是自己无法改变的。或者无从下手的「自私」,可以想象这次触及灵魂深处的冲突,会有不太好的结局。

怎么给别人提容易接受的反馈呢?这有一个三明治的办法

三明治是两片面包夹一片肉肉是最好吃,但是光给肉不行,我们还需要做好铺垫,强调双方的共同同团队共同的愿景讲起,让对方觉得处于一个安全的环境。

之后再把肉放上,这时候就可以把建设性的意见加工好,再加上生菜佐料,比方说,过去你做的不够好,但是我们以后可以做得更好。下面还是以刚刚的例子举例。

作为三明治的第一片面包,也可以说,我觉得你这几段代码写得很快,而且解决问题的思路不错,比刚来的时候好很多,值得我们学习。

然后再说中间的肉。我注意到你在缩进代码时候,没有按照规定来命名,咱们在当初团队规范,说好了四个空格,如果别人使用不同风格,大家在修改和阅读代码会有不便,如果这样简单的规范不能实施的话,大家会感觉不好,也会在后续的工作也有影响。

放上最后一片面包。这么说,你这几段代码还是很关键的,以后很多同事会拿来参考,既然你已经做了很多工作,那就再进一步提高留下高质量的代码。

相信这样的反馈,同伴会更乐于接受。

3. 敏捷流程

3.1 20%的代码量与80%的时间

我这里摘要出敏捷流程的问题和解法的第三步半。书中讲到,当任务都完成了的时候,那只是说开发人员该写的代码都写完了,但实际上还有很多事情没有做完。

例如说验证这个功能,或者这个版本在不同操作系统下都显示正确,计算结果正确,而且一致。

通常来说,在这一步,也就是感觉好像项目完成了80%,但实际上,后面的20%往往要花费80%的时间

3.2 敏捷流程的经验教训

在这一点里,书中的作者讲到了一些经验教训,我简单摘要感兴趣的点如下。

作为每日例会的主持人,他并不是一个有行政权力或者关机的沟通者,他还需要在团队中做具体的任务。如果把之前的经理变成例会主持人,这通常来说行不通。

某一些项目需要很多,政治上或者暗箱操作能搞定。在敏捷流程中,这些矛盾会摆到明处,这虽有好处,但也有风险。

在复杂的项目,要让在一线流血掉肉的猪或者说一线团队成员做决定。创业公司团队其实经常是在敏捷的模式中。管理层不谈流程,他们只关心结果。

4. 需求分析

在这一章节,一个很有意思的事情在于,当我们拥有了很大量的用户数据,我们想用这样的数据去分析用户的喜好、需求时,如果我们做过头了,会发生什么样的情景?

4.1 过度分析

担任谷歌视觉设计主管的道格拉斯·鲍曼在2009年的一天说到。当公司要求你用数据来证明,41种蓝色到底哪一种更好?或者为了一个边栏宽度是三像素还是像素一或是五像素而争论不休,纷纷要求要拿数据证明,你该怎么办?

4.2 需求中的创新

竞争需求分析的框架这一个小节说到了,创新有两种:

4.3 项目经历对需求的数字估计

项目经理最重要的一种探寻数字数值背后假设的能力。当我们的团队成员对某件事情作出估计时,比方说需求完成的时间,或者是项目的进度时间估计。

既作为主持人和项目经理来说,要尽可能的让我们的这个时间估计收敛到一个大家都比较满意的精度数值。的。推动述职述廉,同时,这也意味着,项目经理要清楚这个数值背后的假设这也要让要求大家的假设也要收敛,不要天马行空,在每一轮的讨论中,估计值的上限和下界要不断接近。

有一种估计的方法叫Wideband Delphi,他的目的不是比谁的第一轮估计猜得准,而是在较短时间内让团队充分沟通,交换意见,假设最后得到的估计数值,也许和某人提出最初的数值很接近,但这由于最后达成的假设和最初的假设或许不同,所以即使最初提出的数字很接近,可能意义也不大。

其实最重要的问题在于,估计团队自身的能力。有的大牛两天写好模块,可能你自己去要花几个星期。所以有人提到,团队也需要先培训,这是很好的角度,在开始一个全新的技术前,一段时间的培训和练习是有必要的,但是你的估计中是否包括这些因素呢?

对于长期的项目,一个高级的产品经理也需要考虑这样的问题,不可避免的会有投入和变动的情况,大家走着走着,有人受伤了,或者有人和当地的少数民族少女投入爱河了。

但有一点我们不能忘记,那就是:探险者总是高估自己的能力,低估未知的困难,不然他们就不会出门探险!

4.4 更进一步的估计

快速原型法,换句话,用一两个先锋去探路,或许是一个不错的方法。

当然除此之外,软件工程师的长期实践也摸索出一套经验公式:实际时间花费主要取决于两个因素,对某件事的估计时间x,以及他做过类似开发工作的次数n。

y = x ± x ÷ y

y是实际时间花费,中间的±表明或者加上,或者减去。例如某软件学院毕业的学生估计,用户管理模块需要三天完成,但几天前他从没做过,那么这件事情实际花费的时间有两种可能:

y = 3 ± ( 3 ÷ 0 )

就是说这件事也许是无穷大,在项目时间内压根就做不了。

再举一个例子,如果某个工程师要实现同样的功能,但是有一些现有的框架或者包已经提供这样的模块,但是他要自己写一个。结果漏洞百出,其他员工来帮她修改错误,最后他把他写的代码扔了,用现成模块来实现。

当n等于1,一项工作实际时间花费范围是[0…2x],如果员工一直在做相同的项目,他们的n肯定会不断增加,也就意味着估计变化的范围会越来越小,准确度则会越来越高。但是技术,市场以及员工的心态都在变化,员工是不甘于一直做雷同项目的。

我们甚至可以把上述公式展开,项目的复杂程度将由下面两个因素决定:

  1. 需求的复杂程度。程序员第几次实现类似需求,有些外行看起来很复杂,难懂的需求,如银行业务流程,但如果一个程序员已经做过多次,其实不像外人看的那么难。
  2. 技术的复杂程度。程序员是第几次用这个技术实现呢。

极端的例子也就是几个程序员,他们从来没用过的技术,去实现一个他们以前没碰到过的需求,他们的估计时间一定会很飘忽。

4.5 典型用户和场景

我们需要为自己的产品定义典型用户。可能因为我们的定义和思考,有一些典型用户被删除掉,或者他们原本就不属于我们的典型用户。

在有了典型用户之后,还需要决定每一个典型用户的目标。换句话说,他使用这个系统要达到什么目的?例如购物卖产品,滥发广告,等等。

对于一个目标列出他达到这个目标所经历过的过程,这就是场景,也可以称为故事。

有些场景描述了成功的结果,有些场景描述了失败的结果,用户和系统有成百上千种可能的交互情况,写场景时要有针对性。

下面是一个现实生活中,银行从业者的一条微博,他体会了一ATM无卡取现的功能的强大。

特意带上手机和令牌,不带银行卡,享受一下我行ATM的无卡取现,结果连自助银行的门都没有进去,不刷卡怎么开门啊。

如果专业重要功能的设计者,在做需求分析时,可以模仿用户设计场景,实地演一次戏,很快就能发现这场戏演不下去。

场景怎么写呢。针对每一个场景,设计一个场景入口,即描述场景如何开始接着描述典型用户在这个场景中所处的内部和外部环境,内部环境指心理因素等。然后给场景划分优先级,按优先级排序写场景。

4.6 应对变化的灵活性

这其实是典型用户和场景这一章节在技术说明书小结提到的,我觉得这点很有意思,记录下来。

例如一个企业的流程管理软件,可以处理各种请假需求,程序员把每种假期当做一个假期的滋味来处理,但是现在要新增一个家庭类型,例如志愿服务者假期,那么程序的工程师还需要添加,并且要求所有管理软件都更新到最新版本。

另一种做法是把所有假期定义为数据,这样一来,新增一种假期种类型,只是数据增多了一项相应的逻辑引用数据表示,但仅有一些变动而已,而原程序仍然保持不变。

软件如何应对变化,也是软件设计中最重要的一个方面。

5. 软件测试

5.1 软件测试的分类

测试按照功能来分类,有下面几种。

除了基本功能之外,还有很多功能之外的特性,这些叫非功能需求或者服务质量需求,如果没有这些基本功能,这些特性都将无从表现,因此,在开发市场阶段,也就是基本功能完成后,在做这些非功能测试。这些非功能测试如下:

按测试的动机和作用分。在开发过程中,不少曾是起着烽火台的作用,他们告诉我们软件开发的流程是否顺畅,下面就是测试烽火台:

另一些测试名称则是说明不同的测试方法如下表。

5.2 代码覆盖率测试

这里要说的是,即使我们的测试将我们的代码百分之百的被执行,那么是否也意味着,再也不用写新的测试用例了,当然不是。

  1. 不同代码是否执行,有很多种组合。一行代码被执行过,没有出现问题,并不表明这一行代码,所有可能条件的组合下,都能正确无误的运行。
  2. 代码覆盖不能测出来未完成的代码导致的错误。比如没有检查过程调用的返回值,或者没有释放资源。
  3. 代码覆盖不能测出性能问题。
  4. 代码覆盖不能测出时序问题,由时序导致的程序错误,例如线程之间的同步。
  5. 不能简单的代码覆盖率来衡量代码中与用户界面相关的功能的优劣。

5.3 测试在开发中的过程

一般情况下,测试团队要把迄今为止发现的所有小强都重新测试一遍,确保他们都在最后的版本中被清楚,没有出现回归。

发布阶段。测试队伍要把尽可能多的测试用例自动化,并为下一版本的测试工作做好准备。

6. 质量保障

在这一章节中,他提到了软件工程的质量,软件的开发过程有三个主要的特性,好,快,以及便宜,通俗的理解就是软件在功能,成本,时间三方面满足利益的相关者的需求

一个团队虽然可以通过特殊的办法来提高程序的质量,例如在交付前通宵加班,在软件发布后,长期加班修复用户的问题。

但是软件工程的质量需要长期的过程来提高。

6.1 软件工程的质量如何衡量?

其中一套比较成熟的理论是能力成熟度模型集成,简称为CMMI。使用该种模型管理项目,降低了成本,而且提高了项目的质量和按期完成率。该模型包括了连续模型和阶段模型,这两种表示方法。当然也可根据自身情况进行改进使用。

该方法虽然源自美国,但在世界各地得到推广,一些以外包为主业务的公司非常重视CMMI的考核工作。同时,该模型目前已成为许多大型软件从业者,用于改善组织内部软件工程所采用的软件评估标准。

随着等级的提升,从原本目标刚刚得以实现到权责到人,有既定的计划和流程,项目实施人员有相应的培训,对整个流程有监测控制再到通过一些量化技术来实现流程的稳定管理的精度降低,项目实施在质量上的波动,以致到最后一个,也就是最高等级企业,可以通过信息化数字化的实现,对项目管理,而且能充分利用信息资源,对次品进行预防改善,改善流程。

6.2 和测试角色相关的问题

有了独立测试角色后,是否万事大吉,分工意味着一件事交给别人去做,而且依赖别人给出的结果,但这会出现一些问题。

有一个生活中的歪理,那就是既然有清洁工,那我扔垃圾又算什么?因为清洁垃圾是清洁工的工作。

同样作为有专人负责测试工作,但是保证质量,却是所有成员的职责

某次在想目标用户演示我们的杀手级功能时,预想的结果却没有出现,相关人员面面相觑。大家小声互相议论。 我不是把那个新模块给你了吗?我就是照着那个接口实现的啊。我不是已经交给那谁了吗?所有的问题不是已经都搞定了吗? 最后也是在尴尬中胜利结束,后来查问题根源,原来这个复杂的功能,由于两个模块接口在最后没有同步,某一个重要参数被忽略,这个功能最出彩的部分压根就不可能工作。

那负责测试的角色怎么解释?所有测试用例通过,同意发布呢?

这还只是开发人员以自豪的杀手级功能,那其他普通的功能又是什么命运呢?

在一些公司中,开发人员搞定后,往往就会把测试交给测试人员去进行测试,但是有时候开发人员没有做好测试就交给测试,或者说开发人员在做测试的时候没有一个实际的场景,只是拿一些简单的测试用例就单独做了测试,没有考虑实际的情况去做模块测试,甚至是集成测试。最终交给测试时,测试人员用实际的场景去跑,发现又出了问题。

我们回过头来,可能要问这么些问题:

  1. 这件事真的要通过这么多环节吗?
  2. 测试人员真的知道最关键的地方如何测试吗?
  3. 在系统上线后,所有为这个功能感到自豪的人,是否去实地测试过呢?

一个开发人员应该负责下面几个部分中哪几个呢?

6.3 稳定和发布阶段

软件团队的血型这一小节讲到一个很有意思的事情。所有软件公司都希望所有缺陷修复之后才发布软件。但是:

做商用软件的人都在为此苦恼,只有优秀的软件公司能找到一个平衡点及时发布能够解决用户问题的软件,并且能及时修改软件中的问题

注意这两个及时,并不一定是同一时间。做课程上的大作业的软件可以不用管这两个及时,交了卷就万事大吉。

6.4 项目的如期发布

为了能使软件正常发布,我们有两个常用的招数。第一个叫做设计需求变更,第二个是构建没有bug的版本。此外,基于第二个,我们还需要做最终回归测试,以及砍掉不必要的功能,另外逐渐提高修复bug的门槛

在这个过程当中,我们还有一招,就是要逐步冻结程序各个方面的功能对应的代码。一般来说,程序的人机交互最先开始冻结,不能随意修改,因为很多项目文字要做本地化成多种语言,只有人机界面中文字和布局等固定,我们才能把文字交给负责本地化的部门,随着时间推移,一些功能也可以冻结,这些功能都需要经过全面测试,并且把所有的代码缺陷都解决,功能进入稳定状态,在下一个版本中不要再碰与此功能相关的代码。如果有新的功能要加,则在当前源代码的基础上创建新分支,让目前版本和将来本本的工作分开进行。

6.5 发布后问题分析

在发布之后呢,我们还要进行事后诸葛亮会议,就像医学的解剖尸体一样,把软件开发的流程也解剖,当然这个解剖过程也可以叫做review,retrospective,postmortem,事后诸葛亮会议等等。

产品发布了,小明建议大家开一个总结会议,这个过程让公司秘书小芳主持,为了能使大家畅所欲言,项目的管理者也就是阿超和大牛考虑到活跃性等等没有参加会议,也为了活跃气氛,还买了零食饮料,啤酒等等。

虽然作为项目管理的阿超,没有参加会议。但他给了会议主持一个讨论模板,要牢记会议的核心问题,如果你可以重新来过,什么方面可以做得更好,另外再问为什么的时候要多问几次,层层递进,找到问题的根源

一般在软件发布,用户报告一个大问题,为什么,因为程序没有考虑某种边界条件,为什么在测试阶段没有测出来,因为这个代码是测试的最后阶段才加进去的,那又为什么不通知产品和测试呢?因为开发认为没有问题,是很简单的修改,那又为什么不通知别人,因为开发认为那些都是软件工程很无聊的规定,而且开发是大牛人不必遵守的,那为什么呢?

问到这个层次,就把问题根源暴露出来了。

7. IT行业的创新

在讲到学校分析时,我们讲过如何通过四个象限对一个产品功能进行分类,同样我们也可以对一个团队的多个产品进行分类,帮助团队实施正确的产品开发策略。

我们根据外围功能,杀手功能,必要需求,辅助需求这四个角度来进行四象限分类。

一个团队也可以采取各种手段,各种投入方式开发各种产品,下面的是常规手段。

但是对于各个产品不能平均下注,要在第一象线投入足够的力量。

8. 人,绩效和职业道德

一个团队中的成员来自五湖四海,虽然大家在一起吃饭,但是不同的人对团队的承诺不一样。

8.1 团队成员与决定

重大决定由猪来定。虽然官僚层次驱动的项目中,往往有鹦鹉控制流程的关卡。虽然鹦鹉对项目具体情况不了解,也很忙,但是一些决定非得由他们来做,做完决定后拍拍翅膀飞走了,这的确让人比较郁闷。

这些可爱的动物在一些项目管理书籍中,以别的名目出现,例如一本叫做项目百态的书中提到一个角色,影评家。影评家不拍电影也没有演技,但是他们对电影的一切都可以指手画脚,而且可以不必承担任何责任,最高领导往往还挺容易受影评家影响,你在辛辛苦苦做项目的时候,是否有一圈影评家在围观呢?

其实还是人的问题。现代软件工程这门课中,大家被分成六七个人小组进行项目开发,但是不同的人的角色是不一样的,一开始可以看到大家都是做事的。但是后来又可以分成做事的和不做事的。又后来进一步可以分成做事的,不做事的,以及不让别人做事的。

就比方说有一个事情很有意思,原本以为学术界应该比较纯洁,但没想到不让别人做事的这类人还不少。有一位老师在回忆他科研经历时说“中国这个问题很严重,我们建立一个学科的时候遇到了很大困难,这些困难都不是来自政治界的,而是来自学术界本身,来自学术界,甚至某些权威。某位权威就是不同意给我立题和资助,并说道’你怎么能研究这个,你怎么能进到我的领域来?’”

9. 绩效管理

虽然前面讲述了如何成长,证明自己成长,但是一个有能力的工程师到了另一个团队。如何衡量自己在新的团队中的绩效呢?

9.1 绩效的考量

这个问题很难回答,因为大家的工作是互相依赖的,产品功能被表扬,不能单纯的归于一个人工作

甚至有人会说,换别人来做,可能还不如我呢,这是底层问题,或者说产品经理根本没有设计好,或者会说,测试人员没有好好测,甚至还有说用户太笨了。

软件工程这门课中,大家被分成小组干活,多的与少的都得到一样的团队成绩。但这似乎不利于调动积极性,因此我在其中设定了一些浮动分数,相当于奖金?那大家如何分配这部分奖金呢?

在《人件》这本书中,衡量劳动生产率和UFO是放在同一个小标题下的,然而任何一种衡量方法都比完全不良要好

9.2 团队风格与萝卜和白菜

再讲一个故事,假设团队里来了两个年轻人,一个叫萝卜,一个叫白菜。

萝卜做事很快,但是它是萝卜快了不洗泥类型,白菜属于慢工出细活类型。分配的任务后,萝卜很快就做好了,但是白菜还在吭哧吭哧的跟项目经理以及测试人员讨论。

领导很高兴,又交给萝卜更多的事情,开发阶段结束了,萝卜比白菜多,做了不少功能。

但是稳定阶段开始了,大家发现萝卜负责的功能出了很多问题,反而白菜的模块儿倒是很稳定。

大家发现萝卜在团队内的曝光率很高,因为很多问题都在等着他解决和修复,统计数据上看,它也修复了不少问题缺陷。然而白菜搞定了自己的模块,也开始帮助其他人,但因为白菜不熟悉其他人的模块儿,他修复的缺陷并不多。

由于萝卜的设计有缺陷,导致模块很复杂,萝卜也成了唯一了解某其模块的开发人员,项目的后期阶段,萝卜基本上工作的最晚也把最后几个缺陷修复了。

领导们说,有问题就找萝卜。

项目结束后,开始绩效考核领导a认为白菜技巧不错,模块按时完成,没有大多问题,还能帮助其他成员。但是领导b认为萝卜是超级明星,不仅第一个模块完成的速度快,修复的缺陷最多,而且掌握了最复杂的模块,离开他根本不行,而且他工作的很晚,有突出贡献。至于白菜领导b没有感觉出他做的啥,仅仅是按要求完成了任务。

萝卜青菜各有所爱,那么到底谁应该得到奖励呢,假设领导b的评价占了上风,萝卜得到奖励,白菜离开了团队,你觉得下一个版本会出现什么情况?

萝卜估计会成为构建大事,每天忙得不可开交,然而项目进展不一定会像以前那样顺利,同时呢,或许有人会怀念白菜。

团队的领导者文化决定了团队的风格。并不是说哪一个好哪一个不好,但是我们要让萝卜快了不洗泥的这类人慢下来减少他带来的危害。同时实行小强地狱,当某个人缺陷数目达到一定程度,就让他暂停当前的开发进度,只去修复缺陷