什麼代碼會遭人恥笑,什麼妹子會受人喜歡?從妹子角度戲說代碼優劣。
- 2019 年 10 月 8 日
- 筆記
今天給大家分享一點關於變量和函數方面的內容。
在日常生活中,丑姑娘和好姑娘一眼就能識別;在代碼中,好代碼與壞代碼卻不容易覺察,這裏面有標準,但每個程序員都覺得自己創造的代碼好。了解這些標準,可以有效避免寫出壞代碼。
目錄
好的標準是什麼
– 可讀性 = 清晰
– 復用性 = 沒脾氣
– 擴展性 = 有所為、有所不為
代碼實例講解
– 變量
– 函數
好的標準是什麼
在人類中,好妹子一般都具有什麼特徵呢?
- 皮膚白皙,五官端正,有立體感
- 脾氣好,好說話,易於相處
- 願意嘗試新鮮事物,並願意把快樂分享給我們
這三點標準,在代碼中,對應的標準是:
- 可讀性
- 復用性
- 擴展性

1,可讀性 = 清晰
現在依次看一下,先看「可讀性」。
可讀性,就是說特徵明顯,淺顯易懂。就像美女,五官端正,稜角分明,身材苗條,舉止有形,遠遠一看就是美女;如果趴近了瞅半天還發現不了美,就不是美女。
拿具體的代碼來講,就是清晰:
- 邏輯清晰
- 代碼清晰
- 注釋清晰等
所有的地方都清晰。
如果拿到一個項目,鑽進去看半天,函數調用是雲里來霧裡去,難覓其規律,也沒有一行半行的注釋,這就是丑代碼,起碼是不漂亮的代碼。
好的代碼,就像美女一樣,只瞥一眼,就被吸引了。寫代碼,就要寫讓人一看就明白的代碼。
2,復用性 = 沒脾氣
再看一下「復用性」。
就像好脾氣的妹子,和什麼樣的人都能聊得來,大家都喜歡她;脾氣不大好的妹子,只能和一部分人聊得來,只願意和一部分人交朋友,為什麼?因為她有個人好惡,不能平等地看待芸芸眾生;脾氣特差的妹子,是不能和任何人交朋友的,像李莫愁李師姐,她逢人就送一枚毒針,尤其是遇到性陸的人,脾氣爆的很,為什麼?因為她覺得人人都是惡的,「男人沒一個是好東西」。心中有偏見,行為才傲慢。
再拿代碼來類比一下吧,好的代碼就是沒脾氣,沒有偏好。因為無偏好,所以適用的場景就多,場景多,復用性就強。舉個具體的例子,微服務能力以 SDK 提供,SDK 是Java 的,就只能由 Java 調用;SDK 是 Go 語言的,就只能由 Golang 調用,而如果以 RESTFul API 的形式提供,只要是能進行 HTTP/HTTPS 協議通訊,甭管是什麼語言,都可以調用。這就是沒脾氣,或者理解為少脾氣。
寫代碼,就要寫沒脾氣的代碼。
3,擴展性 = 有所為、有所不為
最後再看一下「擴展性」。
復用性好的代碼,往往功能是單一的、確定的,其方法往往只干一件事,其模塊往往只集中負責一個業務功能,並且是獨佔負責,不允許其它模塊是攪和。
舉個例子,一個用戶 login 功能,假如已經在一個 user 模塊中提供了,就不要在其它模塊中提供。每個服務對外都是一個資源,資源是唯一的。如果系統中有兩個模塊,一個user,一個auth,都提供了一個login功能,那麼修改 login 的邏輯時,是不是兩個地方都要修改?這就是壞的設計。
好的設計,就是一個模塊集中負責一個功能,一個方法只干一件事。在面向對象中,這叫做「單一職責法則」。
回過頭來,再接着看「擴展性」。因為代碼都是單一職責了,如果想增加職責怎麼辦?
可以添加新的方法;如果是新的功能,可以添加新的模塊。這在軟件設計上,就是擴展性,也叫可擴展性。綜合考慮一下,不讓修改舊方法,而建議添加新方法,這叫做「開放-封閉法則」,也是面向對象中與「單一職責法則」齊名的頂重要的一個設計法則,對修改封閉,對擴展開放。
舉個對比的例子,就像冬天裏美女冷了,要加衣服,裏面穿了毛衣了,此時不要在那個毛衣上打主意,往上綁毛線或打補丁都不可行,這樣是美女也丑了;合理的、簡單的路子就是在外面加件羽絨服。
寫代碼,做設計,就要這樣,有所為,有所不為,有所開放,有所封閉。
代碼實例講解
理解了「可讀性、復用性、擴展性」三個概念,接下來我們看看具體的代碼例子,主要看變量和函數兩部分。
變量
舉 3 個例子
示例 1
// bad code const yyyymmdstr = moment().format('YYYY/MM/DD');
破壞了可讀性
// better code const currentDate = moment().format('YYYY/MM/DD');
示例 2
// bad code const ADDRESS = 'One Infinite Loop, Cupertino 95014'; const CITY_ZIP_CODE_REGEX = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/; saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1], ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);
可讀性差
// better code const ADDRESS = 'One Infinite Loop, Cupertino 95014'; const CITY_ZIP_CODE_REGEX = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/; const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || []; saveCityZipCode(city, zipCode);
在該示例中,好壞代碼的差別在於「ADDRESS.match(CITY_ZIP_CODE_REGEX)」這行代碼的執行結果,沒有析構聲明為臨時常量。
差代碼涉及重複運算,且未做容錯判斷;好代碼這些都做了,並且將中間的計算結果通過析構聲明的語法,聲明為臨時常量,使代碼具有了可描述性。
能夠準確、清晰描述的代碼,就是好代碼。該示例涉及的邏輯簡單,如果一個算法涉及多個算法步驟,前後跨度的上下文執行環境長,在這種情況下,隱含變量多了將非常不利於代碼的理解。
示例 3
// bad code const car = { carMake: 'Honda', carModel: 'Accord', carColor: 'Blue' }; function paintCar(car) { car.carColor = 'Red'; }
破壞了可讀性、簡潔性
// better code const car = { make: 'Honda', model: 'Accord', color: 'Blue' }; function paintCar(car) { car.color = 'Red'; }
可讀性強並不代表信息冗餘,冗餘信息會增加無謂的心智負擔。在該示例中,差代碼car對象中每個字段都有一個car前綴,本身對象的名稱就是car,再加一個car前綴就是畫蛇添足。
先秦宋玉在《登徒子好色賦》中形容鄰家妹子的美,說「增之一分則太長,減之一分則太短」。對比於此處,代碼中描述的對象,信息即不要缺失,也不要冗餘,剛剛好,就是好。
函數
也舉三個例子
示例 1
// bad code function createMenu(...args) { // ... } createMenu( 'Foo','Bar','Baz',true)
壞代碼可讀性差,擴展性也不強
// better code function createMenu({ title, body, buttonText, cancellable }) { // ... } createMenu({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true });
在該示例中,壞代碼定義的是不定參數,傳遞幾個都可以,參數名字和意義也不知道;在好代碼中,參數是一個對象,在形參列表中通過 ES6 的析構語法,變成一個個名稱和意義明確的參數,可讀性強;擴展性也強,在參數對象中增加新字段,不影響舊析構代碼。
例如:
createMenu({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true, count:100 // 此處添加了新的字段 });
原代碼不添加對count字段的析構,也沒事:
function createMenu({ title, body, buttonText, cancellable }) { // ... }
如果用得着,再添加也不遲,這就是具有良好的可擴展性。
示例 2
// bad code const menuConfig = { title: null, body: 'Bar', buttonText: null, cancellable: true }; function createMenu(config) { config.title = config.title || 'Foo'; config.body = config.body || 'Bar'; config.buttonText = config.buttonText || 'Baz'; config.cancellable = config.cancellable !== undefined ? config.cancellable : true; } createMenu(menuConfig);
有損代碼簡潔性
// better code const menuConfig = { title: 'Order', // 'body' 沒有,這個 key 是缺失的,但沒關係,因為有默認值 buttonText: 'Send', cancellable: true }; function createMenu(config) { config = Object.assign({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }, config); // config 就變成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} // ... } createMenu(menuConfig);
在該示例中,壞代碼有點啰嗦;而好代碼通過Object.assign確立了參數對象的默認值,所以可以省去許多短路評價判斷的代碼。
注意,Object.assign是將一個右對象所有可枚舉的屬性,拷貝到左對象中,並返回左對象。語法為: Object.assign(target, …sources);
好的代碼都是簡潔的。為什麼好代碼都能簡潔呢?為什麼有時候我們寫代碼做不到簡潔呢?
每個方法都有它的輸入和輸出。如果輸出只有一個或一種類型,那麼它乾的就是一件事;如果輸出是兩樣東西,它干就不是一件事。對於輸入也是一樣的,如果輸入是確定的,那麼代碼就會很清晰,邏輯就會很簡單;如果輸入是未知的,或是變化的,那麼代碼就需要應對多種複雜的情況。很顯然,給方法的參數設定默認值,可以有效地將輸入簡化,從而增加代碼的可讀性和清晰度。
示例 3
// bad code // 全局變量被一個函數引用 // 現在這個變量從字符串變成了數組,如果有其他的函數引用,會發生無法預見的錯誤。 var name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { name = name.split(' '); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott'];
name作為一個全局變量,直接被 splitIntoFirstAndLastName 函數在內部篡改了,而外界未知。
// better code var name = 'Ryan McDermott'; var newName = splitIntoFirstAndLastName(name) function splitIntoFirstAndLastName(name) { return name.split(' '); } console.log(name); // 'Ryan McDermott'; console.log(newName); // ['Ryan', 'McDermott'];
好代碼沒有直接修改全局變量 name,而是返回一個新值,所以 newName 是另外一種打印結果。壞代碼破壞了擴展性的開放-封閉原則,好代碼就是要有所為,有所不為。
從副作用的角度來講……

了解了概念,我們繼續從副作用的角度講,具有良好復用性的代碼,沒有副作用(或副作用是冪等的)。就像一個易相處的妹子,每次和她相處,都不會留下社交陰影,即副作用;反之,如果每次和妹子相處,她都發些小脾氣,產生一些副作用,也就稱不上易相處了。這種情況在代碼中就是復用性差。
等等,「或副使用是冪等的」如何理解?
就是妹子發完脾氣,趕快和你道謙,給你送小禮物等,迅速消除了不良影響。
–END–
每天學一點編程,講述平頭老師與程序員小明之間的故事。本文屬於《JS Expert 2019》系列中的一篇。本文部分摘自《如何寫出優雅耐看的JavaScript代碼》,原文鏈接為:http://t.cn/AinCoA1C