世界那麼大,景點那麼多。有些時候,換個方式,換個角度,換個陪同,都會有不同感受與收穫。寫代碼也亦如此。
相信不少人有這樣的經歷,在項目比較忙的時候,都是先考慮實現,用當時覺得最好的方式先實現方案,在項目不忙的時候,再看下之前代碼,想下有什麼更好的實現方案,或者優化方案。筆者也不例外,下面就和讀者們分享一下本身最近在特定場合下,代替if-else,switch的解決方案。若是你們有什麼想法,歡迎在評論區內留言,你們多多交流。微信
好比你們可能會遇到相似下面的需求:好比某平臺的信用分數評級,超過700-950,就是信用極好,650-700信用優秀,600-650信用良好,550-600信用中等,350-550信用較差。函數
實現很簡單微信支付
function showGrace(grace) { let _level=''; if(grace>=700){ _level='信用極好' } else if(grace>=650){ _level='信用優秀' } else if(grace>=600){ _level='信用良好' } else if(grace>=550){ _level='信用中等' } else{ _level='信用較差' } return _level; }
運行也沒問題,可是問題也是有優化
1.萬一之後需求,改了好比650-750是信用優秀,750-950是信用極好。這樣就整個方法要改。spa
2.方法存在各類神仙數字:700,650,600,550。往後的維護可能存在問題。code
3.if-else太多,看着有點強迫症orm
因此下面用look-up表,把配數據置和業務邏輯分離的方式實現下對象
function showGrace(grace) { let graceForLevel=[700,650,600,550]; let levelText=['信用極好','信用優秀','信用良好','信用中等','信用較差']; for(let i=0;i<graceForLevel.length;i++){ if(grace>=graceForLevel[i]){ return levelText[i]; } } //若是不存在,那麼就是分數很低,返回最後一個 return levelText[levelText.length-1]; }
這樣的修改,優勢就是若是有需求修改,只須要修改graceForLevel,levelText。業務邏輯不須要改。blog
爲何這裏推薦配數據置和業務邏輯分離1.修改配置數據比業務邏輯修改爲本更小,風險更低圖片
2.配置數據來源和修改均可以很靈活
3.薦配置和業務邏輯分離,能夠更快的找到須要修改的代碼
若是還想靈活一些,能夠封裝一個稍微通用一點的look-up函數。
function showGrace(grace,level,levelForGrace) { for(let i=0;i<level.length;i++){ if(grace>=level[i]){ return levelForGrace[i]; } } //若是不存在,那麼就是分數很低,返回最後一個 return levelForGrace[levelForGrace.length-1]; } let graceForLevel=[700,650,600,550]; let levelText=['信用極好','信用優秀','信用良好','信用中等','信用較差'];
使用推薦配置數據和業務邏輯分離形式開發,還有一個好處,在上面例子沒體現出來,下面簡單說下。好比輸入一個景點,給出景點所在的城市。
function getCityForScenic(scenic) { let _city='' if(scenic==='廣州塔'){ _city='廣州' } else if(scenic==='西湖'){ _city='杭州' } return _city; }
輸入廣州塔,就返回廣州。輸入西湖就返回杭州。可是一個城市不止一個景點,那麼有人習慣這樣寫。
if(scenic==='廣州塔'||scenic==='花城廣場'||scenic==='白雲山'){ _city='廣州' }
若是景點不少,數據很長,看着難受,有些人喜歡這樣寫
let scenicOfHangZhou=['西湖','湘湖','砂之船生活廣場','京杭大運河','南宋御街'] if(~scenicOfHangZhou.indexOf(scenic)){ _city='杭州' }
這樣執行沒錯,可是寫出來的代碼可能像下面這樣,風格不統一。
function getCityForScenic(scenic) { let _city=''; let scenicOfHangZhou=['西湖','湘湖','砂之船生活廣場','京杭大運河','南宋御街']; if(scenic==='廣州塔'||scenic==='花城廣場'||scenic==='白雲山'){ _city='廣州' } else if(~scenicOfHangZhou.indexOf(scenic)){ _city='杭州' } return _city; }
即便用switch,也有可能出現這樣的狀況
function getCityForScenic(scenic) { let _city=''; let scenicOfHangZhou=['西湖','湘湖','砂之船生活廣場','京杭大運河','南宋御街']; switch(true){ case (scenic==='廣州塔'||scenic==='花城廣場'||scenic==='白雲山'):_city='廣州';break; case (!!~scenicOfHangZhou.indexOf(scenic)):return '杭州'; } return _city; }
雖然上面的代碼出現的機率很小,但畢竟會出現。這樣的代碼可能會形成往後維看得眼花繚亂。若是使用了配置數據和業務邏輯分離,那就能夠避免這個問題。
function getCityForScenic(scenic) { let cityConfig={ '廣州塔':'廣州', '花城廣場':'廣州', '白雲山':'廣州', '西湖':'杭州', '湘湖':'杭州', '京杭大運河':'杭州', '砂之船生活廣場':'杭州', '南宋御街':'杭州', } return cityConfig[scenic]; }
有些人不習慣對象的 key 名是中文。也能夠靈活處理
function getCityForScenic(scenic) { let cityConfig=[ { scenic:'廣州塔', city:'廣州' }, { scenic:'花城廣場', city:'廣州' }, { scenic:'白雲山', city:'廣州' }, { scenic:'西湖', city:'杭州' }, { scenic:'湘湖', city:'杭州' }, { scenic:'京杭大運河', city:'杭州' }, { scenic:'砂之船生活廣場', city:'杭州' } ] for(let i=0;i<cityConfig.length;i++){ if(cityConfig[i].scenic===scenic){ return cityConfig[i].city } } }
這樣一來,若是之後要加什麼景點,對應什麼城市,只能修改上面的cityConfig,業務邏輯不須要改,也不能改。代碼風格上面就作到了統一。
這裏簡單總結下,使用配置數據和業務邏輯分離的形式,好處
可是並非全部的if-else都建議這樣改造,有些需求不建議使用look-up改造。好比if-else不是不少,if判斷的邏輯不統一的使用,仍是建議使用if-else方式實現。可是神仙數字,要清除。
好比下面這個根絕傳入時間戳,顯示評論時間顯示的需求,
發佈1小時之內的評論:x分鐘前
發佈1小時~24小時的評論:x小時前
發佈24小時~30天的評論:x天前
發佈30天以上的評論:月/日
去年發佈而且超過30天的評論:年/月/日
實現不難,幾個if-else就好了
function formatDate(timeStr){ //獲取當前時間戳 let _now=+new Date(); //求與當前的時間差 let se=_now-timeStr; let _text=''; //去年 if(new Date(timeStr).getFullYear()!==new Date().getFullYear()&&se>2592000000){ _text=new Date(timeStr).getFullYear()+'年'+(new Date(timeStr).getMonth()+1)+'月'+new Date(timeStr).getDate()+'日'; } //30天以上 else if(se>2592000000){ _text=(new Date(timeStr).getMonth()+1)+'月'+new Date(timeStr).getDate()+'日'; } //一天以上 else if(se>86400000){ _text=Math.floor(se/86400000)+'天前'; } //一個小時以上 else if(se>3600000){ _text=Math.floor(se/3600000)+'小時前'; } //一個小時之內 else{ //若是小於1分鐘,就顯示1分鐘前 if(se<60000){se=60000} _text=Math.floor(se/60000)+'分鐘前'; } return _text; }
運行結果沒問題,可是也存在一個問題,就是這個需求有神仙數字:2592000000,86400000,3600000,60000。對於後面維護而言,一開始可能並不知道這個數字是什麼東西。
因此下面就消滅神仙數字,常量化
function formatDate(timeStr){ //獲取當前時間戳 let _now=+new Date(); //求與當前的時間差 let se=_now-timeStr; const DATE_LEVEL={ month:2592000000, day:86400000, hour:3600000, minter:60000, } let _text=''; //去年 if(new Date(timeStr).getFullYear()!==new Date().getFullYear()&&se>DATE_LEVEL.month){ _text=new Date(timeStr).getFullYear()+'年'+(new Date(timeStr).getMonth()+1)+'月'+new Date(timeStr).getDate()+'日'; } //一個月以上 else if(se>DATE_LEVEL.month){ _text=(new Date(timeStr).getMonth()+1)+'月'+new Date(timeStr).getDate()+'日'; } //一天以上 else if(se>DATE_LEVEL.day){ _text=Math.floor(se/DATE_LEVEL.day)+'天前'; } //一個小時以上 else if(se>DATE_LEVEL.hour){ _text=Math.floor(se/DATE_LEVEL.hour)+'小時前'; } //一個小時之內 else{ //若是小於1分鐘,就顯示1分鐘前 if(se<DATE_LEVEL.minter){se=DATE_LEVEL.minter} _text=Math.floor(se/DATE_LEVEL.minter)+'分鐘前'; } return _text; }
運行結果也是正確的,代碼多了,可是神仙數字沒了。可讀性也不差。
這裏也順便提一下,若是硬要把上面的需求改爲look-up的方式,代碼就是下面這樣。這樣代碼的修改的擴展性會強一些,成本會小一些,可是可讀性不如上面。取捨關係,實際狀況,實際分析。
function formatDate(timeStr){ //獲取當前時間戳 let _now=+new Date(); //求與當前的時間差 let se=_now-timeStr; let _text=''; //求上一年最後一秒的時間戳 let lastYearTime=new Date(new Date().getFullYear()+'-01-01 00:00:00')-1; //把時間差添加進去(當前時間戳與上一年最後一秒的時間戳的差)添加進去,若是時間差(se)超過這個值,則表明了這個時間是上一年的時間。 //DATE_LEVEL.unshift(_now-lastYearTime); const DATE_LEVEL={ month:2592000000, day:86400000, hour:3600000, minter:60000, } let handleFn=[ { time:DATE_LEVEL.month, fn:function(timeStr){ return (new Date(timeStr).getMonth()+1)+'月'+new Date(timeStr).getDate()+'日'; } }, { time:DATE_LEVEL.day, fn:function(timeStr){ return Math.floor(se/DATE_LEVEL.day)+'天前'; } }, { time:DATE_LEVEL.hour, fn:function(timeStr){ return Math.floor(se/DATE_LEVEL.hour)+'小時前'; } }, { time:DATE_LEVEL.minter, fn:function(timeStr){ return Math.ceil(se/DATE_LEVEL.minter)+'分鐘前'; } } ]; //求上一年最後一秒的時間戳 let lastYearTime=new Date(new Date().getFullYear()+'-01-01 00:00:00')-1; //把時間差(當前時間戳與上一年最後一秒的時間戳的差)和操做函數添加進去,若是時間差(se)超過這個值,則表明了這個時間是上一年的時間。 handleFn.unshift({ time:_now-lastYearTime, fn:function(timeStr){ if(se>DATE_LEVEL.month){ return new Date(timeStr).getFullYear()+'年'+(new Date(timeStr).getMonth()+1)+'月'+new Date(timeStr).getDate()+'日'; } }, }); let result=''; for(let i=0;i<handleFn.length;i++){ if(se>=handleFn[i].time){ result=handleFn[i].fn(timeStr); if(result){ return result; } } } //若是發佈時間小於1分鐘,之際返回1分鐘 return result='1分鐘前' }
好比有一個需求:傳入cash,check,draft,zfb,wx_pay,對應輸出:現金,支票,匯票,支付寶,微信支付。
需求也很簡單,就一個switch就搞定了
function getPayChanne(tag){ switch(tag){ case 'cash':return '現金'; case 'check':return '支票'; case 'draft':return '匯票'; case 'zfb':return '支付寶'; case 'wx_pay':return '微信支付'; } }
可是這個的硬傷仍是和上面同樣,萬一下次又要多加一個如:bank_trans對應輸出銀行轉帳呢,代碼又要改。相似的問題,一樣的解決方案,配置數據和業務邏輯分離。代碼以下。
function getPayChanne(tag){ let payChanneForChinese = { 'cash': '現金', 'check': '支票', 'draft': '匯票', 'zfb': '支付寶', 'wx_pay': '微信支付', }; return payChanneForChinese[tag]; }
同理,若是想封裝一個通用的,也能夠的
let payChanneForChinese = { 'cash': '現金', 'check': '支票', 'draft': '匯票', 'zfb': '支付寶', 'wx_pay': '微信支付', }; function getPayChanne(tag,chineseConfig){ return chineseConfig[tag]; } getPayChanne('cash',payChanneForChinese);
這裏使用對象代替 switch 好處就在於
最近在特定場合下,代替if-else和switch的解決方案就是這麼多了。if-else,switch自己沒錯,主要是想着怎麼優化代碼,讓代碼更加具備可讀性,擴展性。若是你們還有什麼優化的方案或者對方面的方案有更好的實現方案。歡迎在評論區留言。
-------------------------華麗的分割線--------------------
想了解更多,關注關注個人微信公衆號:守候書閣