可讀代碼編寫炸雞一 - 命名

爲啥寫這個炸雞

我一開始實際上是想寫設計模式,寫了必定的積累。雖然我也想寫比較高端的算法,數據結構,甚至 AI 的東西。可是很無奈,如今能力不足沒法下筆。算法

可是在寫代碼的過程當中,我逐漸發現一個問題,不只是在學習仍是工做上。設計模式

包括我在內,許多人的代碼可讀性其實一塌糊塗。先不從代碼組織,設計模式這些較大的方面來講。光是一個變量,一個函數的命名,註釋的規範都沒有提供幫助理解 的做用,讓人看的一頭霧水。服務器

起碼我看我本身的代碼就是這個感受,幾個月後,就不認識了。數據結構

代碼的編寫規範,是不多人去注意的,這段時間,個人主程讓我看一本書 ——《編寫可讀代碼的藝術》,正好直擊痛點,因而我打算寫一寫關於這個方面的東西。函數

但願經過這個系列的炸雞,能讓我和各位在代碼可讀性上,有所進步。學習

前提準備

在閱讀本篇前,先得了解一些原則。ui

基本可讀性定理

代碼應該寫得讓人在最短期內理解。理解的含義包括,徹底理解,而且能定位問題,修改,還有能看出這段代碼和其餘代碼的聯繫。

對本身的要求

多問問本身,這段代碼真的容易理解嗎?想象另一我的在看本身的代碼,或者有一個與你志同道合的人加入了你的項目組,哪怕只有一我的。他接手閱讀這些代碼的時候,是緊鎖眉頭,仍是眉頭舒展。編碼


好了,咱們開始吧。lua

命名須要增長更多的信息

咱們先從最容易更改的方面來講,就是命名。通常來講,不少人寫程序的時候對於命名是比較苦惱的,寫程序一半的時間在思考命名。或者有時候,圖方便,將命名寫的很隨意。spa

因爲這個代碼是本身寫的,一兩天內,瀏覽下來仍然清楚這些命名下的變量,函數等,它們的做用於意義。

可是再過些時日再看,你本身都會摸不着頭腦了,這樣代碼的可讀性是不好的。

因此,命名應該表達更多的信息

用意思更明確的詞語

爲了命名能表達更多的信息,命名的用詞須要更加具體,咱們應該用意思更明確的詞語去作命名。

咱們看一下以下代碼,這是一個描述文本文件的類。

class Text
{
public:
    Text();
    ~Text();
    ...
    int size();
};

size 的使用可謂是一個重災區,乍一看沒什麼問題。可是這個 size 有一些問題:

  1. size 多爲屬性命名,以致於這是函數調用仍是公共屬性,會使閱讀者感到困惑。
  2. size 意義不明朗。這是表明文件的大小,仍是文件內容的長度,或者說,單詞數?
  3. size 若是是函數,返回的是當前文件大小,仍是文件限定大小,是文件內容的當前大小,仍是……。

看到了吧,一個意味不明的命名能夠帶來多少閱讀理解上的麻煩。

若是咱們使用 getCurrentBytes(),就能大機率理解爲:

得到此文件的大小的函數

若是咱們使用 countWordNum(),就能大機率理解爲:

計算得到文件內容的單詞數量的函數

因此,命名的時候選詞要選擇意義明確,能準確表達做用和目的的詞。

豐富的詞語

爲了能表達更明確的意思,那麼咱們的英語詞彙量實際上是要足夠的。

這就須要咱們多多使用翻譯軟件,多多積累詞彙量了

在書中提供了一個示例,我將其列出。

通常用詞

更加豐富多義的詞語

send

deliver, dispatch, announce, distribute, route

find

search, extract, locate, recover

start

launch, create, begin, open

make

create, set up, build, generate, compose, add, new

避免不明確、寬泛的用詞

從上文咱們知道,命名要更加具體,選用表達更加準確的詞語。

因此,咱們不只要會選擇,還要回規避。

規避一些意義不明確的詞語

注意,這個意義不明確,是針對須要表達許多信息這種狀況來講的。

用兩個重災區舉例

1. 返回值。

咱們來看這段代碼。

local ret = Calculator:calculate(1, 2)

這個 ret,咱們都能意識到他是一個返回值。可是這樣表達的信息實在太少,從而致使意義不明確。

若是我用的是 iret,這就多了一個數據類型的信息,告訴閱讀者:

這個是一個整型的返回值。

若是使用 multiplyRet,這就將 calculate 的邏輯大體歸納,告訴閱讀者:

使用乘法運算獲得結果。
2. tmp

同樣的,來看一段代碼。

tmp = ""
tmp += 'name:'
tmp += '\t{}\n'.format('dogge')
tmp += 'age:'
tmp += '\t{}\n'.format(122)
tmp += 'email:'
tmp += '\t{}\n'.format('545749402@qq.com')
tmp += 'address:'
tmp += '\t{}\n'.format('地球')

