日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
Javascript條件邏輯設(shè)計(jì)重構(gòu)
  • 分解條件表達(dá)式
  • 合并條件表達(dá)式
  • 以衛(wèi)語(yǔ)句替代嵌套條件表達(dá)式
  • 以多態(tài)取代條件表達(dá)式
  • 引入特例
  • 引入斷言

條件表達(dá)式易產(chǎn)生的問(wèn)題:

  • 復(fù)雜度極高: 表現(xiàn)是if嵌套兩三層設(shè)置更多
  • 大型函數(shù)可讀性下降: 不知道為什么會(huì)發(fā)生這樣事情

重構(gòu)手法1: 分解條件表達(dá)式

和任何大塊頭代碼一樣,我可以將它分解為多個(gè)獨(dú)立的函數(shù),根據(jù)每個(gè)小塊代碼的用途,為分解而得的新函數(shù)命名,并將原函數(shù)中對(duì)應(yīng)的代碼改為調(diào)用新函數(shù),從而更清楚地表達(dá)自己的意圖。對(duì)于條件邏輯,將每個(gè)分支條件分解成新函數(shù)還可以帶來(lái)更多好處:可以突出條件邏輯,更清楚地表明每個(gè)分支的作用,并且突出每個(gè)分支的原因。本重構(gòu)手法其實(shí)只是提煉函數(shù)的一個(gè)應(yīng)用場(chǎng)景”。

if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) 
 charge = quantity * plan.summerRate;
else
 charge = quantity * plan.regularRate + plan.regularServiceCharge;

進(jìn)行重構(gòu)后將條件提煉到一個(gè)獨(dú)立的函數(shù), 用三元運(yùn)算符重新安排條件語(yǔ)句 :

charge = summer() ? summerCharge() : regularCharge();

function summer() {
 return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd);
}
function summerCharge() {
 return quantity * plan.summerRate;
}
function regularCharge() {
 return quantity * plan.regularRate + plan.regularServiceCharge;
}

重構(gòu)手法2: 合并條件表達(dá)式

檢查條件各不相同,最終行為卻一致。如果發(fā)現(xiàn)這種情況,就應(yīng)該使用“邏輯或”和“邏輯與”將它們合并為一個(gè)條件表達(dá)式。

之所以要合并條件代碼,有兩個(gè)重要原因。首先,合并后的條件代碼會(huì)表述“實(shí)際上只有一次條件檢查,只不過(guò)有多個(gè)并列條件需要檢查而已”,從而使這一次檢查的用意更清晰。當(dāng)然,合并前和合并后的代碼有著相同的效果,但原先代碼傳達(dá)出的信息卻是“這里有一些各自獨(dú)立的條件測(cè)試,它們只是恰好同時(shí)發(fā)生”。其次,這項(xiàng)重構(gòu)往往可以為使用提煉函數(shù)(106)做好準(zhǔn)備。將檢查條件提煉成一個(gè)獨(dú)立的函數(shù)對(duì)于厘清代碼意義非常有用,因?yàn)樗衙枋觥白鍪裁础钡恼Z(yǔ)句換成了“為什么這樣做”。

