美化java代碼,從合理注釋開(kāi)始
請(qǐng)停止代碼注釋
“干凈的代碼應(yīng)該像寫(xiě)好的散文一樣” - Robert C. Martin
不良代碼的通病就是有很多注釋。這是凌亂的源代碼最明顯的跡象。
每個(gè)程序員的目標(biāo)應(yīng)該是編寫(xiě)干凈和富有表現(xiàn)力的代碼,以避免代碼注釋。每個(gè)變量,函數(shù)和類(lèi)的目的應(yīng)該隱含在其名稱(chēng)和結(jié)構(gòu)中。
當(dāng)其他人讀取您的代碼時(shí),他們不應(yīng)該閱讀注釋以了解你的代碼正在做什么。命名良好的類(lèi)和函數(shù)應(yīng)該引導(dǎo)讀者通過(guò)你的代碼,就像一本寫(xiě)得很好的小說(shuō)一樣。當(dāng)讀者看到一個(gè)新的類(lèi)或功能時(shí),他們不應(yīng)該對(duì)他們?cè)诶锩婵吹降臇|西感到困惑難以理解。
請(qǐng)記住,開(kāi)發(fā)人員的工作時(shí)間很少花在編寫(xiě)代碼上,花在閱讀代碼和理解代碼上的時(shí)間要多得多。
注釋掩飾錯(cuò)誤
在代碼中命名是非常重要的。您應(yīng)該花費(fèi)大量精力準(zhǔn)確而精確地命名每一段代碼,以便其他開(kāi)發(fā)人員能夠理解您的代碼。
// 按狀態(tài)查找員工 List<Employee> find(Status status) { ... }
在此示例中,名稱(chēng)find不夠描述,因此此函數(shù)的作者需要留下描述函數(shù)功能的描述性注釋。當(dāng)我們看到從另一個(gè)模塊調(diào)用的find函數(shù)時(shí),它的作用是一個(gè)謎。它發(fā)現(xiàn)了什么?究竟是什么意思?它返回了它發(fā)現(xiàn)的東西嗎?怎么找到它發(fā)現(xiàn)的東西?就像鮑勃叔叔在他的書(shū)《Clean Code》中所說(shuō),如果你需要寫(xiě)注釋?zhuān)憔蜔o(wú)法通過(guò)代碼表達(dá)自己真實(shí)的用意。
我們不希望檢查每個(gè)函數(shù)上面的注釋?zhuān)粤私馑淖饔谩?/p>
List<Employee> getEmployeesByStatus(Status status) { ... }
現(xiàn)在很明顯能看出來(lái)這個(gè)函數(shù)的具體作用,這使得注釋變得多余。這讓我想到了注釋糟糕的下一個(gè)方式。
冗余注釋
這些混亂了你的代碼,完全沒(méi)必要。
//此函數(shù)發(fā)送電子郵件 void sendEmail() { ... } //此函數(shù)發(fā)送電子郵件 public class Employee { ... } / ** * @param title CD的標(biāo)題 * @param作者CD的作者 * @param track CD上的曲目數(shù) * / public void addCd(String title, String author, int tracks) { ... }
多數(shù)情況是強(qiáng)制冗余。很多公司在每個(gè)功能和類(lèi)別上都要求這一點(diǎn)。如果你的上司要求這樣做,請(qǐng)他們不要。
錯(cuò)誤的抽象程度
如果您有一個(gè)很長(zhǎng)的功能或需要記錄代碼的哪一部分做了什么,那么您可能違反了這些規(guī)則:
1.功能應(yīng)該做一件事。
2.功能應(yīng)該很小。
這是一個(gè)例子
//此函數(shù)計(jì)算價(jià)格,與銷(xiāo)售額進(jìn)行比較 //促銷(xiāo),檢查價(jià)格是否有效,然后 //向用戶(hù)發(fā)送促銷(xiāo)電子郵件 public void doSomeThings(){ //計(jì)算價(jià)格 ... ... ... //將計(jì)算出的價(jià)格與促銷(xiāo)活動(dòng)進(jìn) ... ... ... //檢查計(jì)算的價(jià)格是否有效 ... ... ... //向用戶(hù)發(fā)送促銷(xiāo)信息 ... ... ... }
當(dāng)你成功地將邏輯的每個(gè)部分封裝到一個(gè)單獨(dú)的函數(shù)中時(shí),代碼不需要注釋就會(huì)表現(xiàn)的應(yīng)該像它的作用描述一樣。
重構(gòu)如下:
public void sendPromotionEmailToUsers(){ calculatePrices(); compareCalculatedPricesWithSalesPromotions(); checkIfCalculatedPricesAreValid(); sendPromotionEmail(); }
而不是注釋代碼的每個(gè)部分,每個(gè)邏輯塊應(yīng)該很好地封裝在它自己的函數(shù)中。
首先,這提高了可讀性。每個(gè)代碼塊不必逐行讀取。我們可以簡(jiǎn)單地讀取輔助函數(shù)名稱(chēng)并理解它的作用。如果我們想要了解每個(gè)函數(shù)內(nèi)部的更多細(xì)節(jié),就能去看具體實(shí)現(xiàn)。
其次,它提高了可測(cè)試性。在上面的示例中,我們可以為每個(gè)函數(shù)單獨(dú)進(jìn)行單元測(cè)試。如果不封裝這些單獨(dú)的函數(shù),則很難測(cè)試較大函數(shù)sendPromotionEmailToUsers()的每個(gè)部分。執(zhí)行多個(gè)功能的功能很難測(cè)試。
最后,它提高了可重構(gòu)性。通過(guò)將邏輯的每個(gè)部分封裝到自己的函數(shù)中,將來(lái)更改維護(hù)更容易,并且單獨(dú)功能的函數(shù)會(huì)被隔離以?xún)H更改該函數(shù)的行為。當(dāng)我們使用局部變量的長(zhǎng)函數(shù)在整個(gè)函數(shù)中持續(xù)存在時(shí),由于函數(shù)的緊耦合,很難在不導(dǎo)致其他地方變化的情況下重構(gòu)函數(shù)。
注釋掉的代碼
注釋掉的代碼應(yīng)該被視為roadkill。不要看它,不要聞它,不要問(wèn)它從哪里來(lái),只是擺脫它。保持它的時(shí)間越長(zhǎng),其余代碼聞到的時(shí)間就越長(zhǎng)......
/ * public void oldFunction(){ noOneRemembersWhyIAmHere(); tryToUnCommentMe(); iWillProbablyCauseABuildFailure(); HAHAHA(); } * /
盡管刪你不刪別人更不敢刪。如果你以后需要它,你可以隨時(shí)檢查版本控制系統(tǒng),因?yàn)槟憧隙ㄓ昧薞CS,對(duì)嗎?(如果不是當(dāng)我沒(méi)說(shuō))
TODO注釋
不要寫(xiě)TODO注釋?zhuān)粌H僅是......做到了嗎?大多數(shù)時(shí)候這些注釋都會(huì)被遺忘,后來(lái)可能變得無(wú)關(guān)或錯(cuò)誤。當(dāng)另一個(gè)程序員稍后看到TODO注釋時(shí),他們?nèi)绾沃朗欠襁€需要這樣做?
不過(guò)偶爾TODO注釋是好的,如果你正在等待另一個(gè)隊(duì)友的合并(一般不會(huì)太久)。就可以這么做,直到你可以進(jìn)行修復(fù)并提交它。
“當(dāng)你覺(jué)得有必要寫(xiě)注釋時(shí),首先要嘗試重構(gòu)代碼,以便任何注釋都變得多余?!?- Martin Fowler
注釋的謊言
當(dāng)Jimmy在他寫(xiě)的新功能上面打上注釋時(shí),他認(rèn)為他正在幫助任何看到他的代碼的未來(lái)開(kāi)發(fā)人員。其實(shí)呢他真正在做的是設(shè)置一個(gè)陷阱。他的注釋可能是彌天大謊(沒(méi)有雙關(guān)語(yǔ)意圖)蟄伏數(shù)月或數(shù)年沒(méi)有被觸及,只是等待成為一個(gè)令人討厭的陷阱。然后有一天,在數(shù)百個(gè)重構(gòu)和需求變更之一中,他的注釋從一些遙遠(yuǎn)的模塊中失效,但是仍然在錯(cuò)誤的引導(dǎo)著無(wú)數(shù)的接盤(pán)俠。
當(dāng)你更改一行代碼時(shí),你怎么知道你更改的代碼會(huì)不會(huì)使其他地方的注釋無(wú)效?沒(méi)有辦法知道
public class User { ... //它包含用戶(hù)的名字和姓氏 String name; ... }
然后,需求更改,他們希望將名稱(chēng)拆分為firstName和lastName。
public class User { ... // 它包含用戶(hù)的名字和姓氏 String firstName; String lastName; ... }
注釋現(xiàn)在已經(jīng)錯(cuò)了。你可以更新注釋以反映更改,但是你是否真的想在每次更改后手動(dòng)維護(hù)所有注釋?zhuān)磕闶情_(kāi)發(fā)人員,而不是文檔。
但是這個(gè)注釋很容易被注意到并且沒(méi)有問(wèn)題需要改變。但是你很難保證在程序的其他地方,會(huì)不會(huì)也注釋了這個(gè)參數(shù)name是用戶(hù)的名字和姓氏。更改一小塊地方的代碼,可能會(huì)讓很多的代碼注釋都失效。
讓我們看另一個(gè)例子:
//根據(jù)狀態(tài)處理員工 void processEmployees(){ ... List < Employee > employees = findEmployees(statusList); ... } //這會(huì)按狀態(tài)列表查找Employees List < Employee > findEmployees(List < String > statusList){ ... }
然后有人被要求更改函數(shù)findEmployees,以便通過(guò)名稱(chēng)列表而不是狀態(tài)列表查找員工。
//根據(jù)狀態(tài)處理員工 void processEmployees(){ ... List < Employee > employees = findEmployees(statusList); ... } //這會(huì)按狀態(tài)列表查找Employees List < Employee > findEmployees(List < String > nameList){ ... }
首先,上面的注釋findEmployees已經(jīng)失效,因此需要更改。沒(méi)問(wèn)題,對(duì)吧?錯(cuò)了。
processEmployees上面的注釋也已失效,因此也需要更改。還有多少其他評(píng)論被這個(gè)小型重構(gòu)改成無(wú)效?這一次更改在源代碼中創(chuàng)建了多少注釋謊言?
替代方案:
void processEmployees(){ ... List < Employee > employees = findEmployeesByName(nameList); ... } List < Employee > findEmployeesByName(List < Name > nameList){ ... }
如果你準(zhǔn)確而準(zhǔn)確地命名你的函數(shù),則不需要注釋?zhuān)⑶夷悴粫?huì)在代碼中散布謊言。
“代碼永遠(yuǎn)不會(huì)說(shuō)謊,注釋會(huì)?!?- 羅恩杰弗里斯
什么時(shí)候需要注釋呢
我知道很多開(kāi)發(fā)人員都是代碼注釋的死硬支持者,對(duì)他們來(lái)說(shuō),我必須承認(rèn)有時(shí)注釋是可以的。不過(guò)你每寫(xiě)一段都應(yīng)當(dāng)有充足的理由
復(fù)雜表達(dá)式
如果您有復(fù)雜的SQL或正則表達(dá)式語(yǔ)句,請(qǐng)繼續(xù)編寫(xiě)注釋。在代碼中干凈利落地表達(dá)諸如此類(lèi)的陳述可能很困難。在這些表達(dá)式上面添加注釋可以幫助其他開(kāi)發(fā)人員更好地理解您的代碼。
// 格式匹配kk:mm:ss EEE,MMM dd,yyy Pattern timePattern = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w*, \\d*, \\d*");
注釋警告
如果你需要警告其他開(kāi)發(fā)人員這段代碼可能發(fā)生的bug,可以在此代碼附近留下注釋。這些注釋可以充當(dāng)代碼中神秘行為的先兆,并為你的代碼增加價(jià)值。
意圖澄清
如果你實(shí)在命名廢,那就要為你沒(méi)有能力寫(xiě)出富有表現(xiàn)力的代碼而負(fù)責(zé),并寫(xiě)下注釋表明自己的意圖。
如果你必須撰寫(xiě)注釋?zhuān)?qǐng)確保它是本地的。遠(yuǎn)離其引用的非本地評(píng)論注定會(huì)失效并變成謊言。引用函數(shù)或變量的注釋?xiě)?yīng)直接位于其上方。警告注釋可以在它引用的代碼的上方或旁邊。如果您的IDE支持注釋突出顯示,請(qǐng)使您的警告注釋從其余代碼中脫穎而出。
最后
我已經(jīng)建立了對(duì)代碼注釋的感受。我鄙視他們,但我知道有時(shí)他們是需要的。
所以,請(qǐng)停止寫(xiě)這么多注釋。
本文是作者在推特上看到國(guó)外一位大神 布萊恩·諾蘭德 的論述,深以為然因此翻譯后加以修飾進(jìn)行分享的。希望今后自己的代碼也能像散文一樣優(yōu)雅。
相關(guān)文章
BMIDE環(huán)境導(dǎo)入項(xiàng)目報(bào)編碼錯(cuò)誤解決方案
這篇文章主要介紹了BMIDE環(huán)境導(dǎo)入項(xiàng)目報(bào)編碼錯(cuò)誤解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10springBoot項(xiàng)目如何實(shí)現(xiàn)啟動(dòng)多個(gè)實(shí)例
這篇文章主要介紹了springBoot項(xiàng)目如何實(shí)現(xiàn)啟動(dòng)多個(gè)實(shí)例的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringBoot 自動(dòng)掃描第三方包及spring.factories失效的問(wèn)題解決
這篇文章主要介紹了SpringBoot 自動(dòng)掃描第三方包及spring.factories失效的問(wèn)題,本文給大家分享最新解決方法,需要的朋友可以參考下2023-05-05springboot項(xiàng)目實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能
這篇文章主要介紹了springboot項(xiàng)目實(shí)現(xiàn)斷點(diǎn)續(xù)傳,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08SpringBoot實(shí)現(xiàn)的Mongodb管理工具使用解析
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)的Mongodb管理工具使用解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09SpringBoot中添加監(jiān)聽(tīng)器及創(chuàng)建線(xiàn)程的代碼示例
這篇文章主要介紹了SpringBoot中如何添加監(jiān)聽(tīng)器及創(chuàng)建線(xiàn)程,文中有詳細(xì)的代碼示例,具有一定的參考價(jià)值,需要的朋友可以參考下2023-06-06Java BufferedReader相關(guān)源碼實(shí)例分析
這篇文章主要介紹了Java BufferedReader相關(guān)源碼實(shí)例分析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10Maven編譯錯(cuò)誤:程序包c(diǎn)om.sun.*包不存在的三種解決方案
J2SE中的類(lèi)大致可以劃分為以下的各個(gè)包:java.*,javax.*,org.*,sun.*,本文文章主要介紹了maven編譯錯(cuò)誤:程序包c(diǎn)om.sun.xml.internal.ws.spi不存在的解決方案,感興趣的可以了解一下2024-02-02