在開發的過程中,總結了一些最佳化開發的編碼經驗,當然這些經驗都是前人總結出來的,這次就特別拿出來跟大家一起分享,當然這些經驗不一定是最佳實踐,各位讀者有興趣或者有不同意見的可以跟一起探討一下。
拒絕魔法眾所周知,魔法是這樣的:
哦,不是。。
在程式設計的世界裡也有魔法,一般稱其為:魔法數字,魔法變數,魔法字串。例如這樣:
const a = await abcdefg();console.log(a === 200);const b = await asdfgh();if (b === 0) {} else if (b === 1) {} else if (b === 2) {};for (let i = 0; i < 10; i++) {};
以上直接出現的,莫名其妙的變數名,字串以及判斷條件數字,就叫魔法。。。
這種寫法寫出來的程式碼晦澀難懂,難以維護,隱藏 BUG 多,除非你準備給接手的人埋坑,或者準備辭職,不然千萬別這麼寫(容易被打斷腿, )
那麼怎麼寫才更優雅?
語義化首先便是語義化。一個是變數,常量的語義化,例如:
const SUCCESS_STATUS = 200;const requestStatus = await getStatus();console.log(requestStatus === SUCCESS_STATUS);const userRole = await getUserRole();const GUEST_CODE = 0;const USER_CODE = 1;const ADMIN_CODE = 2;if (userRole === GUEST_CODE) {} else if (userRole === USER_CODE) {} else if (userRole === ADMIN_CODE) {};const MAX_NUM = 10;const MIN_NUM = 0;for (let currentNum = MIN_NUM; currentNum < MAX_NUM; currentNum++) {};
一般的規則就是變數用小寫,常量用大寫,把變數名語義化,那麼當你看到這段程式碼的時候,一眼就能知道它是做什麼的,而不是非得要浪費時間看完上下文,或者是猜。
列舉對於上面判斷 userRole 的程式碼,其實我們可以用更優雅的方式去實現,那就是 列舉 。
按照維基百科的說明:在數學和計算機科學理論中,一個集的列舉是列出某些有窮序列集的所有成員的程式,或者是一種特定型別物件的計數。這兩種型別經常(但不總是)重疊。
其實就是組織收集有關聯變數的一種方式。列舉的好處在於方便多狀態的管理,以及可讀性更強。例如:
const ROLES = { GUEST: 0, USER: 1, ADMIN: 2};const userRole = await getUserRole();if (userRole === ROLES.GUEST) {} else if (userRole === ROLES.USER) {} else if (userRole === ROLES.ADMIN) {};
透過列舉的方式歸納起來,維護起來更方便,而且要新增狀態直接在 ROLES 物件裡寫就行,更方便快捷。
策略模式維基百科上說:策略模式作為一種軟體設計模式,指物件有某個行為,但是在不同的場景中,該行為有不同的實現演算法。
上面的程式碼依舊是可最佳化的,在這裡我們可以利用策略模式來做進一層的最佳化。
具體的例子就是如下:
const ROLES = { GUEST: 0, USER: 1, ADMIN: 2};const ROLE_METHODS = { [ROLES.GUEST]() {}, [ROLES.USER]() {}, [ROLES.ADMIN]() {},};const userRole = await getUserRole();ROLE_METHODS[userRole]();
透過上面的寫法,我們可以知道,當我們需要增加角色,或者修改角色數字的時候,只需要修改 ROLES 裡對應的欄位,以及 ROLE_METHODS 裡的方法即可,這樣我們就可以將可能很冗長的 if...else 程式碼給抽離出來,顆粒度更細,更好維護。
更在狀態除了上面的方式之外,我們還可以利用“ 狀態 ”的概念來寫程式碼。在看程式碼之前,我們先了解下什麼是 “有限狀態機”。
根據維基百科的解釋:有限狀態機(英語:finite-state machine,縮寫:FSM)又稱有限狀態自動機(英語:finite-state automation,縮寫:FSA),簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學計算模型。
例如我們熟悉的 Promise ,它就是在狀態集:PENDIN 、 FULFILLED 、 REJECTED 之間單向流轉的有限狀態機。
狀態機的概念跟策略模式類似,實現方式也類似,這裡面最大的不同是在於 “語義” 。
策略模式更適合於互不依賴,同時只能存在一個狀態的場景,例如:
const 吃 = { 沙縣大酒店() { 吃雲吞() }, 開封菜() { 吃漢堡() }, 在家() { 吃外賣() }};
這裡面如果我們肚子餓了,就只能在 沙縣大酒店() , 開封菜() , 在家() 這幾個狀態裡選。
你不能都吃,當然以下情況除外。。。
如果是狀態模式,則會有這種情況:
const 打工人 = { 起床() {}, 上班() {}, 加班() {}, 下班() {}};// 早上6點打工人.起床();// 早上9點打工人.上班();// 晚上6點打工人.加班();// 晚上12點打工人.下班();
這裡的打工人根據不同的時間,進行不同的任務,便是打工人模式,哦不,狀態模式。這裡的時間就是狀態。
我們舉個實際的業務例子,就是訂單列表頁,通常我們的訂單可能有這幾種狀態:
不同的狀態展示的 UI 也不同,所以我們以不同的狀態劃分好模組之後,程式碼寫起來就會清晰很多,我們以 Vue 程式碼為例:
// contants.jsexport const ORDER_STATUS = { INIT: 0, // 初始化 CREATED: 1, // 訂單建立 ARREARAGE: 2, // 待支付 PURCHASED: 3, // 已購買 SHIPPED: 4, // 已發貨 COMPLETED: 5 // 已完成};
// order.vue<template> <div> <section v-if="orderIsInit"></section> <section v-if="orderIsCreated"></section> <section v-if="orderIsArrearage"></section> <section v-if="orderIsPurchased"></section> <section v-if="orderIsShipped"></section> <section v-if="orderIsCompleted"></section> </div></template><script> import ORDER_STATUS from './contants'; import eq from 'lodash'; export default { computed: { /** * @func * @name orderIsInit * @desc 判斷訂單是否初始化的狀態 * @returns {string} 判斷訂單是否初始化的狀態 */ orderIsInit() { return eq(this.orderStatus, ORDER_STATUS.INIT); }, /** * @func * @name orderIsCreated * @desc 判斷訂單是否已建立的狀態 * @returns {string} 訂單是否已建立 */ orderIsCreated() { return eq(this.orderStatus, ORDER_STATUS.CREATED); }, /** * @func * @name orderIsArrearage * @desc 判斷訂單是否未付款的狀態 * @returns {string} 訂單是否未付款 */ orderIsArrearage() { return eq(this.orderStatus, ORDER_STATUS.ARREARAGE); }, /** * @func * @name orderIsPurchased * @desc 判斷訂單是否已購買的狀態 * @returns {string} 訂單是否已購買 */ orderIsPurchased() { return eq(this.orderStatus, ORDER_STATUS.PURCHASED); }, /** * @func * @name orderIsShipped * @desc 判斷訂單是否已發貨的狀態 * @returns {string} 訂單是否已發貨 */ orderIsShipped() { return eq(this.orderStatus, ORDER_STATUS.SHIPPED); }, /** * @func * @name orderIsCompleted * @desc 判斷訂單是否已完成的狀態 * @returns {string} 訂單是否已完成 */ orderIsCompleted() { return eq(this.orderStatus, ORDER_STATUS.COMPLETED); }, }, data() { return { orderStatus: ORDER_STATUS.INIT // 訂單狀態 } }, methods: { /** * @func * @name getOrderStatus * @desc 判斷訂單狀態 * @returns {string} 返回當前訂單狀態 */ async getOrderStatus() {} }, async created() { this.orderStatus = await this.getOrderStatus(); } }</script>
將頁面元件按狀態劃分,實現獨立自治,這樣子既能防止程式碼耦合,方便維護 debug,也方便開發者自測,如果需要看不同狀態的展示效果,只要手動給 orderStatus 賦值即可,方便快捷。
面向切面按照維基百科的解釋:面向切面的程式設計(Aspect-oriented programming,AOP,又譯作面向方面的程式設計、剖面導向程式設計)是計算機科學中的一種程式設計思想,旨在將橫切關注點與業務主體進行進一步分離,以提高程式程式碼的模組化程度。
上面這段文字估計沒有什麼人看,算了,直接上程式碼吧
我們看回上面打工人的場景,假定老闆想要知道打工人每個狀態開始前跟結束前的時間以及做點什麼,那麼該怎麼做呢?這個時候我們不難想到可以直接往狀態函數里寫程式碼,例如:
const 打工人 = { 起床() { 老闆.start(); 打工人.do(); 老闆.end(); }, 上班() { 老闆.start(); 打工人.do(); 老闆.end(); }, 加班() { 老闆.start(); 打工人.do(); 老闆.end(); }, 下班() { 老闆.start(); 打工人.do(); 老闆.end(); }};// 早上6點打工人.起床();// 早上9點打工人.上班();// 晚上6點打工人.加班();// 晚上12點打工人.下班();
但是這樣打工人一下子就察覺到到了老闆在監控他的生活,如果要做到不被人察覺(不影響業務邏輯),那我們既可以採用 AOP 的實現方式。程式碼如下:
import eq from 'lodash';const TYPES = { FUNCTION: 'function'}const 老闆監控中的打工人 = new Proxy(打工人, { get(target, key, value, receiver) { console.log('老闆開始看你了~'); const res = Reflect.get(target, key, value, receiver); const 打工人任務 = eq(typeof res, TYPES.FUNCTION) ? res() : res; console.log('老闆開始記你小本本了~'); return () => 打工人任務; }});
所以我們可以看到以下結果:
這樣子,我們就可以輕鬆簡單地監控到了打工人每天干的活,而且還不讓打工人發現,簡直是資本家聽了都流淚呀。