編寫高質量程式碼

發表於2016-08-02

我們知道,intdouble能表示的數值的範圍不同。其中,64位有符號整數的範圍是[-9223372036854775808,9223372036854775807],而64位無符號整數的範圍是[0,18446744073709551615]。這兩個區間有一定的overlap,而double可以表示的範圍更大。

現在,需要編寫兩個函式:給定一個double型的value,判斷這個value是否是一個合法的int64_t或者uint64_t。本文說的“合法”,是指數值上落在了範圍內。

這裡我們用Double而不是double,原因是我們的double不是基礎資料型別,而是通過一定方法實現的ADT,這個ADT的成員函式有:

通過呼叫get_next_digit,可以返回一個數字,不斷呼叫它,可以得到所有digits。舉個例子,對於值為45.67的一個Double物件,呼叫它的get_next_digit成員函式將依次得到

4 is_decimal = false //表示整數部分

5 is_decimal = false //表示整數部分

6 is_decimal = true //表示小數部分

7 is_decimal = true //表示小數部分

get_next_digit返回-1時,表示讀取完畢。

如何利用Double類裡的成員函式,來實現is_valid_uint64is_valid_int64這兩個函式呢?

一些新手可能會寫這樣的程式碼:

這樣的程式碼,存在諸多問題。

設計問題

不難發現,兩個函式存在很多相似甚至相同的程式碼;而同一個函式內部,也有不少程式碼重複。重複的東西往往不是好的。重構?

效能問題

先獲得所有digits,然後從最低位開始向最高位構造值,效率較低。難道沒有可以從最高位開始,邊獲得邊計算,不需要臨時陣列儲存所有digits的方法嗎?

正確性問題

隨便舉幾個例子:

24行,tmp += digits[i] * base;有沒有考慮到可能的溢位呢?

68行,難道有小數部分就一定不是合法的int64嗎?那麼,123.000?嗯?

規範問題

帥哥,這麼多程式碼,一行註釋都沒有,這樣真的好嗎?

因此,毫無疑問,這是爛程式碼,不合格的程式碼,需要重寫的程式碼。

以下是我個人認為比較好的設計和實現,僅供參考。

程式碼規範

團隊的程式碼規範,一般由領導和大佬們制定後,大家統一實行。這裡面有幾個問題:

真的需要程式碼規範嗎?

言下之意,制定和執行程式碼規範是否浪費時間?

答案是:It depends。如果專案很龐大、程式碼質量要求很高,那麼,制定和執行程式碼規範所花費的時間,將大大少於後期因為不規範開發帶來的種種除錯和維護成本。如果是小打小鬧的程式碼,就無所謂了。

程式碼規範的制定為什麼這麼難?

原因眾多,其中一個很重要的部分是團隊每個人的口味和觀點不盡相同。就程式碼風格而言,有人喜歡對內建型別變數i使用i++,有人堅持認為應該使用++i不管i是不是複雜型別。因此,制定程式碼規範需要在討論之後最後拍板決定,這裡面甚至需要獨裁!是的,獨裁!

程式碼規範制定需要注意什麼事項?

如果程式碼規範限制太鬆,那麼等於沒有規範;如果太嚴,大大影響開發效率。這裡面的尺度,需要根據專案需要、團隊成員特點全面考量,進行取捨。

需要注意的是,沒有任何一種程式碼規範是完美的。例如,在C++中,如果啟用異常,那麼程式碼的流程將會被各種異常處理中斷,各種try catch throw讓程式碼很不美觀;如果禁用異常,也就是在開發的過程中不能使用異常特性,那麼團隊成員可能因為長期沒有接觸這項語言feature而造成知識和技能短板。

程式碼風格舉例

舉兩個我認為比較重要、比較新鮮、比較有趣的程式碼風格。

1,使用引用需要判空嗎?

