不少同行在編寫代碼的時候每每只關注一些宏觀上的主題:架構,設計模式,數據結構等等,卻忽視了一些更細節上的點:好比變量如何命名與使用,控制流的設計,以及註釋的寫法等等。以上這些細節上的東西能夠用代碼的可讀性來歸納。javascript
不一樣於宏觀上的架構,設計模式等須要好幾個類,好幾個模塊才能看出來:代碼的可讀性是可以馬上從微觀上的,一個變量的命名,函數的邏輯劃分,註釋的信息質量裏面看出來的。html
宏觀層面上的東西當然重要,可是代碼的可讀性也屬於評價代碼質量的一個沒法讓人忽視的指標:它影響了閱讀代碼的成本(畢竟代碼主要是給人看的),甚至會影響代碼出錯的機率!java
這裏引用《編寫可讀代碼的藝術》這本書裏的一句話:git
對於一個總體的軟件系統而言,既須要宏觀的架構決策,設計與指導原則,也必須重視微觀上的的代碼細節。在軟件歷史中,有許多影響深遠的重大失敗,其根源每每是編碼細節出現了疏漏。程序員
所以筆者認爲代碼的可讀性能夠做爲考量一名程序員專業程度的指標。github
或許已經有不少同行也正在努力提升本身代碼的可讀性。然而這裏有一個很典型的錯覺(筆者以前就有這種錯覺)是:越少的代碼越容易讓人理解。objective-c
可是事實上,並非代碼越精簡就越容易讓人理解。相對於追求最小化代碼行數,一個更好的提升可讀性方法是最小化人們理解代碼所須要的時間。算法
這就引出了這本中的一個核心定理:編程
可讀性基本定理:代碼的寫法應當使別人理解它所須要的時間最小化。設計模式
這本書講的就是關於「如何提升代碼的可讀性」。 筆者總結下來,這本書從淺入深,在三個層次告訴了咱們如何讓代碼易於理解:
首先來說最簡單的一層如何改進,涉及到如下幾點:
關於如何命名,做者提出了一個關鍵思想:
關鍵思想:把儘量多的信息裝入名字中。
這裏的多指的是有價值的多。那麼如何作到有價值呢?做者介紹瞭如下幾個建議:
一個比較常見的反例:get
。
get
這個詞最好是用來作輕量級的取方法的開頭,而若是用到其餘的地方就會顯得很不專業。
舉個書中的例子:
getPage(url)
經過這個方法名很難判斷出這個方法是從緩存中獲取頁面數據仍是從網頁中獲取。若是是從網頁中獲取,更專業的詞應該是fetchPage(url)
或者downloadPage(url)
。
還有一個比較常見的反例:returnValue
和retval
。這二者都是「返回值」的意思,他們被濫用在各個有返回值的函數裏面。其實這兩個詞除了攜帶他們原本的意思返回值
之外並不具有任何其餘的信息,是典型的泛泛的名字。
那麼如何選擇一個專業的詞彙呢?答案是在很是貼近你本身的意圖的基礎上,選擇一個富有表現力的詞彙。
舉幾個例子:
make
,選擇create
,generate
,build
等詞彙會更有表現力,更加專業。find
,選擇search
,extract
,recover
等詞彙會更有表現力,更加專業。retval
,選擇一個能充分描述這個返回值的性質的名字,例如:var euclidean_norm = function (v){ var retval = 0.0; for (var i = 0; i < v.length; i += 1;) retval += v[i] * v[i]; return Match.sqrt(retval); } 複製代碼
這裏的retval表示的是「平方的和」,所以sum_squares
這個詞更加貼切你的意圖,更加專業。
可是,有些狀況下,泛泛的名字也是有意義的,例如一個交換變量的情景:
if (right < left){ tmp = right; right = left; left = tmp; } 複製代碼
像上面這種tmp
只是做爲一個臨時存儲的狀況下,tmp表達的意思就比較貼切了。所以,像tmp
這個名字,只適用於短時間存在並且特性爲臨時性的變量。
除了選擇一個專業,貼切意圖的詞彙,咱們也能夠經過添加一些先後綴來給這個詞附帶更多的信息。這裏所指的更多的信息有三種:
有些變量是有單位的,在變量名的後面添加其單位可讓這個變量名攜帶更多信息:
duraction
,ducation_secs
攜帶了更多的信息size
,cache_mb
攜帶了更多的信息。有些變量是具備一些很是重要的屬性,其重要程度是不容許使用者忽略的。例如:
html
,html_utf8
更加清楚地描述了這個變量的格式。password
,plaintext_password
更清楚地描述了這個變量的特色。對於命名,有些既定的格式須要注意:
HomeViewController
。userNameLabel
。product_id
。kConstantName
來表示常量:kCacheDuraction
。SCREEN_WIDTH
。名字越長越難記住,名字越短所持有的信息就越少,如何決定名字的長度呢?這裏有幾個原則:
若是一個變量做用域很小:則給它取一個很短的名字也無妨。
看下面這個例子:
if(debug){ map <string,int>m; LookUpNamesNumbers(&m); Print(m); } 複製代碼
在這裏,變量的類型和使用範圍一眼可見,讀者能夠了解這段代碼的全部信息,因此即便是取m
這個很是簡短的名字,也不影響讀者理解做者的意圖。
相反的,若是m
是一個全局變量,當你看到下面這段代碼就會很頭疼,由於你不明確它的類型:
LookUpNamesNumbers(&m);
Print(m);
複製代碼
咱們知道駝峯命名能夠很清晰地體現變量的含義,可是當駝峯命名中的單元超過了3個以後,就會很影響閱讀體驗:
userFriendsInfoModel
memoryCacheCalculateTool
是否是看上去很吃力?由於咱們大腦同時能夠記住的信息很是有限,尤爲是在看代碼的時候,這種短時間記憶的侷限性是沒法讓咱們同時記住或者瞬間理解幾個具備3~4個單元的變量名的。因此咱們須要在變量名裏面去除一些沒必要要的單元:
有些單元在變量裏面是能夠去掉的,例如:
convertToString
能夠省略成toString
。
有些縮寫是你們熟知的:
doc
能夠代替document
str
能夠代替string
可是若是你想用BEManager
來代替BackEndManager
就比較不合適了。由於不瞭解的人幾乎是沒法猜到這個名稱的真正意義的。
因此遇到相似這種狀況咱們不能偷懶,該是什麼就是什麼,不然會起到相反的效果。由於它看起來很是陌生,跟咱們熟知的一些縮寫規則相去甚遠。
有些名字會引發歧義,例如:
need_password
或者is_authenticated
來代替比較好。一般來講,給布爾值的變量加上is
,has
,can
,should
這樣的詞可使布爾值表達的意思更加明確這一節講了不少關於如何起好一個變量名的方法。其實有一個很簡單的原則來判斷這個變量名起的是不是好的:那就是:團隊的新成員是否能迅速理解這個變量名的含義。若是是,那麼這個命名就是成功的;不然就不要偷懶了,起個好名字,對誰都好。其實若是你養成習慣多花幾秒鐘想出個好名字,漸漸地,你會發現你的「命名能力」會很快提高。
在寫程序的過程當中咱們會聲明不少變量(成員變量,臨時變量),而咱們要知道變量的聲明與使用策略是會對代碼的可讀性形成影響的:
相對的,對於變量的聲明與使用,咱們能夠從這四個角度來提升代碼的可讀性:
在一個函數裏面可能會聲明不少變量,可是有些變量的聲明是毫無心義的,好比:
有些變量的聲明徹底是畫蛇添足,它們的存在反而加大了閱讀代碼的成本:
let now = datetime.datatime.now() root_message.last_view_time = now 複製代碼
上面這個now
變量的存在是毫無心義的,由於:
datetime.datatime.now
已經很清楚地表達了意思因此徹底不用這個變量也是徹底能夠的:
root_message.last_view_time = datetime.datatime.now()
複製代碼
有的時候爲了達成一個目標,把一件事情分紅了兩件事情來作,這兩件事情中間須要一個變量來傳遞結果。但每每這件事情不須要分紅兩件事情來作,這個「中間結果」也就不須要了:
看一個比較常見的需求,一個把數組中的某個值移除的例子:
var remove_value = function (array, value_to_remove){ var index_to_remove = null; for (var i = 0; i < array.length; i+=1){ if (array[i] === value_to_remove){ index_to_remove = i; break; } } if (index_to_remove !== null){ array.splice(index_to_remove,1); } } 複製代碼
這裏面把這個事情分紅了兩件事情來作:
index_to_remove
裏面。index_to_remove
之後使用splice
方法刪除它。(這段代碼是JavaScript代碼)這個例子對於變量的命名仍是比較合格的,但實際上這裏所使用的中間結果變量是徹底不須要的,整個過程也不須要分兩個步驟進行。來看一下如何一步實現這個需求:
var remove_value = function (array, value_to_remove){ for (var i = 0; i < array.length; i+=1){ if (array[i] === value_to_remove){ array.splice(i,1); return; } } } 複製代碼
上面的方法裏面,當知道應該刪除的元素的序號i
的時候,就直接用它來刪除了應該刪除的元素並當即返回。
除了減輕了內存和處理器的負擔(由於不須要開闢新的內容來存儲結果變量以及可能不用徹底走遍整個的for語句),閱讀代碼的人也會很快領會代碼的意圖。
因此在寫代碼的時候,若是能夠「速戰速決」,就儘可能使用最快,最簡潔的方式來實現目的。
變量的做用域越廣,就越難追蹤它,值也越難控制,因此咱們應該讓你的變量對儘可能少的代碼可見。
好比類的成員變量就至關於一個「小型局部變量」。若是這個類比較龐大,咱們就會很難追蹤它,由於全部方法均可以「隱式」調用它。因此相反地,若是咱們能夠把它「降格」爲局部變量,就會很容易追蹤它的行蹤:
//成員變量,比較難追蹤 class LargeCass{ string str_; void Method1(){ str_ = ...; Method2(); } void Method2(){ //using str_ } } 複製代碼
降格:
//局部變量,容易追蹤 class LargeCass{ void Method1(){ string str = ...; Method2(str); } void Method2(string str){ //using str } } 複製代碼
因此在設計類的時候若是這個數據(變量)能夠經過方法參數來傳遞,就不要以成員變量來保存它。
在實現一個函數的時候,咱們可能會聲明比較多的變量,但這些變量的使用位置卻不都是在函數開頭。
有一個比較很差的習慣就是不管變量在當前函數的哪一個位置使用,都在一開始(函數的開頭)就聲明瞭它們。這樣可能致使的問題是:閱讀代碼的人讀到函數後半部分的時候就忘記了這個變量的類型和初始值;並且由於在函數的開頭就聲明瞭好幾個變量,也對閱讀代碼的人的大腦形成了負擔,由於人的短時間記憶是有限的,特別是記一些暫時還不知道怎麼用的東西。
所以,若是在函數內部須要在不一樣地方使用幾個不一樣的變量,建議在真正使用它們以前再聲明它。
操做一個變量的地方越多,就越難肯定它的當前值。因此在不少語言裏面有其各自的方式讓一些變量不可變(是個常量),好比C++裏的const
和Java中的final
。
有些表達式比較長,很難讓人立刻理解。這時候最好能夠將其拆分紅更容易的幾個小塊。能夠嘗試下面的幾個方法:
有些變量會從一個比較長的算式得出,這個表達式可能很難讓人看懂。這時候就須要用一個簡短的「解釋」變量來詮釋算式的含義。使用書中的一個例子:
if line.split(':')[0].strip() == "root" 複製代碼
其實上面左側的表達式其實得出的是用戶名,咱們能夠用username
來替換它:
username = line.split(':')[0].strip() if username == "root" 複製代碼
除了以「變量」替換「算式」,還能夠用「變量」來替換含有更多變量更復雜的內容,好比條件語句,這時候該變量能夠被稱爲"總結變量"。使用書中的一個例子:
if(request.user.id == document.owner_id){ //do something } 複製代碼
上面這條判斷語句所判斷的是:「該文檔的全部者是否是該用戶」。咱們可使用一個總結性的變量user_owns_document
來替換它:
final boolean user_owns_document = (request.user.id == document.owner_id); if (user_owns_document){ //do something } 複製代碼
德摩根定理:
not(a or b or c)
等價於(not a) and (not b) and (not c)
not(a and b and c)
等價於(not a) or (not b) or (not c)
當咱們條件語句裏面存在外部取反的狀況,就可使用德摩根定理來作個轉換。使用書中的一個例子:
//使用德摩根定理轉換之前 if(!(file_exists && !is_protected)){} //使用德摩根定理轉換之後 if(!file_exists || is_protected){} 複製代碼
在讀過一些好的源碼以後我有一個感覺:好的源碼每每都看上去都很漂亮,頗有美感。這裏說的漂亮和美感不是指代碼的邏輯清晰有條理,而是指感官上的視覺感覺讓人感受很舒服。這是從一種純粹的審美的角度來評價代碼的:富有美感的代碼讓人賞心悅目,也容易讓人讀懂。
爲了讓代碼更有美感,採起如下實踐會頗有幫助:
有些時候,咱們能夠利用換行和列對齊來讓代碼顯得更加整齊。
換行比較經常使用在函數或方法的參數比較多的時候。
使用換行:
- (void)requestWithUrl:(NSString*)url
method:(NSString*)method
params:(NSDictionary *)params
success:(SuccessBlock)success
failure:(FailuireBlock)failure{
}
複製代碼
不使用換行:
- (void)requestWithUrl:(NSString*)url method:(NSString*)method params:(NSDictionary *)params success:(SuccessBlock)success failure:(FailuireBlock)failure{
}
複製代碼
經過比較能夠看出,若是不使用換行,就很難一眼看清楚都是用了什麼參數,並且代碼總體看上去整潔乾淨了不少。
在聲明一組變量的時候,因爲每一個變量名的長度不一樣,致使了在變量名左側對齊的狀況下,等號以及右側的內容沒有對齊:
NSString *name = userInfo[@"name"];
NSString *sex = userInfo[@"sex"];
NSString *address = userInfo[@"address"];
複製代碼
而若是使用了列對齊的方法,讓等號以及右側的部分對齊的方式會使代碼看上去更加整潔:
NSString *name = userInfo[@"name"];
NSString *sex = userInfo[@"sex"];
NSString *address = userInfo[@"address"];
複製代碼
這兩者的區別在條目數比較多以及變量名稱長度相差較大的時候會更加明顯。
當涉及到相同變量(屬性)組合的存取都存在的時候,最好以一個有意義的順序來排列它們:
舉個例子:相同集合裏的元素同時出現的時候最好保證每一個元素出現順序是一致的。除了便於閱讀這個好處之外,也有助於能發現漏掉的部分,尤爲當元素不少的時候:
//給model賦值
model.name = dict["name"];
model.sex = dict["sex"];
model.address = dict["address"];
...
//拿到model來繪製UI
nameLabel.text = model.name;
sexLabel.text = model.sex;
addressLabel.text = model.address;
複製代碼
在寫文章的時候,爲了能讓整個文章看起來結構清晰,咱們一般會把大段文字分紅一個個小的段落,讓表達相同主旨的語言湊到一塊兒,與其餘主旨的內容分隔開來。
並且除了讓讀者明確哪些內容是表達同一主旨以外,把文章分爲一個個段落的好處還有便於找到你的閱讀「腳印」,便於段落之間的導航;也可讓你的閱讀具備必定的節奏感。
其實這些道理一樣適用於寫代碼:若是你能夠把一個擁有好幾個步驟的大段函數,以空行+註釋的方法將每個步驟區分開來,那麼則會對讀者理解該函數的功能有極大的幫助。這樣一來,代碼既能有必定的美感,也具有了可讀性。其實可讀性又未嘗不是來自於規則,富有美感的代碼呢?
BigFunction{ //step1:***** .... //step2:***** ... //step3:***** .... } 複製代碼
有些時候,你的某些代碼風格可能與大衆比較容易接受的風格不太同樣。可是若是你在你本身所寫的代碼各處可以保持你這種獨有的風格,也是能夠對代碼的可讀性有積極的幫助的。
好比一個比較經典的代碼風格問題:
if(condition){ } 複製代碼
or:
if(condition) { } 複製代碼
對於上面的兩種寫法,每一個人對條件判斷右側的大括號的位置會有不一樣的見解。可是不管你堅持的是哪個,請在你的代碼裏作到始終如一。由於若是有某幾個特例的話,是很是影響代碼的閱讀體驗的。
咱們要知道,一個邏輯清晰的代碼也能夠由於留白的不規則,格式不對齊,順序混亂而讓人很難讀懂,這是十分讓人痛心的事情。因此既然你的代碼在命名上,邏輯上已經很優秀了,就不妨再費一點功夫把她打扮的漂漂亮亮的吧!
首先引用書中的一句話:
註釋的目的是儘可能幫助讀者瞭解得和做者同樣多。
在你寫代碼的時候,在腦海中可能會留下一些代碼裏面很難體現出來的部分:這些部分在別人讀你的代碼的時候可能很難體會到。而這些「不對稱」的信息就是須要經過以註釋的方式來告訴閱讀代碼的人。
想要寫出好的註釋,就須要首先知道:
咱們都知道註釋佔用了代碼的空間,並且實際上對程序自己的運行毫無幫助,因此最好保證它是物有所值的。
不幸的是,有一些註釋是毫無價值的,它無情的佔用了代碼間的空間,影響了閱讀代碼的人的閱讀效率,也浪費了寫註釋的人的時間。這樣的註釋有如下兩種:
//add params1 and params2 and return sum of them
- (int)addParam1:(int)param1 param2:(int)param2
複製代碼
上面這個例子舉的比較簡單,但反映的問題很明顯:這裏面的註釋是徹底不須要的,它的存在反而增長了閱讀代碼的人的工做量。由於他從方法名就能夠立刻意會到這個函數的做用了。
//get information from internet
- (NSString *)getInformation
複製代碼
該函數返回的是從網絡獲取的信息。但這裏使用了get前綴,沒法看出信息的來源。爲了補充信息,使用註釋來彌補。但其實這徹底沒必要要。只要取一個適當的名字就行了:
- (NSString *)fetchInformation
複製代碼
講完了註釋不該該是什麼內容,如今講一下注釋應該是什麼樣的內容:
本書中介紹的註釋大概有如下幾種:
寫代碼時的思考
對代碼的評價
常量
全局觀的概述
你的代碼可能不是一蹴而就的,它的產生可能會須要一些思考的過程。然而不少時候代碼自己卻沒法將這些思考表達出來,因此你就可能有必要經過註釋的方式來呈現你的思考,讓閱讀代碼的人知道這段代碼是哪些思考的結晶,從而也讓讀者理解了這段代碼爲何這麼寫。若是遇到了比你高明的高手,在他看到你的註釋以後興許會立刻設計出一套更加合適的方案。
有些時候你知道你如今寫的代碼是個臨時的方案:它可能確實是解決當前問題的一個方法,可是:
你知道同時它也存在着某些缺陷,甚至是陷阱
你不知道有其餘的方案能夠替代了
你知道有哪一個方案能夠替代可是因爲時間的關係或者自身的能力沒法實現
也可能你知道你如今實現的這個方案几乎就是「完美的」,由於若是使用了其餘的方案,可能會消耗更多的資源等等。
對於上面這些狀況,你都有必要寫上幾個字做爲註釋來誠實的告訴閱讀你的這段代碼的人這段代碼的狀況,好比:
//該方案有一個很容易忽略的陷阱:****
//該方案是存在性能瓶頸,性能瓶頸在其中的**函數中
//該方案的性能可能並非最好的,由於若是使用某某算法的話可能會好不少
複製代碼
在定義常量的時候,在其後面最好添加一個關於它是什麼或者爲何它是這個值的緣由。由於常量一般是不該該被修改的,因此最好把這個常量爲何是這個值說明一下:
例如:
image_quality = 0.72 // 最佳的size/quanlity比率
retry_limit = 4 // 服務器性能所容許的請求失敗的重試上限
複製代碼
對於一個剛加入團隊的新人來講,除了團隊文化,代碼規範之外,可能最須要了解的是當前被分配到的項目的一些「全局觀」的認識:好比組織架構,類與類之間如何交互,數據如何保存,如何流動,以及模塊的入口點等等。
有時僅僅添加了幾句話,可能就會讓新人迅速地瞭解當前系統或者當前類的結構以及做用,並且這些也一樣對開發過當前系統的人員迅速回憶出以前開發的細節有很大幫助。
這些註釋能夠在一個類的開頭(介紹這個類的職責,以及在整個系統中的角色)也能夠在一個模塊入口處。書中舉了一個關於這種註釋的例子:
//這個文件包含了一些輔助函數,尾門的文件系統提供了更便利的接口
複製代碼
再舉一個iOS開發裏衆所周知的網絡框架AFNetworking
的例子。在AFHTTPSessionManager
的頭文件裏說明了這個類的職責:
//AFHTTPSessionManager` is a subclass of `AFURLSessionManager` with convenience methods for making HTTP requests. When a `baseURL` is provided, requests made with the `GET` / `POST` / et al. convenience methods can be made with relative paths
複製代碼
在知道了什麼不該該是註釋以及什麼應該是註釋之後,咱們來看一下一個真正合格的註釋應該是什麼樣子的:
註釋應當有很高的信息/空間率
也就是說,註釋應該用最簡短的話來最明確地表達意圖。要作到這一點須要作的努力是:
其實好的代碼是自解釋的,因爲其命名的合理以及架構的清晰,幾乎不須要註釋來向閱讀代碼的人添加額外的信息,書中有一個公式能夠很形象地代表一個好的代碼自己的重要性:
好代碼 > (壞代碼 + 註釋)
控制流在編碼中佔據着很重要的位置,它每每表明着一些核心邏輯和算法。所以,若是咱們可讓控制流變得看上去更加「天然」,那麼就會對閱讀代碼的人理解這些邏輯甚至是整個系統提供很大的幫助。
那麼都有哪相關實踐呢?
寫代碼也是一個表達的過程,雖然表現形式不一樣,可是若是咱們可以採用符合人類天然語言習慣的表達習慣來寫代碼,對閱讀代碼的人理解咱們的代碼是頗有幫助的。
這裏有兩個比較典型的情景:
首先比較一下下面兩段代碼,哪個更容易讀懂?
//code 1 if(length > 10) //code 2 if(10 < length) 複製代碼
你們習慣上應該會以爲code1容易讀懂。
再來看下面一個例子:
//code 3 if(received_number < standard_number) //code 4 if( standard_number< received_number) 複製代碼
仔細看會發現,和上面那一組狀況相似,大多數人仍是會以爲code3更容易讀懂。
那麼code1 和 code3有什麼共性呢?
它們的共性就是:左側都是被詢問的內容(一般是一個變量);右側都是用來作比較的內容(一般是一個常量)
這應該是符合天然語言的一個順序。好比咱們通常會說「今天的氣溫大於20攝氏度」,而不習慣說「20攝氏度小於今天的氣溫」。
在判斷一些正負邏輯的時候,建議使用if(result)
而不是if(!result)
。
由於大腦比較容易處理正邏輯,好比咱們可能比較習慣說「某某某是個男人」,而不習慣說「某某某不是個女人」。若是咱們使用了負邏輯,大腦還要對它進行取反,至關於多作了一次處理。
在寫if/else語句的時候,可能會有不少不一樣的互斥狀況(好多個elseif
)。那麼這些互斥的狀況能夠遵循哪些順序呢?
在一個函數或是方法裏,可能有一些狀況是比較特殊或者極端的,對結果的產生影響很大(甚至是終止繼續進行)。若是存在這些狀況,咱們應該把他們寫在前面,用return來提早返回(或者返回須要返回的返回值)。
這樣作的好處是能夠減小if/else語句的嵌套,也能夠明確體現出:「哪些狀況是引發異常的」。
再舉一個JSONModel
裏的例子,在initWithDictionary:error
方法裏面就有不少return操做,它們都體現出了「在什麼狀況下是不能成功將字典轉化爲model對象」的;並且在方法的最後返回了對象,說明若是到了這一步,則在轉化的過程當中經過了層層考驗:
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
//check for nil input
if (!dict) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
//invalid input, just create empty instance
if (![dict isKindOfClass:[NSDictionary class]]) {
if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
return nil;
}
//create a class instance
self = [self init];
if (!self) {
//super init didn't succeed
if (err) *err = [JSONModelError errorModelIsInvalid];
return nil;
}
//check incoming data structure
if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
return nil;
}
//import the data from a dictionary
if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
return nil;
}
//run any custom model validation
if (![self validate:err]) {
return nil;
}
//model is valid! yay!
return self;
}
複製代碼
關於代碼組織的改進,做者介紹瞭如下三種方法:
一個函數裏面每每包含了其主邏輯與子邏輯,咱們應該積極地發現並抽取出與主邏輯不相關的子邏輯。具體思考的步驟是:
好比某個函數的目標是爲了尋找距離某個商家最近的地鐵口,那麼這其中必定會重複出現一些計算兩組經緯度之間距離的子邏輯。可是這些子邏輯的具體實現是不該該出如今這個主函數裏面的,由於這些細節與這個主函數的目標來說應該是無關的。
便是說,像這種相似於工具方法的函數實際上是脫離於某個具體的需求的:它能夠用在其餘的主函數中,也能夠放在其餘的項目裏面。好比找到離運動場場最近的幾個公交站這個需求等等。
而像這種「抽取子邏輯或工具方法」的作法有什麼好處呢?
從函數擴大到項目,其實在一個項目裏面,有不少東西不是當前這個項目所專有的,它們是能夠用在其餘項目中的一些「通用代碼」。這些通用代碼能夠對當前的項目一無所知,能夠被用在其餘任何項目中去。
咱們能夠養成這個習慣,「把通常代碼與項目專有代碼分開」,並不斷擴大咱們的通用代碼庫來解決更多的通常性問題。
一個比較大的函數或者功能可能由不少任務代碼組合而來,在這個時候咱們有必要將他們分爲更小的函數來調用它們。
這樣作的好處是:咱們能夠清晰地看到這個功能是如何一步一步完成的,並且拆分出來的小的函數或許也能夠用在其餘的地方。
因此若是你遇到了比較難讀懂的代碼,能夠嘗試將它所作的全部任務列出來。可能立刻你就會發現這其中有些任務能夠轉化成單獨的函數或者類。而其餘的部分能夠簡單的成爲函數中的一個邏輯段落。
在設計一個解決方案以前,若是你可以用天然語言把問題說清楚會對整個設計很是有幫助。由於若是直接從大腦中的想法轉化爲代碼,可能會露掉一些東西。
可是若是你能夠將整個問題和想法滴水不漏地說出來,就可能會發現一些以前沒有想到的問題。這樣能夠不斷完善你的思路和設計。
這本書從變量的命名到代碼的組織來說解了一些讓代碼的可讀性提升的一些實踐方法。
其實筆者認爲代碼的可讀性也能夠算做是一種溝通能力的一種體現。由於寫代碼的過程也能夠被看作是寫代碼的人與閱讀代碼的人的一種溝通,只不過這個溝通是單向的:代碼的可讀性高,能夠說明寫代碼的人思路清晰,並且TA能夠明確,高效地把本身的思考和工做內容以代碼的形式表述出來。 因此筆者相信能寫出可讀性很高的代碼的人,TA對於本身的思考和想法的描述能力必定不會不好。
若是你真的打算好好作編程這件事情,建議你從最小的事情上作起:好好爲你的變量起個名字。不要再以「我英語很差」或者「沒時間想名字」做爲託辭;把態度端正起來,平時多動腦,多查字典,多看源碼,天然就會了。
若是你連起個好的變量名都懶得查個字典,那你怎麼證實你在遇到更難的問題的時候可以以科學的態度解決它? 若是你連編程裏這種最小的事情都很差好作,那你又怎麼證實你對編程是有追求的呢?
本文已經同步到個人我的博客:傳送門
---------------------------- 2018年7月17日更新 ----------------------------
注意注意!!!
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~