最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

javascript 從if else 到 switch case 再到抽象

 更新時(shí)間:2010年07月17日 17:39:44   作者:  
大家覺得在接手遺留代碼時(shí),見到什么東東是最讓人感到不耐煩的?復(fù)雜無比的 UML ?我覺得不是。
我的答案是,超過兩個(gè) else 的 if ,或者是超過兩個(gè) case 的 switch ??墒窃诖a中大量使用 if else 和 switch case 是很正常的事情吧?錯!絕大多數(shù)分支超過兩個(gè)的 if else 和 switch case 都不應(yīng)該以硬編碼( hard-coded )的形式出現(xiàn)。
復(fù)雜分支從何而來
首先我們要討論的第一個(gè)問題是,為什么遺留代碼里面往往有那么多復(fù)雜分支。這些復(fù)雜分支在代碼的首個(gè)版本中往往是不存在的,假設(shè)做設(shè)計(jì)的人還是有點(diǎn)經(jīng)驗(yàn)的話,他應(yīng)該預(yù)見將來可能需要進(jìn)行擴(kuò)展的地方,并且預(yù)留抽象接口。

但是代碼經(jīng)過若干個(gè)版本的迭代以后,尤其是經(jīng)過若干次需求細(xì)節(jié)的調(diào)整以后,復(fù)雜分支就會出現(xiàn)了。需求的細(xì)節(jié)調(diào)整,往往不會反映到 UML 上,而會直接反映到代碼上。例如說,原本消息分為聊天消息和系統(tǒng)消息兩類,設(shè)計(jì)的時(shí)候自然會把這設(shè)計(jì)為消息類的兩個(gè)子類。但接著有一天需求發(fā)生細(xì)節(jié)調(diào)整了,系統(tǒng)消息里面有一部分是重要的,它們的標(biāo)題要顯示為紅色,這時(shí)候程序員往往會做如下修改:
在系統(tǒng)消息類上面加一個(gè) important 屬性
在相應(yīng)的 render 方法里面加入一個(gè)關(guān)于 important 屬性的分支,用于控制標(biāo)題顏色
程序員為什么會作出這樣的修改?有可能因?yàn)樗麤]意識到應(yīng)該抽象。因?yàn)樾枨笳f的是「系統(tǒng)消息里面有一部分是重要的」,對于接受命令式編程語言訓(xùn)練比較多的程序員來說,他或許首先想到的是標(biāo)志位──一個(gè)標(biāo)志位就可以區(qū)分重要跟不重要。他沒想到這個(gè)需求可以用另一種方式來解讀,「系統(tǒng)消息分為重要和不重要兩種類別」。這樣子解讀,他就知道應(yīng)該對系統(tǒng)消息進(jìn)行抽象了。
當(dāng)然也有可能,程序員知道可以抽象,但基于某些原因,他選擇了不這樣做。很常見的一種情況就是有人逼著程序員,以犧牲代碼質(zhì)量來換取項(xiàng)目進(jìn)展速度──加入一個(gè)屬性和一個(gè)分支,遠(yuǎn)比抽象重構(gòu)要簡單得多,如果要做10個(gè)這種形式的修改,是做10個(gè)分支快還是做10個(gè)抽象快?區(qū)別顯而易見。
當(dāng)然, if else 多了,就有聰明人站出來說「不如我們改成 switch case 」吧。在某些情況下,這確實(shí)能夠提升代碼可讀性,假設(shè)每一個(gè)分支都是互斥的話。但是當(dāng) switch case 的數(shù)量也多起來以后,代碼一樣會變得不可讀。
復(fù)雜分支有何壞處
復(fù)雜分支有什么壞處?讓我從百度 Hi 網(wǎng)頁版的老代碼里面截取一段出來做個(gè)例子。
復(fù)制代碼 代碼如下:

switch (json.result) {
case "ok":
switch (json.command) {
case "message":
case "systemmessage":
if (json.content.from == ""
&& json.content.content == "kicked") {
/* disconnect */
} else if (json.command == "systemmessage"
|| json.content.type == "sysmsg") {
/* render system message */
} else {
/* render chat message */
}
break;
}
break;

這段代碼要看懂不難,因此我提一個(gè)簡單問題,以下這個(gè) JSON 命中哪個(gè)分支:
復(fù)制代碼 代碼如下:

{
"result": "ok",
"command": "message",
"content": {
"from": "CatChen",
"content": "Hello!"
}
}

你很容易就能得到正確答案:這個(gè) JSON 命中 /* render chat message */ (顯示聊天消息)這個(gè)分支。那么我想了解一下,你是如何作出這個(gè)判斷的?首先,你要看它是否命中 case "ok": 分支,結(jié)果是命中了;然后,你要看它是否命中 case "message": 分支,結(jié)果也是命中了,所以 case "systemmessage": 就不用看了;接下來,它不命中 if 里面的條件;并且,它也不命中 else if 里面的條件,所以它命中了 else 這個(gè)分支。
看出問題來了嗎?為什么你不能看著這個(gè) else 就說出這個(gè) JSON 命中這個(gè)分支?因?yàn)?else 本身不包含任何條件,它只隱含條件!每一個(gè) else 的條件,都是對它之前的每一個(gè) if 和 else if 進(jìn)行先非后與運(yùn)算的結(jié)果。也就是說,判斷命中這個(gè) else ,相當(dāng)于判斷命中這樣一組復(fù)雜的條件:
復(fù)制代碼 代碼如下:

