網際網路裁員潮下,教你如何寫出在公司不可替代的程式碼!!
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: 相關操作
}
註釋不是我們的擋箭牌,而是我們最後一道防線,當你覺得需要用註釋的時候,請再思考程式碼是否還有可以優化的地方。
總結
你見過的最不可替代
的程式碼是什麼樣的呢?