[譯]軟件的複雜性:命名的藝術

軟件的複雜性:命名的藝術

在計算機科學中只有兩件困難的事情:緩存失效和命名規範。
—— Phil Karltongit

前言

編寫優質代碼自己是一件很困難的事情,爲何這麼說?由於良好的編碼風格是爲了能更好的理解與閱讀。一般咱們會只注重前者,而忽略了後者的重要性。咱們的代碼雖然只編寫一次,可是在閱讀複審時會閱讀許屢次。github

良好的編碼習慣能夠提升咱們的閱讀質量,比寫做自己要輕鬆許多,咱們能夠站在宏觀角度看待問題,遠觀大局,而不失細節。首先咱們須要理解、分析清楚某個問題,而後用特有的,高效的,言簡意賅的方式讓更多人明白。對我來講,應該明確的把軟件工程歸屬到社會科學領域。咱們爲誰編寫代碼,難道不是爲了人類嗎?(感受原文做者裝的有點過)編程

向其餘人傳遞咱們的想法以及編程思想,這就是咱們在編碼時要作的。緩存

命名構造

爲了說明咱們的第一個概念,首先來作一個遊戲,遊戲名爲 「咱們住在哪一個房間?」,以下會爲你提供一張圖片,請你說說看這是什麼房間。網絡

問題 1/3框架

圖片描述

從上面的圖片不難看出,這確定是客廳。基於一件物品,咱們能夠聯想到一個房間的名稱,這很簡單,那麼請看下圖。異步

問題 2/3函數

圖片描述

基於這張圖片,咱們能夠確定的說,這是廁所。
經過上面兩張圖片,不難發現,房間的名稱只是一個標籤屬性,有了這個標籤,甚至咱們不須要看它裏面有什麼東西。這樣咱們即可以創建第一個推論:post

推論1:容器(函數)的名稱應包含其內部全部元素

能夠將這個推論理解爲 鴨子類型。若是有一張牀?那麼它就是臥室。咱們也能夠反過來進行分析。ui

問題:基於一個容器名稱,咱們能夠推斷出它的組成部分。若是咱們以臥室爲例,那麼頗有可能這個房間有一張牀。這樣咱們即可以創建第二個推論:

推論2:根據容器(函數)的名稱推斷其內部組成元素

如今咱們有了兩條推論,據此咱們試着看下面這張圖片。

問題 3/3

圖片描述

好吧,牀和馬桶在同一個房間?根據咱們的推論,如上圖片使咱們很難當即作出判斷,若是依然使用上述兩條推論來給它下定義的話,那麼我會稱它爲:怪物的房間。

這個問題並不在於同一個房間的物品數量上,而是徹底不相關的物品被認做爲具有一樣的標籤屬性。在家中,咱們一般會把有關聯的,意圖以及功能相近的東西放在一塊兒,以避免混淆視聽,因此如今咱們有了第三條推論:

推論3:容器(函數)的明確度與其內部組件的密切程度成正比

這可能比較難理解,因此咱們用下面這一張圖來作說明:

圖片描述

若是容器內部元素屬性關聯性很強,那麼咱們更容易找到一個用來講明它的名字。反之,元素之間的無關性越強,越難以描述說明。屬性維度可能會關係到他們的功能、目的、戰略,類型等等。關於命名標準,須要關聯到元素自身屬性纔有實際意義。跟着個人思路,咱們將很快明白這一點。

在軟件工程方面,這個觀點也一樣適用。例如咱們熟知的 組件函數方法服務應用。羅伯特·德拉奈曾說過:「咱們的理解能力很大程度與咱們的認知相關聯」,那麼在這種技術背景下,咱們的代碼是否可使閱讀者以最簡單的方式感知到業務需求以及相關訴求?

Example 1:HTTP 域與汽車

HTTP 自身是一個域環境,它包含着咱們的網絡請求與響應狀態。若是咱們把一個 Car 的組件放入它的內部,那麼咱們不能再稱它爲 HTTP了,在這種狀況下,它會變得讓人困惑。

public interface WhatIsAGoodNameForThis {
    /* methods for a car */
    public void gas();
    public void brake();
    
    /* methods for an HTTP client */
    public Response makeGetRequest(String param);
}

