新方法學

胡健(轉載自敏捷中國)    20030915日        英文原文版權由Martin Fowler擁有

 過去幾年中興起的敏捷型(agile〕(也被稱之爲輕量型lightweight) 的軟件開發方法,以矯正官僚繁瑣過程、許可對過程進行自主調整爲特徵,在軟件業引起了極大的興趣。在這篇文章裏,我將探索敏捷型方法的合理性, 着重點並不是放在其輕重上,而是於它們的適應性(adaptive〕性質 和以人優先的理念。我在本文也簡要介紹了一些敏捷型方法並給出了進一步的參考材料。另外,我還提出了一些你在決定是否要走這條剛踏出來的 敏捷之路時需考慮的因素。

從無、到繁重、再到敏捷

  多數軟件開發仍然是一個顯得混亂的活動,即典型的邊寫邊改code and fix〕。設計過程充斥着短期的、即時的決定,而無完整的規劃。 這種模式對小系統開發其實很管用,但是當系統變得越大越複雜時,要想加入新的功能就越來越困難。同時錯誤故障越來越多,越來越難於排除。一個典型的標誌就是當系統功能完成後有一個很長的測試階段,有時甚至有遙遙無期之感,從而對項目的完成產生嚴重的影響。

  我們使用這種開發模式已有很長時間了,不過我們實際上也有另外一種選擇, 那就是正規方法methodology〕。這些方法對開發過程有着嚴格而詳 盡的規定,以期使軟件開發更有可預設性並提高效率,這種思路是借鑑了其他工程領域的實踐 - 因此我把它們稱爲工程方法(engineering methodologies)。

  工程方法已存在了很長時間了,但是沒有取得令人矚目的成功,甚至就沒怎麼引起人們的注意。對這些方法最常聽見的批評就是它們的官僚繁瑣,要是按照它的要求來,那有做太多的事情需要做,而延緩整個開發進程。

  作爲對這些方法的反叛,在過去幾年中出現了一類新方法。它們在一段時間裏被稱爲輕量型lightweight)方法,但現在有了一個廣爲接受的的名稱, 敏捷型方法(agile methodologies)。對許多人來說,這類方法 的吸引之處在於對繁文縟節的官僚過程的反叛。它們在無過程和過於繁瑣的過程中達到了一種平衡,使得能以不多的步驟過程獲取較滿意的結果。

  敏捷型與工程型方法有一些顯著的區別。其中一個顯而易見的不同反映在文檔上。敏捷型不是很面向文檔,對於一項任務,它們通常只要求儘可能少的文檔。 從許多方面來看,它們更象是面向源碼code-oriented〕。事實上,它們認爲最根本的文檔應該是源碼。

  但是,我並不以爲文檔方面的特點是敏捷型方法的根本之點。文檔減少僅僅是個表象,它其實反映的是兩個更深層的特點:

  敏捷型方法是適應性而非預見性。工程方法試圖對一個軟件開發項目在很長的時間跨度內作出詳細的計劃,然後依計劃進行開發。這類方法在一般情況下工作良好,但(需求、環境等)有變化時就不太靈了。因此它們本質上是拒絕變化的。而敏捷型方法則歡迎變化。其實,它們的目的就是成爲適應變化的過程,甚至能允許改變自身來適應變化。

  敏捷型方法是面向人(people-oriented) 而非面向過程(process-oriented)。 工程型方法的目標是定義一個過程,不管是誰用都工作。而敏捷型方法則認爲沒有任何過程能代替開發組的技能,過程起的作用是對開發組的工作提供支持。


  在以下各節中,我將詳細地探討這些差別,這樣你可以瞭解適應性和以人爲中心的過程是什麼,它們的好處與不足,以及你作爲軟件開發人員或用戶時是否應該使用它們。

預設性與適應性

將設計與建造分離開來

  傳統的軟件開發正規方法的基本思路一般是從其他工程領域,如土木工程, 借鑑而來。這類工程實踐中,在實際建造之前,通常非常強調設計規劃。工程師首先要畫出一系列的圖紙,這些圖紙準確地說明了要建造什麼以及如何建造(包括部分和整體〕。許多設計決定,如怎樣處理一座橋樑 的負荷,在畫圖紙時就會作出。然後,這些圖紙分發給另外一組人員,通常是另外一個公司,去建造。這種方式其實已假定了建造過程將按圖紙而來。當然,施工中也會碰到一些問題,但這些都是次要的。

  圖紙其實就是一個詳細的建造計劃,它說明了一個項目中必須完成的各個部分,以及如何把這些部分裝配成整體。這樣的計劃可以進一步定出需要完成的各項任務,以及這些任務之間的依賴關係。這樣,能較爲合理地制訂出生產進度表和項目預算。這種模式實際上也規定了建造者如何做(施工〕,這也隱含着建造者不須是高智能型的,儘管他們可能都有非常高超的手上功夫。

  在此,我們看到的是兩類非常不同的活動。設計是難於預見的,並且需要昂貴的有創造性的人員,建造則要易於預設。我們有了設計之後,便可對建造進行計劃了。而有了建造計劃後,我們進行建造則可以是非常可預見性的了。在土木工程中,建造不論在經費上還是在時間上的成本都要比設計和計劃大得多。

  所以,軟件工程方法的途徑是象這樣的:我們想要可預見的生產進度計劃, 以便能使用技能較低的人員。要達到這一點,我們必須得把設計與建造分離開來。因此,在軟件開發中,我們得想法作出這樣的設計,使得計劃一經完成,建造將會是直接而明確的。

  那麼,計劃應該採用什麼形式呢?對許多人來說,這是設計標識符號notation〕,如象 UML需承擔的角色了。如果我們能用UML作出所有主要的技術決定,那麼就可以用UML來做建造計劃,然後把計劃交給程序員去編碼,即是建造活動。

  但這裏存在一個關鍵問題。你是否能作出這樣的設計使得它能夠讓編碼成爲一項建造活動?如果能,那麼這樣乾的成本上是否充分地小而使得這種途徑值得 一用?

  這提出了幾個問題。第一個問題是到底有多困難能使一個用類似UML作出的設計達到交給程序員就能直接編碼的狀態。用象UML那樣的語言作出的設計在紙上看起來非常漂亮,而實際編程時可能會發現嚴重的缺陷。土木工程師使用的模型是基於多年的工程實踐,並結晶在工程典章中。更進一步來說,一些設計上的關鍵部分,如應力作用,都是建立於堅實的數學分析之上。而在軟件設計中,我們對UML圖紙所能做的只是請專家同行審閱。這當然是很有幫助的,但是往往一些設計錯誤只能在編碼和測試時才能發現。甚至於熟練的設計者,我自認爲我屬此列,就常常對在把設計變成軟件的過程中出現的錯誤感到意外。

  另一個問題是費用比較。建一座橋樑時,設計費用一般佔整個工程的10%, 左右,餘下的90%左右爲施工建造費用。而在軟件開發中,編碼所佔的時間一般要少得多。 McConnell指出在大型項目中,編碼和單元測試只佔15%,這幾乎和橋樑工程中的比例倒過來了。即使把所有測試工作都算作是建造的一部分,設計仍要佔到50%。這就提出了一個重要問題,那就是和其他過程領域的設計相比,軟件設計到底是什麼性質。

  這些問題導致了Jack Reeves 提出源碼也應是設計文檔,而建造應該是編譯和鏈接。的確,任何你認爲屬於建造的工作都應當是自動化的。

這些討論導致了下面一些重要結論:

  在軟件開發中,具體建造費用非常低,幾可忽略不計。

  軟件開發中所有工作是設計,因此需要富有創造性的才智之士。


  創造性的過程是不太容易計劃的,因此,可預見性是個不可能達到的目標。


  我們應該對用傳統工程模式來進行軟件開發的做法保持足夠的警覺,因爲它們 是不同類型的活動,因此需要不同的過程。


  需求的不可預見性

  在每個我參加的問題項目都有這樣一種情況,開發人員跑來抱怨說,這個項目的問題是需求老是在變。而讓我意外的是每個人都對此感到意外。其實在建造商用軟件系統中,需求變更是常態,問題是我們如何來處理它。

  一種方法是把需求變更看成是因需求工程(requirements engineering〕沒作好而導致的結果。一般來說,需求工程(或曰進行需求分析〕是要在着手建造軟件之前,獲取一幅已完全理解了的待建系統的畫面,然後取得客戶認可簽發,並且還要建立一套規章來限制需求變更。

  該方法的一個問題是要準確獲取所有需求是困難的,特別是當開發商不能提供某些需求的費用信息時。例如,你買車時想在你的車上裝一個天窗, 而推銷員卻不能告訴你要在車價上只再加10元錢呢,還是10000元。如果不知道這點,你如何能決定你是否願意花錢在車上加個天窗呢。

  作軟件開發的費用估算是不容易的,這有多種原因。部分原因是因爲軟件開發是一種設計活動,因此難於精確計劃。部分原因是系統的基本材料變化非常之快。部分原因是開發活動極大地依賴於項目參與人員,而個體是難於預測和量化的。
  軟件的不可觸摸性也是一個原因。在系統建成之前,有時很難判斷一項功能的具體價值。也就是說,只有當你在實實在在地使用系統時,你才能知道哪些功能是有用的,哪些沒什麼用。

  這樣的結果頗具諷刺意味,即人們期待需求應該是可變的。畢竟,軟件應該是的。所以,需求不僅是可變的,簡直就是應該變的。要讓客戶把需求固定下來要花很大的力氣,特別是當他們參與了軟件開發並且知道軟件是多麼易於修改。

  但是,即使你能把所有的需求都固定下來,並不意味着你的開發就是陽光燦爛了,你可能仍然會在昏暗之中。在當今的經濟形勢下,決定並推動軟件系統功 能特性的商業因素飛快地變化着。現在一組很好的功能六個月以後可能就不那麼好了。 商業世界並不會因你的系統的需求固定下來了而停止不動,商業世界的許多變化是 完全不可預測的。如果有人不承認這一點,要麼他在撒謊,要麼他已炒股成了百萬富翁了。

  軟件開發的一切都取決於系統需求,如果需求不固定,你就不能制訂出一個可 預見性的計劃。

預見性是不可能的嗎?

  一般來說,不可能。當然,有一些軟件開發項目中,預見性是可能的。象 NASA的航天飛機的軟件開發項目,應是這樣一個例子。它需要大量的會議、充足的時間、龐大的團隊、以及穩定的需求。畢竟,這些是航天飛機的項目。 但我並不認爲一般的商用軟件開發屬於這類系統,所以你需要不同的開發 過程。

  如果你不能遵循一個可預見性方法,而你強裝能夠,那麼這是非常危險的。通常,一個正規方法的創造者不是很善於(或樂於)給出其方法的邊界條件,換句話 說,當這些邊界條件不滿足時,則該方法就不適用。許多方法學者希望他們的方法能夠放之四海而皆準,所以他們既不去了解,也不公佈他們方法的邊界條件。這導致了人們在錯誤的情形下來使用一種方法,例如,在不可預見性的環境中使用一種預見性的方法。

  使用預見性方法具有強烈的誘惑力,因爲預見性畢竟是一個非常需要的特性。 可是,當你不能達到預見性時而你相信你能夠,這將會導致這樣一種局面:你 可以很早就制訂出計劃,但不能適當地處理計劃崩潰的情形。你看見現實與計劃慢慢地偏離,而你可以在很長的時間裏,裝着認爲計劃仍是有效可行的。 但是當偏離積累到足夠的時候,你的計劃就崩潰了,這通常是很痛苦的。

  所以說,在不可預見性的環境中是不能使用預見性方法的。認識到這點是 一個很大的衝擊。它意味着我們用的許多控制項目的模式,許多處理客戶 關係的模式,都不會再是正確的了。預見性的確有非常多的好處,我們很難就放棄預見性而失去這些益處。象很多問題一樣,最困難的一點是 認識到這些問題的存在。

  可是,放棄預見性並不意味着回到不可控制的一片混亂之中。你所需要的 是另一類過程,它們可以讓你對不可預設性進行控制,這就是適應性的作用了。

不可預見過程的控制 - 迭代

  那麼,我們如何對付一個不可預測的世界呢?最重要,也是最困難的是要隨時知道我們在開發中的情形處境,這需要一個誠實的反饋機制來不斷準確地告訴 我們。

  這種機制的關鍵之點是迭代式iterative〕開發方法。這並不是一個新思路,迭代式開發方法已存在很久了,只是名稱不同,如遞增式Incremental〕,漸進式Evolutionary)階段式Staged〕,螺旋式Spiral〕等等。迭代式開發的要點是經常不斷地生產出最終系統的工作版本,這些版本逐部地實現系統所需的功能。它們雖然功能不全,但已實現的功能必須忠實於最終系統的要求,它們必須是經過全面整合和測試的產品。

  這樣做的理由是:沒有什麼比一個整合了的、測試過的系統更能作爲一個項目 紮紮實實的成果。文檔可以隱藏所有的缺陷,未經測試的程序可能隱藏許多缺 陷。但當用戶實實在在地坐在系統前來使用它時,所有的問題都會暴露出來。這些問題可能是源碼缺陷錯誤(bug),也有可能是對需求理解有誤。

  雖然迭代式開發也可用於可見性環境,但它基本上還是用作適應性adaptive)過程,因爲適應性過程能及時地對付需求變更。需求變更使得長期計劃是不穩定的,一個穩定的計劃只能是短期的,這通常是一個迭代周 期iteration)。迭代式開發能讓每個迭代週期爲下面的開發計劃提供一個堅實的基礎。

  迭代式開發的一個重要問題是一個迭代週期需要多長。不同的人有不同的答案, XP(極限編程)建議一到三週,SCRUM建議一個月,Crystal(水晶系列〕 更長一些。不過,一般的趨勢是讓每一個週期儘可能地短。這樣你就能得到頻繁的反饋,能不斷地知道你所處的狀況。

適應性的客戶

  這類適應性過程需要與客戶建立一種新型的關係,特別是當開發是由一家簽約公 司來進行的時候。因爲當僱傭一家簽約公司來進行開發時,多數客戶願意訂一個固定價格的合同。他們告訴開發方他們所需要的功能,招標,簽約,然後剩下的便是開發方去建造系統了。

  固定價格合同需要穩定的需求,即一個可預見性過程。適應性過程和不穩定的需 求意味着你不能做這種固定價格的合同。把一個固定價格模式弄到適應性過程 將導致一個痛苦的結局。最糟糕的是客戶將與軟件開發者受到同樣的傷害,畢竟客戶不會想要一個不需要軟件。即使他們未付開發方一分錢,他們仍然失去 許多。的確,他們失去的比要付給開發商的要多(他們憑什麼付這筆錢,如果 這個軟件的商業價值很小?)

  因此,在可預見性過程不能用的情況下,簽訂固定價格合同對雙方來說都有危 險。這意味着客戶須換一種工作方式。

  這並不是說,你不能爲你的軟件固定一筆預算。這實際意味着你不能固定時間、 價格和範圍(scope)。通常一個敏捷方法是固定時間和價格,而讓範圍能夠可控制地變化。

  在適應性過程中,客戶實際上能夠對軟件開發過程進行很深入細微的控制。在 每一個迭代階段中,他們都能檢查開發進度,也能變更軟件開發方向。這導致 了與軟件開發者更密切的關係,或曰真正的商業夥伴關係。但並不是每一個客戶,也並不是每一個開發商都準備接受這種程度的介入,不過如要讓適應性過程能 很好工作,這種合作程度是基本的要求。

  這種開發方式可以給客戶帶來很多的益處。首先,這種開發的迴應性responsive)很好的。一個可用的,儘管是很小的系統能夠儘早投入使用。 客戶可以根據實際使用情況,以及變更了的需求,來及時改變一些系統功能。

  這樣一種方式能夠更真實地反映出項目的實際狀態。可預見性過程的問題是: 項目的質量是根據與計劃的一致性來衡量的。當實際情況與計劃脫節時,人們很難提出來。一般的結果是在項目的後期出現進度上的大滑坡。在敏捷型的項目中,每一個週期都對計劃進行評審。如果有什麼糟糕的事情的話,它也會早點被發現,因此仍然會有時間來解決。的確,這種風險控制是迭代式開發的一個關鍵優點。而敏捷型開發更進了一步,因爲它的週期很短,同時它也把這些變化看作是機會。

  這點對於定義什麼是成功項目有重要的意義。預見性項目是否成功是由它是否很好地按計劃執行來衡量的。一個項目如果在規定的時間和預算內完成,那就是成功的。對於敏捷型環境而言,這種衡量是沒有意義的。對於敏捷型項目實踐者來說,最重要的是商業價值(business value)- 客戶得到的軟件的價值是否大於他們的投入。一個好的預見性項目是依計劃而行,而一個好的敏捷型項目會建造出一個與最初計劃不太一樣卻是更好的軟件。

把人放在第一位

  實施一個適應性過程並不容易,特別是它要求一組高效的開發人員。高效既 體現在高素質的個體,也體現在有能讓團隊協調一致的工作方式。這裏有一個有趣的和諧:並非只是適應性過程需要強的團隊,多數優秀的開發人員也願意採用適應性過程。

可兼容性程序插件

  傳統正規方法的目標之一是發展出這樣一種過程,使得一個項目的參與人員 成爲可替代的部件。這樣的一種過程將人看成是一種資源,他們具有不同的角色,如一個分析員,一些程序員,測試員及一個管理人員。個體是不重要的,只有角色纔是重要的。這樣一來,在你計劃一個項目時,你並不在乎你能得到哪個分析員,哪些測試員,你只需關心你可得到多少,知道資源數量會如何影響你的計劃。

  但這有一個關鍵問題:參與軟件開發的人員是可替代的部件嗎?敏捷型方法 的一個重要特徵就是拒絕這種觀點。

  也許最明確地反對這種觀點的當數Alistair Cockburn. 在他的論文軟件開發中人是非線性,一階的部件中,他指出可預見性軟件開發過程要求部件的行爲也是可預見性的。 但是,人並非可預見性的部件。更進一步,他對軟件項目的研究導致瞭如下結論:人是軟件開發中最重要的因素。

  在本文的標題裏,我將人稱爲部件。(傳統)過程/方法就是這樣看待人的。這種觀點的錯誤在於沒有看到是非常可變的和非線性的,不同的個體具備特有的成功或失敗模式。那些因素是一階的,不可忽略的。一種過程或方法的設計者如不能充分考慮到這些因素,那麼其後果就是項目的無計劃軌跡。就象我們經常看到的那樣。
-- [Cockburn non-linear]


  Cockburn是最鮮明地主張在軟件開發中應以人爲中心,其實這種概念在許多軟件行業的有識人士中已是共識。問題在於所使用的方法是與這種理念背道而馳的。

  這造成了一個很強的正反饋機制。如果你期望你的開發人員是可互替的編程插件,則你不會去試着把他們看成是不同的個體。這會降低士氣(和生產率),並使優秀的人才跳到一個能發揮其個性特長的地方,最後你倒是得到你所需要的:可互替的編程插件。

  作出使人優先的決定是件大事,它需要很大的決心來推行。把人作爲資源的思想在工商界是根深蒂固的,其根源可追溯到泰勒的科學管理方法。當管理一個工廠時,這種泰勒主義途徑是有效的。但是對有着高度創造性和專業性的工作,我相信軟件開發當屬此類,泰勒主義並不適用(事實上現代製造業也在脫離泰勒主義模式)。

程序員是負責任的專業人員

  泰勒主義的一個關鍵的理念是認爲幹活的人並非是那些知道怎樣才能把這件活幹的好的人。在工廠中可能是這樣,原因是許多工廠裏的普通工人並非是最具聰明才智和最富創造力的人員。另一個原因也許是由於管理層和工人的的工資懸殊太大而導致的關係緊張。

  歷史證明這種情形在軟件開發中是不存在的。不斷有優秀人才被吸引到軟件行 業中,吸引他們的既有耀眼的光芒也有豐厚的回報(正是這兩樣誘使我離開電子 工程)。其他一些福利如對公司的股份持有使得程序員的利益與公司緊聯在一起。

  〔可能還有一個產生generational)效應。一些所見所聞讓我想 到是否在過去十來年中有很多的優秀人才轉入軟件行業。如果是這樣,這可能是當今年輕人崇尚IT業的原因,就象其他時尚一樣,其後總有一些實在 的理由。〕

  如果你想聘到並留住優秀人才,你得認識到他們是有能力的專業人員。因此, 他們最有資格決定如何幹好他們的技術工作。泰勒主義讓計劃部門來決定如 何干好一件工作的作法只有當計劃者比實際操作者更能知道怎樣作時纔有效。如果你擁有優秀的、自覺自勵的員工,那麼這點並不成立。

面向人的過程的管理

  敏捷型過程中以人爲本的理念可以有不同的表現,這會導致不同的效果,而並非所有結果都是完全一致的。

  實施敏捷型過程的一個關鍵之處是讓大家接受一個過程而非強加一個過程。 通常軟件開發的過程是由管理人員決定的,因此這樣的過程經常受到抵制,特別是如果管理人員已脫離實際的開發活動很長時間了。而接受一個過程需要一種自願致力commitment),這樣大家就能以積極的態度參與進來。

  這樣導致了一個有趣的結果,即只有開發人員他們自己才能選擇並遵循一個適 應性過程。這一點在XP中特別明顯,因爲這需要很強的自律性來運行這個過程。作爲一個互補,Crystal(水晶系列)過程則只要求最少的自律。

  另一點是開發人員必須有權作技術方面的所有決定。XP非常強調這一點。 在前期計劃中,它就說明只有開發人員才能估算幹一件工作所需的時間。

  對許多管理人員來說,這樣形式的技術領導是一個極大的轉變。這種途徑要求 分擔責任,即開發人員和管理人員在一個軟件項目的領導方面有同等的地位。 注意我說的同等。管理人員仍然扮演着他們的角色,但需認識並尊重開發人員的專業知識。

  之所以強調開發人員的作用,一個重要的原因是IT行業的技術變化速度非常之快。今天的新技術可能幾 年後就過時了。這種情況完全不同於其他行業。即使管理層裏的以前幹技術的人都要認識到進入管理層意味着他們的技術技能會很快消失。因此必須信任和依靠當前的開發人員。

