優(yōu)雅整潔的 Java 代碼命名技巧,風(fēng)之極·凈化
合格的程序員不僅僅是讓代碼跑起來,而是要做到代碼整潔,只滿足為了能讓編譯器通過編譯,機(jī)器能跑就行而寫代碼的程序會算不上開發(fā)者,碼農(nóng)都不算。
好的命名能體現(xiàn)出代碼的特征,含義或者是用途,讓閱讀者可以根據(jù)名稱的含義快速厘清程序的脈絡(luò)。
本篇分享如下代碼命名套路來提高我們代碼命名:
- 勿模糊,準(zhǔn)確達(dá)意
- 避免誤導(dǎo)
- 做有意義的區(qū)分
- 結(jié)合上下文簡化名稱
- 使用可搜索、易讀的名稱
- 包命名規(guī)范
- 類名與方法名規(guī)范
混亂的代價
我相信每個程序員都被某些人的垃圾代碼惡心過,導(dǎo)致開發(fā)進(jìn)度被嚴(yán)重延緩、性能差勁、bug 多。
每次新增和修改代碼如履薄冰,我們只有對那堆腐朽的代碼了然于胸才敢修改。
隨著時間推移,團(tuán)隊生產(chǎn)力下降,所有人都抵觸這個項目,對其束手無策。
新手不熟悉原來的場景和設(shè)計,不知道如何修改才符合實際意圖,導(dǎo)致更容易出現(xiàn)混亂。
最后,開發(fā)團(tuán)隊產(chǎn)生了抵觸心理并造反了,再也無法忍受在這個垃圾代碼基礎(chǔ)上做開發(fā),而管理層不愿意投入資源重新設(shè)計。
一個優(yōu)秀的開發(fā)者應(yīng)該時刻保持代碼整潔,無關(guān) deadline。
為什么會寫出垃圾代碼呢?
有的人可能會說,需求變化違背了最初的設(shè)計、排期太緊沒法干好......
其實,這是一種不專業(yè)的托詞。
推進(jìn)進(jìn)度是產(chǎn)品經(jīng)理他們該干的,雖然癡迷于進(jìn)度,但是多數(shù)產(chǎn)品經(jīng)理也會期望有良好的可拓展代碼以便應(yīng)對市場變換莫測的需求。
連海誓山盟的愛情都會變,又如何做到需求不會改變呢?
所以我們比他們更加重視代碼質(zhì)量,才能應(yīng)對變化的需求。
保護(hù)代碼持續(xù)整潔優(yōu)雅是每個優(yōu)秀開發(fā)者都應(yīng)該遵守的原則。
混亂的代碼只會拖慢未來的開發(fā)進(jìn)度,唯一加快進(jìn)度的方法:始終盡可能保持代碼優(yōu)雅整潔。
好比醫(yī)生在做手術(shù)之前要先消毒,你說消毒太耗時間了,直接拿刀子整吧。
作為專業(yè)的醫(yī)生你會照做么?
作為專業(yè)的程序員,我們要了解代碼變壞的風(fēng)險并堅持保持代碼質(zhì)量。
什么是整潔代碼
代碼質(zhì)量評判需要綜合各種因素得到的,我們并不能從單一的維度去評判。
比如代碼可讀性好,但是空間與時間復(fù)雜度高,這并不能算得上是好代碼。
好的代碼應(yīng)該具備:易拓展和維護(hù)、簡潔(只做好一件事)、可復(fù)用性強(qiáng)(沒有重復(fù)代碼)、能快速寫出單元測試??勺x性強(qiáng)、沒有副作用(做了名稱以外的工作)。
易拓展和維護(hù)
在不破壞原來的代碼設(shè)計下,可以簡單快速的修改和添加代碼實現(xiàn)功能拓展。
簡單地說就是預(yù)留了拓展點,將新代碼放在設(shè)計的可拓展點,不會因為新增一個功能而改動大量原始代碼。
對修改關(guān)閉,對拓展開放,開閉原則。
對于開發(fā)而言,我們維護(hù)舊代碼的時間超過新項目新代碼的時間。
代碼的可維護(hù)性就變得很重要,也就是說代碼分層清晰、模塊劃分精當(dāng),滿足高內(nèi)聚低耦合、抽象出合理的接口,面向接口編程就意味著有較好的可維護(hù)性。
同樣的代碼,熟悉他的資深工程師會覺得很容易維護(hù),而新人因為不熟悉代碼,不懂設(shè)計模式而無法理解。
所以,易拓展具有主觀性,我們需要提高基礎(chǔ)技能才有資格說代碼是否易拓展和維護(hù)。
只做好一件事
單一職責(zé):每個函數(shù)、每個類、每個模塊只專注于一件事。
不要設(shè)計大而全的類或者函數(shù),我們需要將他們拆分成更細(xì)粒度功能更加單一的類。
它不會隱藏設(shè)計者的意圖,干凈利落的抽象和直截了當(dāng)?shù)目刂普Z句。
我們應(yīng)該讓每個函數(shù)每行代碼簡單、邏輯清晰。這樣的話,類依賴和被依賴的類也會變少,減少耦合度。
需要注意的是,也不能拆分太細(xì),否則就會破壞內(nèi)聚性。
高手,就是用最簡單的方法去解決復(fù)雜問題。
沒有重復(fù)代碼
在開發(fā)過程中,我們應(yīng)該盡可能抽象出「變與不變」,復(fù)用已經(jīng)存在的代碼,不要寫重復(fù)的代碼。
比如運用「封裝、繼承、抽象、多態(tài)」特性,代碼封裝成模塊,隱藏變化的細(xì)節(jié),暴露不變的接口。
把業(yè)務(wù)與非業(yè)務(wù)的代碼邏輯分析,抽象成通用的框架、工具類等。
比如應(yīng)用模板方法設(shè)計模式將不變的算法邏輯框架定義出來,把變化的點延遲到子類重寫。
能快速寫成單元測試
代碼的可測試性差,比較難寫單元測試,那基本上就能說明代碼設(shè)計得有問題。
試想下,如果一個類大而全,有一個方法依賴了十幾個外部對象才能完成工作,耦合嚴(yán)重。
當(dāng)你在編寫單元測試的時候,需要 mock 十幾個依賴對象和數(shù)據(jù)。
那說明這個代碼糟透了,需要合理拆分和設(shè)計。
可讀性強(qiáng)
軟件設(shè)計大師 Martin Fowler 說過:「Any fool can write code that a computer can understand. Good programmers write code that humans can understand.」
翻譯成中文就是:"任何二貨都會編寫計算機(jī)能跑的代碼。優(yōu)秀的程序員能夠編寫人能夠理解的代碼。”
而可讀性就會涉及到編碼規(guī)范、命名、注釋、函數(shù)職責(zé)是否單一、長度是否精簡。
有數(shù)據(jù)顯示讀代碼的時間與寫代碼的時間比例超過 10:1,并且編寫當(dāng)前代碼的難度,取決于讀周邊代碼的難度。
所以我認(rèn)為可讀性強(qiáng)是最重要的一點。
高質(zhì)量命名套路
開發(fā)過程后命名隨處可見,我們給變量、方法、參數(shù)、類、包命名。
而命名的好壞會影響我們的可讀性,我們不妨從命名作為切入口來寫好代碼。
勿模糊,準(zhǔn)確達(dá)意
在開發(fā)過程中,一旦發(fā)現(xiàn)更好的名稱,就換掉舊的。
一個變量、方法、或者類的名稱應(yīng)該展示出它該有的功能。根據(jù)名字我們能知道它能做什么事情,如何使用。
如果一個名稱需要大量注釋來補充避免使用者跳坑,那就是糟糕的名字。
- 變量名體現(xiàn)出該字段作用,比如 LocalDate now = LocaDate.now(); now 標(biāo)識當(dāng)前時間。
- 防止出現(xiàn)讓人模糊無法理解,必須還要依據(jù)大量上下文才能理解的代碼。
- 不要使用魔術(shù)。
反例 1 :使用魔數(shù)
- // 從數(shù)據(jù)庫獲取列表
- List<String> buyerList = dao.getList();
- buyerList.forEach(x -> {
- for (int i = 1; i <= 5; i++) {
- processedBuyerList.add(String.format("%s,%s", i, x));
- }
- });
你會疑問,為啥索引是從 1 開始?為啥 <= 5。除此之外, i 與 1 極其相似,難以區(qū)分。
正確的方式應(yīng)該使用實際含義的名字讓人理解這么寫的目的,否則維護(hù)的人將痛苦不堪。
反例 2:使用生僻字,又臭又長
UltimateAssociatedSubjectRunBatchServiceImpl,當(dāng)我們看到這樣的類名,是不是不知道怎么讀,也不知道如何搜索和定位,更不知道到底表達(dá)的意思是什么,可能命這個名字的人還以為準(zhǔn)確表達(dá),其實是“王大媽的裹腳布,又臭又長”。
原本的業(yè)務(wù)含義是:執(zhí)行關(guān)聯(lián)主體任務(wù)相關(guān)業(yè)務(wù)類。
鑒于此,我們第一步要避免使用生僻字,可以命名為LinkSubjectServiceImpl ,清晰簡單的表達(dá)出關(guān)聯(lián)主體的業(yè)務(wù)邏輯都在該類。
不要誤導(dǎo)
盡量不要使用不同之處較小的名稱,這樣讓他人無法一眼區(qū)分兩個名稱是啥意思。
例如:函數(shù) deleteIndex 和函數(shù)deleteIndexEx,這兩個函數(shù)名區(qū)別很小了,加之函數(shù) deleteIndexEx后面Ex還是縮寫,也不知道是什么意思,所以他人只能去看函數(shù)內(nèi)容才能明白兩者的區(qū)別。
- XYZStringHandler與 XYZStringStorage。
- UserController與 UserInfoController。
讓人抓狂,他們到底是一個東西還是不同的?差別在哪?沒有兩年腦血栓寫不出這樣的。
反例 3:名不副實
下面是一個生成文件并提供下載功能的接口。
- public void downloadExcel(HttpServletResponse response) {
- List<File> files = listFile();
- String fileName = System.currentTimeMillis() + ".zip";
- DownloadZip.downLoadFiles(files, filePath);
- DownloadZip.fileDownload(response, filePath, fileName);
- }
我們會疑惑,downLoadFiles 與 fileDownload 到底有啥區(qū)別?為啥要調(diào)用兩次。
這種真的是十年腦血栓才寫得出來。
downLoadFiles 的功能是創(chuàng)建將 files 打包成 zip 文件,而 fileDownload則是把指定的文件輸出給瀏覽器下載。
所以 downLoadFiles 應(yīng)該命名為 createZipFile用于合理區(qū)分避免誤人子弟。
做有意義的區(qū)分
- getActiveOrder();
- getActiveOrderInfo();
- getActiveOrderData();
- getActiveOrders();
上面都是廢話命名,別人你怎么知道到底該調(diào)用那個方法?
哪個表示訂單明細(xì)?還是歷史訂單,還是全部訂單查詢,廢話是另一種沒有意義的區(qū)分。
名稱不同,意思卻無差別。
Order、OrderInfo、OrderData,他們名稱相同 ,意思卻無差別,屬于毫無意義的廢話。如果缺少明確約定,變量moneyAmount就與money沒區(qū)別。
Variable一詞永遠(yuǎn)不應(yīng)當(dāng)出現(xiàn)在變量名中。Table一詞永遠(yuǎn)不應(yīng)當(dāng)出現(xiàn)在表名中。
結(jié)合上下文簡化名稱
- public class Order {
- private String orderNum;
- private String orderCreateTime;
- //...
- }
比如 Order類,在該上下文中,沒必要給每個成員變量重復(fù)添加 order 這個前綴單詞,直接命名為 createTime、num。
因為我們可以借助 Order 這個上下文來獲取信息。
- Order order = new Order();
- order.getCreateTime();
名稱易讀、可搜索
可讀指的是不要使用一些生僻字,難以發(fā)音的單詞。
可搜索是便于利用 IED 的自動補全和搜索功能,能根據(jù)我們的命名規(guī)范快速定位想要找的類或者方法等。
可讀
名稱讀不出來,在討論的時候就好像是一個沙雕。
哎,那個「treeNewBeeAxibaKula」類是什么作用?
聽到這樣的名字尷尬癌都犯了。
使用一些生僻字,猶如「王大媽的裹腳布,又長又臭」,沒有兩年腦血栓寫不出這樣的垃圾代碼。
可搜索
IED 很智能,當(dāng)我們輸入 「Hash」的時候,會列舉出所有 Hash 相關(guān)的類。
命名的時候最好符合項目命名習(xí)慣,列表數(shù)據(jù)查詢大家使用 listXXX,你就不要用 queryXXX,統(tǒng)一命名規(guī)范,很重要。
包命名
包名統(tǒng)一使用小寫,點分隔符之間有且僅有一個自然語義的英文單詞或者多個單詞自然連接到一塊(如 springframework,deepspace 不需要使用任何分割)。
包名的構(gòu)成可以分為以下幾四部分【前綴】 【發(fā)起者名】【項目名】【模塊名】。
以下表格授權(quán)于「Java 填坑筆記」
常見的前綴可以分為以下幾種:
類名
類名使用大駝峰命名形式,應(yīng)該使用名詞或者名詞短語,比如:Customer、Account。
避免使用 Manager、Processor 等動詞。
接口名除了用名詞和名詞短語以外,還可以使用形容詞或形容詞短語,如 Cloneable,Callable 等,表示實現(xiàn)該接口的類有某種功能或能力。
方法名
方法命名一般為動詞或動詞短語,與參數(shù)或參數(shù)名共同組成動賓短語,即動詞 + 名詞。一個好的函數(shù)名一般能通過名字直接獲知該函數(shù)實現(xiàn)什么樣的功能。
布爾返回值的方法
注:Prefix-前綴,Suffix-后綴,Alone-單獨使用
按需執(zhí)行的方法
用來檢查的方法
異步相關(guān)方法
回調(diào)方法
操作對象生命周期的方法
4.7 與集合操作相關(guān)的方法
與數(shù)據(jù)相關(guān)的方法
成對出現(xiàn)的動詞
單詞 | 意義 |
get獲取 | set 設(shè)置 |
add 增加 | remove 刪除 |
create 創(chuàng)建 | destory 移除 |
start 啟動 | stop 停止 |
open 打開 | close 關(guān)閉 |
read 讀取 | write 寫入 |
load 載入 | save 保存 |
create 創(chuàng)建 | destroy 銷毀 |
begin 開始 | end 結(jié)束 |
backup 備份 | restore 恢復(fù) |
import 導(dǎo)入 | export 導(dǎo)出 |
split 分割 | merge 合并 |
inject 注入 | extract 提取 |
attach 附著 | detach 脫離 |
bind 綁定 | separate 分離 |
view 查看 | browse 瀏覽 |
edit 編輯 | modify 修改 |
select 選取 | mark 標(biāo)記 |
copy 復(fù)制 | paste 粘貼 |
undo 撤銷 | redo 重做 |
insert 插入 | delete 移除 |
add 加入 | append 添加 |
clean 清理 | clear 清除 |
index 索引 | sort 排序 |
find 查找 | search 搜索 |
increase 增加 | decrease 減少 |
play 播放 | pause 暫停 |
launch 啟動 | run 運行 |
compile 編譯 | execute 執(zhí)行 |
debug 調(diào)試 | trace 跟蹤 |
observe 觀察 | listen 監(jiān)聽 |
build 構(gòu)建 | publish 發(fā)布 |
input 輸入 | output 輸出 |
encode 編碼 | decode 解碼 |
encrypt 加密 | decrypt 解密 |
compress 壓縮 | decompress 解壓縮 |
pack 打包 | unpack 解包 |
parse 解析 | emit 生成 |
connect 連接 | disconnect 斷開 |
send 發(fā)送 | receive 接收 |
download 下載 | upload 上傳 |
refresh 刷新 | synchronize 同步 |
update 更新 | revert 復(fù)原 |
lock 鎖定 | unlock 解鎖 |
check out 簽出 | check in 簽入 |
submit 提交 | commit 交付 |
push 推 | pull 拉 |
expand 展開 | collapse 折疊 |
begin 起始 | end 結(jié)束 |
start 開始 | finish 完成 |
enter 進(jìn)入 | exit 退出 |
abort 放棄 | quit 離開 |
obsolete 廢棄 | depreciate 廢舊 |
collect 收集 | aggregate 聚集 |
總結(jié)
命名目的都是為了讓代碼和工程師進(jìn)行對話,增強(qiáng)代碼的可讀性,可維護(hù)性。優(yōu)秀的代碼往往能夠見名知意。
本文轉(zhuǎn)載自微信公眾號「碼哥字節(jié)」