如何进行软件项目需求评估

——由一道数学题说起

 

    小A刚成为某公司某软件公司定制化项目经理,上任第一天他就为一个项目的需求评估犯了愁。以前做研发工程师时,拿到的都是具体的实现任务,而现在他手里只有一张来自销售部的excel表格。这可怎么下手?小A只好拿着这个表格去找到了他的导师老C。

    老C看到小A的表格,只是淡淡一笑,说:“我这刚好有一道数学题,你试试解解看。”小A心中郁闷——我急着交评估报告呢你让我解题是闹哪样?但看着老C胸有成竹的样子,只好答应了。

 

【题目:一个车夫,赶着一辆马车,车上坐着3个人,每个人背着3个袋,每个袋里装3只大猫,每只大猫带着3只小猫,每只猫带着3只老鼠作为干粮。

问:一共多少条腿?】

 

1) 此路通不通

小A看完题目,拿起笔和纸。

老C问道:你在干嘛?

小A说:计算呀!

老C说:莫急。你先粗粗地估一下,车上的装载的动物数量,还有每个人袋子的容量。看看计算结果有没有实现的可能。估算的时候,数字不需要精确,在数量级层面上准确就足够了。

小A想了下:每个人袋子里的动物数量级应该是3*3*3*3,81;车上的动物数再乘以3,200多只。吓!有没有那么大的袋子啊!车子够不够结实啊!

老C说:你看到了吗?如果车的承载力和袋子的容量无法满足这个条件,则这个题目根本就是不成立的,也就没有必要往下计算。

小A一拍脑门:这不就是可行性评估吗?

老C说:是的,你反应很快。项目评估的第一步,一定是可行性评估。通俗地说,就是看看此路通不通。你需要找到项目成功的所有必要条件——不光开发,还包括管理,实施以及维护。如果发现这中间哪一个环节中,存在以当前的技术、成本条件无法满足的因素,就有可能影响整个项目的成败。你得提前预警。

 

2) 小坑和大坑

小A问:那有没有可能出现一些不那么致命,却影响项目的进度和质量的因素呢?

老C说:太有可能了!一个项目在进行过程中,会出现各种意想不到的情况。而且发现的越晚,影响越大。甚至到最后发展成致命问题。这就是我们所谓的各种坑。你现在试试找出这道数学题中的坑?

小A又把题目仔细阅读了一遍,倒吸一口凉气:老大,我发现这道题坑可真多——有几匹马,车夫是否坐在车上,人猫和老鼠是否四肢健全,有没有怀孕,等等。

老C说:那应该怎么解决这些坑呢?

小A说:那就必须在需求评估之前,和客户沟通清楚,尽量把所有的疑问都提出来,消灭不确定性。比如说用户可能只想知道车上一共有多少条腿,那么我就不用考虑马的匹数了,等等。

老C点头:不光是需求上的不确定性。运行环境,技术架构上的不确定性,以及实际开发中可能遇到的技术难点,都需要在早期充分暴露,并在进行需求评估时预留足够的富余。

小A说:我懂了,但好像看起来容易操作起来难啊!

老C说:是的,这需要经验和洞察。并且,与客户的充分沟通,并不足以消灭所有的不确定性。需求、技术方案都有可能随时变化。那就需要在技术方案上,考虑一定的灵活性。

 

3) 以不变应万变

小A说:我理解了。这么说来,我刚才提到的不确定因素,是不是都作为变量比较好?小A脑子里浮现出一段C++代码。(特别鸣谢笔者的同事波哥提供的这段代码!)

#include <iostream>
using namespace std;
 
#define PeopleLegsNumber 2
#define HorseLegsNumber 4
#define MouseLegsNumber 4
#define CatLegsNumber 4
#define BagNumCarriedByPeople 3
#define TreeDeep 3 //每棵树中以袋子为根节点的子树深度为3
#define TreeWide 3 //每棵树的度(或宽度)为3
 
int g_iLegsCount = 0;
bool g_bPessengerContainDriver = true; //可变条件,车夫是车上三人中的其中一人或车夫不是车上三人中的其中一人
int g_iHorseCount2DriveCar = 1; //可变条件,一匹马拉车或多匹马拉车
bool g_bDriverHasBags = false; //可变条件,若车夫不是车上三人中的其中一人,那么“每人背3个袋”中的“每人”可能是车上三人或包括车夫在内的所有人
 