測度的困難性

  如果有一個過程,規定工作應該如何來做的人不是具體去幹的人,那麼你需要一些方法來測度幹工作的人是否工作有效。在科學管理中,有種強烈的力量驅使着發展出客觀性方法來測度人們的工作輸出。

  這與軟件特別有關,但是要測度軟件是非常困難的。儘管人們已經盡了很大的努力,我們仍然不能對軟件的一些很簡單方面進行測度,如生產率。如果沒有一套有效的 測度方法,任何外部的控制都會是困難的(doomed)。

  不存在一套有效的測度方法而要在管理中引入測度將會導致管理本身出問題。關於這點, Robert Austin有段出色的討論。他指出,當進行測度時,你必須要獲取影響這種測度的所有重要因素。任何缺失都將不可避免地使得具體幹工作的人改變他們的工作方式以獲得最好的測度成績,甚至於那樣會明顯地降低他們真正的工作 有效性。這種測度的失效dynsfunction)正是基於測度的管理方法的 致命之處(Achilles heel,即阿其里斯的腳踝)。

  Austin的結論是你得在這兩種方法中作選擇:基於測度的管理,或是委託式delegatory)管理(幹工作的人決定該怎麼幹)。基於測度的管理是非常適合簡單的、重複性的工作,知識要求低並且易於測度輸出-- 這恰恰與軟件開發相反。

  關鍵之處是傳統方法假設的前提是基於測度的管理是最有效的管理方式。而 敏捷開發者則認爲軟件開發的特性會使得基於測度的管理導致非常高度的測度失效dysfunction)。實際上使用委託式的管理方式要有效得多,這正是敏捷論者所持觀點的中心所在。