“function disabilityAmount(anEmployee) { 
 if ((anEmployee.seniority < 2)
   || (anEmployee.monthsDisabled > 12)) return 0;
 if (anEmployee.isPartTime) return 0;

這句條件表達(dá)式使用提煉函數(shù) ,使用邏輯與與邏輯或:

function disabilityAmount(anEmployee) {
 if (isNotEligableForDisability()) return 0;
 // compute the disability amount

function isNotEligableForDisability() {
 return ((anEmployee.seniority < 2)
     || (anEmployee.monthsDisabled > 12)
     || (anEmployee.isPartTime));
}

重構(gòu)手法3: 以衛(wèi)語(yǔ)句替代嵌套條件表達(dá)式

條件表達(dá)式通常有兩種風(fēng)格,一種是兩個(gè)條件分支都屬于正常行為,第二種風(fēng)格是只有一個(gè)分支是屬于正常行為,另一個(gè)分支則是異常行為。如果兩個(gè)分支都屬于正常行為,就應(yīng)該使用例如if else條件表達(dá)式;如果某個(gè)條件極其罕見(jiàn),就應(yīng)該“就應(yīng)該單獨(dú)檢查該條件,并在該條件為真時(shí)立刻從函數(shù)中返回。這樣的單獨(dú)檢查常常被稱為“衛(wèi)語(yǔ)句"

以衛(wèi)語(yǔ)句取代嵌套條件表達(dá)式的精髓就是:給某一條分支以特別的重視。如果使用if-then-else結(jié)構(gòu),你對(duì)if分支和else分支的重視是同等的。這樣的代碼結(jié)構(gòu)傳遞給閱讀者的消息就是:各個(gè)分支有同樣的重要性。衛(wèi)語(yǔ)句就不同了,它告訴閱讀者:“這種情況不是本函數(shù)的核心邏輯所關(guān)心的,如果它真發(fā)生了,請(qǐng)做一些必要的整理工作,然后退出。

每個(gè)函數(shù)只能有一個(gè)入口和一個(gè)出口”的觀念,根深蒂固于某些程序員的腦海里。我發(fā)現(xiàn),當(dāng)我處理他們編寫(xiě)的代碼時(shí),經(jīng)常需要使用以衛(wèi)語(yǔ)句取代嵌套條件表達(dá)式?,F(xiàn)今的編程語(yǔ)言都會(huì)強(qiáng)制保證每個(gè)函數(shù)只有一個(gè)入口,至于“單一出口”規(guī)則,其實(shí)不是那么有用。在我看來(lái),保持代碼清晰才是最關(guān)鍵的:如果單一出口能使這個(gè)函數(shù)更清楚易讀,那么就使用單一出口;否則就不必這么做。

以下例子中的代碼可能我們很多人都寫(xiě)過(guò),我現(xiàn)在認(rèn)為它是壞味道的代碼:

function payAmount(employee) { 
 let result;
 if(employee.isSeparated) {
  result = {amount: 0, reasonCode:"SEP"};
 }
 else {
  if (employee.isRetired) {
   result = {amount: 0, reasonCode: "RET"};
  }
  else {
   // logic to compute amount
   lorem.ipsum(dolor.sitAmet);1
   consectetur(adipiscing).elit();
   sed.do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
   ut.enim.ad(minim.veniam);
   result = someFinalComputation();
  }
 }
 return result;

思考下位語(yǔ)句的概念,如果用位語(yǔ)句對(duì)其進(jìn)行清晰的重構(gòu):

首先我們可以對(duì)isDead以及isSeparated進(jìn)行改寫(xiě)位語(yǔ)句。

function payAmount(employee) {
 let result;
 if (employee.isSeparated) return {amount: 0, reasonCode: "SEP"};
 if (employee.isRetired) return {amount: 0, reasonCode: "RET"};
 xxxxxxxxxx
 return someFinalComputation();
}

有的時(shí)候不容易使用位語(yǔ)句,我們可以使用條件反轉(zhuǎn)來(lái)實(shí)現(xiàn)以衛(wèi)語(yǔ)句取代嵌套條件表達(dá)式。

function adjustedCapital(anInstrument) { 
 let result = 0;
 if (anInstrument.capital > 0) {
  if (anInstrument.interestRate > 0 && anInstrument.duration > 0) {
   result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
  }
 }
 return result;
}

位語(yǔ)句使用重點(diǎn)在與處理一條分支的正常情況的另一種特殊情況,即大多的少走的else,所以這次想將anInstrument.capital >0用位語(yǔ)句表示出來(lái):

function adjustedCapital(anInstrument) { 
 let result = 0;
 if (anInstrument.capital <= 0) return result;
 if (anInstrument.interestRate > 0 && anInstrument.duration > 0) {
  result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
 }
 return result;
}

下一步將anInstrument.interestRate >0&& anInstrument.duration >0進(jìn)行反轉(zhuǎn)。

function adjustedCapital(anInstrument) { 
 let result = 0;
 if (anInstrument.capital <= 0) return result;
 if (!(anInstrument.interestRate > 0 && anInstrument.duration > 0)) return result;
 result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
 return result;
}

!(anInstrument.interestRate >0&& anInstrument.duration >0) 看起來(lái)并不好,進(jìn)行改變“anInstrument.interestRate <= 0 || anInstrument.duration <= 0)并且使用重構(gòu)方法2 合并條件表達(dá)式進(jìn)行。

“function adjustedCapital(anInstrument) { 
 let result = 0;
 if ( anInstrument.capital <= 0
   || anInstrument.interestRate <= 0
   || anInstrument.duration <= 0) return result;
 result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
 return result;
}

最使用去除多余變量進(jìn)行將result去除。

“function adjustedCapital(anInstrument) { 
 if ( anInstrument.capital  <= 0
   || anInstrument.interestRate <= 0
   || anInstrument.duration   <= 0) return 0;
 return (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
}

重構(gòu)手法4: 以多態(tài)取代條件表達(dá)式

以上介紹的重構(gòu)的手法基本上是實(shí)用條件邏輯本身的結(jié)構(gòu)就足以表達(dá),但是我想尋求給條件邏輯添加結(jié)構(gòu),實(shí)用類和多態(tài)可以把邏輯的拆分表述的更加清晰,一個(gè)常見(jiàn)的場(chǎng)景是:我可以構(gòu)造一組類型,每個(gè)類型處理各自的一種條件邏輯。例如,我會(huì)注意到,圖書(shū)、音樂(lè)、食品的處理方式不同,這是因?yàn)樗鼈兎謱俨煌愋偷纳唐?。最明顯的征兆就是有好幾個(gè)函數(shù)都有基于類型代碼的switch語(yǔ)句。若果真如此,我就可以針對(duì)switch語(yǔ)句中的每種分支邏輯創(chuàng)建一個(gè)類,用多態(tài)來(lái)承載各個(gè)類型特有的行為,從而去除重復(fù)的分支邏輯。

另一種情況是:有一個(gè)基礎(chǔ)邏輯,在其上又有一些變體?;A(chǔ)邏輯可能是最常用的,也可能是最簡(jiǎn)單的。我可以把基礎(chǔ)邏輯放進(jìn)超類,這樣我可以首先理解這部分邏輯,暫時(shí)不管各種變體,然后我可以把每種變體邏輯單獨(dú)放進(jìn)一個(gè)子類,其中的代碼著重強(qiáng)調(diào)與基礎(chǔ)邏輯的差異。

多態(tài)一直是解決復(fù)雜條件邏輯改善這種情況工具,并非所有條件邏輯都要用多態(tài)來(lái)取代,避免濫用。

例子:

function plumages(birds) {
 return new Map(birds.map(b => [b.name, plumage(b)]));
}
function speeds(birds) {
 return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}

function plumage(bird) {
 switch (bird.type) {
 case 'EuropeanSwallow':
  return "average";
 case 'AfricanSwallow':
  return (bird.numberOfCoconuts > 2) ? "tired" : "average";
 case 'NorwegianBlueParrot':
  return (bird.voltage > 100) ? "scorched" : "beautiful";
 default:
  return "unknown";
 }
}

function airSpeedVelocity(bird) {
 switch (bird.type) {
 case 'EuropeanSwallow':
  return 35;
 case 'AfricanSwallow':
  return 40 - 2 * bird.numberOfCoconuts;
 case 'NorwegianBlueParrot':
  return (bird.isNailed) ? 0 : 10 + bird.voltage / 10;
 default:
  return null;
 }
}

使用多態(tài)進(jìn)行重構(gòu)的標(biāo)志是,對(duì)于同一個(gè)對(duì)象或者屬性進(jìn)行判斷邏輯操作,有兩個(gè)不同的操作,其行為都隨著“鳥(niǎo)的類型”發(fā)生變化,因此可以創(chuàng)建出對(duì)應(yīng)的類,用多態(tài)來(lái)處理各類型特有的行為。

function plumages(birds) { 
 return new Map(birds
         .map(b => createBird(b))
         .map(bird => [bird.name, bird.plumage]));
}
function speeds(birds) {
 return new Map(birds
         .map(b => createBird(b))
         .map(bird => [bird.name, bird.airSpeedVelocity]));
}

function createBird(bird) {
 switch (bird.type) {
 case 'EuropeanSwallow':
  return new EuropeanSwallow(bird);
 case 'AfricanSwallow':
  return new AfricanSwallow(bird);
 case 'NorwegianBlueParrot':
  return new NorwegianBlueParrot(bird);
 default:
  return new Bird(bird);
 }
}

class Bird {
 constructor(birdObject) {
  Object.assign(this, birdObject);
 }
 get plumage() {
  return "unknown";
 }
 get airSpeedVelocity() {
  return null;
 }
}
class EuropeanSwallow extends Bird {
 get plumage() {
  return "average";
 }
 get airSpeedVelocity() {
  return 35;
 }
}
class AfricanSwallow extends Bird {
 get plumage() {
  return (this.numberOfCoconuts > 2) ? "tired" : "average";
 }
 get airSpeedVelocity() {
  return 40 - 2 * this.numberOfCoconuts;
 }
}
class NorwegianBlueParrot extends Bird {
 get plumage() {
  return (this.voltage > 100) ? "scorched" : "beautiful";
 }
 get airSpeedVelocity() {
  return (this.isNailed) ? 0 : 10 + this.voltage / 10;
 }
}

重構(gòu)手法5: 引入特例

該手法主要是對(duì)一些特殊的值進(jìn)行邏輯判斷,之后才會(huì)進(jìn)行處理,“一個(gè)數(shù)據(jù)結(jié)構(gòu)的使用者都在檢查某個(gè)特殊的值,并且當(dāng)這個(gè)特殊值出現(xiàn)時(shí)所做的處理也都相同。如果我發(fā)現(xiàn)代碼庫(kù)中有多處以同樣方式應(yīng)對(duì)同一個(gè)特殊值,我就會(huì)想要把這個(gè)處理邏輯收攏到一處。處理這種情況的一個(gè)好辦法是使用“特例”(Special Case)模式:創(chuàng)建一個(gè)特例元素,用以表達(dá)對(duì)這種特例的共用行為的處理。這樣我就可以用一個(gè)函數(shù)調(diào)用取代大部分特例檢查邏輯。

特例有幾種表現(xiàn)形式。如果我只需要從這個(gè)對(duì)象讀取數(shù)據(jù),可以提供一個(gè)字面量對(duì)象(literal object),其中所有的值都是預(yù)先填充好的。如果除簡(jiǎn)單的數(shù)值之外還需要更多的行“為,就需要?jiǎng)?chuàng)建一個(gè)特殊對(duì)象,其中包含所有共用行為所對(duì)應(yīng)的函數(shù)。特例對(duì)象可以由一個(gè)封裝類來(lái)返回,也可以通過(guò)變換插入一個(gè)數(shù)據(jù)結(jié)構(gòu)。一個(gè)通常需要特例處理的值就是null,這也是這個(gè)模式常被叫作“Null對(duì)象”(Null Object)模式的原因——Null對(duì)象是特例的一種特例。

