網際網路裁員潮下,教你如何寫出在公司不可替代的程式碼!!

語言: CN / TW / HK

theme: smartblue

前言

本文是學習《重構:改善既有程式碼的設計》後的一些心得,希望能用趣味的方式結合一些例項帶領大家一起學習,提升自身程式碼質量。

想必最近的網際網路裁員訊息大家也有所耳聞,那麼我們怎麼才能夠在這樣的大環境下苟住自身呢?經過我的總結,我認為大家都不具備不可替換性

什麼叫不可替換性呢,通俗點來說就是,這活除了你別人都幹不了。達到這種境界無異於兩種情況,一種是自身過於優秀,優秀到沒人能取代(該情況過少,希望大家能正視己身)。

另一種方法則是,製作出專屬於你的程式碼!!下面我們來一起學習,怎樣寫出專屬於你,不可被替代的程式碼!

以下不可替換寫法皆為反面教材!!!

一、神祕命名(Mysterious Name)

命名讓人猜不透,摸不準!

不可替換寫法

js const getPNum = (number) => { ...... } 無論是函式命名還是入參命名,相信都很難有人能參透你的深意,在別人接手你的程式碼時,必定會來向你請教,這在老闆眼裡你的價值將更為突出。

正常寫法

js const getPhoneCode = (phoneNumber) => { ...... } 從函式的駝峰命名我們可以很輕易猜出是獲取手機驗證碼,入參也能猜出是手機號碼的意思,這樣的程式碼太通俗易懂了,顯然達不到我們的效果。

二、重複程式碼(Duplicated Code)&& 過長函式(Long Function)

重複編寫大量相同程式碼,內容過多的函式,使程式碼變得臃腫難以維護

不可替換寫法

js const showUserInfo = () => { let totalAmount = 0; const userInfo = request.get('/userInfo', 'admin') const userAmountList = request.get('/userAmountList', 'admin') console.log('name', userInfo.name); console.log('age', userInfo.age); console.log('sex', userInfo.sex); console.log('address', userInfo.address); for(let i of userAmountList) { totalAmount += i.amount } console.log('總金額', totalAmount); }

大量重複的程式碼讓人瞬間產生疲勞感,完全不搭邊的程式碼順序混淆人的雙眼,如果再加上一些神祕命名,必將讓程式碼更上一個臺階。

正常寫法

```js const showUserInfo = () => { const printUserDetail = (userInfo) => { const { name, age, sex, address } = userInfo; console.log('name', name); console.log('age', age); console.log('sex', sex); console.log('address', address);

const printTotalAmount = (userAmountList) => { const totalAmount = userList.reduce((pre, cur) => pre + cur.amount, 0) console.log('總金額', totalAmount); }

// 獲取使用者資訊 const userInfo = request.get('/userInfo', 'admin') printUserDetail(userInfo)

// 獲取使用者金額列表 const userAmountList = request.get('/userAmountList', 'admin') printTotalAmount(userAmountList) } ```

重複程式碼都被提煉到單獨的函式模組中,用reduce免去了重複的程式碼相加,並且程式碼順序也被移動至有關聯的地方,這樣的程式碼換做剛學前端的小白恐怕也能看懂,這樣明顯不能凸顯自身的獨特。

三、過長引數列表(Long Parameter List)

函式或元件的引數過多,影響程式碼可讀性,某些環境甚至會對效能造成影響

不可替換寫法

js const getList = (id, name, age, address, sex) => { ... }

將所有引數全部寫進入參,當入參達到十幾甚至上百時,你的函式定會震懾住你所有的同事。

正常寫法

js const getList = (data) => { const { id, name, age, address, sex } = data; ... }

將入參放置到一個物件中,再到函式裡通過解構的方式進行呼叫。這樣的方式太過簡潔,過少的入參凸顯不出你這個函式的重要性。

四、全域性資料(Global Data)

將資料全部掛載到全域性,導致記憶體不及時被釋放以及全域性汙染

不可替代寫法

```js const id = 1; const data1 = request.get('/userInfo', id) const data2 = request.get('/userState', id)

const getUserInfo = () => { ... }

const getUserState = () => { ... }

```

所有變數放入全域性,把後續開發者的路變窄,不敢隨意去更改變數,此刻再加上神祕命名,相信沒有人能夠取代你的位置。

正常寫法

```js const id = 1;

const getUserInfo = () => { const data = request.get('/userInfo', id) ... }

const getUserState = () => { const data = request.get('/userState', id) ... } ```