圖片描述

Example 2:單詞的耦合

有一種常見的命名模式,在名稱時後綴附加上 Builder 或 er 一類的結束詞,例如:SomethingBuilderUserBuilderAccountCreatorUserHelperJobPerformer 等等。

圖片描述

例如上圖中的名字,咱們能夠推斷出三件事情。第一,在類名中使用動詞 Build 意味着它是具有功能性的。第二,它由兩部分組成,一個是 User 用戶,另外一個的 Builder 構造者,這意味着它們之間可能在封裝、維度歸類上存在歧義。第三,Builder 構造者 能夠在類內部訪問 User 用戶 的相關邏輯、數據,由於他們在同一緯度空間內。

這一點與工廠模式很類似,有本身的應用場景,當它在咱們的工程中氾濫使用時,這將會是一個很麻煩的問題。另外,須要提醒你們,在工廠模式中,並不必定須要有一個類,經過一個 createUser 的方法足矣很好的實現工廠模式的功能。

Example 3: 基類

讓咱們先看幾個生活中真實的例子。首先是 i18n Ruby gem(它的類與方法名稱都是很是簡練)。

class Base
    def config
    def translate
    def locale_available?(locale)
    def transliterate
end

這裏,Base 這個命名自己並無傳達太多含義,其中內部結構包含了配置、翻譯,區域設置,音譯。它們能夠看似無關的聚合在一塊兒。

Example 4: 命名與構建

一個合理的命名能夠引導咱們構建出更爲嚴瑾的組件容器。以下例所示。

class PostAlerter
    def notify_post_users
    def notify_group_summary
    def notify_non_pm_users
    def create_notification
    def unread_posts
    def unread_count
    def group_stats
end

PostAlerter 從這個名字自己能夠發現,它意味着在內部會作一些相似提醒通知的功能。然而,其中 unread_postsunread_countgroup_status 並不在這個功能的主要範疇內,從這一點來看,這個類的名稱並非很理想。咱們能夠將這個三個方法移動到一個名爲 PostStatistics 的類中,這樣解耦後,事件功能會變得更加清晰,更可預測。

class PostAlerter
    def notify_post_users
    def notify_group_summary
    def notify_non_pm_users
    def create_notification
end

class PostsStatistics
     def unread_posts
     def unread_count
     def group_stats
end

Example 5: 奇怪的命名

在 Spring 框架中有一些例子,組件作的事情太多,其名稱都很是冗長奇怪。這裏只舉一個例子(由於實在太多了):

class SimpleBeanFactoryAwareAspectInstanceFactory {
    public ClassLoader getAspectClassLoader()
    public Object getAspectInstance()
    public int getOrder() 
    public void setAspectBeanName(String aspectBeanName) 
    public void setBeanFactory(BeanFactory beanFactory)
}

Example 6: 說說好的名稱

咱們聊了許多不太合理的命名,在 D3arc 中就有許多不錯的命名定義,例如:

export default function() {
      /* ... */
      arc.centroid     = function() { /* ... */ }
      arc.innerRadius  = function() { /* ... */ }
      arc.outerRadius  = function() { /* ... */ }
      arc.cornerRadius = function() { /* ... */ }
      arc.padRadius    = function() { /* ... */ }
      arc.startAngle   = function() { /* ... */ }
      arc.endAngle     = function() { /* ... */ }
      arc.padAngle     = function() { /* ... */ }

      return arc;
}

上面這個例子中,每個方法都是徹底有意義的:他們都是以 arc 開頭。而且他命名風格就像繪製下面的圖片同樣簡練,使人歡喜。

圖片描述

方法 1: 拆解

圖片描述

應用場景:當你不能爲類或方法找到一個合適的命名,可是你知道如何拆解它們,而且指望給他們的組合找到一個好的名稱。

主要有兩個步驟:

  1. 分辨出他們之間的特色和概念

  2. 將它們拆分開

在牀和馬桶這種特定耦合的場景下,爲了拆解他們的不一樣之處,咱們將牀移動到左側,將馬桶移動到右側。這樣咱們便將兩個不一樣的事物分離開了。

