你好呀,我是歪歪。
前幾天看到“Qunar技術沙龍”公眾號推送了一篇關於他們舉辦了一場“Code Review大賽”的文章。
看到 Code Review 我很感興趣啊,“啪”的一下就點進去了啊,很快啊。
難能可貴的是,“Qunar技術沙龍”本著開放共享原則,把他們的 CR 大賽真題進行了免費開放。
我當時“啪”的一下就先把真題拿到手了,然後對著真題“刷刷刷”就是一頓輸出。
不得不說,這套題出的是真的很不錯,所以寫篇文章帶大家感受一波。
但是我覺得為了讓你有更好的體驗,我強烈建議你先在拿到這套真題,自己做一遍,然後在來和我對一下答案。
那麼問題就來了:怎麼拿到真題呢?
在公眾號"why技術"後臺回覆關鍵字“CR”即可拿到。
如果你還是覺得麻煩,那麼你也可以點開下面這張圖片,然後往右邊滑動,這樣你就可以先只看到題目,而不看我的解析,(但是有可能突然看到一些奇怪的圖片),可以自己先在心裡做一遍。
還得提前說明一下的是,我寫文章的時候,官方並沒有給出每道題的標準答案是什麼,官方題解也只是出了七道,後面還會慢慢做出來,所以我給出的解析也不一定是全部正確的。
還有,我能提醒你的是,一個題目裡面可能有多個問題點,你需要儘量多的識別出來,寧可錯殺,不可放過。
因為官方還未完全公佈正確答案,那這樣就更加有意思了,因為當你發現你的答案和我的答案不一樣的時候,就是我們相互學習的時候。我特別希望你能指正我的答案,也特別感謝你能在評論區分享你的、和我不一樣的答案。
我們碰撞一下,共同進步。
另外,官方題解在這裡:
目前只解答了 7 道題目,後面的解答還在路上。但是官方的題解中整了個活兒,就是把整段程式碼餵給 ChatGPT 讓它幫忙診斷有什麼問題,可以去看看。
我只能用一個字來形容它:
第 1 題
首先,拿到這題第一眼,我就看到一個非常彆扭的地方,方法入參:cazeList?
不應該是 caseList 嗎?
其實我理解 core review 不只是應該關注業務邏輯,也應該關注到這些命名規範、變數拼寫的“小地方”。
所以,秉著寧可錯殺,不可放過的原則,這算是一個需要修改的點。
然後第二個點:入參不判斷非空的嗎?
你看第 9 行,拿著 caseList 這個入參就在開始 get(0) 了。caseList 是傳入進來的,萬一傳了一個 null 怎麼辦?
不要給我說外面可能判斷了,不會傳遞 null。不要基於假設去分析問題,再次強調:寧可錯殺,不可放過。
在這裡加上一個非空判斷,程式碼的健壯性更強。
接著看程式碼,在第 9 行做了一個批次插入的動作。被 try-catch 程式碼塊包裹了起來。
在 catch 程式碼塊裡面執行了表不存在則建立,然後再插入的動作。這個程式碼能用,但是我個人覺得:不要在 catch 程式碼塊裡面寫過多的邏輯,也就是儘量不要基於異常去做程式邏輯控制。
在插入之前先執行建立表的動作,然後再執行批次插入,這樣的正向邏輯它不香嗎?
總結一下第一題:
- 入參名字拼寫錯誤,cazeList 應該是 caseList。
- caseList 沒有做非空檢驗。
- 應該把 catch 程式碼塊裡建立表的動作,放到 try 邏輯裡面去。先嚐試建立表,再批次插入。
第 2 題
這題沒啥好說的,直接秒殺。
五秒之內沒有看出問題,說明“八股文”背的不牢。
我問你:Spring 事務註解生效的前提是什麼?
是不是事務方法的呼叫方得是經過 Spring 代理後的物件?
那麼上面程式碼中的事務會生效嗎?
肯定不會的。
經典的 this 呼叫導致事務註解失效的場景,一眼秒殺之。
第 3 題
這題也應該屬於可以“秒殺”的題目。
你看一下 getInstance 方法的實現,這是什麼老夥計?
這不就是我們可愛的基於雙重鎖檢查實現的單例模式嗎?
當面試官問你雙重鎖檢查的時候,考點是什麼?
第一個看鎖物件,這裡是 class 物件,全域性一把鎖,沒毛病。
第二個問你為什麼要判斷兩次 instance == null。
因為有可能 A,B 兩個執行緒都執行到了 16 行去搶鎖。這個時候 A 先搶到了,B 就被阻塞。A 執行緒建立完一個例項之後釋放了鎖,這個時候 B 繼續執行,如果不再次判斷 instance == null,那麼就會再次執行初始化的動作。
第三個考察點 instance 為什麼要加 volatile?
因為一個物件的建立是分為三步的:
- 分配物件的記憶體空間
- 初始化物件
- 設定instance指向剛分配的記憶體地址
如果 2 和 3 步被重排序了,那麼就會出現一個有記憶體地址,但是還沒有初始化完成的物件。
如果這個物件被其他執行緒訪問到了,那麼就是有問題的。所以我們加一個 volatile 來就禁止指令重排序,來規避這個問題。
這些都是老八股了,就不細說了。
拿著剛剛說的三個點,去和題目裡面的程式碼對比,就會發現題目裡面的程式碼是沒有加 volatile,所以這就是要修復的地方。
另外,多說一句。加 volatile 這個方案,得 JDK5 版本以上使用,因為在 JDK5 之後,使用的新的 JSR-133 記憶體模型規範,在這個規範裡面增強了 volatile 的語義。
該死,這句話是怎麼出現在我的腦子裡面的?
它就像是突然蹦出的一樣,這就是傳說中的刻在基因裡面的“八股文”吧。
第 4 題
這題第一個非常明顯的最佳化點在第 20 行。
要是看到這句話之後你還沒反應過來是咋回事,那完犢子了,多半是要回去等通知了。
對於“流”的操作,關閉流的程式碼必須放在 finally 程式碼塊裡面啊,或者用 try-with-resource 語法糖,這是早期學 JavaSE 的時候就要重點掌握的東西了。
第二個要一眼秒殺的最佳化點是 22 行。
誰教你這樣列印日誌的?這樣列印可以說完全沒有任何卵用啊,因為丟失了“故事現場”。
應該這樣寫:
log.error("出現異常啦",e);
保留整個異常堆疊,方便後續定位問題,
第三個問題是 fileName,檔名稱用的是時間戳,這樣是有併發風險的。雖然看描述,這是一個給運營人員使用的功能,沒啥併發量。在這個場景下,屬於“錯殺”。
但是,“併發”這根弦還是應該繃緊的。萬一是外部客戶匯出功能呢?萬一外部客戶很多呢?萬一很多外部客戶就是在同一時間發起了統計呢?
第四個問題是我沒搞懂第 11 行裡面在幹啥,因為題目中沒有給出這部分的程式碼。
我理解應該是一個從某個資料來源根據指定引數查詢資料的功能。
為什麼要把資料查出來之後再和 TOP_CAPCITY 比較並擷取 list 呢?
如果運營商很多,比如有 10w 個運營商,但是運營只想看前 10 的運營商,也就是 TOP_CAPCITY=10 的情況。
這樣把 10w 個運營商查詢出來完全沒有意義,而且還容易產生大物件,對 GC 也不友好啊。
所以,為什麼不在查詢資料來源的時候,把 TOP_CAPCITY 作為引數傳進去呢?
我認為這是第四個最佳化點。
總結一下第四題:
- 輸出流關閉未放到 finally 程式碼塊,或者用 try-with-resource。
- 日誌列印不規範。
- 檔名稱用的是時間戳,有併發風險。
- 查詢資料的時候應該把 TOP_CAPCITY 作為條件,而不是全部查詢出來再擷取。
第 5 題
針對 future 進行異常捕獲的時候,只捕獲了 ExecutionException 異常。我覺得至少還得捕獲一個 TimeoutException 吧。
因為這個 future 是 dubbo 介面非同步獲取的,所以 get 操作獲取超時的場景應該還是比較常見吧。
另外一個就是 19 行和 29 行的日子列印,我不知道為什麼沒有采用 {} 佔位符的方式去做,而是採用了 + 拼接。這個點由於 TITLE_Arg1 引數到底長啥樣的,程式碼裡面沒有體現出來,所以只是提上一嘴。
但是同樣是日誌列印,34 行的日誌列印和前面的一樣,丟失了“故事現場”,沒有把 e 打出來。
第 6 題
這一題,我首先想到的第一個是沒有對 redPlanIdList 物件進行非空判斷。
然後一看 Safes.of 這是什麼寫法,沒見過啊。
於是用關鍵字搜尋了一把:
相關的結果很少很少,只有看到這個靠譜一點:
這是別人自己封裝的一個方法,並不是什麼元件裡面的。
好吧,就姑且當這個方法是對 null 物件進行了處理吧,把 null 轉化為了空集合,避免了空指標異常。
然後我們接著往下看,在第 7 行使用了 parallelStream,並行流來對 redPlanIdList 物件進行操作。
但是你看一下第五行,我們知道 redPlanIdList 這個物件是一個 List,但是它具體是什麼 List 呢,是 ArrayList 還是 CopyOnWriteArrayList 呢?
不得而知。
那我們假設是非執行緒安全的 ArrayList 呢?
有的同學看到這裡的時候,是不是要開始搶答了:這題我會,在多執行緒裡面使用了 ArrayList,有執行緒安全問題...
不對啊,搶答的不對啊。
即使這個地方的 redPlanIdList 是非執行緒安全的 ArrayList 也沒有關係。因為在並行流裡面,只是遍歷了它,並沒有對它進行新增操作。
所以 redPlanIdList 並沒有執行緒安全問題。
有問題的是 15 行,result 是 ArrayList,呼叫了 addAll 方法,有執行緒安全問題。需要使用執行緒安全的集合類。
或者,把 parallelStream 修改為單執行緒的 stream,對吧?
對嗎?
從程式邏輯上講是對的,使用單執行緒沒有任何毛病。
但是從需求背景上講,這個地方完完全全就可以、也應該使用多執行緒的方式去提升響應速度嘛。
所以,總結一下這題:在多執行緒裡面呼叫了 arrayList 的 addAll 方法,有執行緒安全問題。需要使用執行緒安全的集合類。
另外,追問一個八股文:請問 ArrayList 的執行緒不安全具體體現在什麼地方?
針對這個問題,早年間,我還寫過相關的文章,如果你不清楚的話,可以去翻翻:《ArrayList 的執行緒不安全》
這裡就直接說答案了。
ArrayList 的執行緒不安全體現在多執行緒呼叫 add 方法的時候。具體有兩個表現:
- 當在需要進行陣列擴容的臨界點時,如果有兩個執行緒同時來進行插入,可能會導致陣列下標越界異常。
- 由於往陣列中新增元素不是原子操作,所以可能會導致元素覆蓋的情況發生。
第 7 題
這題,我靠,第一眼,不得秒了它?
遍歷 ArrayList 的同時,還在呼叫它的 remove(int index) 方法。這寫法,江湖大忌啊。
在這個題目裡面,由於呼叫了 remove(int index) 方法,所以導致佇列的長度在變化,因此第 7 行會丟擲陣列下標越界的異常:
此外,如果把題目中的 for 迴圈修改為這樣:
for (String s : testStrArray) {
testStrArray.remove(s);
}
也是要完犢子的寫法,會丟擲 ConcurrentModificationException,老八股了,不細說了。
牢記一句話:針對 ArrayList 的刪除,用迭代器來實現,穩得一筆。
除了這個顯而易見的問題之外,在上面的題目中還有一處可以最佳化的地方。
就是 emp、empany、q、t 這幾個變數名字取的真醜。你取名的時候,取做 isAllMatch、isAnyMatch、findFirstOfStr 它不香嗎?
第 8 題
問題很好發現,精度丟失。
改正嘛...
為什麼不問問神奇的 ChatGPT 呢?
第 9 題
這個 B...
我是說這個類名稱,B,是不是有點太隨意了?
當然我猜測這可能是主辦方為了脫敏,隨便取了一個 B,不過這是不是有點太隨意了?
然後看看第 10 行,我理解不應該把異常拋給前端吧,除非你專案裡面有全域性異常處理器,但是題目中沒體現,所以也提一嘴,保平安。
接著 15 行,又是流沒有關閉,前面已經出現過一次了,不贅述。
再看看 16 行,HashSet 這玩意也不是執行緒安全的啊。所以線上程池裡面,多執行緒呼叫其 add 方法,得改。
另外,你這個 HastSet 物件的名稱叫做 visted。
我知道你是想表達這個 Set 裡面放的是被訪問過的 url。
但是 visted,什麼鬼?不應該叫做 visited 嗎,老鐵。
既然都說到拼寫錯誤了,那麼你在看看這個方法名:proceessLog。
處理日誌,應該是 processLog 吧。
多了一個字母,有一點程式碼潔癖的人,看得難受。
最後,最嚴重的一個問題,你看看第 10 行的這個執行緒池:newCachedThreadPool。
這玩意的呼叫的執行緒池的構造方法是這樣的:
這是一個可以不斷開新執行緒的執行緒池啊,然後你再讀題:在檔案中有大量的 URL。
“大量”,完犢子,執行緒過多,效能反而拉胯。
另外,執行緒池提交用的是 execute 方法,應該用 try-catch 程式碼塊把執行緒池裡面的邏輯給包起來,在 catch 裡面記錄日誌,免得後面出問題了,日誌都找不到。
第 10 題
官方給的題目中,第 9 題和第 10 題重複了。
我大抵是病了,橫豎都睡不著,坐起身來點起了一支菸,這悲傷沒有由來,黯然看著面前的兩套考卷,一套是我的,另一套也是我的。
我翻開考卷一查,這題目沒有答案,歪歪斜斜的每頁上都寫著“找出BUG”四個字。我橫豎睡不著,仔細看了半夜,才從字縫裡看出字來,滿本都寫著兩個字是:捲起來。
第 11 題
這題,讀完題就得秒啊,得秒之。
都已經說的很明白了:
- is 開頭的 boolean 型別的屬性,用的是 FastJson 序列化。
- 反序列化的工具有可能是 FastJson,也有可能是 gson。
首先第一點,即使你之前不知道 FastJson 對於 is 開頭的 boolean 型別屬性的處理方式,但是你從題目上也能讀出來,題目上特意寫了這句話,說明裡面是有故事的。
第二個,序列化工具是 FastJson,但是反序列化可能是 FastJson 也可能是 gson。
這不就離了大譜了嗎?
上面的程式碼我也拿出來給你跑一下,就很清楚了:
FastJson 序列化的時候,把 isWmsSend 變成了 wmsSend。然後用 Gson 反序列化的時候,發現沒有 isWmsSend 這個屬性呀,再一看,是基本型別的 boolean,那就給預設值,false 吧。
所以 isWmsSend 應該用包裝類 Boolean,儘量不使用基本型別 boolean。因為包裝類的預設值是 null,基本型別的預設值是 false。
在你的業務程式碼中,null 和 false 代表的可完全不是一個東西。
因為反序列化的問題導致把 true 變成了 false,這一聽就是一個大鍋啊,可不敢亂背。
第 12 題
看到這個題的第一眼,我的注意力是放在註釋上的。
你看第 13 行到第 15 行的註釋是放在程式碼後面的。但是第 23 行到 28 行的註釋又是放在程式碼上面的。
這是兩種不同的風格,我個人喜歡的是第二種。但是不管你喜歡哪一種,你至少統一一下吧。
還有,這裡又出現了 proccess 這個單詞。前面第 9 題的時候,寫的是 proceess。
我能說什麼呢?
我只能單打一個:6。
我承認,這兩個點確實有點吹毛求疵了,但是作為有點程式碼潔癖的我來說,必須要說出來才爽。
然後,再說說這個程式碼真正有問題的地方。
第一個其實很明顯,一千萬的資料量,一頁讀取一千條資料,用傳統的分頁,到後面的深度分頁,你會發現越來越慢,因為這個是有效能問題的。
應該採取遊標的方式,比如返回本次查詢的 max(id),然後下一次分頁的時候帶著 id 去查詢。
這個也算是一個八股文吧,如果你不瞭解的話,去找資料背一背。
第二個問題是沒有對 objectList 進行非空判斷,防止空指標。
第三個問題,應該在 26 行的 for 迴圈裡面搞個執行緒池,來提升資料處理速度。你這 1000w 的資料量了,單執行緒得搞到什麼時候啊。
可能營銷活動都要結束了,使用者還沒收到推送。到時候活動效果不好,又是程式設計師背鍋。
第四個問題是 35 行這個方法。雖然我不懂 UserDao 這個寫法是什麼意思,應該是公司內部封裝的持久化框架。
但是我看到這裡是把 tableName 作為引數傳遞到了方法裡面,這個時候就得提高警惕了:同志們,謹防 SQL 注入啊。安全的那幫哥們就喜歡掃這樣的程式碼,拿出來公示。
第 13 題
這個題很有意思啊,因為它的考點比較多,也算比較“偏一點”。
另外,終於把 process 寫對了,舒服了。
首先第一個,第 7 行加鎖,第 8 行萬一丟擲異常了怎麼辦?
所以這個鎖弄的有大問題啊,按照規範來說,加鎖操作是要放在 try 程式碼塊外的第一行。這之間不應該有任何其他的程式碼,以免加鎖成功了,但是未解鎖,出現死鎖的現象。
第二個問題,很明顯,你看第 15 行,processWorkB 方法有兩個入參,但是第 10 行呼叫的時候只給了一個?
這個應該是題出的失誤了,不做追究。要是有人真寫出了這樣的程式碼並沒有發現問題,說明他是個奇才。關鍵是這玩意,程式碼都編譯不過啊。
第三個問題,還是 processWorkB 方法,沒有對 param 引數進行非空判斷。
來我問你一個問題:如果 param 是 null 會出現什麼情況?
肯定是空指標異常了啊。
但是,你有沒有想過一個問題:switch/case 裡面為什麼不做成支援 null 的模式呢?
如果表示式為 null ,我們就拿著 null 去 case 裡面匹配,這樣理論上做也是可以做的。
關於這個問題,我再這篇文章裡面做過探討,有興趣可以去看看:《被阿里一道基礎面試題給幹懵了,一氣之下寫出萬字長文。》
第四個問題:在 processWorkA 裡面,隱藏的比較深,但是確實是一個我在實際編碼的過程中踩到過的一個坑。
簡單來說就是三目表示式裡面的拆箱問題。
巧了,這個問題,我之前也提到過一次:《三目表示式的自動拆箱問題》
網上的相關解析也很多,如果不瞭解的話可以定向攻破一下。
具體到這個程式碼中的問題,就是中間的 params1*params2 之後的結果,型別是 int 型。由於三目表示式型別要對齊的特性,所以 params3 會被拆箱為 int 型別。
結果,params3 引數又是一個 null。
哦豁,空指標就來了。
第 14 題
題目太長,我實在是看的費勁兒。
就......不解析了吧。
你發現了問題,可以再評論區教教我。
第 15 題
這一題,說實話,我第一眼沒看出來問題。
隔了一會,第二眼看,也沒看出來啥問題。
要不是這是一道題目,代表它確實有問題,不然在實際 review 的時候,我應該就放過這個部分了。
當我帶著它一定是有問題的心態去看問題的時候,終於還是發現了端倪。
第 6 行,多執行緒操作,一般出題人會在多執行緒裡面埋坑,只要找出多執行緒裡面執行緒不安全的操作即可。
而多執行緒裡面的核心邏輯是給 Status 的 reason 欄位賦值。再定眼一看:Status 是個列舉啊。
列舉是單例啊。
單例物件的一個欄位,在多執行緒裡面被瘋狂操作...
你明白我意思吧?
我給你搞段程式碼驗證一下:
首先,把 List 裡面的物件搞得多多的。
然後,closeStatus 方法裡面列印 log 的時候,入參的 id 肯定要和 Status 列舉中的 reason 這個字串裡面拼接的 id 一樣,對吧?
所以,我在 log 裡面列印日誌的時候,判斷 reason 不包含當前的 id。
如果有輸出,則說明有問題,能反應過來吧?
程式跑起來,確實有輸出:那就說明確實有問題:
id:4548 當前的狀態是:關閉 變更原因為:關閉當前狀態3370
id:796 當前的狀態是:關閉 變更原因為:關閉當前狀態3300
id:8621 當前的狀態是:關閉 變更原因為:關閉當前狀態8000
id:9791 當前的狀態是:關閉 變更原因為:關閉當前狀態7528
id:8283 當前的狀態是:關閉 變更原因為:關閉當前狀態7842
不要在多執行緒裡面對單例物件進行修改操作,你把握不住。
第 16 題
這題,拿到手第一眼,非常扎眼的一個問題就是 SearchCategory 物件裡面的屬性沒有被 private 修飾,不滿足 Java 對於物件進行封裝的思想。
第二個考察點其實也不難,第 22 行,兩邊都是 Integer 物件,針對封裝物件的對比,應該使用 !equals 而不是 !=。
別問為什麼,問就是 Integer 快取了 -128~127 之間的數字,在這個範圍內用 == 和這個範圍外用 ==,執行結果不一樣。
老八股了,不細說了。
第三個考察點,稍微稍微隱蔽一點:SearchCategory 沒有重寫 hashcode 和 equals 方法。
categorySet 是一個 Set 集合物件。第 25 行裡面,在把 SearchCategory 往 categorySet 裡面放。
自定義物件往 Set 集合裡面放,如果不重寫 hashcode 和 equals 方法,那麼會有重複元素滴。
如果這個你一時間沒反應過來的話,那我問你一個老八股:HashMap 的 key 是自定義物件的時候,應該注意什麼?
這樣一聽,是不是就熟悉多了。
你在結合我之前寫過的這篇文章:《悄悄給你說幾個HashCode的破事。》
輕鬆拿下。
第 17 題
這題我沒搞懂第 6 行和第 9 行,呼叫了 queryUserStock 方法,但是這個方法又在同一個類裡面。
為什麼要透過自己注入自己的方式去呼叫呢?
又不涉及到比如事務、快取這些切面相關的東西。所以我覺得這兩行程式碼沒用。
然後是第 4 行裡面的入參 userId 是基本型別 long,而到了 14 行裡面就變成了包裝型別 Long。
所以,從這個程式碼片段來說,15 行的判斷永遠為 true,因為傳遞進來的 userId 是基本型別,預設值為 0,不可能為 null。
應該保持型別一致,都用包裝型別。
還有一個點是 11 行,對於返回的集合沒有做非空判斷,萬一空指標了呢,對吧?
對個毛線,我在這裡虛晃一槍,看看你有沒有帶著自己的思考看文章。
14 行的方法是不會返回空指標的,如果沒查到資料,MyBatis 會自動幫我們封裝為空集合,而不是 null。
所以外面拿到空集合也沒有任何毛病。
另外,再這裡我再次重申一下:官方也沒有給出全部的題目解析,所以我給的答案不一定是正確的,你要帶著批判的眼光來看。
我等著你發現錯誤,指正我呢。
第 18 題
題目做到這裡後,一看到多執行緒,就開始條件反射式的想到了執行緒安全問題。
定眼一看,33 行,CopyOnWriteArrayList 是執行緒安全的,看來這次的考察點不一樣了。
從上往下看,首先是自定義執行緒池,我一眼就看出了問題:
(r, executor) -> log.error("...")
自定義拒絕策略中,僅僅列印了一行日誌,沒有做其他任何操作,有坑,容易喜提生產事件:
詳情見連線:《雖然是我遇到的一個棘手的生產問題,但是我寫出來之後,就是你的了。》
所以這個拒絕策略需要重新寫一個。
第二個問題:21 行呼叫了 filter 方法,在這個方法裡面用到了執行緒池,但是你仔細分析一下 34 行的這個 for 迴圈,要迴圈多少次?
是不是要迴圈“一頁的大小”,也就是第 19 行的 500 次。但是這個執行緒池的容量才多少?
這個執行緒池同時間做多也只能容納 120 個任務,所以這個地方引數不合理,需要調整執行緒池引數。
另外,34 行這個迴圈物件也沒有判空啊。這是一個 JSONObject 物件,一不留神,取出一個 null 怎麼辦?
第三個問題,35 行,用了 submit 提交任務,這個方法是有返回值的,但是程式碼裡面又沒有使用這個返回值,那麼為什麼不用 execute 方法呢?
第四個問題,47 行,await 方法沒有指定等待時間,容易死等。
比如就這個程式碼,執行緒池滿了之後,並不會執行 countDown 方法,喜提一個永遠在阻塞的執行緒。
這題靠多執行緒,算是撞我槍口上了,秒了,再見。
第 19 題
這題沒啥特別好說的,第 18 行 repaceFirst 的第一個入參是正規表示式。
所以,如果供應商的名字裡面有一些什麼 *?.| 這些玩意,就會出現問題。
其實 Sting 的一些關於內容替換的方法裡面好多都是支援正則的,所以如果你不清楚這個“坑”的話,那後面需要注意一下了。
這個題就不多說了,寫個案例,一目瞭然:
別問我為什麼一眼就看到了 repaceFirst,因為我在它身上吃過虧。還有 split,這個“坑爹”的方法,也是一樣,有正則坑。
第 20 題
這題...
恕我學藝不精,確實沒發現有什麼問題啊?
難道是 13 行這個 false?它代表用 JDK 的動態代理,要實現介面,但是這個程式碼也確實實現了介面呀?
肉眼沒看出來,我把程式碼放到自己的 idea 裡面去跑也是能正常跑的:
如果這題在 AOP 上有考點的話,而且我覺得肯定是有考點的,我確實拿不到這個分。
如果考的是不要把 http 介面寫在專案啟動類上的這種編碼結構上的規範的話。好吧,只能說考查角度刁鑽。
不知道你有沒有看出啥問題,可以在評論區教教我。
第 21 題
只有幾行程式碼,秒之。
第 7、8 行,用 redis 加鎖,set 方法和給值設定過期時間的方法非原子性,如果 set 完成之後,expire 無法執行, 會出現死鎖。
第 11 行,捕獲異常後沒有透過日誌列印異常具體的資訊,不方便追溯問題。
第 22 題
其實題目做到這裡的時候,都已經有點掉入思維陷阱裡面了。
啥意思呢。
比如這個題,我只是瞟了一眼,我就知道 34 行的這個方法裡面肯定是有問題的。
因為如果沒有問題,按照出題人的習慣,這個方法的實現會略去。
所以,當我帶著這個想法去看這部分程式碼的時候,getCerts 之後直接呼叫了 get(0),不用判斷集合物件是否為空嗎?
但是,你注意啊,其實在 23 行的時候判斷了。
所以這個地方其實不是空指標的問題。
我覺得應該是需要結合到業務場景來看的,certs 是一個集合,裡面放的是乘機人的證件,一個人可能有多個證件,比如身份證、護照、工牌、打工人認證、一疊好人卡等等這些玩意,所以用集合來裝,和合理。
但是,get(0) 你怎麼給我保證獲取出來的不是好人卡,而是身份證呢?
程式碼中都沒有體現這個邏輯,所以我懷疑出題人在這裡是埋坑了。
第二個問題就很明顯了,你仔細看看題目中的三次 stream 操作。前兩次操作的都是同一個集合,然後用結果去覆蓋了 passengers 引數。
看程式碼,是會一點點流式操作的,但是不多,不然也不會分開寫了,三次操作完全可以合併為一個。
第 23 題
這題一看...
程式碼太多,看得我眼睛充血,腦殼疼。
再一看題,按照“充血模式”開發,補全一些程式碼。
這玩意感覺得結合著業務來才行啊,算了,我放棄,好吧。
這題我不做了。
你來。
第 24 題
感覺問題在 21 行的這個 Lists.transform 方法上,但是我沒有用過這個方法,不清楚是幹啥的。
於是在網上查了一圈:
簡單來說就是第 24 行這個 for 迴圈是沒有用的。你可以理解未返回的這個 addresses 僅僅是一個檢視,對檢視進行修改,沒有任何卵用。
然後是同樣沒有看到對於異常的處理機制,如果沒有全域性異常處理機制的話,有可能會直接拋給使用者不友好的錯誤提醒。
第 25 題
第 29 行,又看到事務了,又是事務不生效,就不多說了。這個事務不生效這一點確實是一大考點,一定要把這個玩意搞的滾瓜爛熟才行。
然後是 41 行到44 行,這是個什麼神奇的寫法,鏈式 set? 沒見過。
所以我感覺這個地方也是有問題的,應該是想用 Builder 模式來構建物件吧。
另外,saveData 方法裡面有一些資料庫操作,難免會出現一點異常情況,在這個方法裡面我沒有看到對於異常的相關捕獲和處理。
最後一個點,38 行,除非你用了 MDC 鏈路追蹤,不然這行日誌沒有任何意義啊,至少再加上一個訂單號吧。才能一眼看出來是哪個訂單號被成功更新了。
附加題
官方的題目只公開了 25 題,但是歪歪歪師傅也想給你出幾道附加題,讓你來體驗一下。
我醜話說在前頭:做不出來的,都是假粉絲。
再多說一句,我都說了是附加題了,這些題肯定就很偏了,看著圖個樂就行,真正的程式碼 review 注意點,還是得看前面的題目。
附加題一
請找出下面程式碼的問題:
附加題二
請找出下面程式碼的問題:
附加題三
請解釋下面程式碼不能正常結束的原因:
附加題四
asyncResult 是一個 Future 物件,下面框起來的程式碼可以認為是無限期等待,請指出其和 future.get() 的區別:
附加題五
請找出下面程式碼的問題:
附加題六
請解釋下面程式碼不能正常結束的原因:
附加題七
以下程式是否會丟擲空指標異常,並解釋為什麼:
附加題八
請找出下面程式碼的問題:
再說一次,附加題,考點很偏,看著圖個樂就行。別把自己給 PUA 了。
最後,求個贊,不過分吧?