摘要:我故意保持示例簡單,以說明公共接口是如何自我文檔化的。這種類型的函數產生更多的自我文檔化代碼的另一個原因是你可以信任他們的輸出。
在代碼里面找到一個完全沒有地方或沒有用的注釋是不是很有趣?
這是一個很容易犯的錯誤:你改變了一些代碼,但忘記刪除或更新注釋。壞的注釋不會破壞你的代碼,但你可以想象一下調試時會發生什么。你讀了注釋,但代碼卻在做另一件事,也許最終你浪費了一些時間來弄懂它,甚至最壞的情況是,它誤導了你。
但沒有編寫任何注釋的代碼不是一個選擇。在我超過15年的編程經驗里,我從來沒有見過一個代碼庫,其中的評論是完全不必要的。
注釋不僅有助于使我們的代碼更容易理解,也可以幫助我們改進整個程序的設計。
這種類型的編碼叫做自我文檔化,現在讓我來告訴你如何采用這種方式編程。雖然在這里我的例子使用的是JavaScript,但你可以應用到期其他的語言和技術中去。
技術概述一些程序員把注釋作為代碼自我文檔化的一部分,在本文中,我們只關注代碼,注釋固然很重要,但它是一個需要多帶帶討論的大話題。
我們可以將代碼自我文檔化的技術分為3大類:
structural(結構):其中代碼或目錄的結構用于闡明目的
naming related(命名相關):例如函數或變量的命名
syntax related(語法相關):我們利用(或者避免使用)語言的特征來是代碼清晰
其中很多是看起來很簡單,挑戰來自于你要知道什么時候用什么技術。我會告訴你一些實際例子,我們將會處理的每一個例子。
結構首先,我們來看一下結構類別。結構變化時為了增強代碼的清晰度而移動代碼。
將代碼移動到函數中這與提取代碼重構相同——意味著我們采用現有代碼并將其移動到一個新的函數中:我們將代碼提取到一個新函數中。
例如,猜想一下下面的代碼是做什么的:
var width = (value - 0.5) * 16;
上述代碼不是很清楚,此時注釋可能是非常有用的,但或許我們可以提取一個函數,使其自我文檔化:
var width = emToPixels(value); function emToPixels(ems) { return (ems - 0.5) * 16; }
唯一的變化時我把計算移動到一個函數里,函數的名稱描述了它的作用,所以代碼不再需要注釋。作為一個額外的好處,我們現在有了一個有用的函數,我們可以在其他地方使用這個函數,這種方法有助于減少代碼重復冗余。
用函數替換條件表達式很多時候帶有多個操作數的代碼,如果沒有注釋是很難理解的。我們可以應用類似上述的方法來使代碼清晰:
if(!el.offsetWidth || !el.offsetHeight) { }
上訴條件的目的是什么?
function isVisible(el) { return el.offsetWidth && el.offsetHeight; } if(!isVisible(el)) { }
再一次,我們把代碼移動到一個函數內,代碼立即更容易理解。
用變量替換表達式用變量替換某個東西類似于將代碼移動到一個函數中,而不是一個函數,此時我們只需要一個變量。
讓我們再看一下if條件語句的例子:
if(!el.offsetWidth || !el.offsetHeight) { }
我們還可以通過引入一個變量,而不是提取一個函數,來使我們的代碼自我文檔化:
var isVisible = el.offsetWidth && el.offsetHeight; if(!isVisible) { }
這可能是比提取函數更好的選擇,例如,當你當你想要闡明的邏輯對于僅在一個地方使用的某個算法非常特定時。
這種方法最常見的是用于數字表達式:
return a * b + (c / d);
我們可以通過分割計算來使上述代碼更清晰:
var multiplier = a * b; var divisor = c / d; return multiplier + divisor;
因為我害怕數學,想象上述的例子還是有一些算法的。在任何情況下,代碼自我文檔化的關鍵是你可以將復雜的表達式移動到變量中,并增加意義,否則你的代碼是難以的理解的。
類和模塊接口類和模塊的接口——即公共方法和屬性,可以作為其使用的文檔。
讓我們來看一下這個例子:
class Box { setState(state) { this.state = state; } getState() { return this.state; } }
這個類可以包含一些其他的代碼。我故意保持示例簡單,以說明公共接口是如何自我文檔化的。
你能告訴我應該如何使用這個類嗎?也許有一點點作用,但它不明顯。
這兩個函數都有合理的名字:它們要做的是闡明自己的名字。但是盡管如此,它不是很清楚你應該如何使用它們,很可能你需要閱讀更多的代碼或類的文檔來弄清楚。
如果我們把它改成這樣:
class Box { open() { this.state = "open"; } close() { this.state = "closed"; } isOpen() { return this.state === "open"; } }
這是更容易理解的用法,你不覺得嗎?注意我們只是改變了公共接口,內部表示仍然與this.satte屬性相同。
現在你可以一眼就看出Box類是如何使用的了。這表明這表明即使第一個版本的函數具有良好的名稱,但完整的包仍然是混亂的,如何通過這樣簡單的變化,你可以有一個非常大的影響。很多時候你需要想想大局。
代碼分組代碼分組的不同部分也可以作為一種文檔形式。
例如,你應該將變量聲明盡可能地靠近它們被使用的位置,并嘗試將變量使用組合在一起。
這可以用于指示代碼不同部分之間的關系,以便將來更改它的任何人都可以更容易地找到他們需要查閱的部分。
思考如下的例子:
var foo = 1; blah() xyz(); bar(foo); baz(1337); quux(foo);
你能一眼看出foo被調用了多少次嗎?對比下面的例子:
var foo = 1; bar(foo); quux(foo); blah() xyz(); baz(1337);
通過把foo的所用用途分組在一起,我們很容易可以看出代碼的哪些部分取決于它。
使用純函數純函數比依賴性強的函數更容易理解。
什么是純函數?當調用一個具有相同參數的函數時,如果它總是產生相同的輸出,它很有可能是一個“純”函數。這意味著純函數不應該有任何副作用或依賴狀態,如時間、對象屬性、Ajax等。
這種類型的函數更容易理解,因為影響其輸出的任何值都明確傳遞,你不必弄清楚其中的某個值是什么、來自哪里,或什么因素會影響結果,因為它是一目了然的。
這種類型的函數產生更多的自我文檔化代碼的另一個原因是你可以信任他們的輸出。不管什么時候,函數總是輸出基于你傳遞給它的參數的值,它也不會影響任何的外部代碼,所以你可以相信它不會導致意想不到的副作用。
一個很好的例子是,錯誤地使用document.write(),有經驗的JS開發者知道不應該使用它,但是很多初學者都被它絆倒。有時候它工作的很好,但在其他時候,在某些情況下,它可以把整個頁面擦干凈。談一個副作用的痛!
為了更好地闡釋純函數是什么,可以查看Functional Programming: Pure Functions。
目錄和文件結構當命名文件或目錄時,遵循項目中用到的命名約定。如果項目中沒有明確的命名約定,請遵循您選擇的語言命名標準。
例如,你要添加有關UI的新的代碼,請找到項目中放置類似功能的位置,如果UI相關的代碼放在src/ui中,那你應該放置在這里。
基于你已經知道項目中的其他代碼段,目錄和文件結構清晰使得你更容易找到代碼, 并明白其目的。所有的UI代碼都放在同一個地方,所以它必須是和UI相關的代碼。
命名這里有一個流行的摘引關于計算機科學的兩個艱難的方面:
There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton
那么,讓我們來談談如何使用合理的命名來使我們的代碼自我文檔化。
重命名函數函數的命名一般不太難,這里有一些簡單的規則,你可以遵循:
避免使用handle或manage這樣的模糊詞:handleLinks(), manageObjects(),這些都做了什么?
使用主動性動詞:cutGrass(), sendFile()函數積極地執行了某事
表明返回值:getMagicBullet(), readFile(),這不是你總是可以做到的,但賦予它意義是有幫助的
強類型的語言可以使用類型命名來幫助表明返回值
重命名變量對于變量,這里有兩個好的經驗法則:
表明單位:如果有數字參數,可以包含參數的預期單位。例如,widthPX而不是width表明值得單位是像素而不是其他單位
不要使用快捷方式:a或b不是可接受的變量名稱, 除了在循環計算器中
遵循既定的命名約定嘗試在代碼中遵循相同的命名約定。例如,如果你有一個特定類型的對象,調用它相同的名稱:
var element = getElement();
不用突然覺得稱之為node:
var node = getElement();
如果你遵循與代碼庫中其他地方相同的命名約定,閱讀代碼的任何人都可以基于此變量在別的地方的命名含義安全地假設它在此處的含義。
使用有意義的錯誤未定義不是一個對象!
每個人的最愛。讓我們拋開JavaScript的例子,讓我們確保代碼拋出的任何錯誤都是有意義的消息。
什么可以使錯誤消息有意義?
它應該描述錯誤是什么
如果可能,它應該包括任何導致錯誤地變量值或其他數據
關鍵點:拋出的錯誤應該幫助我們找出哪里出錯了——因此錯誤消息應該像函數那樣告訴我們應該怎么做
語法自我文檔化代碼的語法相關方法可以有一些語言特點。例如,Ruby和Perl允許你寫一些奇怪的語法技巧,一般來說,應該避免。
讓我們來看幾個在JavaScript中遇到的問題:
不要使用語法技巧不要使用語法技巧。這很容易讓人疑惑:
imTricky && doMagic();
上面的這行代碼相當于如下更健全的代碼:
if(imTricky) { doMagic(); }
習慣使用后一種寫法,語法技巧并不討任何人的喜歡。
使用常量命名,避免使用magic值如果你的代碼中有特殊值——例如數字或字符串值,請考慮使用常量命名。即使現在看起來很清楚,但在一個月或者兩個月后,沒人會知道為什么這么一個特定的號碼放在那里,意義是什么。
const MEANING_OF_LIFE = 42;
(如果你不使用ES6,你可以用var,是一樣的。)
避免使用布爾值布爾值會讓人難以理解代碼,考慮這個:
myThing.setData({ x: 1 }, true);
此處true的作用是什么呢?除非找到setDate()方法并閱讀它。
相反你可以添加另一個函數,或重命名現有的函數:
myThing.mergeData({ x: 1 });
現在,你立即就可以知道這行代碼發生了什么。
使用語言優勢我們甚至可以使用我們編寫的語言的一些特征來更好地表述代碼背后的意義。
JavaScript中一個很好的例子是數組的迭代:
var ids = []; for(var i = 0; i < things.length; i++) { ids.push(things[i].id); }
上面的代碼將一個ID列表收集到一個新的數組中,但是為了理解這塊代碼是做什么的,我們需要閱讀整個循環的全部。下面我們使用map()來進行比較:
var ids = things.map(function(thing) { return thing.id; });
在這種情況下,我們立即知道這會產生一系列的新東西因為這是map()的目的。如果你有更復雜的循環邏輯,這是很有益的寫法。list of other iteration functions on MDN
JavaScript的另一個好例子是const關鍵字。
通常,你聲明的變量值應該永遠不會改變,一個常見的例子是使用CommonJS加載模塊時:
var async = require("async");
你可以用如下寫法做出不糊改變意圖的語句:
const async = require("async");
作為一個額外的好處,如果有人不小心試圖改變這一點,我們將會得到一個錯誤。
反模式通過所有這些方法,你可以做很多事情,但是,有些事情你應該注意。
Extracting for the sake of having short functions有些人主張使用簡短的小函數,如果你把所有東西都提取出來,那就是你能得到的。但是,這可能不利于代碼的理解程度。
例如,假設你正在調試一些代碼。你想查看a()函數,然后你會發現b()函數,接著你會發現使用到c()函數,等等。
雖然簡短的功能可以很好而且易于理解,但如果你只在一個地方使用該功能,那么請考慮使用replace expression with variable方法。
別強迫像往常那樣,沒有絕對正確地方法來使代碼自我文檔化。因此,如果某些東西似乎是一個好主意,但不能強制使用。
總結使你的代碼自我文檔化可以大大提高代碼的可維護性,每個注釋都是需要額外維護的,所以在有可能刪除注釋的情況下,編寫自我文檔化的代碼是一個好選擇。
但是自我文檔化的代碼并不能取代文檔或者注釋,例如,代碼本身在表達意圖的時候收到限制時,你還是需要有很好的注釋的。在一些庫中,API文檔是很重要的,因此單純靠閱讀代碼是不可取的,除非你的庫非常小。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82146.html
摘要:文檔規范文檔規范制定了文檔的編寫規范,可部分遵守,也可全部遵守,看開發要求。標簽行內元素,表示一行中的一小段內容,沒有具體的語義。表示當前文件所在目錄下的上一級目錄,比如表示當前目錄下的上一級目錄下的文件夾中的的圖片。 1.1 html概述和基本結構 html概述 HTML是 HyperText Mark-up Language 的首字母簡寫,意思是超文本標記語言,超文本指的是超鏈接,標記指...
摘要:是一款輕量級易擴展的播放器,是為解決一些中小型的視頻業務場景。同時各插件由于是面向的播放器接口,插件不知道插件的存在,因此能極大地降低各插件功能間的耦合。 larkplayer 是一款輕量級 & 易擴展的 html5 播放器,是為解決一些中小型的視頻業務場景。這些業務不一定需要大而全的解決方案,并且他們往往有自己的定制化需求。 背景 為什么要編寫 larkplayer?(注意,這里面有...
摘要:是一款輕量級易擴展的播放器,是為解決一些中小型的視頻業務場景。同時各插件由于是面向的播放器接口,插件不知道插件的存在,因此能極大地降低各插件功能間的耦合。 larkplayer 是一款輕量級 & 易擴展的 html5 播放器,是為解決一些中小型的視頻業務場景。這些業務不一定需要大而全的解決方案,并且他們往往有自己的定制化需求。 背景 為什么要編寫 larkplayer?(注意,這里面有...
摘要:效果如下配置方法參考下的配置方法完美支持提供了比默認更好的語法高亮,而且他完美支持。語法高亮默認安裝的對的支持讓人抓狂,幀動畫別開玩笑了你只會看到一片白色的純文本一樣的代碼。事實上不光,我建議用完全替代原來的來完成語法高亮。 文章轉載自本人的博客《三省吾身丶丶》點擊查看喜歡的話請瘋狂的推薦吧! ^_^ 本文章會在本人有插件或者設置更新時,進行不定時更新 偷懶了,圖片地址直接設置的博客...
閱讀 804·2023-04-25 19:40
閱讀 3405·2023-04-25 17:41
閱讀 2993·2021-11-11 11:01
閱讀 2588·2019-08-30 15:55
閱讀 3218·2019-08-30 15:44
閱讀 1347·2019-08-29 14:07
閱讀 478·2019-08-29 11:23
閱讀 1314·2019-08-27 10:54