當你不能爲某個事物找到一個好的名稱時,也許是由於你所面臨的不止一件事物。不過如今咱們已經知道,對多個事物進行命名是一件很是困難的事情,當咱們遇到這類問題時,不妨確認一下構造這個事物的組成部分,以及動做行爲。

事例:

咱們現有一個未命名的類,其中包含了 requestreponseheadersURLsbodycachingtimeout,把全部這些從類中拉取出來,咱們剩下這樣一些組件:RequestResponeHeadersURLsReponseBodyCacheTimeout 等。若是咱們已知這些類的名稱,那麼咱們能夠肯定這個類是用於處理 Web 請求的,HTTPClient 是一個不錯的 Web 請求組件的命名。

當咱們編碼遇到困難時,先不要想着總體,先考慮一下局部細節。

方法 2: 發現新概念

圖片描述

應用場景:當一個類並不簡單或者內容並不相干。

發現新的概念須要大量業務領域的知識,當軟件的命名和業務保持一致時,一個廣泛的語言便創建起來,它容許來自不一樣專業領域的人來使用相同的語言。

方法 3: 分組標準

應用場景:當有一個好的命名,可是他們他們之間並不適合。

組件元素以前能夠經過各類標準進行分組,譬如組件元素的物理性質,經濟性,情感性,社會性以及軟件中最經常使用的功能。

在軟件工程中,咱們傾向於按功能對組件元素進行分組。若是列出你的項目文件,你可能會看到像 controller/models/adapters/templates/ 等等目錄名稱,而後,有些時候,這些名稱組合在一塊兒並必定適合,這也是從新評估模塊,從新定義,規劃命名的時候。

每一個應用程序都有本身不一樣的上下文環境,每一個模塊、每一個類、每一個方法也一樣都有。User 這個詞所表明的含義能夠是操做系統用戶,或是一張數據表,也能夠是一個第三方的服務憑證,不一樣的上下文環境,它所表示的含義不盡相同。

圖片描述

無心義的詞與新詞

多年以來,命名規範的演變上變得更具備意義,有更多的人來填補這個陳舊的空缺。

Helperhelpers 是一個支持應用程序實現的主要方式。應用程序實現與定義的標準是什麼呢?應用程序中的全部內容都應該支持並實現其主要目標。

在實踐中,它們被擊中在一個非天然的分組中,爲一寫其餘經常使用的操做提供可重用性。通常狀況下,helpers 須要另外一個組件元素的內部數據的依賴。這種命名通常會在找不到合適的名稱時折中使用。

Base,許久以前,在 C# 中有須要繼承類的命名方式都是以 Base 命名。例如:汽車和自行車的父類都是 Base 而不是 Vehicle。儘管微軟提出建議去避免這類命名方式,但他依然影響了 Ruby 這門語言,其中最具表明性的是 ActiveRecord 類的繼承。到目前爲止,咱們依然將 Base 看作爲開發人員找不到合適命名的一種替代方式。

變更調整後的 Base 含括了 CommonUtils,例如,JSON Ruby gem 的 Common 類具備 parsegenerateload 以及 jj 等方法,但這裏 Common 真的具有它的含義嗎?

Tasks,在 JavaScript 社區興起了一種經過異步調用函數的方式,這種方式起源於 task.js,即便目前這個開源庫不多再被提使用,可是這個術語流傳了下來。

若是團隊中全部人都能清楚的理解它的含義,那是可喜的。但若是有新人加入團隊,而且他遇到了被拋棄在垃圾堆中的 60 年代便存在的古怪命名,那又怎麼辦呢?

在我以前的項目工做中,曾遇到過這樣的一個類的命名,大家猜猜看,Atlanta,是的,亞特蘭大,操蛋的亞特蘭大。沒人知道或者能夠告訴我爲何要起這麼個名字,以及含義是什麼。

參考資料

Cwalina,Krzysztof.2009,框架設計指南:可重用 .NET 庫的約定、慣用語和模式,第二版。 Boston: Pearson Education, Inc. 206。

Evans, Eric. 2003。域驅動設計:解決軟件核心複雜性。Boston: Addison-Wesley Professional。

原文連接https://medium.com/hacker-dai...

相關文章
相關標籤/搜索