如何進行軟件項目需求評估
——由一道數學題說起
小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嘴角一翹,輕輕念了一句話。
有傳言他說的是“有進步啊有進步”,但也有人信誓旦旦聽到的是“不歸路啊不歸路”。誰知道呢。