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