<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          代碼review,瑞出事來(lái)了!

          共 3898字,需瀏覽 8分鐘

           ·

          2022-04-11 12:22

          不久之前,部門進(jìn)行了一次代碼評(píng)審。

          代碼整體比較簡(jiǎn)單,該吹B的地方都已經(jīng)吹過(guò)了,無(wú)非是些if else的老問(wèn)題而已。當(dāng)翻到一段定時(shí)任務(wù)的一步執(zhí)行代碼時(shí),我的雙眼一亮,覺(jué)得該BB兩句了。

          誰(shuí)知這群家伙,評(píng)審的時(shí)候滿滿的認(rèn)同感,但評(píng)審結(jié)束不久,就給我冠了個(gè)事B的稱號(hào)。

          今天我就把當(dāng)時(shí)的這些話兒整理整理,讓大家說(shuō)道說(shuō)道,我到底是不是個(gè)事B。淦!

          一個(gè)任務(wù)處理例子

          代碼的結(jié)構(gòu)大體是這樣的。

          通過(guò)定時(shí),這段代碼每天晚上凌晨都要對(duì)數(shù)據(jù)庫(kù)的記錄進(jìn)行一遍對(duì)賬。主要的邏輯,就是使用獨(dú)立的線程,漸進(jìn)式的讀取數(shù)據(jù)庫(kù)中的相關(guān)記錄,然后把這些記錄,放在循環(huán)中逐條進(jìn)行處理。

          ExecutorService?service?=?Executors.newFixedThreadPool(10);
          ...
          service.submit(()->{
          ????while(true){
          ????????if(CollectionUtils.isEmpty(items)){
          ????????????break;
          ????????}
          ????????List?items?=?queryPageData(start,?end);?//?分頁(yè)邏輯
          ????????for(Data?item?:?items){
          ????????????try?{
          ????????????????Thread.sleep(10L);
          ????????????}?catch?(InterruptedException?e)?{
          ????????????????//noop?
          ????????????}
          ????????????processItem(item);
          ????????}
          ????}
          });

          等一下。在代碼馬上被翻過(guò)去的時(shí)候,我叫停了,這里的processItem沒(méi)有捕獲異常

          通常情況下,這不會(huì)有什么問(wèn)題。但靜好的歲月,總是偶爾會(huì)被一些隨機(jī)的事故打斷。如果這是你任務(wù)的完整代碼,那它就有一種非常隱晦的故障處理方式。即使你的單元測(cè)試寫的再好,這段代碼我們依然可以通過(guò)遠(yuǎn)程投毒的方式,通過(guò)問(wèn)題記錄來(lái)讓它產(chǎn)生問(wèn)題。

          是的。以上代碼的根本原因,就是沒(méi)有捕捉processItem函數(shù)可能產(chǎn)生的異常。如果在記錄處理的時(shí)候,有任何一條拋出了異常,不管是checked異常還是unchecked異常,整個(gè)任務(wù)的執(zhí)行都會(huì)終止!

          不要覺(jué)得簡(jiǎn)單哦,踩過(guò)這個(gè)坑的同學(xué),請(qǐng)記得扣個(gè)666?;蛘叻幌履愕娜蝿?wù)執(zhí)行代碼,看看是不是也有這個(gè)問(wèn)題。

          Java編譯器在很多情況下都會(huì)提示你把異常給捕捉了,但總有些異常會(huì)逃出去,比如空指針異常。如下圖,RuntimeException和Error都屬于unchecked異常。

          RuntimeException可以不用try...catch進(jìn)行處理,但是如果一旦出現(xiàn)異常,則會(huì)導(dǎo)致程序中斷執(zhí)行,JVM將統(tǒng)一處理這些異常。

          你捕捉不到它,它自然會(huì)讓你的任務(wù)完蛋。

          如果你想要異步的執(zhí)行一些任務(wù),最好多花一點(diǎn)功夫到異常設(shè)計(jì)上面。在這上面翻車的同學(xué)比比皆是,這輛車并不介意再帶上你一個(gè)。

          評(píng)審的小伙很謙虛,馬上就現(xiàn)場(chǎng)修改了代碼。

          不要生吞異常

          且看修改后的代碼。

          ExecutorService?service?=?Executors.newFixedThreadPool(10);
          ...
          service.submit(()->{
          ????while(true){
          ????????if(CollectionUtils.isEmpty(items)){
          ????????????break;
          ????????}
          ????????List?items?=?queryPageData(start,?end);?//?分頁(yè)邏輯
          ????????for(Data?item?:?items){
          ????????????try?{
          ????????????????Thread.sleep(10L);
          ????????????}?catch?(InterruptedException?e)?{
          ????????????????//noop?
          ????????????}
          ????????????try{
          ????????????????processItem(item);
          ????????????}catch(Exception?ex){
          ????????????????LOG.error(...,ex);
          ????????????}
          ????????}
          ????}
          });
          ...
          service.shutdownNow();

          為了控制任務(wù)執(zhí)行的頻率,sleep大法是個(gè)有效的方法。

          代碼里考慮的很周到,按照我們上述的方式捕捉了異常。同時(shí),還很貼心的把sleep相關(guān)的異常也給捕捉了。這里不貼心也沒(méi)辦法,因?yàn)椴谎a(bǔ)齊這部分代碼的話,編譯無(wú)法通過(guò),我們姑且認(rèn)為是開發(fā)人員的水平夠?qū)拧?/p>

          由于sleep拋出的是InterruptedException,所以代碼什么也沒(méi)處理。這也是我們代碼里常見的操作。不信打開你的項(xiàng)目,忽略InterruptedException的代碼肯定多如牛毛。

          此時(shí),你去執(zhí)行這段代碼,雖然線程池使用了暴力的shutdownNow函數(shù),但你的代碼依然無(wú)法終止,它將一直run下去。因?yàn)槟愫雎粤薎nterruptedException異常。

          當(dāng)然,我們可以在捕捉到InterruptedException的時(shí)候,終止循環(huán)。

          try?{
          ????Thread.sleep(10L);
          }?catch?(InterruptedException?e)?{
          ????break;
          }

          雖然這樣能夠完成預(yù)期,但一般InterruptedException卻不是這么處理的。正確的處理方式是這樣的:

          while?(true)?{
          ????Thread?currentThread?=?Thread.currentThread();
          ????if(currentThread.isInterrupted()){
          ????????break;
          ????}
          ????try?{
          ????????Thread.sleep(1L);
          ????}?catch?(InterruptedException?e)?{
          ????????currentThread.interrupt();
          ????}
          }

          除了捕捉它,我們還要再次把interrupt狀態(tài)給復(fù)位,否則它就隨著捕捉給清除了。InterruptedException在很多場(chǎng)景非常的重要。當(dāng)有些方法一直阻塞著線程,比如耗時(shí)的計(jì)算,會(huì)讓整個(gè)線程卡在那里什么都干不了,InterruptedException可以中斷任務(wù)的執(zhí)行,是非常有用的。

          但是對(duì)我們現(xiàn)在代碼的邏輯來(lái)說(shuō),并沒(méi)有什么影響。被評(píng)審的小伙伴不滿意的說(shuō)。

          還有問(wèn)題!

          有沒(méi)有影響是一回事,是不是好的習(xí)慣是另一回事?。我盡量的裝了一下B,其實(shí),你的異常處理代碼里還有另外隱藏的問(wèn)題。

          還有什么問(wèn)題?,大家都一改常日慵懶的表情,你倒是說(shuō)說(shuō)。

          我們來(lái)看一下小伙伴現(xiàn)場(chǎng)改的問(wèn)題。他直接使用catch捕獲了這里的異常,然后記錄了相應(yīng)的日志。我要說(shuō)的問(wèn)題是,這里的Exception粒度是不對(duì)的,太粗魯。

          try{
          ????processItem(item);
          }catch(Exception?ex){
          ????LOG.error(...,ex);
          }

          processItem函數(shù)拋出了IOException,同時(shí)也拋出了InterruptedException,但我們都一致對(duì)待為普通的Exception,這樣就無(wú)法體現(xiàn)上層函數(shù)拋出異常的意圖。

          比如processItem函數(shù)拋出了一個(gè)TimeoutExcepiton,期望我們能夠基于它做一些重試;或者拋出了SystemBusyExcption,期望我們能夠多sleep一會(huì),給服務(wù)器一點(diǎn)時(shí)間。這種粗粒度的異常一股腦的將它們捕捉,在新異常添加的時(shí)候根本無(wú)法發(fā)現(xiàn)這些代碼,會(huì)發(fā)生風(fēng)險(xiǎn)。

          一時(shí)間會(huì)議室里寂靜無(wú)比。

          我覺(jué)得你說(shuō)的很對(duì)?,一位比較資深的老鳥說(shuō),?你的意思是把所有的異常情況都分別捕捉,進(jìn)行精細(xì)化處理。但最后你還是要使用Exception來(lái)捕捉RuntimeException,異常還是捕捉不到啊

          果然是不同凡響的發(fā)問(wèn)。

          優(yōu)秀的、標(biāo)準(zhǔn)的代碼寫法,其中無(wú)法實(shí)施的一個(gè)重要因素,就是項(xiàng)目中的其他代碼根本不按規(guī)矩來(lái)。如果我們下層的代碼,進(jìn)行了正確的空指針判斷、數(shù)組越界操作,或者使用類似guava的Preconditions這類API進(jìn)行了前置的異常翻譯,上面的這種問(wèn)題根本不用回答。

          但上面這種代碼的情況,我們就需要手動(dòng)的捕捉RuntimeException,進(jìn)行單獨(dú)的處理。

          你們這個(gè)項(xiàng)目,爛代碼太多了,所以不好改。我雖然有情商,但我更有脾氣。

          大家不歡而散。

          End

          我實(shí)在是想不通,代碼review就是用來(lái)發(fā)現(xiàn)問(wèn)題的。結(jié)果這review會(huì)一開下來(lái),大家都在背后諷刺我。這到底是我的問(wèn)題呢?還是這個(gè)團(tuán)隊(duì)的問(wèn)題呢?讓人搞不懂。

          你們?cè)诩m結(jié)使用Integer還是int的時(shí)候,我也沒(méi)說(shuō)什么呀,現(xiàn)在就談點(diǎn)異常處理的問(wèn)題,就那么玻璃心受不了了。這B不能全都讓你們裝了啊。

          什么?你要review一下我的代碼?看看我到底有沒(méi)有像我說(shuō)的一樣寫代碼,有沒(méi)有以身作則?是在不好意思,我可是架構(gòu)師哎,我已經(jīng)很多年沒(méi)寫代碼了。

          你的這個(gè)愿望讓你落空了!


          往期推薦

          輕量級(jí)動(dòng)態(tài)線程池才是“王道”?


          synchronized 底層了解一下...


          如何抓到入侵網(wǎng)站的黑客?




          有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號(hào)


          好文章,我在看??

          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美黄色三级视频 | 日本亚洲欧洲视频 | 麻豆91AV | 亚洲黄片在线免费看 | 人妻夜夜爽天天爽麻豆三区网站 |