id作為多處用到變數,寫到全域性,剩下的區域性變數都寫在各自函式中,即不會引起全域性汙染,也不會擔心命名重複的問題。在各自的作用域中作用也清晰明瞭。

五、發散式變化(Divergent Change)

將需要做的事分散到各個地方,每次修改需要修改對應函式,修改不當會導致另一個依賴此函式的功能崩塌

不可替代寫法

```js const getPrice = (list) => {

const printName = (item) => { if(item.type === 'totalList') { console.log('totalName', item.name); }else if(item.type === 'frozenList'){ console.log('frozenName', item.name); } }

const calcPrice = (item) => { if(item.type === 'totalList') { // todo: 計算totalPrice const price = ...; return price; }else if(item.type === 'frozenList'){ // todo: 計算frozenPrice const price = ...; return price; } }

printName(list.totalList); printName(list.frozenList); return calcPrice(list.totalList) - calcPrice(list.frozenList) } ```

將方法寫成公用方法,在每次修改或者新增時候都需要去修改對應的方法。無法知道每個價格對應著哪些操作,當增加一個新的價格型別時,需要同時去多個函式中新增對應的判斷邏輯。一不注意就會忘加漏加形成bug。測試績效max!

正常寫法

```js const getPrice = (list) => { const totalPrice = (item) => { // todo: 計算totalPrice const price = ... console.log('totalName', item.name); console.log('price', price); }

const frozenPrice = (item) => { // todo: 計算frozenPrice const price = ... console.log('frozenName', item.name); console.log('price', price); }

return totalPrice(list.totalList) - frozenPrice(list.frozenList) } `` 每個價格對應需要的操作都被提煉到單獨的函式`,專注於負責自己的事,如果價格計算方式需要改變,可以更加直觀的修改。若需要新增新的價格品種,也將會更好新增。

六、霰彈式修改(Shotgun Surgery)

多處共用一個屬性,不設定全域性變數管理,每次修改需要修改大量程式碼

不可替代寫法

js getList(globalModel.id) getUserInfo(globalModel.id) getUserAmount(globalModel.id)

當需求改變,需要在多處進行修改,這樣的工作量倍增,會讓你的工作力max!

正常寫法

```js const id = globalModel.id;

getList(id) getUserInfo(id) getUserAmount(id) `` 同一個屬性被多處使用,使用一個變數進行儲存,當需求發生改變(例如globalModel.id變為globalModel.userId),只需要修改一處`便能完成,大大節省時間。

七、依戀情結(Feature Envy)

大量引入其他函式或模組方法,導致程式碼耦合度極高,動一處則牽扯全身

不可替代寫法

```js class Price { constructor() {}

add(...num) { return num.reduce((pre, cur) => pre + cur, 0); }

dataFilter(value) { return parseInt(value.substring(0, value.length - 2)) * 100; } }

class Amount { constructor() {}

getAmount(amountList) { const _amountList = amountList.map(item => { return new Price().dataFilter(item); }); return new Price().add(..._amountList); } } ```

所有的計算函式全部使用其他類裡的方法,形成大量依賴。你要出事我跟著一起死,我就是要用你的,我就是玩~

正常寫法

```js class Amount { constructor() {}

add(...num) { return num.reduce((pre, cur) => pre + cur, 0); }

dataFilter(value) { return parseInt(value.substring(0, value.length - 2)) * 100; }

getAmount(amountList) { const _amountList = amountList.map(item => { return this.dataFilter(item); }); return this.add(..._amountList); } } `` 類裡所有使用的方法都在本身完成,所有的問題都在自身解決,形成閉環`。

八、資料泥團(Data Clumps)

眾多資料糅合在一起,當其中某一項資料失去意義時,其他項資料也失去意義。

不可替代寫法

js const lastName = "盧" const firstName = "本偉" const name = `${lastName}${firstName}`

發現當其中某個變數失去意義的時候,另一個變數也失去意義,一損俱損。

正常寫法

js const person = { lastName: "盧", firstName: "本偉" } const name = `${person.lastName}${person.firstName}` 有強聯絡的資料,應為它們產生一個新物件。

九、基本型別偏執(Primitive Obsession)

認為基本型別一定更加簡單,偏執的使用大量的基本型別而不去定義應有的結構

不可替代寫法

