Null Pointer meme

Những bất cập trong cơ chế thông báo Exception của Java

Bất kỳ ai từng lập trình Java hẳn đã từng điên đầu với các thông báo kiểu như:

java.lang.NullPointerException: null

hoặc là:

java.lang.NullPointerException: null
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:64) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:27) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:82) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:21) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.handleRequest(CommonServlet.java:144) [CommonServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.doPost(CommonServlet.java:64) [CommonServlet.class:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:647) [servlet-api.jar:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) [servlet-api.jar:na]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.filters.ExpiresFilter.doFilter(ExpiresFilter.java:1179) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.filters.AddDefaultCharsetFilter.doFilter(AddDefaultCharsetFilter.java:88) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.42]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.42]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) [catalina.jar:7.0.42]
        at com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve.invoke(RedisSessionHandlerValve.java:26) [tomcat-redis-session-manager-1.2.jar:na]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [catalina.jar:7.0.42]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.42]
        at ch.qos.logback.access.tomcat.LogbackValve.invoke(LogbackValve.java:189) [logback-access-1.1.2.jar:na]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.42]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [catalina.jar:7.0.42]
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023) [tomcat-coyote.jar:7.0.42]
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) [tomcat-coyote.jar:7.0.42]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1686) [tomcat-coyote.jar:7.0.42]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_65]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_65]
        at java.lang.Thread.run(Thread.java:745) [na:1.7.0_65]

Và bài viết này sẽ giúp bạn trút bỏ những gánh nặng liên quan tới Null Pointer Exception. Cụ thể, chúng ta sẽ xem xét và khắc phục một vài khiếm khuyết của cơ chế trace Exception (truy vết Exception) trong Java.

 

Quy trình xử lý lỗi 

Giả dụ bạn dính phải NullPointer Exception, việc đầu tiên bạn làm là trace (truy vết) Exception dựa trên log message:

java.lang.NullPointerException: null
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:64) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:27) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:82) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:21) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.handleRequest(CommonServlet.java:144) [CommonServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.doPost(CommonServlet.java:64) [CommonServlet.class:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:647) [servlet-api.jar:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) [servlet-api.jar:na]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.filters.ExpiresFilter.doFilter(ExpiresFilter.java:1179) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.filters.AddDefaultCharsetFilter.doFilter(AddDefaultCharsetFilter.java:88) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.42]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.42]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) [catalina.jar:7.0.42]
        at com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve.invoke(RedisSessionHandlerValve.java:26) [tomcat-redis-session-manager-1.2.jar:na]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [catalina.jar:7.0.42]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.42]
        at ch.qos.logback.access.tomcat.LogbackValve.invoke(LogbackValve.java:189) [logback-access-1.1.2.jar:na]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.42]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [catalina.jar:7.0.42]
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023) [tomcat-coyote.jar:7.0.42]
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) [tomcat-coyote.jar:7.0.42]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1686) [tomcat-coyote.jar:7.0.42]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_65]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_65]
        at java.lang.Thread.run(Thread.java:745) [na:1.7.0_65]

Sau khi lược bớt các thứ râu ria, ta được thông tin cốt yếu:

java.lang.NullPointerException: null
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:64) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:27) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:82) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:21) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.handleRequest(CommonServlet.java:144) [CommonServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.doPost(CommonServlet.java:64) [CommonServlet.class:na]

Như vậy, mọi đau khổ bắt nguồn từ dòng code thứ 64 trong class GetUserBillingServlet. Bạn mở class này ra, và sẽ có 2 tính huống ở đây:

 

Tình huống 1: Nếu bạn gặp may, tại dòng code thứ 64 này chỉ có 1 object. Và không còn nghi ngờ gì nữa, đó là thủ phạm.

if(user.isCustomer() != false) {
  …
}

Tuy nhiên, đời không như là mơ, nếu có 2 object thì ....

Tình huống 2:

if(user.isCustomer() != false && account.equals(id)) {
  ...
}

Chúng ta không thể khẳng định user hay account bị null....

Mặc dù không quá khó để xử lý các trường hợp như vậy. Nhưng đôi khi chúng sẽ làm nhiều fresher hoặc junior bối rối.

 

Vỏ quýt dày có móng tay nhọn. Hãy tham khảo 4 giải pháp sau:

 

#1 Chia tách các dòng code dài

if ((user.isCustomer() != false) &&
    (account.equals(id))) {
  ...
}

Nếu dòng code trên được tách ra như mẫu này, chắc chắn phần log vẫn chỉ báo lỗi ở 1 dòng (hoặc 2 dòng nếu cả 2 object bị null) nhưng rõ ràng chúng ta đã có đủ chứng cứ để tìm được "thủ phạm".

Nếu có thể, hãy thực hành trên aggregate operation cho stream của Java 8.

Một số guide line của Google, Twitter, Mozilla cũng đề cập tới những tác động tích cực của việc viết code sạch đẹp, hãy tham khảo ở đây 

 

#2 Bổ sung code Check null

Một giải pháp mà... ai cũng nghĩ ra. Tất nhiên check null sẽ làm code trở nên dài dòng, nhưng không check null thì fix bug khổ. 

Check null

#3 Xuất log dài hơn

Hãy tham khảo bài viết sau: The Ultimate Guide - 5 Methods for debugging production at server scale

 

#4 Đứng trên vai người khổng lồ

Takipi (hiện này là Over Ops) đã phát triển một tool phân tích lỗi. Khi có NullPointer Exception hoặc bất kỳ lỗi nào khác, Over Ops sẽ phân tích và dẫn developer đến biến bị lỗi. 

Giải pháp này rất hữu ích khi cần fix bug nhanh nhưng mặt trái của nó là làm developer "lười" hơn.

Check Null

Tham khảo bản gốc tại Dzone của Alex Zhitnitsky.