業務專家的引領作用(The Role of Business Leadership

  但技術人員並不能包打天下,他們需要應用系統的需求引導。這導致了適應性過程 的另一個重要方面:他們需要與應用領域的業務專家非常緊密的聯繫。

  這種聯繫的緊密度超過了一般項目中業務人員的介入程度。如果開發人員和 業務人員只有偶爾的溝通,那麼敏捷型過程是不可能存在的。他們需要不斷地 獲取業務方面的專門知識。此外,這種溝通不是由管理層來處理的,而是每個開發人員需要做的事。因爲開發人員在他們的行業裏是有能力的專業人員,因此他們 能夠與其他行業的專業人員同等地在一起工作。

  這是由適應性過程的特點來決定的。因爲敏捷開發的前提是在整個開發過程中, 事情變化很快,你需要經常不斷的聯繫溝通以使每個人都能及時知道這些變化。

  對開發人員來說,沒有什麼比看見自己的辛勤工作白白浪費更讓人痛苦的了。 因此,開發人員能隨時獲取準確的高質量的應用系統的業務知識就顯得很 重要了。

自適應過程

  到目前爲止,我談到的適應性是指在一個開發項目中如何頻繁地修改軟件以適 應不斷的需求變更。但是,還有另一種適應性,即是過程本身隨着時間推移變 化。一個項目在開始時用一個適應性過程,不會到一年之後還在用這個過程。隨着時間的推移,開發團隊會發現什麼方式對他們的工作最好,然後改變過程 以適應之。

  自適應的第一步是經常對過程進行總結檢討。一般來說,在每一次迭代結束後, 你可以問自己如下問題 〔 Norm Kerth〕:

  。有哪些做的好的部分
  。有哪些教訓

  。有哪些可以改進的部分

  。有哪些沒搞清楚的部分

  這些問題會幫助你考慮在下一次迭代中如何對過程進行修正。在樣,如果 開始時使用的過程有問題的話,隨着項目的進行,該過程會得以逐步的完善, 以使其能更好地適合開發團隊。

  如果一個項目採用了自適應方法,則可以進一步在一個組織內引入這種方法。 如果要深化自適應過程,我建議開發人員專門用一段時間做一次更爲正式的 回顧總結,象Norm Kerth所建議的那樣,這些活動包括離開工作地點,到另外 一個地方,在一位有經驗的專家主持下開23天的總結會。這不僅是給開發組 提供一次學習機會,同時也給整個組織一次學習機會。

  自適應性導致的結果是你絕不能期待着只用一個過程。相反,每個項目組不僅 能選擇他們自己的過程,並且還能隨着項目的進行而調整所用的過程。公開 發表的過程和其他項目的經驗都可以拿來作爲參考和樣本。但是開發人員需根據手中項目的具體情況而對其加以調整,這也是開發人員的專業職責。

  這種自適應性在ASDCrystal(水晶系列)中都鮮明地提及。XP的嚴格規則 似乎不允許這樣,但這只是表面現象,因爲XP是鼓勵調整過程的。這一點上 XP和其他方法的主要區別之處在於,XP的倡導者建議在採用XP時,先根據書本 循規蹈矩不走樣地做幾個迭代之後,再考慮調整。另外,回顧總結這點在XP中沒有被強調,也不是這個過程的組成部分,儘管XP建議經常性的回顧應作爲XP 的實踐準則之一。

敏捷型方法

  好幾個方法都可以歸入敏捷型旗下,它們有許多的共同特徵,但也有一些重 要的不同之處。在此簡短的綜述中,我不可能列出這些過程所有的特點, 但至少我可以告訴你可以到什麼地方去查找更詳細的材料。這些方法的大多數我都沒有深入的實際經驗。我有很多工作是基於XP的,也對RUP有些經驗。但是對其他方法來說,我的知識主要是來自書本(當然這是很不夠的)。

XPExtreme Programming -- 極限編程)

  在所有的敏捷型方法中,XP是最爲引人矚目的。部分原因是因爲XP的領軍人物們的卓越能力,特別是Kent Beck,他能夠把人們吸引到這種方法來, 並一直處於領先地位。但是XP熱也帶來了一個問題,就是它把其他一些方法 和它們非常有價值的思想給擠了出去。

  XP根源於Smalltalk圈子,特別是Kent BeckWard Cunningham在(1980年代末的 密切合作。90年代初,他們在一系列項目上的實踐深化擴展了他們關於軟件開發應是適應性的、應以人爲中心思想。

  從非正式的、探索性的實踐到形成系統化的正規方法的關鍵一步是在1996年 春。Kent被邀對Chrysler的一個工資管理項目(C3〕的開發進度進行審覈。 該項目由一個簽約公司用Smalltalk開發,正處於困境之中。由於源碼質量低劣,Kent建議推倒重來。該項目然後在他的領導下從頭開始併成了早期 XP的旗艦和培訓基地。

  C3的第一期系統在1997年初投入運行。項目繼續進行了一段時間後,遇到了麻煩,導致了在1999年開發計劃被撤銷。(這也證明了XP並不是成功的保證)

  XP的四條基本價值原則是:交流,反饋,簡潔和勇氣。在此基礎上建立了十多條XP項目應遵循的實踐準則。其實,許多準則是以前就存在的並經過實踐檢驗的,而常常被忽略了的。XP重新建立了這些準則,並把它們編織成 了一個和諧的整體,使得每一項準則都能在其他準則裏得以強化。

  XP有一個最具衝擊力的,也是最初吸引我的特點,是它對測試的極端重視。 誠然,所有的過程都提到測試,但一般都不怎麼強調。可是XP將測試作爲開發的基礎,要求每個程序員寫一段源碼時都得寫相應的測試碼。這些測試片段不斷地積累並被整合到系統中。這樣的過程會產生一個高度可靠的建造平臺,爲進一步開發提供了良好的基礎。

  在此基礎上XP建立了一個漸進型的開發過程,它依賴於每次迭代時對源碼 的重整(refactoring)。所有的設計都是圍繞着當前這次迭代,而不管將 來的需求。這種設計過程的結果是紀律性適應性的高度統一, 使得XP在適應性方法中成爲發展的最好的一種方法。

  XP產生了一批領軍人物,許多是從C3項目中出來的。關於XP有許多文獻可 讀。Kent Beck寫的 Extreme Programming Explained, 是一篇XP的宣言,它闡述了隱藏在XP後面的理念。此書對有心於XP並致 力於將其發揚光大者提供了充足的說明和解釋。過去兩年裏也出版了一批多姿多彩XP書籍,但多數都很相似,主要是從一些XP早期的實踐者們的角度上描述了XP的整個過程。

  除了書之外,還有不少網上資源。如果你想找到更有結構性的材 料,你最好訪問兩位C3成員的網站:Ron Jefferies xProgramming.com Don Wells extremeProgramming.org。 許多XP早期的倡導和發展可在Ward Cunningham wiki web (合作寫作)中找到。wiki是個令人着迷的地方,盡 管它的漫遊性質常令人身不由己地陷入其中。 XP討論組〔xp discussion egroup〕也很有意思。 還有一篇很有意思的文章從(XP圈)外面來審視XP,這就是Mark Paulk所寫的 從CMM的角度看XPMark PaulkCMM的領軍人物之一。

Cockburn的水晶系列方法

  Alistair Cockburn 90年代初受IBM之約進行正規方法的研究,從那時起他就活躍於這個領域。 但他的研究途徑和其他方法學者有所不同。一般的方法 學者是將他們個人的經驗昇華成理論,而Cockburn除了歸納整理他自己的實 踐經驗以外,他還積極地造訪其他項目,和項目組成員進行廣泛的討論, 考察這些項目是怎樣運作的。難能可貴的是,他從不固守自己的觀點,他會根據新的發現而隨時修正自己的理論。他的這些特點使得他成爲我 最喜歡的方法學者。

  他的著作 Surviving Object-Oriented Projects 彙集了很多如何順利運行軟件開發項目的建議,此書也是我推薦的運行迭代式項目的首選書。最近,Alistair寫了一本關於 敏捷型軟件開發 的綜述性著作,探討了這些方法的基本原則。

  Alistair還更進一步地探索了敏捷型方法,並提出了水晶(Crystal)方法系列。之所以是個系列,是因爲他相信不同類型的項目需要不同的方法。他認爲決定一個方法與兩個因素有關:項目參與人數和出錯後果。如果用兩個座標軸來分別表示這兩個變量的話,那麼在這張圖上,每一種方法都有其相應的座標位置。例如,有兩個項目,一個是有40人蔘加,如果失敗造成的資金損失可以接受;另一個項目只有6人,其成敗生存悠關。那麼這兩個項目所用的方法在座標圖上就有不同的位置。

  水晶系列與XP一樣,都有以人爲中心的理念,但在實踐上有所不同。Alistair 考慮到人們一般很難嚴格遵循一個紀律約束很強的過程,因此,與XP的高度 紀律性不同,Alistair探索了用最少紀律約束而仍能成功的方法,從而在產出 效率與易於運作上達到一種平衡。也就是說,雖然水晶系列不如XP那樣的產 出效率,但會有更多的人能夠接受並遵循它。

  Alistair也費了不少筆墨強調每次迭代後的總結回顧,因而鼓勵過程本身的 自我完善。他的理由是迭代式開發是用來儘早發現問題並解決之。這樣就更加強調了開發人員要隨時觀察他們所用的過程,並隨着項目的進行而調整。

開放式源碼

  看到這個標題你可能會有些意外。畢竟,開放式源碼(Open Source) 是軟件的一類風格,而非一種過程。這裏我是指開放源碼界所用的一種運作方式,這種方式適用於開放源碼項目,其實它的許多做法也可爲封閉式源 碼項目所用。開放式源碼項目有一個特別之處,就是程序開發人員在地域上分 布很廣。注意到這點相當重要,因爲一般適應性過程都強調項目組成員在同一地點工作。

  多數開放源碼項目有一個或多個源碼維護者(maintainer)。只有維護者 才能將新的或修改過的源碼段併入源碼庫。其他衆人可以修改源碼,但需將他們所做的改動送交給維護者,由維護者對這些改動進行審覈並決定是否併入源碼庫。 通常來說,這些改動是以補丁patch)文件的形式,這樣處理起來容易一些。 維護者負責協調這些改動並保持設計的一致性。

  維護者的角色在不同的項目中有不同的產生和處理方式。有些項目只有一個 維護者,有些項目把整個系統分成若干個模塊,每個模塊有一個維護者。有 些是輪流做維護者,有些是同一個源碼部分有多個維護者,有些則是這些方式的組合。許多開放源碼項目的參與者只是部分時間(或業餘時間)幹,如 果項目要求是全日制的,那麼這有一個問題,就是怎樣才能把這些開發人員 有效地協調組織起來。

  開放源碼的一個突出特點就是查錯排故(debug)的高度並行性,因爲許多 人都能同時參與查錯排故。如果他們發現了錯誤,他們可將改正源碼的補丁文件發給維護者。這種查錯排故角色對非維護者來說合適,對那 些設計能力不是很強的人來說,也是一項挺好的工作。

  關於開放源碼的方法過程還沒有很系統的文獻。目前最著名的一篇文章是 Eric Raymond寫的 The Cathedral and the Bazaar(教堂與集市), 文章雖短,但很精彩。另外,Karl Fogel所著的 關於CVS的書中也有幾章 描述了開放源碼的方法。即使你不想使用CVS,這幾章還是值得一看。

Highsmith的適應性軟件開發方法(ASD--Adaptive Software Development)

  Jim Highsmith 多年來一直從事可預見性方法的研究,建立和教學,而最後得出的 結論是這些方法都有着根本性的缺陷,特別是在用來作現代應用系統的開發時。

  他最近的 一本書 集中論述了新方法的適應特性,重點討論了把一些起源於 複雜適應性系統(通常稱之爲混沌理論--chaos theory〕的思想在軟件 開發中加以應用。此書沒有象XP那樣提供詳盡的實踐準則,但它從根本上 說明了爲什麼適應性開發方法是重要的,並進一步說明了其對組織結構和管理層的影響。

  ASD的核心是三個非線性的、重迭的開發階段:猜測、合作與學習。

  在一個適應性環境中,因爲結果是不可預見的,Highsmith把計劃看成是一個反論paradox〕。在傳統的計劃中,偏離計劃是錯誤,應予糾正。而在一個適應性環境裏,偏離計劃則是在引導我們向正確的目標邁進。

  在不可預見的環境裏,你需要大家能以多種多樣的方式合作來對付不確定性。 在管理上,其重點不在於告訴大家做什麼,而是鼓勵大家交流溝通,從而使他們能自己提出創造性的解決方案。

  在可預見性環境裏,通常是不大鼓勵學習的。設計師已經都設計好了,你跟着走就行了。

  在適應性環境中,學習對所有各方,包括開發人員和客戶,都是一個挑戰。他們需要學習以檢驗他們作的假設,需要學 習以便能用每次開發週期的結果去適應下一個週期。
-- [Highsmith]

  這樣的學習是連續不斷的,這已成爲這種方法的一個重要特點,因此我們 必須得認識到計劃和設計都得隨開發的推進而改變。

  適應性開發週期的最強力的、不可分割的好處是其對我們自以爲是的心理 模式的挑戰,它迫使我們更實際地估計自己的能力。
-- [Highsmith]


  有了這樣的出發點,Highsmith把他的工作集中放在適應性開發的難點上, 特別是如何在一個項目中增強合作和學習。基本上說,他的這本書是側重於方法,這樣對那些從開發實踐中提煉出來的方法如XPFDD和水晶系列來說,這本書將是一個很有益的互補。

SCRUM

  SCRUMOO界裏已很有些時日了,不過我得承認我對其歷史發展並不是太知其詳。象前面所論及的方法一樣,該方法強調這樣一個事實,即明確定義了的可重複的(defined and repeatable)方法過程只限於在明 確定義了的可重複的環境中,爲明確定義了的可重複的人員所用,去解決明確定義了的可重複的問題。

  SCRUM把一個項目分成若干個爲期30天的迭代階段,稱之爲 sprint〕。開之前,你得明確這一要實現的功能,然後 交給開發組去完成。但是,在期間,需求必須是固定的。

  管理人員並非在的時候就撒手不管了。每天,他需召集一個短會(15分鐘左右),稱之爲一個scrum,會上大家討論決定第二天幹什麼。特別是大家會對管理層提出那些阻礙項目進行的因素,並希望管理層能予以解決。當然,他們也需要報告目前完成了什麼,這樣管理層每天都能瞭然項目的進展情況。

  SCRUM文獻多集中論述迭代階段計劃與進度跟蹤。它與其他敏捷型方法在 許多方面都很相似,特別是它可以與XP的編程準則很好地結合起來。

  相當長的一段時間沒有關於SCRUM的專門書籍,直到最近 Ken SchwaberMike Beedle寫了 第一本SCRUM的專著。Ken Schwaber還主持了一個網站 ControlChaos.com,可能是對SCRUM的最好的綜述。Jeff Suthurland有個總是很活躍的網站討論OO技術,其中有 一部分是專門討論SCRUM的。另外,在 PLoPD 4書中也有一篇關於SCRUM的很好的綜述。 Scrum也有一個Yahoo討論組.

功用驅動開發方法(FDD--Feature Driven Development

FDD
是由Jeff De LucaOO大師Peter Coad提出來的。象其他方法一樣, 它致力於短時的迭代階段和可見可用的功能。在FDD中,一個迭代週期 一般是兩週。

FDD有以下五項任務:

  。建立總體模型
  。提出功用清單

  。針對功用逐項制訂計劃

  。針對功用逐項進行設計

  。針對功用逐項開發實現

  頭三項在項目開始時完成,後兩項在每一次迭代週期時都要做。每一項 任務又可進一步分解並制訂出相應的檢驗準則。

  在FDD中,編程開發人員分成兩類:首席程序員和程序員(class owner〕。首席程序員是最富有經驗的開發人員,他們負責開發實現系統的各項功能。對每一項功能,首席程序員要定出需要哪些類(class) 來實現這項功能,並召集程序員們組成一個針對這項功能的開發 組。首席程序員作爲協調者,設計者和指導者,而程序員則主要 作源碼編寫。

  關於FDD的文檔資料比較少。直到最近終於有了一本全面 論述FDD的著作。FDD 的主要提出者Jeff De Luca現已建立了一個 FDD門戶網站, 收錄了一些文章、筆記和討論。FDD的最早的論述見於Peter Coad等所著的 UML in Color 。他的公司 TogetherSoft 也從事FDD的諮詢和培訓工作。

動態系統開發方法〔DSDM--Dynamic System Development Methods

  DSDM1994年始於英國。 英國一些想用RAD和迭代方式進行系統 開發的公司組成了一個社團〔Consortium〕。剛開始有17個組建成員, 到現在成員已超過1000個,遍佈英國內外。DSDM由於是由一個社團所 發展,它與其他一些敏捷型方法有些不同。它有專門的組織支持, 有手冊,培訓課程,認證程序等。因爲它上面的價格標籤而限制了我對此方法的進一步調查。不過Jenifer Stapleton已寫了 一本書來介紹這種方法。

  如果你要用這種方法,你得先作可行性和業務分析。可行性分析要考慮 DSDM是否適合手上這個項目。而業務分析則是開一系列的討論會,以期能充分了解應用域,同時也要提出大致的系統結構與項目計劃。

  餘下的工作由三個互相交織的週期組成:功能模型週期產生文檔和原型 (實驗系統),設計建造週期生成可操作使用的系統,實現週期 處理系統安裝部署(deployment)問題。

  DSDM有一些基本的原則包括與用戶積極的交流,頻繁的交付(delivery)。有充分職權的項目組,完全的系統測試。象其他敏捷型方法一樣, DSDM的一個週期在26周之間。它強調高質量的系統以及對需求變更的高 度適應性。

  我還沒有在英國之外的地方看到有項目使用DSDMDSDM的基本結構有許多成熟的傳統方法的東西,同時又遵循了敏捷型途徑。但這裏的確有一點 值得注意,即是這種方法是否有鼓勵一種面向過程與繁瑣的傾向。

敏捷軟件開發宣言

  可以看出,前面所提到的這些方法有很多的相似之處,那麼自然大家 就會有興趣進行某種形式的合作。20012月,這些方法的代表人物們 被邀至猶它州Snowbird舉行了一個爲期兩天的討論會。我也身在其列,但開始並未抱太大希望。畢竟,當你把一堆方法學者塞進一間房子時, 他們能以禮相待就是上上大吉了。

  結果卻出乎我的意料之外。每個與會者都認識到這些方法有許多的共同 點,這種共識遠遠大於他們之間的分歧。這次討論會使這些一流的 方法學者們增進了聯繫,大家還有意發表一份聯合聲明--呼籲推動發展輕靈型開發過程(我們也同意用敏捷agile)這個詞來表達我們 共有的理念)。

  成果便是一份 敏捷軟件開發宣言〔Manifesto for Agile Software Development〕,它表述了敏捷型過程所共同具備的價值和原則。與會者也有意在將來進 一步合作,並鼓勵方法學者與業務人士使用敏捷型方法進行軟件開發。 Software Development Magazine〔軟件開發雜誌〕有一篇關於 宣言的評註與解釋的文章。

  當然,宣言只是一份發表的文件,是一個讓有着共同理念的人們的聚集點。這個 努力的另一個成果是建立了一個 敏捷聯盟。敏捷聯盟是個 非盈利性組織,其宗旨是推廣敏捷型方法的知識和促進這方面的討論。許多我已提到的敏捷方法的領軍人物也都是這個聯盟的成員和領導者。

相關環境驅動測試(Context Driven Testing

  敏捷開發運動最初是由軟件開發人員來推動的。但是,參與軟件開發的其他方面 的一些人士也受到這個運動的影響。一個明顯的羣體是測試人員,他們通常是生活在由瀑布式開發所限定的世界裏。一般來說,測試的作用是保證軟件與開始的設計 相符合。而在敏捷世界裏,測試人員的角色還很不清楚。

  實際上,在測試圈內有好幾位人士有相當一段時間一直在質疑這種主流的測試思維。 這導致了一個稱之爲相關環境測試context driven testing)的羣體。 對這個概念的最好的論述是 “Lessons Learned in Software Testing這本書。 這個圈子在Web上也很活躍,你可以去看看這幾位的網站, Brian Marick(他也是敏捷宣言的作者之一), Brett Pettichord James Bach,和 Cem Kaner

RUP是一種敏捷型方法嗎?

  當我們開始討論OO領域的方法時,不可避免地會碰到 RUPRational Unified Process〕。該過程由Philippe Kruchten Ivar Jacobson以及Rational公司的其他一些人士開發,主要是作爲一個與UML相配合和補充的過程。RUP其實是個過程的框架,它可以包容許多不同類型的過程,這一點正是我對RUP的主要 批評。因爲它可以是任何東西,那麼就跟什麼都不是一樣了。我願意選擇的過程是它能明確告訴你幹什麼,而不是給你無窮無盡的選擇。

  由於RUP是一種框架,你可以以不同的方式來使用它,如象非常傳統的瀑布式開發方式,或敏捷式。你可以把它用得輕捷靈便,也可把它弄成繁文縟節。 這取決於你如何在你的環境中對它裁剪運用。

  Craig Larman極力主張以敏捷方式來使用RUP。在他的關於OO開發的這本出色的引論著作中,他提出了一個過程,就是基於這種輕型”RUP的思想。他的觀點是: 目前如此衆多的努力以推進敏捷型方法,只不過是在接受能被視爲RUP 的主流OO開發方法而已。在做一個項目時,Craig要乾的事情之一便是在爲期一月的迭代週期的頭兩三天和整個開發組呆在一起,用UML勾勒出這個迭代 階段的設計。這個設計並非是一個不可更改的,它只是一個使大家能 知道這個階段如何幹的草圖。

  另一種對待RUP的策略是Robert Martin dX過程。dX過程是一個完全符合RUP 的過程,而又碰巧與XP完全等同(把dX180度可見,一句戲言〕。dX是 特別適合於那些不得不用RUP而又想用XP的夥計們。由於dX既是XP又同時是 RUP,它可作爲以敏捷方式運用RUP的一個極好的例子。

  對我而言,在運用RUP時的一個關鍵之處在於業界RUP的領頭者們需強調他們的軟件開發途徑。曾經不止一次,我聽到使用RUP的人是在使用瀑布式開發過程。根據我在業界的聯繫,我知道Philippe Kruchten和他的小組是堅定的迭代式開發信奉者。澄清這些原則並鼓勵敏捷式使用RUP,如象Craig Robert的工作,將對軟件開發有着重要的影響和作用。

其他參考材料

  最近,有兩本好書出版,分別由 Alistair Cockburn Jim Highsmith所著,他們在書中廣泛地討論了敏捷開發的方方面面。

  關於敏捷型方法有不少文章和討論組,它們可能不會提供完整的方法,但可以 給你一個窗口以觀察這個正興起的領域在如何發展。

  程序設計的模式語言(Patterns Language of Programming〕 大會經常會有一些材料討論這個題目,這也許是因爲許多對模式(Pattern〕感興趣者也對適應 型和人道方法過程感興趣的緣故吧。這方面有一篇早期的一流論文, Jim Coplein所著,收集在 PLoP1中。Ward CunninghamEpisodes模式語言收集在 PLoP2 中。Jim Coplein現主持一個網站, OrgPatterns, 以wiki方式收集了不少組織結構模式。 Dirk RiehleXP2000大會上提交了一篇論文,該文 比較了XP和適應性軟件開發(Adaptive Software Development, ASD〕的 價值系統。Coad Letter的 七月期比較了XPFDDIEEE Software的七月期有幾篇文章論述過程多樣性process diversity〕,也提及這些方法。

  Mary Poppendieck寫了一篇很精彩的文章 比較敏捷型方法與精悍(lean)型製造業。

你是否應走向敏捷?

  並非人人都能使用敏捷型方法。當你決定走這條路時,你得記住許多準則。 但是,我確切相信,這些新方法可被廣泛的應用。只是考慮使用它們遠遠 不夠,應該有更多的人來實踐中運用它們。

  在目前的軟件開發中,多數方法仍是邊寫邊改(code and fix),那麼,引入 一些紀律約束肯定會比一片混亂要好。敏捷型途徑的主要優點在於它比重型方法的步驟要少得多。如果你已習慣於無過程,那麼遵循簡單過程應該比遵循 繁瑣過程更容易一些。

  這些新方法的一個主要侷限是如何對付較大的項目組。象其他許多新方法一樣, 這些敏捷型方法也是願意先在小規模上使用,而通常它們被提出來時的重心 也是放在小規模的項目組上。XP明確說明它適合於最多20人的項目組。這裏 值得一提的是,許多軟件項目組可以減少人數而不會減少總體的生產率。

  其他敏捷型方法試圖能用在大一些的團隊。FDD最初是爲50人左右的項目設計的。ThoughtWorks曾在三個大陸(洲)上使用過類似XP的方法,項目組成員是100人左右。Scrum也在類似規模的項目中使用過。

  這篇文章至少清楚地傳遞了這樣一個信息,就是適應性方法對需求不確定或常 常變更的情形是有效的。如果你沒有穩定的需求,那麼你就不可能進行穩定的 設計並遵循一個計劃好了的過程。這種情況下,適應性過程可能感覺上不太舒服,但實踐上會更有效。通常來說,使用適應性方法最大的障礙來自客戶。我認 爲,重要的一點是讓客戶理解在一個需求不斷變更的環境中,遵循可預設性過程 對他們是有風險的,同樣對開發方也是有風險的。

  如果你要採用適應性方法,你需要信任你的開發人員,並讓他們參與(技術) 決策。適配性過程的成功依賴於你對你的開發人員的信任。如果你認爲你的開 發人員素質不夠,那麼你應採用可預設性途徑。

  總結一下,如下的因素建議你採用適應性過程:

  。不確定或常變更的需求
  。負責任的、自覺自勵的開發人員

  。理解並願意參與其中的客戶

而如下這些因素則建議你使用可預見性過程:

  。100人以上的項目組
  。固定價格,或更確切的說,固定任務範圍的合同

 
發佈了25 篇原創文章 · 獲贊 2 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章