本文爲開發小結-編程實踐類下篇。linux
模塊設計要明確職責,劃分功能範圍。c++
在設計較爲通用的組件時,考慮到使用場景以及使用時的最低要求,對調用者有一些基本的入參要求,如不知足,則發出錯誤提示。這些額外要求,在函數註釋中須要有明確註明,在內部實現時,配合適當的檢測和提示機制。程序員
一個對外提供服務的控件,它的初始化函數須要的默認參數,最好在該組件的頭文件中定義.
當參數不少時並且又沒法避免時,每一個參數單獨一行顯示,同時仔細設計參數名稱,努力作到見名知意。
對於默認參數的設置,外部須要關注的,每次變化的參數,放在第一位,其餘不須要關注的,採用默認值就能夠.編程
對於一個顯示類控件來講,它只須要接受外部的數據而且正常顯示便可,至於這個控件的UI上的顯示位置,應該由它的父窗口來決定。數組
對於業務相關的控件,須要考慮到業務靈活性,這類控件設計時要預留可變參數,不可只着眼與眼前的業務需求。好比顯示行情信息爲例子,需求給出顯示五檔行情,實際狀況有一檔行情、還有十擋行情,那麼該控件對於行情顯示數目可做爲參數傳入,內部動態顯示特定擋位數的行情,確保靈活性和可擴展性。緩存
函數的註釋,一行精簡必要的解釋,在不少狀況下就足夠,若是函數名稱起的好的話,達到自注釋的狀況就更好。安全
如因業務規則須要,在某處須要特殊處理,那麼在此處必定要加上 需求單號以及特殊規則的簡要說明和文檔出處,方便維護,既是方便本身查看,,也方便其餘同事理解。數據結構
程序中必要的ASSERT
和相關的註釋,要特別的保留,增長可維護性。ide
對於一些很差翻譯成英文的業務,建議頭文件中加上中文說明,方便其餘同事查看。函數
註釋規範,塊註釋建議採用多行的//
,而不是使用 /* */
,緣由有以下幾點:
考慮linux平臺上的代碼對比工具diff
,若是用 /**/ 來作多行註釋,從diff不必定可以看出來你是在修改代碼仍是修改註釋。
局部變量和成員變量的定義, 基本原則是:一行代碼只定義一個變量,有利於用diff
查看出改了什麼,有利於版本管理。
若是函數聲明和函數調用中的入參個數大於3,那麼須要在逗號後面換行,這樣每一個參數佔一行,便於diff
查看
與業務相關的數據結構定義以及配套的數據結構獲取、設置、更新等,應該放在一塊兒,保證相關業務以及對外接口的關聯性,讓別人看到這個定義,下面就能夠看到相關的業務輔助函數。
在設計業務數據結構時,結合後臺給出的接口文檔給出基本的字段設計,舉個例子,後臺返回整型的幣種信息,數據字典爲1表明人民幣,2表明港幣,3表明美圓。此時的數據結構如何設計呢?
幣種信息定義類枚舉類型,
ECurrencyType { RMB = 1, HK = 2, US = 3 }
有以下如下幾種方式:
方式一:
ECurrencyType m_nCurrency; // 保存由原始數據通過轉換後的貨幣枚舉類型 const char* GetCurrencyName(ECurrencyType eCurrencyType) // 用於將枚舉類型轉換爲可讀字符串,此轉換函數與後臺無依賴
方式二:
ECurrencyType m_nCurrency; string m_strCurrencyName; // 該成員經過 GetCurrencyName 轉換獲得的描述性文字,
方式三:
int m_nCurrency; // 保存後臺返回的原始數據,具體顯示完成交由上層界面去完成。 提供 `const char* GetCurrencyName(int nCurrency)` // 用於將枚舉類型轉換爲可讀字符串,此轉換函數與後臺有依賴
這三種方式,我都用過,使用次序依次是3,2,1,寫的代碼多了,對這些細微的考量有了更多的理解,這三種保存數據的方式,在不一樣場景下有不一樣的側重,沒法一律而論。字段映射的職責應該交由底層數據層,仍是交由上層UI層,具體狀況具體分析。
對於類的初始化,應該所有設置爲無效值,而不要一部分設置爲無效值,另外一部分設置爲默認值。在構造函數語義中,最好是設置爲無效值,設置爲默認值的操做,應該屬於類的Init函數要作的事情。特別是對於接口定義的數據類,初始化過程當中,若是由默認值的設置,那麼在後續填充發送字段時,該默認值字段要不要再設置一次就存在不肯定性,這裏可作可不作,從業務上來沒問題,但從可維護性上,後來接手的人看到這裏時,內心要多一個心眼,要去看下構造函數才知道要不要設置,增大心智負擔。
構造函數裏面的初始化,要作無效的初始化,整型統一置爲0,指針設置爲NULL等,和業務相關的初始化,建議放到單獨的Init函數中去作,好比讀取配置信息,加載相應的資源,設置某些變量的默認值等等。
在含有衆多成員變量的結構體中,如何快速清空,能夠採用 memset(this, 0, sizeof(TData))
的方法來清空,該方法使用的前提條件是成員中無stl類型變量。
另外一種方法是提供獨立的函數(Reset(),Clear())來進行復位,該函數既能夠在構造函數中使用,又能夠在其餘地方使用。
重構必定要從最小模塊出發,切記一次搞定全部,保證任務的聚焦性和重構目標的可解決性是最重要的。
過早的優化是萬惡之源,在一個正確的代碼上讓它跑的更快的難度遠低於在一個跑的更快的代碼上讓它正確。
重構的好,會增長代碼的可維護性,重構的很差,則會增長維護成本、測試成本。因此,重構的首要目標是加強代碼的可維護性,至於對性能的影響如何,是加強了性能仍是下降了性能,在重構時,不該過多關注。跑的對優於跑的快。經過代碼來改善性能只是一個途徑而已,做爲追求卓越的程序員,應該要了解不少在代碼以外的行之有效的改善方法,而可維護性基本上只能靠代碼自己,而改善性能,除了代碼自己以外,還有更多更加有效的方法。
針對 if-else 類型的代碼重構整體思路:儘量維持正常業務流程代碼在最外層, 減小if-else嵌套。
具體的手法有:
對於一些基礎組件的重構改進,不建議直接在原有公用組件上進行修改。較好的流程是
解除耦合的方法:
若是是進行組件以及系統的重構,工程實踐上最好從主分支拉出一個重構分支,在重構分支上的提交的同時,按期合併來自主分支的改動。重構須要劃分模塊,小步快跑,同一類型的重構修改按一次提交。對於已經通過測試的代碼,不要貿然去修改,較好的方法時,用一個全新的實現去逐步代替舊版實現。從流程上來講,重構的測試,更多的靠開發人員的單元測試,如在重構過程當中,有修改實現流程方面的動做,必定要在提交備註中寫明並告知測試。
業務實體一致性重構,類、函數、頁面、組件,各自內部要作到足夠的語義化,同一類型的數據定義,其名稱在整個工程中要保持一致性,要作到這點,就必須針對具體的業務特徵制定規範,什麼業務使用什麼類型的核心詞彙來描述之類的。該規範由團隊成員共同制定,在此舉一個例子:好比說:股東代碼,從業務上來看,它應該是一個集合概念,內部包含代碼、市場、貨幣類型。
class CHolder { CString m_strHolderCode; // 股東代碼 char m_cExchange; // 市場類型 ECURRENCY m_eCurrency; // 貨幣類型 }
外部用到股東的地方,核心命名爲Holder
,好比成員變量可用m_vtHolder
,局部變量用Holder
等,無論在哪裏,核心詞彙均爲Holder
。
現有工程中有基礎工具類函數,內部經過封裝底層函數來實現,本身看到時會思考,爲何不直接使用底層函數,而採用封裝一層呢?列出本身遇到的坑,以獲取程序當前所在目錄這個功能爲例子,系統級API有提供,基礎工具類也有提供相關的函數,那麼在使用時,選哪種呢?
GetCurrentDirectory 得到當前進程的工做目錄。若是進程處於調試狀態,那得到的目錄爲調試器給該進程設置的工做目錄,默認爲$(ProjectDir),即爲包含該項目文件的目錄。若是處於運行狀態,則得到的是當前的工做目錄。這裏要特別注意:當以快捷方式啓動時,該函數返回的路徑是配置在快捷方式中的起始位置編輯框中,而該屬性能夠人爲修改,而且不會影響直接雙擊啓動時得到的路徑。所以,使用使用該函數來獲取時,在Debug和Release中,在測試時,會返回不一樣的值,這個函數存在不肯定性。
GetModuleFileName: 得到可執行程序所在的可執行文件路徑,與是否處於調試狀態無關,推薦使用。
類的初始化列表,遵循一行一個的原則,而且初始化順序和在類中定義的順序保持一致,若有新增成員變量,也要對應保持一致增長。
浮點數統一使用 double,不要使用float。
無論是針對簡單類型,仍是複雜類型, 優先使用++i,而不是i++,減小心智負擔。
永遠不要在頭文件中使用 using namespace,這會致使全部包含該頭文件的文件都隱式此命名空間,形成命名空間污染。在cpp實現文件中使用using std::XXX,須要哪一種類型,就引用哪一種類型。
使用 std::array 或者 std::vector來代替C風格的數組
c++風格的cast(dynamic_cast 或者 static_cast)能夠提供更多的編譯器檢查和安全特性,用於替代c風格的cast。
c++11 中新增了 override 關鍵字,用於重載虛函數時檢查函數簽名一致性,virtual用在基類中,用於標示該函數是虛函數。在stackoverflow中的有一個很好的解釋:
基類須要virtual關鍵字來聲明虛函數,在派生類中,函數成爲虛函數的方式是具有同基類同樣的簽名類型,override關鍵字指示編譯器來檢查在派生類中修飾的函數是否在基類中有相同的簽名函數。若是在派生類中重載虛函數時加上virtual,那如何肯定該virtual修飾的函數是重寫父類的函數,仍是該派生類自身提供的虛函數? override關鍵字正是用在這個地方,取消重寫的歧義性。假如父類的維護者給該虛函數新增了一個默認參數,那麼帶有override修復的派生類能夠經過編譯器來檢查是否有影響.
所以,綜上所述:在基類聲明中,使用virtual來修飾虛函數聲明是必要的。在派生類中,無需再次添加virtual來修飾,而是經過 override 修飾符來加強可維護性。
不只僅是簡單完成基本需求,而要在此之上,看有哪些操做是能夠避免的,哪些操做是能夠延時的,哪些指令是能夠推遲發送的,哪些指令返回的數據是能夠緩存使用的。
想到這裏,以一個實際開發的一個功能來舉例:在價格框中價格變化後,須要主動查詢最大可交易數量。
這個功能,實現起來不難,難在如何更優雅、更高效的實現。這裏有幾個前提,價格框能夠手工輸入價格觸發改變,也可由程序自動設置價格。
在實現時,有兩種方式
第一種方式是: 在每個價格框改變的地方,在後面手動調用請求最大可交易數量的函數。
優勢:邏輯直接,價格變化後請求數據
缺點:改變價格框的狀況較多時,容易遺漏
第二種方式是:價格框改變時,由價格框發消息通知父窗口,在父窗口中經過消息響應來處理價格變化以後的請求。
優勢:分離變化事件的產生和處理,從邏輯上解耦,在須要新增響應變化時,改動的地方較少,可控。
缺點:這種方式,對於開發人員來講,不必定容易直觀想到,須要較好的程序組織能力才能想到。
第三種方式,在第二種方式基礎上,在價格框內部增長進一步的細節處理,記錄上一次的價格,當修改後的價格與上一次價格不一致時,才發出價格改變的消息,若一致,則不發送消息,進一步優化效率,而隨着而來的代碼會更加複雜些。
第一種是很容易想到的處理方式,我當初在作這個功能時,第一時間浮如今腦海中的是第一種方式,後來隨着和同事商量,纔有了後面幾種方式的思路。要作到高效開發,首先要有一顆精益求精的心,其次要認清軟件開發的事實,最優方案或者說最優實現是不可能一蹴而就的,軟件開發是一個不斷迭代的過程,無論是什麼水平的開發人員,第一次作新功能時,想要一次性作到極致完美,一次性經過全部的測試案例,是很是難的。咱們要認清這一點,在這一前提的基礎上,面對需求作好一個設計後,不要着急着去動手實現,而是多和同事討論下,看有沒有更好的思考問題的角度和解決方案。
針對UI界面類的交互開發,團隊必定要制定統一的交互流程,在需求討論過程當中,一些沒有提到的點和關聯邏輯,就按照通用流程來走。舉個例子,
打開界面進行初始化時,哪些數據要清空,哪些控件要復位,復位的規則是什麼,都要一一肯定好。
若某個編輯框中內容變更,會觸發哪些後續流程,數據回來後,要更新那些內容,焦點定位在哪裏等交互問題。
操做的有效性校驗的提示內容以及提示方式等,確認錯誤後的復位邏輯等?
若是產品需求中沒明說,那麼在實施時,就和已有的流程一致。若是產品需求由特別規定,就嚴格按照產品定的流程來作。