!(json.content.from == "" && json.content.content == "kicked") && !(json.command == "systemmessage" || json.content.type == "sysmsg")

再套上外層的兩個(gè) switch case ,這個(gè)分支的條件就是這樣子的:
復(fù)制代碼 代碼如下:

json.result == "ok" && (json.command == "message" || json.command == "systemmessage") && !(json.content.from == "" && json.content.content == "kicked") && !(json.command == "systemmessage" || json.content.type == "sysmsg")

這里面有重復(fù)邏輯,省略后是這樣子的:
復(fù)制代碼 代碼如下:

json.result == "ok" && json.command == "message" && !(json.content.from == "" && json.content.content == "kicked") && !(json.content.type == "sysmsg")

我們花了多大力氣才從簡簡單單的 else 這四個(gè)字母中推導(dǎo)出這樣一長串邏輯運(yùn)算表達(dá)式來?況且,不仔細(xì)看還真的看不懂這個(gè)表達(dá)式說的是什么。
這就是復(fù)雜分支難以閱讀和管理的地方。想象你面對一個(gè) switch case 套一個(gè) if else ,總共有3個(gè) case ,每個(gè) case 里面有3個(gè) else ,這就夠你研究的了──每一個(gè)分支,條件中都隱含著它所有前置分支以及所有祖先分支的前置分支先非后與的結(jié)果。
如何避免復(fù)雜分支
首先,復(fù)雜邏輯運(yùn)算是不能避免的。重構(gòu)得到的結(jié)果應(yīng)該是等價(jià)的邏輯,我們能做的只是讓代碼變得更加容易閱讀和管理。因此,我們的重點(diǎn)應(yīng)該在于如何使得復(fù)雜邏輯運(yùn)算變得易于閱讀和管理。
抽象為類或者工廠
對于習(xí)慣于做面向?qū)ο笤O(shè)計(jì)的人來說,可能這意味著將復(fù)雜邏輯運(yùn)算打散并分布到不同的類里面:
復(fù)制代碼 代碼如下:

switch (json.result) {
case "ok":
var factory = commandFactories.getFactory(json.command);
var command = factory.buildCommand(json);
command.execute();
break;
}

這看起來不錯,至少分支變短了,代碼變得容易閱讀了。這個(gè) switch case 只管狀態(tài)碼分支,對于 "ok" 這個(gè)狀態(tài)碼具體怎么處理,那是其他類管的事情。 getFactory 里面可能有一組分支,專注于創(chuàng)建這條指令應(yīng)該選擇哪一個(gè)工廠的選擇。同時(shí) buildCommand 可能又有另外一些瑣碎的分支,決定如何構(gòu)建這條指令。
這樣做的好處是,分支之間的嵌套關(guān)系解除了,每一個(gè)分支只要在自己的上下文中保持正確就可以了。舉個(gè)例子來說, getFactory 現(xiàn)在是一個(gè)具名函數(shù),因此這個(gè)函數(shù)內(nèi)的分支只要實(shí)現(xiàn) getFactory 這個(gè)名字暗示的契約就可以了,無需關(guān)注實(shí)際調(diào)用 getFactory 的上下文。
抽象為模式匹配
另外一種做法,就是把這種復(fù)雜邏輯運(yùn)算轉(zhuǎn)述為模式匹配:
復(fù)制代碼 代碼如下:

Network.listen({
"result": "ok",
"command": "message",
"content": { "from": "", "content": "kicked" }
}, function(json) { /* disconnect */ });
Network.listen([{
"result": "ok",
"command": "message",
"content": { "type": "sysmsg" }
}, {
"result": "ok",
"command": "systemmessage"
}], function(json) { /* render system message */ });
Network.listen({
"result": "ok",
"command": "message",
"content": { "from$ne": "", "type$ne": "sysmsg" }
}, func  tion(json) { /* render chat message */ });

現(xiàn)在這樣子是不是清晰多了?第一種情況,是被踢下線,必須匹配指定的 from 和 content 值。第二種情況,是顯示系統(tǒng)消息,由于系統(tǒng)消息在兩個(gè)版本的協(xié)議中略有不同,所以我們要捕捉兩種不同的 JSON ,匹配任意一個(gè)都算是命中。第三種情況,是顯示聊天消息,由于在老版本協(xié)議中系統(tǒng)消息和踢下線指令都屬于特殊的聊天消息,為了兼容老版本協(xié)議,這兩種情況要從顯示聊天消息中排除出去,所以就使用了 "$ne" (表示 not equal )這樣的后綴進(jìn)行匹配。
由于 listen 方法是上下文無關(guān)的,每一個(gè) listen 都獨(dú)立聲明自己匹配什么樣的 JSON ,因此不存在任何隱含邏輯。例如說,要捕捉聊天消息,就必須顯式聲明排除 from == "" 以及 type == "sysmsg" 這兩種情況,這不需要由上下文的 if else 推斷得出。
使用模式匹配,可以大大提高代碼的可讀性和可維護(hù)性。由于我們要捕捉的是 JSON ,所以我們就使用 JSON 來描述每一個(gè)分支要捕捉什么,這比一個(gè)長長的邏輯運(yùn)算表達(dá)式要清晰多了。同時(shí)在這個(gè) JSON 上的每一處修改都是獨(dú)立的,修改一個(gè)條件并不影響其他條件。
最后,如何編寫一個(gè)這樣的模式匹配模塊,這已經(jīng)超出了本文的范圍。

