什麼程式碼會遭人恥笑,什麼妹子會受人喜歡?從妹子角度戲說程式碼優劣。

  • 2019 年 10 月 8 日
  • 筆記

今天給大家分享一點關於變數和函數方面的內容。

在日常生活中,丑姑娘和好姑娘一眼就能識別;在程式碼中,好程式碼與壞程式碼卻不容易覺察,這裡面有標準,但每個程式設計師都覺得自己創造的程式碼好。了解這些標準,可以有效避免寫出壞程式碼。

目錄

好的標準是什麼

– 可讀性 = 清晰

– 復用性 = 沒脾氣

– 擴展性 = 有所為、有所不為

程式碼實例講解

– 變數

– 函數

好的標準是什麼

在人類中,好妹子一般都具有什麼特徵呢?

  1. 皮膚白皙,五官端正,有立體感
  2. 脾氣好,好說話,易於相處
  3. 願意嘗試新鮮事物,並願意把快樂分享給我們

這三點標準,在程式碼中,對應的標準是:

  1. 可讀性
  2. 復用性
  3. 擴展性

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