我們都知道,在g中,使用*p前需要對p是否為NULL進行判斷,那麼f呢?如果質量非常關鍵、程式碼安全非常重要的場景,那麼實際上,也是需要的。因為呼叫者可能這樣:

因此,需要在f裡增加if(NULL == &p)的判斷。

2,級聯if else語句。

首先看一個我個人認為不好的程式碼風格:

這個函式的核心在於do sth部分。其實我們可以改寫為級聯if-else形式,如下:

是不是優美多了?前面只做一些錯誤處理、前期準備、引數檢查等,最後的else分支做實實在在的功能性事情。

Code Review

什麼是Code Review?

很多人把它翻譯為程式碼審查,我覺得太政治味了。程式設計師尤其是新手寫完程式碼後,可能會有風格問題(比如不符合團隊的程式碼規範)、安全性問題(比如忘記指標判空)、優雅性問題(比如大量冗餘程式碼)、正確性問題(比如演算法設計錯誤),那麼在釋出程式碼到公共庫之前,提交給師兄或者mentor,讓他幫你review一下程式碼,並提出可能的問題和建議,讓你好好修改。這樣的過程,就叫做Code Review。

我的天吶,那這不是很佔用時間?

是的。一個寫程式碼,一個看程式碼,看程式碼的時間可能並不比全新寫一份程式碼少。那麼,這又是何必呢?

主要的原因有:

1,review確實佔用了開發時間,然而開發,或者說寫程式碼,其實只佔很少的時間比例。很多時間花在debug、除錯、寫文件、需求分析、設計演算法、維護等等上。

2,程式碼質量非常重要,這點時間投入是值得的。與其後期苦逼追bug,不如前期多投入點時間和人力。

3,培養新人,讓新手更快成長。

如何更好的執行Code Review

這裡給幾點建議:

1,不走過場。走過場,還不如不要這個流程。

2,作為Reviewer,看不懂程式碼就把作者拉過來,當面詢問,不要不懂裝懂,也不要愛面子不好意思問。

3,作為Coder,心裡要有感激之情。真的。不要得了便宜還賣乖,感恩reviewer,感激reviewer對自己的進步和成長所做出的貢獻,所花費的心血。中國人裡狼心狗肺、忘恩負義、不懂感恩的人還算少嗎?

4,作為Coder,給Reviewer Review之前,請先做單元測試並確保通過,並自己嘗試先整體看一遍自己本次提交的程式碼。注意,不要給別人提還沒除錯通過的程式碼,這是非常不尊重別人的表現。

質量保證

1,測試不是專屬QA的活兒,對自己寫的程式碼提供質量保證,是程式設計師的職責。QA要負責的,是系統的質量,不是模組的質量。

2,測試,需要意識,需要堅持。我發現C++程式設計師、前端程式設計師的測試意識或者說質量意識最強;資料科學家或者資料工程師的質量意識最差,很多人甚至不寫測試用例。當然,這不怪他們,畢竟,有時候程式碼裡有個bug,準確率和召回率會更高。

3,測試用例的編寫和設計需要保證一定的程式碼覆蓋率,力求讓每個分支和流程的程式碼都走到,然後分析執行結果是否是符合期望的,不要只考慮正確路徑上的那些分支。

4,測試用例的編寫和設計力求全面,考慮到方方面面。以非常經典的二分搜尋為例:

int binary_search(int *p, int n, int target, int &idx);

binary_search函式返回值為0表示成功執行,輸出引數idx返回target在有序陣列p中(第一次出現)的位置,-1表示不存在。

那麼測試用例至少應該涵蓋:

  • p為NULL的情況
  • 陣列大小n分別為負數、0、1、2時情況
  • 陣列p不是有序陣列的情況
  • target在陣列中出現0次、1次、n次的情況

你是否都考慮到了呢?

4,有時候,自己書寫測試用例顯得刀耕火種,現在已經有很多輔助的工具,讀者可以自行google一下。

相關文章