軟件開發的一些"心法"

從事軟件開發也有好幾年了,和一開始那個懵懵懂懂的小菜鳥相比,自己也感覺到了一些變化. 也許是熟能生巧,
趟過很多坑,但核心的絕不是這些細節的東西. 打個比方,如果說對某種語言的特性和技巧的掌握屬於身法,
那麼對應核心的東西,就叫心法. 沒有身法,心法難以實戰;但是沒有心法,身法再炫也不過是無謂的雜耍而已.
今天,就來講講多年浸淫軟件開發所感悟的一些"心法".

三部曲

軟件開發,無論是用什麼語言,在什麼操作系統,都有其本身不變的東西,稱之爲編程思想.對我而言,
我所遵循的開發思想其實很簡單,卻都是血淚的經驗所匯結而成. 我將其總結爲三點:

  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縮進,每個公司或者小組都應該有固定的規章,這樣可以免去爲這些細枝末節的事情操心,從而專心投入功能開發之中.

總結

綜上所述,一個高效的軟件開發過程應該是這樣的:

  1. 明確開發需求.
  2. 針對需求切分不同功能模塊.
  3. 針對每個模塊編寫代碼/單元測試.
  4. 對每個模塊進行結構整理(即重構).
  5. 對每個模塊進行性能優化(可選).
  6. 整合所有模塊,如果需要可以再次進行重構,提煉公共部分.
  7. 最終測試/交付.

最後,這只是筆者的一家之言,對於本人來說確實可以顯著提高開發效率, 但每個人的經驗和習慣可能並不相同,
也許細節上也會有所不同, 但即便道路不同,我們想要"做出點東西"的心情也都是一樣的. 所以需要做的則是摒棄偏見,
兼聽並濟,取長補短,最終形成屬於自己的高效開發模式.

博客地址:

歡迎交流,文章轉載請註明出處.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章