2012年6月4日 星期一

如何當一個優秀的救火員之打蛇打七寸

Standard
經歷過許多救火任務(這邊當然是指軟體系統專案的問題),從 Kernel、Driver、Porting 到各類系統應用程式、Web 應用程式,幾乎無所不救,有社群內的朋友嘲笑著說:『在國內大概沒有團隊,像我們一樣什麼火都救。』。有許多的感觸是肯定的,從問題發生的那一面看去,加以急迫時間的摧化之下,感受到許多程式開發者最真實的思維和邏輯,當然,還有發案老闆的著急,以及清楚看到許多關係人暗地裡的算盤。我不敢說自己是優秀的救火員,但至少一旦承諾並接下案子,有再大的困難和阻礙也總是使命必達,哪怕連續數個月每天平均只睡兩小時,三四天只睡一次,也不會倒下。我想,最少我可以談談如何救火,還有從火堆中看到的許多『人』的問題。

其實,救火沒什麼訣竅,撲滅它是其次,主要是確保不會再燃起。俗話說『打蛇打七寸』,解問題時也要解到夠力才行。但是,或許你常聽有人說:『產品要出貨,能動最優先,好不好其次。』,似乎恰恰與我所提到的相反。沒錯,因為要確保火不會再燃起,理論上是要找出問題根源,從根源著手和解決,很多人乍聽之下,都會覺得是費工又花時間的工作。但是,每次的經驗都告訴我,實際上,找出問題根源,並解決他,往往比修補來得快很多,而且一勞永逸。尤其是碰到死線(deadline)將至,卻一直解不掉的一大坨 Bugs(指同類相關或互相影響的問題),從根本上並深入解決問題才是最快的辦法。

這邊有個簡單的案例:
有一個應用程式,與 Library、System API 和 Driver 都有相關,他們相依關係是:

應用程式 -> Library -> System API -> Driver

假設現在有個問題,這支應用程式出現了一個 Bug,而這個問題最終被發現是 Driver 缺少一些功能,而造成功能不正常。那我們應該從哪裡去解決?

一般人的做法,若是為了因應『產品要出貨,能動最優先』,所採取的方法當然是拿掉應用程式上的某些受影響的功能,或是改寫應用程式以另循途徑的方法達成同功能。但是,若你選擇這樣的做法,最可怕問題是,如果應用程式太過複雜,修改起來會有更多的副作用(Side effect),你要花更多時間去處理更多原先不存在的問題。

況且,這都還沒講到,我們根本都還不知道 Driver 造成的影響範圍有多大,有哪些 System API 、Library API 和依賴他們的應用程式受影響。而今天這個應用程式僅僅只是剛好被發現到有問題而已,一切都還只是開端,之後會發現其他地方也開始到處起火了。或許這邊講的有點危言聳聽,但都是實際上常遭遇的狀況。

不過,這種方法也不是完全不對,如果你很肯定,問題只會出現在一個地方,而且該功能可以被容許直接拿掉,這是最佳的救火方案。

可是往往情況沒有這麼簡單,你必需保留該功能或是更多功能,不但如此,你也必需修好它。所以,在這種情況下,通常我自己會使用前後夾擠式的做法,去尋求解決方案。由上而下(從應用程式往Driver 方向)的部份,主要還是與之前一樣,尋找只出現在一個地方的問題點,我們可以直接移除以滅火。而從下而上,就是盡可能直接把根源問題點解決。因此,首先當然是盡可能下手修好 Driver 的問題,不過,如果有不能修改的理由,就依序往上找到可修改的問題點。

要知道,修復一個 API 可以修好數十數百個使用它的應用程式,但你如果是從應用程式著手,那你就要重覆一一修改不同的應用程式,更別說還要花上每次測試找問題的時間,其數量是以指數級數來算的。所以除了直接刪除功能的做法,最好的處理位置依序為『Driver > System API > Library > 應用程式』。

曾經有碰過一場火,客戶軟體所在的平台本身不穩定,但他們不旦不釐清平台的問題,反而一直著手修改應用程式去避開平台本身的問題,最終不但把自己原本的程式改得亂七八糟難以維護,更是讓修改後的程式打了結,得到許多無解的副作用。更可悲的是,下次換了一個平台,這次做的所有修改,都是白做要重新來過。


後記

你或許覺得本文提供的方法,並沒有什麼大不了的,那是因為已經公布了問題點所在。事實上,實際情況可能更為糟糕,你可能根本不知道是哪一層發生問題。而如何在尋求解決方案的過程中,找出問題可能的發生源,又會是另一個故事了。:-)