我們會(huì)首先創(chuàng)建一個(gè)關(guān)于判斷特例對(duì)象為真的一個(gè)類,這個(gè)類具有這個(gè)特例為null的所有屬性以及方法,我們將對(duì)即將要對(duì)某個(gè)對(duì)象的屬性的獲取判斷,轉(zhuǎn)化為代理這個(gè)值,如果存在就使用正常對(duì)象,如果不存在就要使用我們創(chuàng)建的那個(gè)特例對(duì)象的屬性以及方法了。

Class Site {
get customer() {return this._customer;}
}
class Customer{
get name() {...}
get billingPlan() {...}
set billingPlan(arg) {...}
get paymentHistory() {...}
}

client one

“const aCustomer = site.customer;
// ... lots of intervening code ...
let customerName;
if (aCustomer === "unknown") customerName = "occupant";
else customerName = aCustomer.name;”

clint two

const plan = (aCustomer === "unknown") ?
registry.billingPlans.basic
: aCustomer.billingPlan;

clint three

const weeksDelinquent = isUnknown(aCustomer) ?
0
: aCustomer.paymentHistory.weeksDelinquentInLastYear;

改寫(xiě):

class Site {
get customer() {
return (this._customer === "unknown") ? createUnknownCustomer() : this._customer;
}
}

