有讀者看到標題就開始敲鍵盤了,我知道,命名不就是不能用 abc、123 命名,名字要有意義嘛,這有什么好講的?
然而,即便懂得了名字要有意義,很多程式員依然無法逃離命名沼澤,
不精準的命名
什么叫精準?
廢話不多說,CR 一段代碼:
public void processChapter(long chapterId) {
Chapter chapter = this.repository.findByChapterId(chapterId);
if (chapter == null) {
throw new IllegalArgumentException("Unknown chapter [" + chapterId + "]");
t
}
chapter.setTranslationState(TranslationState.TRANSLATING);
this.repository.save(chapter);
}
看上去挺正常,
但我問你,這段代碼在干嘛?你就需要調動全部注意力,去認真閱讀這段代碼,找出其中邏輯,經過閱讀發現,這段代碼做的就是把一個章節的翻譯狀態改成翻譯中,
為什么你需要閱讀這段代碼細節,才知道這段代碼在干嘛?
問題就在函式名,processChapter,這個函式確實是在處理章節,但這個名字太寬泛,如果說“將章節的翻譯狀態改成翻譯中”叫做處理章節,那么:
- “將章節的翻譯狀態改成翻譯完”
- “修改章節內容”
是不是也能叫處理章節?
所以,如果各種場景都能叫處理章節,那么處理章節就是個寬泛名,沒有錯,但不精準!
表面看,這個名字是有含義,但實際上,并不能有效反映這段代碼含義,
如果我在做的是一個資訊處理系統,你根本無法判斷,是一個電商平臺,還是一個圖書管理系統,從溝通的角度看,這就不是一個有效的溝通,要想理解它,你需要消耗大量認知成本,無論是時間,還是精力,
命名過于寬泛,不能精準描述,這是很多代碼在命名上存在的嚴重問題,也是代碼難以理解的根源所在,
或許這么說你的印象還是不深刻,看看下面這些詞是不是經常出現在你的代碼里:data、info、flag、process、handle、build、maintain、manage、modify 等等,這些名字都屬于典型的過寬泛名字,當這些名字出現在你的代碼里,多半是寫代碼的人當時沒有想好用什么名字,就開始寫代碼了,
回到前面那段代碼上,如果它不叫“處理章節”,那應該叫什么?首先,命名要能夠描述出這段代碼在做的事情,這段代碼在做的事情就是“將章節修改為翻譯中”,那是不是它就應該叫 changeChapterToTranlsating呢?
相比于“處理章節”,changeChapterToTranlsating這個名字已經進了一步,然而,它也不算是一個好名字,因為它更多的是在描述這段代碼在做的細節,
之所以要將一段代碼封裝起來,是我們不想知道那么多細節,如果把細節平鋪開來,那本質上和直接閱讀代碼細節差別不大,
所以,一個好的名字應該描述意圖,而非細節,
就這段代碼而言, 我們為什么要把翻譯狀態修改成翻譯中,這一定是有原因,也就是意圖,
我們把翻譯狀態修改成翻譯中,是因為我們在這里開啟了一個翻譯的程序,所以,這段函式應該命名 startTranslation,
public void startTranslation(long chapterId) {
Chapter chapter = this.repository.findByChapterId(chapterId);
if (chapter == null) {
throw new IllegalArgumentException("Unknown chapter [" + chapterId + "]");
t
}
chapter.setTranslationState(TranslationState.TRANSLATING);
this.repository.save(chapter);
}
用技術術語命名
我們再來看一段代碼:
List<Book> bookList = service.getBooks();
常見得不能再常見的代碼,但卻隱藏另外一個典型得不能再典型的問題:用技術術語命名,
這個 bookList 變數之所以叫 bookList,原因就是它宣告的型別是 List,這種命名在代碼中幾乎是隨處可見的,比如 xxxMap、xxxSet,
這是一種不費腦子的命名方式,但這種命名卻會帶來很多問題,因為它是一種基于實作細節的命名方式,
面向介面編程,從另外一個角度理解,就是不要面向實作編程,因為介面是穩定的,而實作易變,雖然在大多數人的理解里,這個原則是針對型別的,但在命名上,我們也應該遵循同樣的原則,為什么?我舉個例子你就知道了,
比如,如果我發現,我現在需要的是一個不重復的作品集合,也就是說,我需要把這個變數的型別從 List 改成 Set,變數型別你一定會改,但變數名你會改嗎?這還真不一定,一旦出現遺忘,就會出現一個奇特的現象,一個叫 bookList 的變數,它的型別是一個 Set,這樣,一個新的混淆產生了,
有什么更好的名字嗎?我們需要一個更面向意圖的名字,其實,我們在這段代碼里真正要表達的是拿到了一堆書,所以,這個名字可以命名成 books,
List books = service.getBooks();
這個名字其實更簡單,但從表意的程度上來說,它卻是一個更有效的名字,
雖然這里我們只是以變數為例說明了以技術術語命名存在的問題,事實上,在實際的代碼中,技術名詞的出現,往往就代表著它缺少了一個應有的模型,
比如,在業務代碼里如果直接出現了 Redis:
public Book getByIsbn(String isbn) {
Book cachedBook = redisBookStore.get(isbn);
if (cachedBook != null) {
return cachedBook;
}
Book book = doGetByIsbn(isbn);
redisBookStore.put(isbn, book);
return book;
}
通常來說,這里真正需要的是一個快取,Redis 是快取這個模型的一個實作:
public Book getByIsbn(String isbn) {
Book cachedBook = cache.get(isbn);
if (cachedBook != null) {
return cachedBook;
}
Book book = doGetByIsbn(isbn);
cache.put(isbn, book);
return book;
}
再進一步,快取這個概念其實也是一個技術術語,從某種意義上說,它也不應該出現在業務代碼,
這方面做得比較好的是 Spring,使用 Spring 框架時,如果需要快取,我們通常是加上一個 Annotation(注解):
@Cacheable("books")
public Book getByIsbn(String isbn) {
...
}
之所以喜歡用技術名詞去命名,一方面是因為,這是習慣的語言,另一方面也是因為學寫代碼,很大程度上是參考別人代碼,而行業里面優秀的代碼常常是一些開源專案,而這些開源專案往往是技術類專案,在一個技術類的專案中,這些技術術語其實就是它的業務語言,但對于業務專案,這個說法就必須重新審視了,
如果這個部分的代碼確實就是處理一些技術,使用技術術語無可厚非,但如果是在處理業務,就要盡可能把技術術語隔離開來,
- xxxMap這種命名表示映射關系,比如:書id與書的映射關系,不能命名為bookIdMap么?
Map 表示的是一個資料結構,而映射關系我會寫成 Mapping
用業務語言寫代碼
無論是不精準的命名也好,技術名詞也罷,歸根結底,體現的是同一個問題:對業務理解不到位,
撰寫可維護的代碼要使用業務語言,怎么才知道自己的命名是否用的是業務語言呢?
把這個詞講給產品經理,看他知不知道是怎么回事,
從團隊的角度看,讓每個人根據自己的理解來命名,確實就有可能出現千奇百怪的名字,所以,一個良好的團隊實踐是,建立團隊的詞匯表,讓團隊成員有資訊可以參考,
團隊對于業務有了共同理解,我們也許就可以發現一些更高級的壞味道,比如說下面這個函式宣告:
public void approveChapter(long chapterId, long userId) {
...
}
確認章節內容審核通過,這里有一個問題,chapterId 是審核章節的 ID,這個沒問題,但 userId 是什么呢?了解了一下背景,我們才知道,之所以這里要有一個 userId,是因為這里需要記錄一下審核人的資訊,這個 userId 就是審核人的 userId,
你看,通過業務的分析,我們會發現,這個 userId 并不是一個好的命名,因為它還需要更多的解釋,更好的命名是 reviewerUserId,之所以起這個名字,因為這個用戶在這個場景下扮演的角色是審核人(Reviewer),
public void approveChapter(long chapterId, long reviewerUserId) {
...
}
這個壞味道也是一種不精準的命名,但它不是那種一眼可見的壞味道,而是需要在業務層面上再進行討論,所以,它是一種更高級的壞味道,
能夠意識到自己的命名有問題,是程式員進階的第一步,
@GetMapping("getTotalSettlementInfoByYear")
@ApiOperation("公司結算資訊按年求和")
public Result<List<RepMonthCompanyDTO>> getTotalSettlementInfoByYear(@RequestParam String year) {
List<RepMonthCompanyDTO> list = repMonthCompanyService.getTotalSettlementInfoByYear(year);
return new Result<List<RepMonthCompanyDTO>>().ok(list);
}
名字長不是問題,問題是表達是否清晰,像repMonthCompanyService這個名字,是不太容易一眼看出來含義的,
另外,傳給 service 的引數是一個字串,這個從邏輯上是有問題的,沒有進行引數的校驗,后面的內容也會講到,這個做法是一種缺乏封裝的表現,
變數名是 list,按照這一講的說法是用技術術語在命名,
再有,這個 URI 是 getTotalSettlementInfoByYear,這是不符合 REST 的命名規范的,比如,動詞不應該出現在 URI 里,分詞應該是“-”,byYear 實際上是一個過濾條件等等,
總結
兩個典型的命名壞味道:
不精準的命名;
用技術術語命名,
命名是軟體開發中兩件難事之一(另一個難事是快取失效),不好的命名本質上是增加我們的認知成本,同樣也增加了后來人(包括我們自己)維護代碼的成本,
- 好的命名要體現出這段代碼在做的事情,而無需展開代碼了解其中的細節
- 再進一步,好的命名要準確地體現意圖,而不是實作細節
- 更高的要求是,用業務語言寫代碼
好的命名,是體現業務含義的命名,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/302000.html
標籤:其他
上一篇:Java學習 -- 多型性