int PeopleCount()
{
         if (g_bPessengerContainDriver)
                   return 3;
         else
         {
                   if(g_bDriverHasBags)
                            return 4;
                   else
                            return 3;
         }
}
 
void CalChildNodeCount(int treeDeep, int &nodeCount)
{
         if (treeDeep <= 1) return;
         for (int i = 0; i < TreeWide; ++i)
         {
                  CalChildNodeCount(treeDeep - 1, ++nodeCount);
         }
}
 
int main()
{
         int _catCountInBag = 0; //以袋子为根节点的树中猫的数量
         CalChildNodeCount(TreeDeep, _catCountInBag);
         int _catCountPerTree = _catCountInBag * BagNumCarriedByPeople; //以人为根节点每棵树中猫的数量
         int _mouseCountPerTree = _catCountPerTree * 3; //以人为根节点每棵树中鼠的数量
         int _4legsAniCountPerTree = (_catCountPerTree + _mouseCountPerTree); //以人为根节点每棵树中四条腿动物数量
         int _totalLesgCountPerTree = (_4legsAniCountPerTree * 4) + 1 * PeopleLegsNumber; //每棵树中所有腿的数量
         int _totalLegsInForest = _totalLesgCountPerTree * PeopleCount(); //森林中腿的数量
 
         if(!g_bPessengerContainDriver && !g_bDriverHasBags) //车夫不是车上三人中的其中一人并且车夫不背袋子
         {
                   g_iLegsCount = _totalLegsInForest + g_iHorseCount2DriveCar * HorseLegsNumber + 1 * PeopleLegsNumber; //腿总数
         }
         else
         {
                   g_iLegsCount = _totalLegsInForest + g_iHorseCount2DriveCar * HorseLegsNumber; //腿总数
         }
 
         cout << "The total legs number is " << g_iLegsCount
                   << " when the variable 'g_bPessengerContainDriver' is " << g_bPessengerContainDriver
                   << " ,the variable 'g_bPessengerContainDriver' is " << g_bDriverHasBags
                  << " and the horse number is " << g_iHorseCount2DriveCar << endl;
         system("pause");
         return 0;
}

 

老C点点头,又摇摇头:这样产品是灵活了,可是灵活度的代价,是复杂度的提高和运行效率的降低。过度追求灵活度的系统往往不够高效和健壮。所以通常的做法是,用20%的灵活度应对80%最可能的变化。

假设我们通过和客户沟通,弄清楚原来用户想知道的是车上的腿数。并且已经得知车上三人四肢健全。我们可以从常识出发,认为未出生的动物可以不计算在内,猫残疾的可能性较低,先忽略。老鼠是被猫抓来的,被抓的过程中弄断腿的可能性倒很高。把老鼠剩余腿数的期望值设为P(当然如何拿到准确的P值,又是另一个专业的范畴了,今天咱们先不赘述)。你看现在就只剩一个变量需要代入了,复杂度大大降低。

小A说:说到这里我突然发现,要想很好地评估工作量,必须先完成架构设计啊!

老C说:是这样的,否则不足以提供有说服力的依据。而只有你真正完成了架构设计,才有可能进行下一步评估,也就是工作量分解。

4) 分解再分解

小A说:这个我明白,不就是把工作细分到每个人头上,然后估算出各项工作的大致需时间,最后加在一起就是总的工作量呗!

老C点点头:大体正确。现在咱们来完成这道题。还继续刚才3)中的假设:

分解1:

人腿总数  = 人数(X) * 每人2腿

猫腿总数  = 猫数(Y) * 每猫4腿

鼠腿总数  = 鼠数(Z) * P(见第3节)

分解2:

人数X = 3

猫数Y = X * 每人袋数 * 每袋猫数 = 3 * 3 * 4 = 36

鼠数Z = Y * 每猫鼠数 = Y * 3 = 108

结论:

车上腿数 = 人腿总数 + 猫腿总数 + 鼠腿总数 = X * 2 + Y * 4 + Z * P = 6 + 144 + 108 * P = 150 + 108 * P

在实际评估中,还需要注意几点——第一:细分粒度应该以单人的独立工作为宜;第二,注意工作的先后依赖和并行需求;第三,为测试预留充足的时间。到这里,也就基本完成需求评估了。

小A说:太感谢!我知道该怎么做了。那我先去干活了!

扭头便走。

 

看着小A风风火火地离去,老C嘴角一翘,轻轻念了一句话。

 

有传言他说的是“有进步啊有进步”,但也有人信誓旦旦听到的是“不归路啊不归路”。谁知道呢。