隨著程式設計師經驗的增長,他們必然會學到越來越多的方法來解決同樣的問題。
最開始關心的是簡單性。我們可能只想儘可能地使用最簡單最直接的方法來避免過度設計。但最簡潔的解決方案不一定是最短的方案。
考慮完簡單性後,接下來要考慮的是表現力。你應當時刻思考著,一個新人要深入研究你的程式碼到什麼程度才能理解你的程式碼。
程式碼如詩。
寫富有表達力的程式碼會有助於以後的程式設計師來理解程式碼的用途。有可能也會在以後給你自己帶來幫助。它也可能幫助你更好地理解問題。認真思考如何定義和封裝解決方案的元件通常能夠幫助你更好地理解問題、得到更合理的方案。
“自注釋程式碼”
“自注釋程式碼”是通過程式碼的結構和方法及變數的命名來讓大部分程式碼可以實現自描述。這是一個非常好的做法,可以省去很多註釋。
1 2 3 |
$user = new User(); // 建立一個新的user物件 $user->loadFromSession(session); // 通過session更新user if ($user->isAuthenticated()) { ... } // 如果user物件被授權…… |
然而,根據最近和我的一個朋友討論中對我的啟發,富有表現力的程式碼並不是註釋的一個替代品–沒有程式碼是可以完全實現“自描述的”。寫程式碼的時候要儘可能地讓程式碼具有表現力,同時在需要的時候也要及時註釋。方法,函式和類總是需要有一個總結性的註釋,就像Python 程式設計約定中提到的。
措辭
認真思考如何對變數和方法命名很重要。
不要簡寫
1 2 |
var uid = 10; // 離開上下文,我可能不會明白uid是代表什麼意思 var userIdentifier = 10; // 這樣更好一些 |
要明確
儘可能地使用具體和明確的名詞來描述方法和函式:
1 2 |
var event; // 不好 -- 太寬泛 var newsLinkClickEvent; // 很好 -- 明確 |
封裝
沒有人喜歡閱讀過程很長的程式。它會很難領會。而閱讀一組短的封裝好的方法呼叫會更加容易。如果你要更深入地鑽研,那麼直接查閱相關方法即可。
1 2 3 4 5 6 7 8 |
// 與向你展示我們如何更新user的全部細節相反 // 我們將這些封裝在updateDetails的方法中 // 允許你能夠迅速明白頂層的處理過程 function saveUserDetails(userStore, userDetails) { var user = new User(); user.updateDetails(userDetails); // 對user的全部細節進行設定 userStore.save(user); // 將user的資料轉化為正確的格式,並儲存在user儲存區 } |
你需要 else 嗎?
使用很多if..else條件語句會使程式變得混亂。在很多情況下,為使得程式更加易讀,else的部分可以使用一個單獨的方法或者函式呼叫來進行封裝:
1 2 3 4 5 6 |
// 使用else語句 if (user.permissionGroup == 'administrator') { article.delete(); } else { page.showError("Sorry you don't have permission to delete this article"); } |
1 2 3 4 |
// 不使用else語句 if (!user.deleteArticle(article)) { page.showError("Sorry you don't have permission to delete this article"); } |
在使用switch 語句或者多重if..else if語句的時候,你可以考慮使用不同型別來替換:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class User { function deleteArticle($article) { $success = false; if ( user->permissionGroup == 'administrator' || user->permissionGroup == 'editor' ) { $success = $article->delete(); } return $success; } } |
你可以通過設計專門的型別來刪除掉這裡的if語句:
1 2 3 4 5 6 7 8 |
trait ArticleDeletion { function deleteArticle($article) { return $article->delete(); } } class Editor implements User { use ArticleDeletion; } class Administrator implements User { use ArticleDeletion; } |
注意,我謹慎地選擇組成Administrator和Editor,而不是讓Administrator繼承自Editor。這樣做能保持我的程式結構更加扁平和靈活。這是組合優於繼承原則中的一個例子。
深度
鑑於封裝能夠讓程式在更抽象的層面上容易理解,所以通過將分離的關注點不封裝在一起來保持單一職責準則是很重要的。
例如,可以像下面這樣寫:
1 2 3 |
var user = new User(); user.UpdateFromForm(); // user物件從頁面表格中匯入資料 user.SaveToDatabase(); |
雖然這樣寫很短很清晰,但是也會帶來兩個問題:
- 使用者只能通過更深入的查閱程式碼才能找到一些基本資訊,比如資料庫類的名字,或者細節將存入哪些表中等等。
- 如果我們想用另外一個資料庫的例項,我們必須重新編輯使用者類。這樣做沒有多大意義。
總之,你應當選擇傳遞物件,而不是在每一個裡面進行例項化:
1 2 3 4 5 6 |
var user = new User(); var userData = Request.Form; var database = new DatabaseManager(); user.ImportData(userData); database.Save(user); |
這裡程式碼行數更多,但卻是更加清楚到底做了什麼。這樣更通用。
整潔性
時時對自己的程式碼進行格式,這樣做能讓程式碼更加易讀。不用擔心空白,但要謹慎地使用縮排來讓自己的程式碼結構更加清晰。
如果有公認的程式碼風格指導,那麼你應該遵循它。例如,PHP語言有FIG 標準。
然而,我認為也沒有必要過分糾結於程式碼標準(我的想法有些變化)因為你永遠不可能讓所有人都完全用同樣地方式進行編碼。所以如果你像我一樣,隨時都覺得有必要對程式碼進行格式調整來保證符合繁瑣的標準,那麼你可能需要訓練自己來擺脫這個習慣。只要你能讀懂,那就讓它保持原樣。
刪除已註釋掉的程式碼
如果你正在使用版本控制系統(例如Git),那麼實在沒有必要保留大塊的已註釋掉的或者用不到的程式碼。你只需要刪除掉,就能讓你的程式碼庫更加整潔。如果你確實還會再用到,那麼你可以在版本控制歷史中找到那些程式碼。
權衡
在表現力和簡潔性之間,總是會需要一些權衡。
深度 vs. 封裝
程式設計師總是希望在自己的物件中儘可能地讓結構扁平化,這樣就不用依靠不斷重複查詢父類來獲取相關程式碼。但保持程式碼按邏輯單元進行封裝也十分重要。
通過使用依賴注入或者特徵/多重繼承來實現組合優於繼承原則,可以同時達到這兩個目標。
專門的語法
在很多語言中,通常會有一些略微艱澀的結構能夠減少時間。這些結構大多會存在可讀性與效率間的權衡。
三元操作符與空值合併運算子
C#和PHP都有空值合併運算子:
1 2 |
var userType = user.Type ?? defaultType; // C# $userType = $user->Type ?: $defaultType; // PHP |
而且幾乎所有語言都支援三元運算子:
1 |
var userType = user.Type != null ? user.Type : defaultType; |
這兩種結構都比if..else的結構更加簡潔,但是相對而言,他們在語義上不夠清晰,所以存在權衡取捨。個人來說,我覺得在像這樣條件簡單的情況下使用三元運算子沒有問題。但是如果很複雜,那麼你還是應當使用完整的if..else語句。
外掛 / 庫
舉例來說,在C#語言程式碼中:
1 2 3 4 5 6 7 8 |
var brownFish; foreach (var fish in fishes) { if (fish.colour == "brown") { brownFish = fish; break; } } |
可以使用Linq庫簡化為:
1 2 |
using System.Linq; var brownFish = fishes.First(fish => fish.colour == "brown"); |
後面的一個更加簡潔,而且也不會太難理解。但是它要求:
1. 瞭解Linq庫;
2. 理解lambda表示式的原理。
我想在這個例子中,Linq解決方法是一定會被推薦的,因為它更加簡潔,更具有表現力。即使另外的程式設計師不瞭解Linq,他們也能夠非常輕鬆地理解學會,這同時也會增加他們的知識儲備。
一次性變數
下面這樣定義變數是不值得的:
1 2 |
var arrayLength = myArray.length; for (var arrayIterator; arrayIterator < arrayLength; arrayIterator++) { ... } |
在有些情況下,變數能夠被用來新增有用的語義資訊。例如:
1 2 |
var slideshowContainer = jQuery('main>.show'); slideshowContainer.startSlideshow(); |