公司剛?cè)肼毩艘幻屑?jí)Java開(kāi)發(fā),短短4行代碼居然湊齊了3個(gè)bug!我...
一、前言
Hello 大家好,我是團(tuán)長(zhǎng),今天帶來(lái)一個(gè)真實(shí)案例,讓大家更深刻的理解空指針異常。
公司剛?cè)肼毩艘幻屑?jí)Java開(kāi)發(fā),經(jīng)過(guò)一個(gè)星期的適應(yīng)學(xué)習(xí),各方面表現(xiàn)還不錯(cuò),于是分配了一個(gè)小的迭代給新人做。
需求很簡(jiǎn)單,把從第三方拉取的數(shù)據(jù)匹配到自身公司后臺(tái)設(shè)置的渠道后,聚合到一個(gè)列表中,批量入庫(kù)。
然而就在匹配的邏輯中,上線(xiàn)后報(bào)了個(gè)NPE,這是作為一名中級(jí)開(kāi)發(fā)不應(yīng)犯的簡(jiǎn)單錯(cuò)誤,新人被我狠狠的訓(xùn)了,記生產(chǎn)事故一次。
二、事故重現(xiàn)
1、偽代碼
說(shuō)明:偽代碼并非真實(shí)線(xiàn)上代碼,只是為了更方便,更形象的重現(xiàn)事故現(xiàn)場(chǎng)而編寫(xiě)的;真實(shí)的業(yè)務(wù)場(chǎng)景往往更加復(fù)雜,NPE的漏洞隱藏在更深處,不易code view出來(lái),也不易測(cè)試出來(lái);生產(chǎn)環(huán)境NPE是較常見(jiàn)的異常,希望大家不要糾結(jié)為什么測(cè)試沒(méi)測(cè)出來(lái),關(guān)鍵還是通過(guò)這樣一個(gè)案例了解NPE的原因和解決方案。
//?后臺(tái)設(shè)置的渠道
String?channelNo?=?channelDao.getOne().getChannelNo();
//?第三方拉取的數(shù)據(jù)
List<ThirdData>?thirdDataList?=?httpClientUtils.getThirdDatas(DateUtils.today());
//?匹配過(guò)濾
thirdDataList.stream().filter(o?->channelNo.equals(o.getChannelNo())).collect(Collectors.toList());
//?批量入庫(kù)
thirdDataDao.saveAll(thirdDataList);
2、分析與解決
有經(jīng)驗(yàn)、技術(shù)扎實(shí)的同學(xué)看到這里應(yīng)該或多或少能發(fā)現(xiàn)問(wèn)題了。其實(shí)啊,這四段代碼是作者精心設(shè)計(jì)的,可謂是臥龍鳳雛。
短短四行代碼居然湊齊了3個(gè)NPE,我枯了~~
我們逐行分析:
3、第一行分析
channelDao.getOne()如果返回為null,那么調(diào)用getChannelNo()會(huì)報(bào)NPE。
4、解決辦法
1、使用防御性編程,提前返回(需根據(jù)具體業(yè)務(wù)場(chǎng)景而定)
//?如果channelNo是方法邏輯執(zhí)行的必須元素,推薦用此方法
Channel?channel?=?channelDao.getOne();
if?(channel?==?null)?{
????return;
}
2、使用三目運(yùn)算,返回空字符串("")
//?返回兜底的空字符串
String?channelNo?=?channelDao.getOne()?==?null???""?:?channelDao.getOne().getChannelNo();
3、使用Optional函數(shù),返回空字符串("")
String?channelNo?=?Optional.ofNullable(channelDao.getOne()).orElse("");
5、第三行分析(1)
thirdDataList如果為null,那么調(diào)用stream()會(huì)報(bào)NPE。
通過(guò)下面的源碼截圖就能知道原因:


6、解決辦法
1、使用防御性編程,提前返回(推薦)
//?推薦使用集合工具類(lèi)判空
if?(CollectionUtils.isEmpty(thirdDataList))?{
????return;
}
2、使用if條件語(yǔ)句包裹(不推薦)
if?(CollectionUtils.isNotEmpty(thirdDataList))?{
????//?執(zhí)行后面的邏輯
}
7、第三行分析(2)
channelNo如果返回為null,那么執(zhí)行channelNo.equals(o.getChannelNo())會(huì)報(bào)NPE。
我們知道,按Java的規(guī)范String的equals()方法的調(diào)用,要求左邊是確定值,就是為了避免調(diào)用方為null的情況。然而這里調(diào)用方和equals的入?yún)⒍际亲兞?,這種情況該怎么辦呢?
1、再加一句判斷:
channelNo?!=?null?&&?channelNo.equals(o.getChannelNo())
2、其實(shí)可以用java.uti包下的Objects類(lèi)的equals方法
Objects.equals(channelNo,?o.getChannelNo())
看源碼一目了然,該方法對(duì)左邊的對(duì)象做了非空判斷

3、用其他開(kāi)源的工具類(lèi)庫(kù)或者自己實(shí)現(xiàn)
如:
org.apache.commons.lang3.StringUtils
cn.hutool.core.util.StrUtil;
最后
在這里團(tuán)長(zhǎng)要推薦一款I(lǐng)DEA的插件:
需要開(kāi)通正版IDEA的可以加我微信:poxiaozhiai6,56元一年。
這款插件就是:SonarLint

能動(dòng)態(tài)的幫您檢查代碼漏洞,像NPE這種代碼風(fēng)險(xiǎn)都會(huì)給于相應(yīng)的提示。
SonarLint還有一個(gè)大名鼎鼎的服務(wù)端叫SonarQube。
碼字不易,望多多在看、多多點(diǎn)贊??
原文鏈接:juejin.cn/post/7031445206152577061 原文作者:l拉不拉米
