一天一點代碼壞味道(1)


作為一個后端工程師,想必在職業生涯中都寫過一些不好維護的代碼。本文是我學習《代碼之丑》的學習總結,今天第一天發車,先來看看在命名上的一些常犯的壞味道。

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著,熊傑譯,《重構:改善既有代碼的設計》(推薦至少學習第三章)

👇掃碼訂閱《代碼之丑》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM