作為一個后端工程師,想必在職業生涯中都寫過一些不好維護的代碼。本文是我學習《代碼之丑》的學習總結,今天第一天發車,先來看看在命名上的一些常犯的壞味道。
0 為何要品代碼壞味道
Martin Flower在《重構》一書中給不好維護的這一類代碼取了一個藝名:代碼的壞味道,而這些壞味道一旦堆積多了,整個系統雖然還是可以平穩運行,但是就是讓程序員不敢動,更別提重構了。
因為......
哎,都是打工人,誰容易啊?

1 不精准的命名
直接來看一段壞味道代碼:
public void ProcessChapter(long chapterId) { Chapter chapter = _repository.GetChapterById(chapterId); if (chapter == null) { throw new ArgumentException($"Unknown chapter [{chapterId}]"); } chatper.TransactionState == TransactionState.TRANSLATING; _repository.UpdateChapter(chapter); }
此段代碼存在的問題:方法名ProcessChapter命名過於寬泛,不能精准描述意圖,是代碼難以理解的根源所在。
同類型的詞匯請各位看官自查,例如:data,info,flag,process,handle,build,maintain,manage,modify等等等,它們應該都出現在你我的項目代碼中過。
重構方法:命名需要能夠描述出這段代碼在做的事情,根據代碼的邏輯含義,調整其命名為 ChangeChapterToTranslating,是不是無論誰來接手這個方法的代碼就都好理解了?
public void ChangeChapterToTranslating(long chapterId) { ...... }
一個好的名字應該是描述意圖,而非細節的。那么,我們再重構一下,結合具體的業務,將其命名改為 StartTranslation,即開始翻譯,是不是就更能體現業務含義了?
public void StartTranslation(long chapterId) { ...... }
2 用技術術語命名
這是一個我也經常犯的壞味道:
List<Book> bookList = service.GetBooks();
這個bookList的原因是因為它的類型是List,還有xxxDict,xxxMap等,它不費腦子,但它卻只是一種基於實現細節的命名方式。
重構方法:使用面向意圖的名字。
List<Book> books = service.GetBooks();
又如,如果在業務代碼中出現了Redis這類的中間件:
public Book GetByIsbn(string isbn) { Book cachedBook = redisBookStore.Get(isbn); if (cachedBook != null) { return cachedBook; } Book book = service.GetByIsbn(isbn); redisBookStore.Put(isbn, book); return book; }
通常來說,這里只是需要一個緩存,那么Redis也就只是這個緩存的一個具體實現,如果換為其他的NoSQL存儲如Memcached呢?
因此,我們其實缺少了一個模型,在這里其實就是一個接口,假設這個接口如下:
public interface ICacheClient { object Get(string key); void Put(string key, object value); }
使用接口替代之后,我們就是面向接口編程了:
public Book GetByIsbn(string isbn) { Book cachedBook = ICacheClient.Get(isbn); if (cachedBook != null) { return cachedBook; } Book book = service.GetByIsbn(isbn); ICacheClient.Put(isbn, book); return book; }
技術人喜歡用技術名詞命名,因為這是大家習慣的語言。但是,對於業務項目而言,需要盡可能將技術術語隔離開,因為業務語言才是最好的命名方式。
DDD領域驅動設計方法就號召我們建立統一語言,這個統一語言是可以和業務部門的人員無障礙交流的語言,換句話說,也就是業務語言。另一方面來看,這就倒逼技術團隊需要建立團隊的業務詞匯表,讓團隊成員達成統一共識,這也是要統一共識需要付出的額外成本,但是這個成本是有價值的。

編寫可維護的代碼之路
比如,我們經常會寫下面的參數命名:
public void ApproveChapter(long chapterId, long userId) { ... }
假設通過業務分析,這里的user其實是審核人,而我們為了方便就命名為userId了。那么,將其改為reviewerUserId是不是更加貼近業務?
public void ApproveChapter(long chapterId, long reviewerUserId) { ... }
在實際開發中,這樣子的例子還有很多。
3 亂用英語來命名
作為一個受了多年義務教育與高等教育的我們來說,English一點也不陌生,但是就是始終感覺用不到位。
如果能用中文來命名,我們是不會到處查單詞用英文來命名的,但是,用中文,沒有B格啊,我放棄!

違反語法規則的命名
類是一個名字,表示一個對象。而方法名一般是一個動詞或動賓短語,表示一個動作。但是,有時候,我們喜歡忽視這個規則。
public void CompletedTranslate(List<string> chapterIds) { List<Chapter> chapters = _repository.GetByChapterIds(chapterIds); chapters.ForEach(chapter => chapter.TranslationState = TranslationState.TRANSLATED); _repository.SaveChapters(chapters); }
CompletedTranslate 是個啥(四不像結構)?改為 CompleteTranslation 是不是好一點(動賓結構)?
public void CompleteTranslation(List<string> chapterIds) { ...... }
又比如,一個方法名叫 ReTranslation(額,Translation好像是個名詞吧),意為重新翻譯,但作為方法名,應該選擇動詞,改為 ReTranslate 會更好(Translate是個動詞,棒!)。
不准確的英語詞匯
由於英文單詞可以有多個含義,且在不同場景下含義也可以不同,這就難為我們中國人了。
比如,下面的一個枚舉,想表達的是審核狀態:
public enum ChapterAuditStatus { PENDING, APPROVED, REJECTED }
但是,Audit這個詞雖然也有審核的含義,但是它更偏重於財務審計這塊,而這里的業務是文稿的審核。

因此,改為Review可能會更加貼近一些:
public enum ChapterReviewStatus { ...... }
從上也可以看出,技術團隊建立一個統一的業務詞匯表,很有必要!這是集體的智慧,而非個體的。一個人的英語可能不太好,但是一群人在一起,總有那么一個會找出合適的說法。
英語單詞拼寫錯誤
這個,我相信大家各自項目中都有不少拼錯的案例,很多人查了單詞之后,都還是拼錯...
但是,往往很多時候就是差了或者多了那么一個字母,就會讓人懵逼了。

4 小結
本文總結了命名相關的兩類壞味道,一是命名是否具有業務含義,二是命名是否符合英語語法。
最后,感謝鄭曄老師的這門《代碼之丑》課程,讓我受益匪淺!我也誠心把它推薦給關注Edison博客的各位童鞋!
參考資料
鄭曄,《代碼之丑》(推薦訂閱學習)
Martin Flower著,熊傑譯,《重構:改善既有代碼的設計》(推薦至少學習第三章)
👇掃碼訂閱《代碼之丑》


