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