從事軟體開發也有好幾年了,和一開始那個懵懵懂懂的小菜鳥相比,自己也感覺到了一些變化. 也許是熟能生巧,
趟過很多坑,但核心的絕不是這些細節的東西. 打個比方,如果說對某種語言的特性和技巧的掌握屬於身法,
那麼對應核心的東西,就叫心法. 沒有身法,心法難以實戰;但是沒有心法,身法再炫也不過是無謂的雜耍而已.
今天,就來講講多年浸淫軟體開發所感悟的一些"心法".
三部曲
軟體開發,無論是用什麼語言,在什麼作業系統,都有其本身不變的東西,稱之為程式設計思想.對我而言,
我所遵循的開發思想其實很簡單,卻都是血淚的經驗所匯結而成. 我將其總結為三點:
1) Make it work, 2) Make it clean, 3) Make it fast. 排序分先後,而且缺一不可.
Make it work (使其可用)
這個規則似乎是顯而易見的, 軟體開發的目的就是為了解決實際問題. 但是,忘記這個規則的
程式設計師,也不在少數. 君不見,每次上技術論壇,都有人在問:"我是新手,應該學哪門語言?",
或者討論"XXX語言怎麼臃腫複雜難用","XXX語言怎麼語法奇異古怪",等等.
說真的,這些事情重要嗎? 我學的第一門程式語言是Verilog,之後轉到SystemC做系統模擬,後來順其自然地學習了C++,
之後才完全轉到軟體行業. 說這個想說明, 對於新手而言,第一門學習的語言並不重要, 它的作用是讓你瞭解人與機器的互動介面,
也就是條件,迴圈,函式等基本概念. 再者,學習某一門程式語言,最好的辦法就是那句至理名言:JUST DO IT
,
糾結於語言,平臺,難度這些東西反而是本末倒置, 程式設計首先要明確的事情是你想做什麼.比如想做嵌入式,硬體相關,那C/C++是首選;
想做手機app, 當然是Java(Android)或Objective C;想做些資料處理,或者小工具簡化日常工作,那我會推薦Python;
想做網頁,除了JavaScript還有其他選擇嗎?因此, 忘記網上那些討論吧. 語言聖戰,也許只有新手才會熱衷於此.
聽聞使用不同開發語言的人會互相鄙視,比如C++鄙視JAVA, JAVA鄙視Python, Python鄙視JS, 等等, 這讓我深感無聊且幼稚.
對於有經驗的軟體開發人員,太執著於開發語言更是不必要. 既然是有經驗,那至少是對各種型別的語言都有所瞭解,
比如強型別的C/JAVA, 動態型別的Python以及某種函式式語言如Scala或Haskell等. 多種語言之間雖然語法稍微有所不同,
但大體上都能在上述型別中找到很多相似的地方,也就是說,只要稍微花一兩週學習語法,應該要能很快投入到團隊專案中.
當然對於特別複雜的語言(如C++),多花點時間瞭解語言特性也是必要的.
總而言之,好的軟體工程師,應該要有舉一反三,快速學習的能力. 最為重要的,不要忘記自己出手的目的,
那就是Make it work, 客戶端應用也好, 服務端組建也罷, 都是為了實現其功能, 對其他服務或者人進行有效而正確的交付.
在這個階段, 除了功能無需考慮太多其他事情, 不忘初心, 才能免於掉入效率的陷進之中.
Make it clean (使其整潔)
程式碼整潔,是每個軟體工程師都或多或少聽說過的概念. 但是這個概念又不像第一點那樣顯而易見.
因為我們即使不管程式碼整潔與否,程式都能實現最初的功能,交付給老闆他也很是高興. 相反,
如果多花時間在程式碼的整潔性上,那必然會延時交付,從而會使得老闆不高興.因此, 這一條也是
三部曲中分歧最大的.
當然了,對於軟體工程師而言,在時間足夠的情況下,幾乎不會有人反對程式碼整潔. 但現實是專案的時間
往往緊迫,而且改善程式碼質量在短期內也看不出有什麼收穫. 可是如果不注重,等到專案規模擴大之後,
開發者就會陷入程式碼耦合,結構混亂,難以擴充和難以維護的屎坑之中.
關於程式碼整潔,細說起來也是內容龐雜. 在這裡可以推薦三本書:
- <>
- <>
- <>
在軟體功能開發結束之後,我們可以優化現有的程式碼,將其中的模組和邏輯重新整理,使得整體結構清晰明朗,
一些有用的模組,可以獨立出來,可以複用到以後的專案之中,以減少重複造輪子的時間.
值得一提的是, 程式碼整潔往往離不開重構, 而重構又離不開單元測試. 因為只有單元測試有足夠的覆蓋率,
你才能在改善程式碼的時候保證不影響現有的功能. 不論是對現有程式碼的重構, 還是保證新程式碼的一致性(coding style),
都需要額外花費時間, 但最後你會發現所付出的小部分時間, 會在將來以10x的效率提升而返還.
Make it fast (使其高效)
沒人喜歡慢吞吞的程式碼. 對於面向使用者的服務更是如此. 如果每次開啟一個APP或者渲染一個頁面,都需要5,6秒的時間,
那很可能這個使用者就流失掉了. 除去I/O的原因, 程式執行的效率也是一個重要的考量因素.
影響程式執行速度的原因有很多,比如演算法的複雜度,記憶體分配/拷貝的頻率,以及系統上下文切換等.
很多時候我們也不能想當然地就進行優化. 正確的做法是通過profiler
來進行分析. 現代的整合開發環境(IDE),
應該都會提供對應的profiler. 以Linux的c/c++程式為例, 我們就可以用gprof
對應用程式進行分析.
其提供了每個函式的執行時間(百分比)/累計執行時間,呼叫次數等有用的資訊,幫助我們查詢程式熱點,
從而改善程式的執行效率. 畢竟,根據二八定律,程式執行所消耗80%的時間,大都產生於20%的程式碼之中.
改善效率,有可能是減少某個具體演算法函式的時間複雜度(比如替換random函式),有可能是用引用取代複製減少記憶體拷貝,
也有可能是增加快取減少網路/磁碟的IO頻率. 具體的方法取決與程式的熱點所在. 一些看起來似乎有用的做法,
比如迴圈展開,函式inline以期減少堆疊開銷等, 簡直是大腿上把脈——瞎搞.
亂序陷阱
上面講了軟體開發過程中的三部曲,但是有一點非常重要,即三部曲的順序是嚴格從上倒下的. 而其中一些亂序錯誤,
也成為了如今常見的開發阻礙:
提前優化
如果開發時第一步考慮的不是使其可用,而是使其高效,那麼很有可能就掉進了提前優化的陷阱. 相信大家對
"提前優化是萬惡之源"這句話也不會陌生. 如果開發者在八字還沒一瞥的時候就說, 我要弄三級快取減少資料庫訪問,
或者我要整合xxx-kqueue支援C1000K的高併發, 那麼這個專案可能就危險了. 且不說優化是否能成功地work around,
即便這個針對性的優化達到其效能要求,也未必是最終應用的熱點所在. 花了大把時間, 最後可能快取命中率極低,
或實際最高併發數還不到500, 那這些功夫就有點得不償失了. 再者,提前的優化為初期開發套上了枷鎖,
從某種程度上說,也降低了專案的開發效率.
濫用"設計模式"
上面第二點程式碼整潔中提到了,軟體開發,特別是物件導向的軟體開發,其好處在於可以切分模組邊界,使得程式碼可以複用.
但是我卻不提倡對此過於執著. 首先想程式碼能夠重用, 就必須給模組提供對應的靈活性, 也就是說單一模組的功能應該儘量
簡單且通用. 事實上功能越具體的模組就越難以重用, 只有一些抽象的功能才值得花功夫去提煉.
設計模式, 通常指的是GoF的23中物件導向的設計模式. 有的程式設計師看過此書後,就急著上手應用,每次專案剛開始,就考慮
要用哪幾個模式,這來個Factory,那來個Delegate. 這其實有點本末倒置,無異於拿著錘子找釘子. 設計模式的初衷是
改善程式碼結構,減少耦合提高擴充套件性. 這隻在專案到了一定規模才會有實際好處, 如果只是中小型專案, 增加的這些間接層,
很有可能反而提高了複雜性,純屬畫蛇添足.當然, 如果你是個非常有經驗的程式設計師, 對於這些模式的best practice瞭然於胸,
在某些情況下一開始就採用某種結構也無不可, 但我還是建議對其採用保守態度, 畢竟'Simple is better than complex'.
設計模式最好還是在重構的階段再按情況決定是否採用為好.
除了重構,程式碼整潔的一個重要方面是編碼規範,這倒是要在專案開始前制定好的,比如變數命名,大括號換不換行,
用空格還是TAB縮排,每個公司或者小組都應該有固定的規章,這樣可以免去為這些細枝末節的事情操心,從而專心投入功能開發之中.
總結
綜上所述,一個高效的軟體開發過程應該是這樣的:
- 明確開發需求.
- 針對需求切分不同功能模組.
- 針對每個模組編寫程式碼/單元測試.
- 對每個模組進行結構整理(即重構).
- 對每個模組進行效能優化(可選).
- 整合所有模組,如果需要可以再次進行重構,提煉公共部分.
- 最終測試/交付.
最後,這只是筆者的一家之言,對於本人來說確實可以顯著提高開發效率, 但每個人的經驗和習慣可能並不相同,
也許細節上也會有所不同, 但即便道路不同,我們想要"做出點東西"的心情也都是一樣的. 所以需要做的則是摒棄偏見,
兼聽並濟,取長補短,最終形成屬於自己的高效開發模式.
部落格地址:
歡迎交流,文章轉載請註明出處.