function createUnknownCustomer() {
return {
isUnknown: true,
name: "occupant",
billingPlan: registry.billingPlans.basic,
paymentHistory: {
weeksDelinquentInLastYear: 0,
}
};
}

function isUnknown(arg) {
return arg.isUnknown;
}

const customerName = aCustomer.name;

const plan = aCustomer.billingPlan;

const weeksDelinquent = aCustomer.paymentHistory.weeksDelinquentInLastYear;

重構(gòu)手法6:引入斷言

這樣的假設(shè)通常并沒(méi)有在代碼中明確表現(xiàn)出來(lái),你必須閱讀整個(gè)算法才能看出。有時(shí)程序員會(huì)以注釋寫(xiě)出這樣的假設(shè),而我要介紹的是一種更好的技術(shù)——使用斷言明確標(biāo)明這些假設(shè)?!皵嘌允且粋€(gè)條件表達(dá)式,應(yīng)該總是為真。如果它失敗,表示程序員犯了錯(cuò)誤。

斷言的失敗不應(yīng)該被系統(tǒng)任何地方捕捉。整個(gè)程序的行為在有沒(méi)有斷言出現(xiàn)的時(shí)候都應(yīng)該完全一樣。實(shí)際上,有些編程語(yǔ)言中的斷言可以在編譯期用一個(gè)開(kāi)關(guān)完全禁用掉?!俺?匆?jiàn)有人鼓勵(lì)用斷言來(lái)發(fā)現(xiàn)程序中的錯(cuò)誤。這固然是一件好事,但卻不是使用斷言的唯一理由。斷言是一種很有價(jià)值的交流形式——它們告訴閱讀者,程序在執(zhí)行到這一點(diǎn)時(shí),對(duì)當(dāng)前狀態(tài)做了何種假設(shè)。另外斷言對(duì)調(diào)試也很有幫助。而且,因?yàn)樗鼈冊(cè)诮涣魃虾苡袃r(jià)值,即使解決了當(dāng)下正在追蹤的錯(cuò)誤,我還是傾向于把斷言留著。自測(cè)試的代碼降低了斷言在調(diào)試方面的價(jià)值,因?yàn)橹鸩奖平膯卧獪y(cè)試通常能更好地幫助調(diào)試,但我仍然看重?cái)嘌栽诮涣鞣矫娴膬r(jià)值。

