close

前言

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

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

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

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

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

一、神秘命名(Mysterious Name)

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

不可替換寫法

constgetPNum=(number)=>{......}

無論是函數命名還是入參命名,相信都很難有人能參透你的深意,在別人接手你的代碼時,必定會來向你請教,這在老闆眼裡你的價值將更為突出。

正常寫法

constgetPhoneCode=(phoneNumber)=>{......}

從函數的駝峰命名我們可以很輕易猜出是獲取手機驗證碼,入參也能猜出是手機號碼的意思,這樣的代碼太通俗易懂了,顯然達不到我們的效果。

二、重複代碼(Duplicated Code)&& 過長函數(Long Function)

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

不可替換寫法

constshowUserInfo=()=>{lettotalAmount=0;constuserInfo=request.get('/userInfo','admin')constuserAmountList=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(letiofuserAmountList){totalAmount+=i.amount}console.log('總金額',totalAmount);}

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

正常寫法

constshowUserInfo=()=>{constprintUserDetail=(userInfo)=>{const{name,age,sex,address}=userInfo;console.log('name',name);console.log('age',age);console.log('sex',sex);console.log('address',address);constprintTotalAmount=(userAmountList)=>{consttotalAmount=userList.reduce((pre,cur)=>pre+cur.amount,0)console.log('總金額',totalAmount);}//獲取用戶信息constuserInfo=request.get('/userInfo','admin')printUserDetail(userInfo)//獲取用戶金額列表constuserAmountList=request.get('/userAmountList','admin')printTotalAmount(userAmountList)}

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

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

函數或組件的參數過多,影響代碼可讀性,某些環境甚至會對性能造成影響

不可替換寫法

constgetList=(id,name,age,address,sex)=>{...}

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

正常寫法

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

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

四、全局數據(Global Data)

將數據全部掛載到全局,導致內存不及時被釋放以及全局污染

不可替代寫法

constid=1;constdata1=request.get('/userInfo',id)constdata2=request.get('/userState',id)constgetUserInfo=()=>{...}constgetUserState=()=>{...}

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

正常寫法

constid=1;constgetUserInfo=()=>{constdata=request.get('/userInfo',id)...}constgetUserState=()=>{constdata=request.get('/userState',id)...}

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

五、發散式變化(Divergent Change)

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

不可替代寫法

constgetPrice=(list)=>{constprintName=(item)=>{if(item.type==='totalList'){console.log('totalName',item.name);}elseif(item.type==='frozenList'){console.log('frozenName',item.name);}}constcalcPrice=(item)=>{if(item.type==='totalList'){//todo:計算totalPriceconstprice=...;returnprice;}elseif(item.type==='frozenList'){//todo:計算frozenPriceconstprice=...;returnprice;}}printName(list.totalList);printName(list.frozenList);returncalcPrice(list.totalList)-calcPrice(list.frozenList)}

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

正常寫法

constgetPrice=(list)=>{consttotalPrice=(item)=>{//todo:計算totalPriceconstprice=...console.log('totalName',item.name);console.log('price',price);}constfrozenPrice=(item)=>{//todo:計算frozenPriceconstprice=...console.log('frozenName',item.name);console.log('price',price);}returntotalPrice(list.totalList)-frozenPrice(list.frozenList)}

每個價格對應需要的操作都被提煉到單獨的函數,專注於負責自己的事,如果價格計算方式需要改變,可以更加直觀的修改。若需要添加新的價格品種,也將會更好添加。

六、霰彈式修改(Shotgun Surgery)

多處共用一個屬性,不設置全局變量管理,每次修改需要修改大量代碼

不可替代寫法

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

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

正常寫法

constid=globalModel.id;getList(id)getUserInfo(id)getUserAmount(id)

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

七、依戀情結(Feature Envy)

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

不可替代寫法

classPrice{constructor(){}add(...num){returnnum.reduce((pre,cur)=>pre+cur,0);}dataFilter(value){returnparseInt(value.substring(0,value.length-2))*100;}}classAmount{constructor(){}getAmount(amountList){const_amountList=amountList.map(item=>{returnnewPrice().dataFilter(item);});returnnewPrice().add(..._amountList);}}

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

正常寫法

classAmount{constructor(){}add(...num){returnnum.reduce((pre,cur)=>pre+cur,0);}dataFilter(value){returnparseInt(value.substring(0,value.length-2))*100;}getAmount(amountList){const_amountList=amountList.map(item=>{returnthis.dataFilter(item);});returnthis.add(..._amountList);}}

類里所有使用的方法都在本身完成,所有的問題都在自身解決,形成閉環。

八、數據泥團(Data Clumps)

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

不可替代寫法

constlastName="盧"constfirstName="本偉"constname=`${lastName}${firstName}`

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

正常寫法

constperson={lastName:"盧",firstName:"本偉"}constname=`${person.lastName}${person.firstName}`

有強聯繫的數據,應為它們產生一個新對象。

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

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

不可替代寫法

classPrice{constructor(name,money){this.name=name;this.money=money;}getname(){returnname;}getcount(){returnparseFloat(this.money.slice(1));}getcurrent(){returnthis.money.slice(0,1);}getunit(){switch(this.money.slice(0,1)){case'¥':return'CNY';case'$':return'USD';case'k':return'HKD';}}calcPrice(){//todo:金額換算}}constmyPrice=newPrice("罐頭","$30")

偏執地使用字符串基本類型定義money,表面是Price的類,但在裡面充斥着大量的money的數據處理。

正常寫法

classMoney{constructor(value){this.value=value;}getcount(){returnparseFloat(this.value.slice(1));}getcurrent(){returnthis.value.slice(0,1);}getunit(){switch(this.value.slice(0,1)){case'¥':return'CNY';case'$':return'USD';case'k':return'HKD';}}}classPrice{constructor(name,money){this.name=name;this.money=newMoney(money);}getname(){returnname;}calcPrice(){//todo:金額換算}}constmyPrice=newPrice("罐頭","$20")

money中存在着大量的數據處理,應為期單獨建立個對象來作為它的類型。

十、重複的switch(Repeated Switches)

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

不可替代寫法

classMoney{constructor(value){this.value=value;}getcount(){returnparseFloat(this.value.slice(1));}getcurrent(){returnthis.value.slice(0,1);}getunit(){switch(this.current){case'¥':return'CNY';case'$':return'USD';case'k':return'HKD';}}getsuffix(){switch(this.current){case'¥':return'元';case'$':return'美元';case'k':return'港幣';}}getcurrentCount(){switch(this.current){case'¥':returnthis.count;case'$':returnthis.count*7;case'k':returnthis.count*0.8;}}}

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

正常寫法

classMoney{constructor(value){this.value=value;}getcount(){returnparseFloat(this.value.slice(1));}getcurrent(){returnthis.value.slice(0,1);}}classcnyMoneyextendsMoney{constructor(props){super(props);}getunit(){return'CNY';}getsuffix(){return'元';}getcurrentCount(){returnthis.count;}}classusdMoneyextendsMoney{constructor(props){super(props);}getunit(){return'USD';}getsuffix(){return'美元';}getcurrentCount(){returnthis.count*7;}}classhkdMoneyextendsMoney{constructor(props){super(props);}getunit(){return'HKD';}getsuffix(){return'港幣';}getcurrentCount(){returnthis.count*0.8;}}

每一個分支項專注於自身的變化,修改時不會擔心某處遺漏。

十一、循環語句(Loops)

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

不可替代寫法

constarr=[1,2,3,4,5,6];//找到數組指定項for(letiofarr){if(i===3){console.log(i);}}//數組求和letsum=0;for(letiofarr){sum+=i;}console.log(sum);

什麼ES6,算法我都不用,就for循環一把梭!

正常寫法

constarr=[1,2,3,4,5,6];//找到數組指定項console.log(arr.find((item)=>item===3));//數組求和letsum=arr.reduce((pre,cur)=>pre+cur);console.log(sum);

巧用各類api,提升代碼整潔度。

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

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

不可替代寫法

constgetUserInfo=(user)=>{constinfo={};constgetName=(data)=>{constfirstName=data.firstName;constlastName=data.lastName;returnfirstName+lastName;}info.name=getName(user)//todo:獲取用戶其他信息...returninfo;}

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

正常寫法

constgetUserInfo=(user)=>{constinfo={};info.name=user.firstName+user.lastName//todo:獲取用戶其他信息...returninfo;}

區分方法和命令的區別,能用命令解決的事不要多此一舉。

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

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

不可替代寫法

constgetCampaign=(campaign,type)=>{switch(type){case1://todo:type為1的處理return...default://todo:正常處理returnCampaign;}}

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

正常寫法

constgetCampaign=(campaign)=>{//todo:處理方案包returncampaign;}

只處理當前需求所需要做的事,後面的事後面再說。

十四、臨時字段(Temporary Field)

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

不可替代寫法

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

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

正常寫法

classPerson{constructor(data){this.name=data.name;this.age=data.age;this.address=data.address;}...}classRenewData{constructor(){this.balance=data.balance;this.isRenew=data.isRenew;}}

將特殊情況字段提取出來,使用時再進行調用。

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

A函數訪問B函數,B函數訪問C函數或變量,這樣的代碼會形成長長的消息鏈。

不可替代寫法

constdata=getData(getName(getiId(cookie)));

多層函數進行嵌套,耦合度更高,且無法理解函數這樣組合的目的。

正常寫法

constgetPackage=(cookie)=>{constid=getId(cookie);constname=getName(id);returngetData(name);}constdata=getPackage(cookie);

將方法提煉成函數,並將每一步解脫出來。

十六、中間人(Middle Man)

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

不可替代寫法

classPerson{constructor(data){this.balance=data.balance;this.info=data.info;this.id=data.id;}getname(){returnthis.info.name;}getaddress(){returnthis.info.address;}getage(){returnthis.info.age;}}

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

正常寫法

classPerson{constructor(data){this.balance=data.balance;this.info=data.info;this.id=data.id;}getinfo(){returnthis.info;}}

免去Person來代理info里的變量獲取,更加清晰明了。

十七、內幕交易(Insider Trading)

在類的內部有着與其他類的數據交換,調用者在調用時需要去了解其他接口的細節,增加類的耦合

不可替代寫法

classPerson{constructor(data){this.name=name;}getname(){returnname;}setpackage(arg){this.package=arg;}getpackage(){returnthis.package;}}classPackage{constructor(name){this.name=name;}setid(arg){this.id=arg;}getid(){returnthis.id;}}

要訪問person的packageId時需要與Package類進行數據交換,了解Package的內部細節。

正常寫法

classPerson{constructor(data,package){this.name=name;this.package=package;}getname(){returnname;}getpackageId(){returnthis.package.id;}}classPackage{constructor(name){this.name=name;}setid(arg){this.id=arg;}getid(){returnthis.id;}}

將內部的數據交換直接引入到Person類,搬到明面上進行處理

十八、過大的類(Insider Trading)

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

不可替代寫法

classProduct{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;}getrealPrice(){returnthis.price*this.discount;}getctr(){returnthis.uv/this.click;}}

商品的所有相關字段全部放入,複雜的場景中甚至能達到幾十上百個字段。

正常寫法

classProduct{constructor(data){this.name=data.name;this.id=data.id;this.user=data.user;}}classProductPrice{constructor(data){this.price=data.price;this.discount=data.discount;}getrealPrice(){returnthis.price*this.discount;}}classProductData{constructor(data){this.uv=data.uv;this.pv=data.pv;this.click=data.click;}getctr(){returnthis.uv/this.click;}}

將內部強相關的字段提煉成一個新的類,會讓結構更加清晰。

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

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

不可替代寫法

classAmericanCompany{constructor(name,createTime,address,phone,isLegitimate){this.name=name;this.createTime=createTime;this.address=address;this.phone=phone;this.isLegitimate=legitimate;}getname(){returnthis.name;}getaddress(){returnthis.address;}getphone(){switch(this.address){case'群島':return`+1-340${phone}`;default:return`+1${phone}`;}}}classChineseCompany{constructor(name,createTime,address,phone,isOverTime){this.name=name;this.createTime=createTime;this.address=address;this.phone=phone;this.isOverTime=isOverTime;}getname(){returnthis.name;}getaddress(){returnthis.address;}getphone(){switch(this.address){case'香港':return`+852${phone}`;case'台灣':return`+886${phone}`;default:return`+86${phone}`;}}}

美國公司和中國公司類的字段絕大部分和方法都是一樣的,若字段更多...若還有其他國家...每一個都創建一個類,真是不可想象。

正常寫法

classCompany{constructor(name,createTime,address,phone){this.name=name;this.createTime=createTime;this.address=address;this.phone=phone;}getname(){returnthis.name;}getaddress(){returnthis.address;}}classAmericanCompanyextendsCompany{constructor(name,createTime,address,phone,isLegitimate){super(name,createTime,address,phone);this.isLegitimate=legitimate;}getphone(){switch(this.address){case'群島':return`+1-340${this.phone}`;default:return`+1${this.phone}`;}}}classChineseCompanyextendsCompany{constructor(name,createTime,address,phone,isOverTime){super(name,createTime,address,phone);this.isOverTime=isOverTime;}getphone(){switch(this.address){case'香港':return`+852${this.phone}`;case'台灣':return`+886${this.phone}`;default:return`+86${this.phone}`;}}}

將重複部分提煉成一個類,再分別繼承。

二十、純數據類(Data Class)

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

不可替代寫法

classPerson{constructor(name,age,address){this.name=name;this.age=age;this.address=address;}getname(){returnthis.name;}getage(){returnthis.age;}getaddress(){returnthis.address;}}

哪怕只是簡單的存儲,我也要製作一個類!面向類編程!

正常寫法

constperson={......}

或者

classPerson{constructor(name,age,address){this.name=name;this.age=age;this.address=address;}getname(){returnthis.name;}getage(){returnthis.age;}getaddress(){returnthis.address;}//todo:一些與該類有關的處理方法}

單純的數據類使用對象去承載便可,如果有必要使用類,請將相關函數方法搬至類的內部。

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

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

不可替代寫法

classCompany{constructor(name,createTime,address,phone,isOverTime){this.name=name;this.createTime=createTime;this.address=address;this.phone=phone;this.isOverTime=isOverTime;}//todo:相關方法...}classAmericanCompanyextendsCompany{constructor(name,createTime,address,phone,isLegitimate){super(name,createTime,address,phone);this.isLegitimate=legitimate;}...}classChineseCompanyextendsCompany{constructor(name,createTime,address,phone,isOverTime){super(name,createTime,address,phone,isOverTime);}//todo:相關方法...}classUKCompanyextendsCompany{constructor(name,createTime,address,phone){super(name,createTime,address,phone);}//todo:相關方法...}

即使父類Company中的isOverTime只被ChineseCompany使用,我也要給它加上!

正常寫法

classCompany{constructor(name,createTime,address,phone){this.name=name;this.createTime=createTime;this.address=address;this.phone=phone;}//todo:相關方法...}classAmericanCompanyextendsCompany{constructor(name,createTime,address,phone,isLegitimate){super(name,createTime,address,phone);this.isLegitimate=legitimate;}...}classChineseCompanyextendsCompany{constructor(name,createTime,address,phone,isOverTime){super(name,createTime,address,phone);this.isOverTime=isOverTime;}//todo:相關方法...}classUKCompanyextendsCompany{constructor(name,createTime,address,phone){super(name,createTime,address,phone);}//todo:相關方法...}

將專屬字段isOverTime下沉至ChineseCompany中,自己的事自己做!

二十二、注釋(Comments)

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

不可替代寫法

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

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

正常寫法

constgetPhoneNumber=()=>{//todo:相關操作}

注釋不是我們的擋箭牌,而是我們最後一道防線,當你覺得需要用注釋的時候,請再思考代碼是否還有可以優化的地方。

總結

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

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()