為什么你的代碼如此難以理解
“我到底在想什么????”
凌晨1:30分,我正盯著不到一個月前我寫的一段代碼。當(dāng)時它看起來像是件藝術(shù)品,全部是可理解的,優(yōu)雅、簡單、讓人嘆為觀止。這一切都不再了,明 天是我的***期限,數(shù)小時前我發(fā)現(xiàn)了一個bug。當(dāng)時看起來的簡單和邏輯再也說不通了??梢钥隙ǖ氖?,如果我寫代碼,我應(yīng)該足以聰明到理解代碼?
經(jīng)過了多次這種經(jīng)歷以后,我開始認(rèn)真思考,為什么我的代碼在我編寫的時候很清楚、而當(dāng)我數(shù)周或數(shù)月后回頭看的時候,它們卻那么費解。
問題1,過度復(fù)雜的邏輯模型
為了理解當(dāng)你間隔一段時間返回到你的代碼、卻發(fā)現(xiàn)代碼難以理解的***步,就是理解我們?nèi)绾螐男闹巧辖栴}模型。你寫的幾乎所有代碼都是盡量解決現(xiàn)實世界的問題。在你寫代碼之前,你需要理解你正試圖解決的問題。這常常是編程里最難的一步。
為了解決現(xiàn)實世界的問題,我們首先需要形成該問題的心智模型【注1】,以此作為編程意圖。接下來你需要形成實現(xiàn)編程意圖的方案模型,我們姑且稱為語 義模型(semantic model)。從來不要混淆你的編程意圖和此意圖的方案。我們傾向于主要考慮方案方面的東東,而常常忽略意圖的模型。
你接下來的步驟是形成可能最簡單的語義模型。這是容易搞錯的第二步。如果你不花時間去真正理解你正試圖解決的問題,你將在寫代碼時被絆倒在模型上。另一方面,如果你真正考慮了你正盡量做的事情,你經(jīng)常得到一個非常簡單的模型,這足以讓你掌握最初的意圖。
如果你想容易地維護簡單的代碼,就盡可能多些地消除意外的復(fù)雜性。我們正試圖解決的問題是足夠復(fù)雜的。如果你不必那么做,就不要把意外的復(fù)雜性增加進來。
問題2,笨拙的把思想轉(zhuǎn)化成代碼
一旦你盡全力形成了***的語義模型,那么就到了把它轉(zhuǎn)化為代碼的時候了。我們稱之為句法模型(syntactic model)。你正試圖把你的語義模型的意義轉(zhuǎn)化為計算機能夠理解的句法。
如果你有非常不錯的語義模型、而在轉(zhuǎn)化為代碼時搞砸了,那么在你需要在以后某個階段回頭修改代碼時,你將比較痛苦。當(dāng)你腦子里還有語義模型時,把你 代碼映射到語義模型是容易的?;貞浧鹱兞?ldquo;x”實際上代表一條記錄被創(chuàng)建的日期、而“y”代碼記錄被刪除的日期,這是不難的。當(dāng)你3個月后再回來看代碼, 你的腦子里將沒有這個語義模型了,因此無法理解同樣的變量名字。
把語義模型轉(zhuǎn)化為句法的任務(wù)就是盡量多地留下線索,讓你在今后返回時,能夠重建當(dāng)初的語義模型。
好了,你該怎么做呢?
類結(jié)構(gòu)和命名
如果你在使用面向?qū)ο笳Z義,請盡量讓你的類結(jié)構(gòu)和命名靠近語義模型。領(lǐng)域驅(qū)動設(shè)計(Domain Driven Design)【注2】是在這種練習(xí)上投入了相當(dāng)重要性的一種運動。即使你沒有相信完全的DDD方法,你也應(yīng)當(dāng)非常小心地考慮類結(jié)構(gòu)和命名。每個類都是你 留給自己和其他人的線索,它幫助你在將來返回的時候重建你的心智模型。
變量、參數(shù)和方法命名
盡量避免普通的變量和方法命名。不要把方法命名為“Process”,因為“PaySalesCommision”更有意義。不要把變量命名為 “x”,因為它應(yīng)當(dāng)是“currentContract”。不要把參數(shù)命名為“input”,因為“outstandingInvoices“更好。
單一功能原則(Single responsibility principle,簡稱SRP)
SRP【注3】是面對對象設(shè)計原則的核心之一,關(guān)聯(lián)著好的類和變量命名。它認(rèn)為,任何類或方法都應(yīng)該完成一個單一的功能,只能是一個單一的功能。如 果你想為類和方法給出有意義的名字,那么它們需要有一個唯一的較好定義的目的。如果一個單一類從數(shù)據(jù)庫讀和寫、計算營業(yè)稅、通知交易客戶并生成賬單,那么 你就可能無法給出合適的名字。我常常停留在重構(gòu)類上,因為我總是努力取一個足夠短的名字,以描述它做的每個功能。為了更多地討論SRP和其它面向?qū)ο笤?則,可以參考我的博文《面向?qū)ο笤O(shè)計》。
適當(dāng)?shù)淖⑨?/strong>
如果因為某種原因,你不能讓代碼變得清晰,你同情將來的自己,需要不得不做些事情,那就留下注釋來說明你為什么不得不那樣做。注釋傾向于快速地變得陳舊,因此我寧愿盡可能讓代碼自描述,注釋用來說明為什么你不得不那樣做,而不是它如何做。
問題3,沒有足夠的組塊
心理學(xué)上的組塊被定義是,把信息組塊定位為單一的實體。那么這該如何應(yīng)用到編程上呢?作為一名開發(fā)者,在你積累經(jīng)驗時,你開始發(fā)現(xiàn)你解決方案里反復(fù) 出現(xiàn)的模式。***影響的設(shè)計模式:《可重用的面向?qū)ο筌浖肥?**本整理和解釋一些模式的書。盡管如此,組塊不僅僅用在設(shè)計模式和面向?qū)ο?。在函?shù)式編程 (FP)里,存在大量的著名標(biāo)準(zhǔn)函數(shù)具備這同樣的目的。算法是組塊的另一種形式(后續(xù)會更多)。
當(dāng)你合理地使用組塊(設(shè)計模式、算法和標(biāo)準(zhǔn)函數(shù))時,它讓你停下來思考,你編寫的代碼是如何運行的、而不是考慮它做了什么。這縮短了你的語義模型(你的代碼)和句法模型(你腦子里的模型)的距離。這個距離越短,你就越容易重建你的心智模型。
如果你有興趣了解更多FP里的函數(shù),請移步到我的文章面向web開發(fā)者的函數(shù)式編程。
問題4,費解的用法
目前,我們主要討論了如何結(jié)構(gòu)化你的類、方法和變量命名。心智模型的另一個重要部分是理解這些方法應(yīng)該怎樣被使用。再次強調(diào),當(dāng)你最初形成心智模型 時,這是相當(dāng)清晰的。當(dāng)你后來返回時,就非常難以重建你的類和方法的、所有有意圖的用法了。通常這是因為不同的用法散布在你的程序其它地方。有時候甚至出 現(xiàn)在很多不同的項目中。
我就是在這種情況下發(fā)現(xiàn)測試用例是非常有用的。除了相應(yīng)地知道一個修改是否破壞了代碼的明顯好處,測試為你的代碼提供了一整套的用例。你不必搜遍100個文件,只需看測試就能得到引用的全景。
注意為了達到這個目的,你需要有一整套完整的測試用例。如果你的測試僅僅覆蓋了一部分、而你認(rèn)為測試是完整的,那么你后來將陷入困境。
問題5,不同的模型之間沒有清晰的途徑
你的代碼從技術(shù)角度看,常常是優(yōu)秀的、非常優(yōu)雅,但是從程序意圖到語義模型、再到代碼存在非常不自然的跳躍。考慮你選擇的一堆模型的透明性是重要 的。從程序意圖到語義模型、再到代碼的過程需要盡可能平滑。你應(yīng)當(dāng)能夠看透對應(yīng)到問題的每個模型的所有方面。多數(shù)情況下,***選擇特定類結(jié)構(gòu)或算法不是為 了它在隔離方面的優(yōu)雅,而是可以連接各種模型,為你重建的目的而留下 一條自然的途徑。當(dāng)你從抽象的編程意圖走到具體的代碼時,你做的選擇應(yīng)該受到 你能夠表現(xiàn)更為抽象模型 的清晰度驅(qū)使。
問題6,發(fā)明算法
作為程序員,我們經(jīng)常認(rèn)為,我們在為了解決問題而發(fā)明著算法。事實很難是這樣的。幾乎所有情況下,已經(jīng)有現(xiàn)成的算法可以被組合在一起解決你的問題 了。像最短路徑搜索法、字符串相似度算法、粒子群算法等。大部分編程是以正確的組合、選擇現(xiàn)存算法來解決你的問題。如果你正在發(fā)明新算法,那么,要么你不 知道合適的算法、要么你正忙于你的博士論文。
總結(jié)
***總結(jié):作為一名程序員,你的目標(biāo)是建立能夠解決你問題的、盡可能簡單的語義模型。把語義模型盡可能靠近地轉(zhuǎn)化為句法模型(代碼),盡可能多地提供線索,便于你之后無論哪個人看你的代碼,都能重建像你最初腦子里的、相同的語義模型。
設(shè)想一下,當(dāng)你走過你代碼的被照亮的森林時,你在身后留了面包屑。相信我,當(dāng)你需要找到回去的路時,森林將充滿了黑暗、朦朧和不詳?shù)念A(yù)感。
聽起來容易,實際做起來是很難的。
-
原文地址:https://medium.com/on-coding/why-your-code-is-so-hard-to-understand-83057c115a2b
-
注1:心智模型是用于解釋個體為現(xiàn)實世界中之某事所運作的內(nèi)在認(rèn)知歷程。http://zh.wikipedia.org/wiki/心智模型
-
注2:要通過創(chuàng)建領(lǐng)域模型來加速復(fù)雜的軟件開發(fā),就需要利用大量***實踐和標(biāo)準(zhǔn)模式在開發(fā)團隊中形成統(tǒng)一的交流語言;不僅重構(gòu)代碼,而且要重構(gòu)代碼底層的模型;同時采取反復(fù)迭代的敏捷開發(fā)方法,深入理解領(lǐng)域特點,促進領(lǐng)域?qū)<遗c程序員的良好溝通。http://baike.baidu.com/view/3705331.htm
-
注3:馬丁把功能(職責(zé))定義為:“改變的原因”,并且總結(jié)出一個類或者模塊應(yīng)該有且只有一個改變的原因。一個具體的例子就是,想象有一個用于編 輯和打印報表的模塊。這樣的一個模塊存在兩個改變的原因。***,報表的內(nèi)容可以改變(編輯)。第二,報表的格式可以改變(打?。?。這兩方面會的改變因為完 全不同的起因而發(fā)生:一個是本質(zhì)的修改,一個是表面的修改。單一功能原則認(rèn)為這兩方面的問題事實上是兩個分離的功能,因此他們應(yīng)該分離在不同的類或者模塊 里。把有不同的改變原因的事物耦合在一起的設(shè)計是糟糕的。http://zh.wikipedia.org/wiki/單一功能原則
原文出處:www.labazhou.net



