很明顯,tmp 只表達了本身字符串拼接的暫存變量。可是從結果來看,它更適合取名爲userInfo。閱讀者看到這個命名,不只瞭解了更多的信息,還不用閱讀這麼多行才能分析出這個變量的意義。

可是如同以前說的,若是隻須要表達單一的意思,例如 tmp 只用於暫存變量。那麼,tmp 這個命名是很合適的。

循環迭代的不明確

循環迭代的變量,最經典的命名就是i, j,k

咱們來看一下以下代碼,代碼功能是去遍歷每一個年級的每一個班級的每一個同窗的名字。

for (int i = 0; i < grades.size(); i++)
    for (int j = 0; j < grades[i].class.size(); j++)
        for (int k = 0; k < classMember.size(); k++)
            cout << classMember[k].name << endl;

能夠看到,循環邏輯若是複雜,i, j, k 在不一樣的嵌套中互相使用對方,錯綜複雜。編寫這段代碼的人都要當心 i,j,k 是否使用出錯,閱讀者的閱讀難度也加大。

因此,循環變量不要寬泛不明確,也要加入更多的信息

i, j, k 修改成 grade_i, class_j, member_k。修改代碼以下:

for (int grade_i = 0; grade_i < grades.size(); grade_i++)
    for (int class_j = 0; class_j < grades[grade_i].class.size(); class_j++)
        for (int member_k = 0; member_k < classMember.size(); member_k++)
            cout << classMember[member_k].name << endl;

這樣方便閱讀者的理解,不用花精力去記住 i, j, k 對應的數據,同時定位變量使用的錯誤 grades[class_j] 也更方便。

準確 > 寬泛

這樣的狀況一般發生在函數命名。有時候咱們的函數命名偏向作歸納,使得命名過於寬泛。

以下代碼就是一個例子,編寫者想的是,服務器是否準備就緒,只須要檢查一下端口是否被佔用便可。

bool serverIsReady()
{
    // 檢查端口是否被佔用
    ...
}

調用這個函數時返回 true,但因爲 IP 問題,服務器啓動失敗。閱讀者看着這個命名,就會疑惑,不是服務器準備就緒了嗎?

if (serverIsReady())
{
    launchServer(); // 失敗
}

這就是過於命名寬泛的結果,咱們把函數名修改成 canListenPort,就準確地描述了函數功能。閱讀者就知道,這只是檢查端口占用,IP 等其餘環境沒有作檢查。也方便了閱讀者作必定的封裝和修改。

命名能夠加入什麼信息

上頭一直在講,命名要加入更多信息,怎麼加入更多的信息

因此如今講講能夠加入什麼信息

單位

說實話,看到這個的時候,我有一種恍然大悟的感受。先前寫一些與時間相關的邏輯,都會疑惑。

local starttime = os.clock()

...

local elapse = os.clock() 
print(elapse) -- 1

這算出來,究竟是 1 ms, 1 us, 1 s ?固然,你能夠說我對 lua 的 os.clock 不熟悉。那麼若是是編寫者本身封裝的函數呢。

local starttime_ms = myClock()

...

local elapse_ms = myClock() - starttime_ms

因此相似的,測量性質的變量,最好都要加上單位。

如下是書中的實例,我截取出來。

沒有使用單位

使用單位

Start(int delay )

delay → delay_secs

CreateCache(int size )

size → size_mb

ThrottleDownload(float limit)

limit → max_kbps

Rotate(float angle)

angle → degrees_cw

重要的屬性

什麼是重要的屬性?就是必定要閱讀者知道的重要信息。例如,你的邏輯中,須要一個字符串。可是這個字符串內容是 十六進制 字符串。

若是光光一個 str,是不能表達這個信息的。

local str

因此咱們能夠修改爲:

local hex_str

再考慮一個場景,後臺對接收到的密碼進行處理。可是要求入庫編碼要求是 utf8,而且是明文密碼。一個 password,明顯不能表達更多信息。

那咱們試試這個:

local plaintext_utf8_password

總結

本次炸雞主要是針對命名信息過於匱乏的問題,從兩個方面來闡述了供參考的解決方案。

  1. 命名如何加入更多的信息。
  2. 命名加入什麼信息。

從 1 來講,命名須要表達具體。因此 1 展開分爲兩個部分。

  1. 因爲命名須要表達具體,那麼用詞須要準確和具體地描述變量/函數做用和邏輯。
  2. 既然知道了要具體,那麼就要規避寬泛的用詞。

從 2 展開,主要講了

  1. 測量類型變量,須要加入單位
  2. 變量的重要屬性,須要將這個屬性加入命名之中。

參考資料

《The Art of Readable Code》

最後吟唱

本文使用 mdnice 排版

相關文章
相關標籤/搜索