優(yōu)秀的代碼是優(yōu)秀應用的根基,通過代碼審查可以不斷的提高自己的代碼能力腹暖。但是提高這個事兒汇在,還是得思考的。
一脏答、isDebugEnabled()到底要不要
事情是這樣的糕殉,在使用sonar進行代碼檢查的時候,會提示你在調(diào)用日志組件時(日志等級為error以下)殖告,要預先判斷是否對應等級的日志打印功能是否開啟阿蝶,如下圖所示:
if (logger.isDebugEnabled()) {
logger.debug(message);
}
很多文章都會提到這個問題,我也說下這個例子:
//早期的日志
logger.debug("error occurred:" + somMethod());
在這個語句中黄绩,無論debug是否開啟羡洁,待打印的內(nèi)容,包含字符串拼接和方法都會被執(zhí)行爽丹,在這種情況下筑煮,當debug
等級日志為關(guān)閉的情況下,就存在性能消耗习劫。試想:日志內(nèi)容沒有打印出來,卻花了n秒來構(gòu)造參數(shù)嚼隘,是多么得不償失的诽里。
因此,sonar提示需要預先判斷飞蛹,用微小的損耗谤狡,換取系統(tǒng)可靠的保證,是很明智的卧檐。
很顯然墓懂,日志組件的開發(fā)們也發(fā)現(xiàn)了這樣的缺陷,他們也做了相應的改進:添加了字符串拼接功能
//改進后日志
logger.debug("error occurred:{}", description);
在執(zhí)行對應方法之前先執(zhí)行判斷霉囚,這樣就不會做無謂的字符串拼接工作了捕仔。但是,當參數(shù)是一個返回字符串的方法時,仍然無法避免對應方法的執(zhí)行榜跌。
顯而易見闪唆,需不需要isDebugEnabled()
方法成了一個辯證的命題。
二钓葫、異常日志堆棧去哪兒了
這個事情是這樣的悄蕾,有一次線上環(huán)境出了問題,排查了一圈我除了異常類型和信息础浮,竟找不到任何異常的堆棧信息帆调,簡直是奇恥大辱啊。案發(fā)現(xiàn)場是這樣的:
//底下這個e是捕獲的異常
logger.error("error occurred...{}, {}", id, e)
為了防止這類慘案再次發(fā)生豆同,我去翻了源碼(使用的版本為slf4j-api 1.7.1
番刊,源碼部分略),還做了一個實驗:
//先定義一個異常
Exception e = new RuntimeException("this is a runtime exception");
//情況1:給異常對象分配一個占位符
logger.debug("error occurred... {}, {}", "first string", e);
//調(diào)用異常對象的toString方法
logger.debug("error occurred... {}, {}", "third string", e.toString());
//未給異常對象分配占位符
logger.debug("error occurred... {}", "second string", e);
結(jié)果果然是還原了案發(fā)過程诱告,如下圖所示:情況1和2并沒有打印出異常的堆棧信息撵枢;在對于有占位符的參數(shù)對象,消息格式轉(zhuǎn)換會調(diào)用對象的toString()
方法精居,所以說情況1和2是相同的锄禽。
在希望打印出異常堆棧的情況下(一般情況下,異常堆棧是需要的)靴姿,不要給Throwable
參數(shù)添加字符串占位符{}
,否則不能如愿沃但。
當然,我依然相信日志組件的開發(fā)們佛吓,肯定也會發(fā)現(xiàn)這樣的一個問題宵晚,于是嘗試了最新版本的日志,發(fā)現(xiàn)無論是否有占位符维雇,異常的堆棧信息都是能正常打印出來的淤刃。不過為了避免踩這個坑,還是建議大家注意占位符的問題吱型。
三逸贾、工具類的封裝
工具類是對通用方法的封裝,通過代碼復用來提高編程效率津滞。一般工具類的使用铝侵,都是只調(diào)用其方法,不涉及到類的任何屬性和變量触徐;因此我們看到的工具類中大都如下圖所示:
- 私有的構(gòu)造方法
工具類都是靜態(tài)方法咪鲜,無需實例化,有私有的構(gòu)造方法后撞鹉,程序就無法通過new Math()
來實例化對象疟丙,要知道颖侄,實例化是有開銷的。 - final修飾
無法被繼承隆敢,其方法無法被重寫发皿。
高效的內(nèi)聯(lián)調(diào)用(也有人說是內(nèi)嵌、inline) - 靜態(tài)的方法
無法實例化拂蝎,肯定只能通過靜態(tài)方法來訪問了穴墅。 - 職責單一 (面向?qū)ο蟮幕驹瓌t)
在項目的代碼中見過這樣一個工具類:MyUtils.java
。光看這個名字温自,肯定是不知道這個類是做什么用的玄货,看下實現(xiàn)的方法:
簡單的對這些方法進行歸類:對象和Json對象的轉(zhuǎn)換、時間格式化悼泌、字符串操作松捉,這個類可以拆為三個工具類。
建議:多看看源碼馆里,積累工具類隘世,形成自己的工具類庫。
四鸠踪、線程安全對象的使用
線程安全和非線程安全的概念就不多贅述丙者,對于線程安全對象,肯定都非常熟悉营密,典型的是線程安全集合類:ConcurrentHashMap
械媒、HashTable
等
舉個例子吧,由于String.java是final修飾的類评汰,造成了很多問題纷捞,其中就有一個字符串拼接的問題。在涉及大量字符串拼接方法中被去,直接使用"str1" + "str2"
效率是極其低下的(是因為需要不斷的生成新的對象)主儡,這時候就需要借助StringBuilder
和StringBuffer
。這兩個到底哪個是線程安全的呢惨缆?忘記了沒關(guān)系糜值,點開源碼看下說明就好了:
注釋說明了StringBuilder
是非線程安全,而StringBuffer
是線程安全的(多閱讀源碼踪央,養(yǎng)成依賴源碼而不是百度的習慣臀玄。對于會思考的程序員來說是大有裨益的)
只要判斷當前的場景瓢阴,是否需要線程安全就能夠合理的使用正確的對象了畅蹂。
案例一:
public String getXxxMessage(Xxx xxx){
StringBuffer sb = new StringBuffer(); //在方法內(nèi)進行拼裝,應使用StringBuilder
sb.append(xxx.getName).append("...")
//省略部分代碼
return sb.toString()
}
案例二:
//用內(nèi)存做緩存
private Map map = new ConcurrentHashMap();
public Object getCache(String key){
....
}
public void saveCache(Object o){
....
}
案例三:
//高并發(fā)場景
private Random random = new Random();
public Object saveXX(Xx x){
int i = random.nextInt(10); //高并發(fā)情況下存在鎖競爭引起線程阻塞荣恐,影響性能
doSomething(i);
}