相關(guān)文章

  • 有關(guān)js的變量作用域和this指針的討論

    有關(guān)js的變量作用域和this指針的討論

    在ECMAScript中,只有兩種執(zhí)行環(huán)境,全局環(huán)境和函數(shù)環(huán)境,每個(gè)函數(shù)都是一個(gè)執(zhí)行環(huán)境,包括嵌套函數(shù)。換句話說,其他情況下即使變量聲明在一對大括號中,在括號外部仍然可以訪問這些變量
    2010-12-12
  • Bootstrap5的斷點(diǎn)與容器的具體使用

    Bootstrap5的斷點(diǎn)與容器的具體使用

    本文將結(jié)合實(shí)例代碼,介紹Bootstrap5的斷點(diǎn)與容器的具體使用,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • echarts餅圖扇區(qū)添加點(diǎn)擊事件的實(shí)例

    echarts餅圖扇區(qū)添加點(diǎn)擊事件的實(shí)例

    下面小編就為大家?guī)硪黄猠charts餅圖扇區(qū)添加點(diǎn)擊事件的實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • JavaScript使用鏈?zhǔn)椒椒ǚ庋bjQuery中CSS()方法示例

    JavaScript使用鏈?zhǔn)椒椒ǚ庋bjQuery中CSS()方法示例

    這篇文章主要介紹了JavaScript使用鏈?zhǔn)椒椒ǚ庋bjQuery中CSS()方法,結(jié)合具體實(shí)例形式分析了JS采用鏈?zhǔn)讲僮鞣椒ǚ庾Query中連綴操作實(shí)現(xiàn)css()方法的相關(guān)技巧,需要的朋友可以參考下
    2017-04-04
  • 自己做的模擬模態(tài)對話框?qū)崿F(xiàn)代碼

    自己做的模擬模態(tài)對話框?qū)崿F(xiàn)代碼

    最近做完一個(gè)項(xiàng)目,發(fā)現(xiàn)瀏覽器兼容問題,模態(tài)對話框只有IE支持,但是IE9又不能支持帶框架的對話框,那個(gè)對話框的大小打死都設(shè)置不了,在網(wǎng)上查說因?yàn)楹枚喙δ鼙籌E9屏蔽了,于是自己做了一個(gè)模擬對話框的東西
    2012-05-05
  • js直接編輯當(dāng)前cookie的腳本

    js直接編輯當(dāng)前cookie的腳本

    非常有創(chuàng)意的一個(gè)腳本,直接利用window.prompt彈出修改對話框,不用到cookies目錄去找cookies文件
    2008-09-09
  • javascript中的取反再取反~~沒有意義

    javascript中的取反再取反~~沒有意義

    操作符~, 是按位取反的意思,表面上~~(取反再取反)沒有意義,下面有個(gè)不錯的示例,大家可以參考下
    2014-04-04
  • javascript下有關(guān)dom以及xml節(jié)點(diǎn)訪問兼容問題

    javascript下有關(guān)dom以及xml節(jié)點(diǎn)訪問兼容問題

    javascript下有關(guān)dom以及xml節(jié)點(diǎn)訪問兼容問題...
    2007-11-11
  • antd-mobile ListView長列表的數(shù)據(jù)更新遇到的坑

    antd-mobile ListView長列表的數(shù)據(jù)更新遇到的坑

    這篇文章主要介紹了antd-mobile ListView長列表的數(shù)據(jù)更新遇到的坑,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • js 綁定鍵盤鼠標(biāo)事件示例代碼

    js 綁定鍵盤鼠標(biāo)事件示例代碼

    用jq提交時(shí)想同時(shí)按回車鍵可以提交,此時(shí)就不要用form,需要的朋友可以參考下
    2014-02-02

最新評論

延边| 徐闻县| 正安县| 专栏| 灵璧县| 新乡县| 武鸣县| 安顺市| 墨玉县| 织金县| 灵山县| 揭东县| 金门县| 北票市| 潮州市| 清徐县| 雅安市| 普陀区| 宁河县| 通渭县| 防城港市| 南陵县| 海门市| 麦盖提县| 兴国县| 大化| 枣阳市| 宜兰县| 弋阳县| 雷波县| 手机| 章丘市| 开江县| 财经| 浠水县| 米泉市| 乐至县| 东丽区| 大埔县| 武定县| 乌拉特后旗|