js class Price { constructor(name, money) { this.name = name; this.money = money; } get name() { return name; } get count() { return parseFloat(this.money.slice(1)); } get current() { return this.money.slice(0, 1); } get unit() { switch (this.money.slice(0, 1)) { case '¥': return 'CNY'; case '$': return 'USD'; case 'k': return 'HKD'; } } calcPrice() { // todo: 金額換算 } } const myPrice = new Price("罐頭", "$30")

偏執地使用字串基本型別定義money,表面是Price的類,但在裡面充斥著大量的money的資料處理。

正常寫法

js class Money { constructor(value) { this.value = value; } get count() { return parseFloat(this.value.slice(1)); } get current() { return this.value.slice(0, 1); } get unit() { switch (this.value.slice(0, 1)) { case '¥': return 'CNY'; case '$': return 'USD'; case 'k': return 'HKD'; } } } class Price { constructor(name, money) { this.name = name; this.money = new Money(money); } get name() { return name; } calcPrice() { // todo: 金額換算 } } const myPrice = new Price("罐頭", "$20") money中存在著大量的資料處理,應為期單獨建立個物件來作為它的型別。

十、重複的switch(Repeated Switches)

大量使用重複邏輯的switch,這樣的程式碼是臃腫且脆弱的

不可替代寫法

js class Money { constructor(value) { this.value = value; } get count() { return parseFloat(this.value.slice(1)); } get current() { return this.value.slice(0, 1); } get unit() { switch (this.current) { case '¥': return 'CNY'; case '$': return 'USD'; case 'k': return 'HKD'; } } get suffix() { switch (this.current) { case '¥': return '元'; case '$': return '美元'; case 'k': return '港幣'; } } get currentCount() { switch (this.current) { case '¥': return this.count; case '$': return this.count * 7; case 'k': return this.count * 0.8; } } }

大量相同邏輯的switch,若想增加一個判斷項,需找到所有switch項進行修改,一不注意則會遺漏,引發bug

正常寫法

js class Money { constructor(value) { this.value = value; } get count() { return parseFloat(this.value.slice(1)); } get current() { return this.value.slice(0, 1); } } class cnyMoney extends Money { constructor(props) { super(props); } get unit() { return 'CNY'; } get suffix() { return '元'; } get currentCount() { return this.count; } } class usdMoney extends Money { constructor(props) { super(props); } get unit() { return 'USD'; } get suffix() { return '美元'; } get currentCount() { return this.count * 7; } } class hkdMoney extends Money { constructor(props) { super(props); } get unit() { return 'HKD'; } get suffix() { return '港幣'; } get currentCount() { return this.count * 0.8; } } 每一個分支項專注於自身的變化,修改時不會擔心某處遺漏。

十一、迴圈語句(Loops)

捨棄ES自帶api,全面擁抱for、while迴圈語句

不可替代寫法

js const arr = [1, 2, 3, 4, 5, 6]; // 找到陣列指定項 for (let i of arr) { if (i === 3) { console.log(i); } } // 陣列求和 let sum = 0; for (let i of arr) { sum += i; } console.log(sum);

什麼ES6,演算法我都不用,就for迴圈一把梭!

正常寫法

js const arr = [1, 2, 3, 4, 5, 6]; // 找到陣列指定項 console.log(arr.find((item) => item === 3)); // 陣列求和 let sum = arr.reduce((pre, cur) => pre + cur); console.log(sum); 巧用各類api,提升程式碼整潔度。

十二、冗贅的元素(Lazy Element)

單一的功能也會提煉成相應函式,即使它的實現程式碼和函式名基本一樣。

不可替代寫法

js const getUserInfo = (user) => { const info = {}; const getName = (data) => { const firstName = data.firstName; const lastName = data.lastName; return firstName + lastName; } info.name = getName(user) // todo: 獲取使用者其他資訊 ... return info; }

獲取姓名,獲取地址,哪怕只是簡單的字串相加我也要提煉成函式!面向函式程式設計!

正常寫法

js const getUserInfo = (user) => { const info = {}; info.name = user.firstName + user.lastName // todo: 獲取使用者其他資訊 ... return info; } 區分方法和命令的區別,能用命令解決的事不要多此一舉。

十三、誇誇其談通用性(Speculative Generality)

當我們設計程式碼時總會提前設計好一些結構,想著未來有一天會有新功能新增,但事實上,這一天永遠不會到來。

不可替代寫法

js const getCampaign = (campaign, type) => { switch (type) { case 1: // todo: type為1的處理 return ... default: // todo: 正常處理 return Campaign; } }

認為以後可能會對該包進行一些特殊處理,設定一個type進行判斷,增加"通用性"。

正常寫法

js const getCampaign = (campaign) => { // todo: 處理方案包 return campaign; } 只處理當前需求所需要做的事,後面的事後面再說。

十四、臨時欄位(Temporary Field)

一個類中為了某種特定情況設定一個特殊欄位,但除了那種情況外這個欄位永遠不會被使用。

不可替代寫法

js class Person{ constructor(data) { this.name = data.name; this.age = data.age; this.address = data.address; // 下列兩個變數只有在進行續費處理時才會使用 this.balance = data.balance; this.isRenew = data.isRenew; } ... }

所有特殊情況需要用到的欄位全往類裡塞,當被使用時對這些欄位產生疑惑,並給予強行賦值。

正常寫法

js class Person{ constructor(data) { this.name = data.name; this.age = data.age; this.address = data.address; } ... } class RenewData { constructor() { this.balance = data.balance; this.isRenew = data.isRenew; } } 將特殊情況欄位提取出來,使用時再進行呼叫。

十五、過長的訊息鏈(Message Chains)

A函式訪問B函式,B函式訪問C函式或變數,這樣的程式碼會形成長長的訊息鏈。

不可替代寫法

js const data = getData(getName(getiId(cookie)));

多層函式進行巢狀,耦合度更高,且無法理解函式這樣組合的目的。

正常寫法

```js const getPackage = (cookie) => { const id = getId(cookie); const name = getName(id); return getData(name); }

const data = getPackage(cookie); ``` 將方法提煉成函式,並將每一步解脫出來。

十六、中間人(Middle Man)

將所有操作委託給另一個類,但事實上這一層是沒有必要的。

不可替代寫法

js class Person { constructor(data) { this.balance = data.balance; this.info = data.info; this.id = data.id; } get name() { return this.info.name; } get address() { return this.info.address; } get age() { return this.info.age; } }

info裡的每一項獲取都通過Person類代理,過多代理會讓這個類變得十分臃腫。

正常寫法

js class Person { constructor(data) { this.balance = data.balance; this.info = data.info; this.id = data.id; } get info() { return this.info; } } 免去Person來代理info裡的變數獲取,更加清晰明瞭。

十七、內幕交易(Insider Trading)

在類的內部有著與其他類的資料交換,呼叫者在呼叫時需要去了解其他介面的細節,增加類的耦合

不可替代寫法

```js class Person { constructor(data) { this.name = name; } get name() { return name; } set package(arg) { this.package = arg; } get package() { return this.package; } }

class Package { constructor(name) { this.name = name; } set id(arg) { this.id = arg; } get id() { return this.id; } } ```

要訪問person的packageId時需要與Package類進行資料交換,瞭解Package的內部細節。

正常寫法

```js class Person { constructor(data, package) { this.name = name; this.package = package; } get name() { return name; } get packageId() { return this.package.id; } }

class Package { constructor(name) { this.name = name; } set id(arg) { this.id = arg; } get id() { return this.id; } } ``` 將內部的資料交換直接引入到Person類,搬到明面上進行處理

十八、過大的類(Insider Trading)

所有與該類相關的欄位全部放入類中,顯得類十分的龐大臃腫。

不可替代寫法

js class Product { constructor(data) { this.name = data.name; this.price = data.price; this.id = data.id; this.user = data.user; this.uv = data.uv; this.pv = data.pv; this.click = data.click; } get realPrice() { return this.price * this.discount; } get ctr() { return this.uv / this.click; } } 商品的所有相關欄位全部放入,複雜的場景中甚至能達到幾十上百個欄位。

正常寫法

```js class Product { constructor(data) { this.name = data.name; this.id = data.id; this.user = data.user; } }

class ProductPrice { constructor(data) { this.price = data.price; this.discount = data.discount; } get realPrice() { return this.price * this.discount; } }

class ProductData { constructor(data) { this.uv = data.uv; this.pv = data.pv; this.click = data.click; } get ctr() { return this.uv / this.click; } } ``` 將內部強相關的欄位提煉成一個新的類,會讓結構更加清晰。

十九、異曲同工的類(Alternative Classes with Different Interfaces)

類的好處之一就是可以替換,但要避免出現做相似事情的類。

不可替代寫法

js class AmericanCompany { constructor(name, createTime, address, phone, isLegitimate) { this.name = name; this.createTime = createTime; this.address = address; this.phone = phone; this.isLegitimate = legitimate; } get name() { return this.name; } get address() { return this.address; } get phone() { switch (this.address) { case '群島': return `+1-340${phone}`; default: return `+1${phone}`; } } } class ChineseCompany { constructor(name, createTime, address, phone, isOverTime) { this.name = name; this.createTime = createTime; this.address = address; this.phone = phone; this.isOverTime = isOverTime; } get name() { return this.name; } get address() { return this.address; } get phone() { switch (this.address) { case '香港': return `+852${phone}`; case '臺灣': return `+886${phone}`; default: return `+86${phone}`; } } } 美國公司和中國公司類的欄位絕大部分和方法都是一樣的,若欄位更多...若還有其他國家...每一個都建立一個類,真是不可想象。

正常寫法

js class Company { constructor(name, createTime, address, phone) { this.name = name; this.createTime = createTime; this.address = address; this.phone = phone; } get name() { return this.name; } get address() { return this.address; } } class AmericanCompany extends Company { constructor(name, createTime, address, phone, isLegitimate) { super(name, createTime, address, phone); this.isLegitimate = legitimate; } get phone() { switch (this.address) { case '群島': return `+1-340${this.phone}`; default: return `+1${this.phone}`; } } } class ChineseCompany extends Company { constructor(name, createTime, address, phone, isOverTime) { super(name, createTime, address, phone); this.isOverTime = isOverTime; } get phone() { switch (this.address) { case '香港': return `+852${this.phone}`; case '臺灣': return `+886${this.phone}`; default: return `+86${this.phone}`; } } } 將重複部分提煉成一個類,再分別繼承。

二十、純資料類(Data Class)

一個類中只有單純的儲存資料的作用,只能進行簡單讀取功能。

不可替代寫法

js class Person { constructor(name, age, address) { this.name = name; this.age = age; this.address = address; } get name() { return this.name; } get age() { return this.age; } get address() { return this.address; } } 哪怕只是簡單的儲存,我也要製作一個類!面向類程式設計!

正常寫法

js const person = { ...... } 或者 js class Person { constructor(name, age, address) { this.name = name; this.age = age; this.address = address; } get name() { return this.name; } get age() { return this.age; } get address() { return this.address; } // todo: 一些與該類有關的處理方法 } 單純的資料類使用物件去承載便可,如果有必要使用類,請將相關函式方法搬至類的內部。

二十一、被拒絕的饋贈(Refused Bequest)

父類中某個欄位只被一個或極少數的繼承類繼承,其他的類並不需要這個欄位。

不可替代寫法

js class Company { constructor(name, createTime, address, phone, isOverTime) { this.name = name; this.createTime = createTime; this.address = address; this.phone = phone; this.isOverTime = isOverTime; } // todo: 相關方法 ... } class AmericanCompany extends Company { constructor(name, createTime, address, phone, isLegitimate) { super(name, createTime, address, phone); this.isLegitimate = legitimate; } ... } class ChineseCompany extends Company { constructor(name, createTime, address, phone, isOverTime) { super(name, createTime, address, phone, isOverTime); } // todo: 相關方法 ... } class UKCompany extends Company { constructor(name, createTime, address, phone) { super(name, createTime, address, phone); } // todo: 相關方法 ... } 即使父類Company中的isOverTime只被ChineseCompany使用,我也要給它加上!

正常寫法

js class Company { constructor(name, createTime, address, phone) { this.name = name; this.createTime = createTime; this.address = address; this.phone = phone; } // todo: 相關方法 ... } class AmericanCompany extends Company { constructor(name, createTime, address, phone, isLegitimate) { super(name, createTime, address, phone); this.isLegitimate = legitimate; } ... } class ChineseCompany extends Company { constructor(name, createTime, address, phone, isOverTime) { super(name, createTime, address, phone); this.isOverTime = isOverTime; } // todo: 相關方法 ... } class UKCompany extends Company { constructor(name, createTime, address, phone) { super(name, createTime, address, phone); } // todo: 相關方法 ... } 將專屬欄位isOverTime下沉至ChineseCompany中,自己的事自己做!

二十二、註釋(Comments)

註釋是一個雙刃劍,好的註釋能更好地幫助理解程式碼,但過於依賴註釋會讓人放棄對程式碼質量的要求。

不可替代寫法

js // 獲取手機號碼 const getPNum = () => { // todo: 相關操作 }

什麼程式碼質量我全都不管,反正我有註釋,別人看得懂。

正常寫法

js const getPhoneNumber = () => { // todo: 相關操作 } 註釋不是我們的擋箭牌,而是我們最後一道防線,當你覺得需要用註釋的時候,請再思考程式碼是否還有可以優化的地方。

總結

你見過的最不可替代的程式碼是什麼樣的呢?