隨着軟件行業的不斷髮展,歷史遺留的程序愈來愈多,代碼的維護成本愈來愈大,甚至大於開發成本。而新功能的開發又經常依賴於舊代碼,閱讀舊代碼所花費的時間幾乎要大於寫新功能的時間。html
我前幾天看了一本書,書中有這麼一句話:編程
「複雜的代碼每每都是新手所寫,只有經驗老道的高手才能寫出簡單,富有表現力的代碼」
此話雖說的有點誇張,但是也說明了經驗和智慧的的重要性。架構
咱們所寫的代碼主要是爲了閱讀,其次纔是被機器執行。因此咱們要寫:函數式編程
其中2,3點更多強調的是面向對象的設計原則。而本文則更多關注於局部的代碼問題,本文經過舉例的方式,總結平時常犯的錯誤和優化方式。函數
本文的例子基於兩個指導原則:工具
一.DRY(Don't repeat yourself)測試
此原則如此重要,簡單來講是由於:優化
二.TED原則插件
三.舉例說明設計
1.拒絕註釋,用代碼來闡述註釋
反例:
/// <summary> /// !@#$%^&^&*((!@#$%^&^&*((!@#$%^&^&*((!@#$%^&^&*(( /// </summary> /// <returns></returns> public decimal GetCash() { //!@#$%^&^&*((!@#$%^&^&*(( var a = new List<int>() { 2, 3, 10 }; var b = 2m; var c = 0m; //!@#$%^&^&*((!@#$%^&^&*((!@#$%^&^&*(( foreach (var p in a) { c += p*b; } return c; }
重構後:
public decimal CalculateTotalCash() { var itemCounts=new List<int>(){2,3,10}; var price = 2m; return itemCounts.Sum(p => p*price ); }
良好的代碼命名徹底能夠替代註釋的做用,若是你正在試圖寫一段註釋,從某種角度來看,你正在試圖寫一段別人沒法理解的代碼。
當你沒法爲你的方法起一個準確的名稱時,極可能你的方法不止作了一件事,違反了(Do one thing)。特別是你想在方法名中加入:And,Or,If等詞時
2. 爲布爾變量賦值
反例:
public bool IsAdult(int age) { bool isAdult; if (age > 18) { isAdult = true; } else { isAdult = false; } return isAdult; }
重構後:
public bool IsAdult(int age) { var isAdult = age > 18; return isAdult; }
3.雙重否認的條件判斷
反例:
if (!isNotRemeberMe) { }
重構後:
if (isRemeberMe) { }
無論你有沒有見過這樣的條件,反正我見過。見到這樣的條件判斷,我頓時就暈了。
4.拒絕HardCode,拒絕挖坑
反例:
if (carName == "Nissan") { }
重構後:
if (car == Car.Nissan) { }
既然我們玩的是強類型語言,咱就用上編譯器的功能,讓錯誤發生在編譯階段
5.拒絕魔數,拒絕挖坑
反例:
if (age > 18) { }
重構後:
const int adultAge = 18; if (age > adultAge) { }
所謂魔數(Magic number)就是一個魔法數字,讀者徹底弄不明白你這個數字是什麼,這樣的代碼平時見的多了
6.複雜的條件判斷
反例:
if (job.JobState == JobState.New || job.JobState == JobState.Submitted || job.JobState == JobState.Expired || job.JobTitle.IsNullOrWhiteSpace()) { //.... }
重構後:
if (CanBeDeleted(job)) { // } private bool CanBeDeleted(Job job) { var invalidJobState = job.JobState == JobState.New || job.JobState == JobState.Submitted || job.JobState == JobState.Expired; var invalidJob = string.IsNullOrEmpty(job.JobTitle); return invalidJobState || invalidJob; }
有沒有豁然開朗的趕腳?
7.嵌套判斷
反例:
var isValid = false; if (!string.IsNullOrEmpty(user.UserName)) { if (!string.IsNullOrEmpty(user.Password)) { if (!string.IsNullOrEmpty(user.Email)) { isValid = true; } } } return isValid;
重構後:
if (string.IsNullOrEmpty(user.UserName)) return false; if (string.IsNullOrEmpty(user.Password)) return false; if (string.IsNullOrEmpty(user.Email)) return false; return true;
第一種代碼是受到早期的某些思想:使用一個變量來存儲返回結果。事實證實,你一旦知道告終果就應該儘早返回。
8.使用前置條件
反例:
if (!string.IsNullOrEmpty(userName)) { if (!string.IsNullOrEmpty(password)) { //register } else { throw new ArgumentException("user password can not be empty"); } } else { throw new ArgumentException("user name can not be empty"); }
重構後:
if (string.IsNullOrEmpty(userName)) throw new ArgumentException("user name can not be empty"); if (string.IsNullOrEmpty(password)) throw new ArgumentException("user password can not be empty"); //register
重構後的風格更接近契約編程,首先要知足前置條件,不然免談。
9.參數過多,超過3個
反例:
public void RegisterUser(string userName, string password, string email, string phone) { }
重構後:
public void RegisterUser(User user) { }
過多的參數讓讀者難以抓住代碼的意圖,同時過多的參數將會影響方法的穩定性。另外也預示着參數應該聚合爲一個Model
10.方法簽名中含有布爾參數
反例:
public void RegisterUser(User user, bool sendEmail) { }
重構後:
public void RegisterUser(User user) { } public void SendEmail(User user) { }
布爾參數在告訴方法不止作一件事,違反了Do one thing
10.寫具備表達力的代碼
反例:
private string CombineTechnicalBookNameOfAuthor(List<Book> books, string author) { var filterBooks = new List<Book>(); foreach (var book in books) { if (book.Category == BookCategory.Technical && book.Author == author) { filterBooks.Add(book); } } var name = ""; foreach (var book in filterBooks) { name += book.Name + "|"; } return name; }
重構後:
private string CombineTechnicalBookNameOfAuthor(List<Book> books, string author) { var combinedName = books.Where(b => b.Category == BookCategory.Technical) .Where(b => b.Author == author) .Select(b => b.Name) .Aggregate((a, b) => a + "|" + b); return combinedName; }
相對於命令式代碼,聲明性代碼更加具備表達力,也更簡潔。這也是函數式編程爲何愈來愈火的緣由之一。
四.關於DRY
平時你們重構代碼,一個重要的思想就是DRY。我要分享一個DRY的反例:
項目在架構過程當中會有各類各樣的MODEL層,例如:DomainModel,ViewModel,DTO。不少時候這幾個Model裏的字段大部分是相同的,因而有人就會想到DRY原則,乾脆直接用一種類型,免得粘貼複製,來回轉換。
這個反例失敗的根本緣由在於:這幾種Model職責各不相同,雖然大部分狀況下內容會有重複,可是他們擔當着各類不一樣的角色。
考慮這種場景: DomainModel有一個字段DateTime Birthday{get;set;},ViewModel一樣具備DateTime Birthday{get;set;}。需求升級:要求界面再也不顯示生日,只須要顯示是否成年。咱們只須要在ViewModel中添加一個Bool IsAdult{get{return ....}}便可,DomainModel徹底不用變化。
五.利用先進的生產工具
以vs插件中的Reshaper爲例,本文列舉的大部分反例,Reshaprer均能給予不一樣程度的提示。通過一段時間的練習,當Reshaper對你的代碼給予不了任何提示的時候,你的代碼會有一個明顯的提升。
截圖說明Reshaper的提示功能:
光標移動在波浪線處,而後Alt+Enter,Resharper 會自動對代碼進行優化
若是你可以避免本文總結的反例,你的代碼就已經具有了優秀代碼應有的基因。固然高質量的代碼還須要良好的設計和遵循面向對象編程的原則。 若是想了解更多相關內容,請閱讀《代碼大全》,《代碼整潔之道》,《重構 改善既有代碼的設計》,《敏捷軟件開發 原則、模式與實踐》
追加:既然你們對拒絕註釋這個建議以爲不可行,我拿出來詳細說說: 1.若是你的團隊代碼命名不錯,代碼寫的還算不錯,沒有一些奇怪的實現方式,不妨能夠嘗試去掉註釋試試效果。由於寫註釋須要成本,而且重構代碼的時候每每會忽略註釋的修改,時間長了註釋也不夠準確,反而成了一種累贅。 2.若是你的團隊代碼寫的醜陋不堪,命名也不規範,註釋必需要有,註釋再不許確也比讀代碼讀着強些。