假設(shè)這擴(kuò)率永遠(yuǎn)為整數(shù)。

applyDiscount(aNumber) { 
return (this.discountRate)
? aNumber - (this.discountRate * aNumber)
: aNumber;
}

先將三元改成if-else形式。

applyDiscount(aNumber) {
if (!this.discountRate) return aNumber;
else return aNumber - (this.discountRate * aNumber);
}
------------------

加入斷言
applyDiscount(aNumber) {
if (!this.discountRate) return aNumber;
else {
assert(this.discountRate >= 0);
return aNumber - (this.discountRate * aNumber);
}
}

注意,不要濫用斷言。我不會(huì)使用斷言來(lái)檢查所有“我認(rèn)為應(yīng)該為真”的條件,只用來(lái)檢查“必須為真”的條件。濫用斷言可能會(huì)造成代碼重復(fù),尤其是在處理上面這樣的條件邏輯時(shí)。所以我發(fā)現(xiàn),很有必要去掉條件邏輯中的重復(fù),通??梢越柚釤捄瘮?shù)手法。我只用斷言預(yù)防程序員的錯(cuò)誤。如果要從某個(gè)外部數(shù)據(jù)源讀取數(shù)據(jù),那么所有對(duì)輸入值的檢查都應(yīng)該是程序的一等公民,而不能用斷言實(shí)現(xiàn)——除非我對(duì)這個(gè)外部數(shù)據(jù)源有絕對(duì)的信心。斷言是幫助我們跟蹤bug的最后一招,所以,或許聽(tīng)來(lái)諷刺,只有當(dāng)我認(rèn)為斷言絕對(duì)不會(huì)失敗的時(shí)候,我才會(huì)使用斷言。


文章標(biāo)題:Javascript條件邏輯設(shè)計(jì)重構(gòu)
網(wǎng)頁(yè)路徑:http://www.dlmjj.cn/article/djcoije.html