Khi mới làm quen với Java, sau khi thấy dòng NullPointerException trên console. Tôi sẽ làm như thế này :
Thú thật là cách làm này không hay tẹo nào. Nhưng tôi cá là lúc đó sẽ có kha khá bạn làm giống tôi :D
Thế nhưng, nước chảy đá mòn. Tính đến thời điểm hiện tại, tôi tự tin rằng mình có thể khống chế được NULL, tự tin đến nỗi tôi không thể nhớ chính xác lần cuối cùng tôi gặp NullPointerException là khi nào (do lâu quá rồi).
Tự hào thế đủ rồi, bây giờ tôi sẽ ghi lại cho các bạn một số kinh nghiệm xử lý NULL của chính tôi. Hy vọng một ngày nào đó trong tương lai gần, bạn có thể khống chế được chúng.
#1 Đừng phức tạp hóa một vấn đề phức tạp
Bản thân việc xử lý NULL đã khá phức tạp, do đó chúng ta nên tiếp cận và tiến hành xử lý nhanh gọn, chính xác nhất có thể, tránh dài dòng mất thời gian.
Tôi đã chứng kiến một anh chàng Junior vác cả Object method và Optional class để xử lý một đoạn logic nhỏ (trong khi đó chỉ cần 3-4 dòng null check bình thường là có thể giải quyết được rồi).
// Dùng máy chém để giết ruồi
if(Optional.ofNullable(myVariable).isPresent();
// Dùng dao mổ trâu để giết ruồi
if(Objects.nonNull(myVariable))
// ....trong khi đây là giải pháp giết ruồi hiệu quả nhất
if(myVariable != null)
#2 sử dụng object method như là stream predicate
Từ ví dụ trên bạn thấy Objects.isNull() và Objects.nonNull() không phải là đáp án tối ưu cho bài toán xử lý NULL.
Tuy vậy, khi áp dụng cho stream, chúng ta sẽ cần 2 method này, và đây cũng chính là lý do chúng được đưa vào trong JDK
myStream.filter(Objects::nonNull)
myStream.anyMatch(Objects:isNull)
#3 Không bao giờ pass argument mang giá trị null
Đây là một nguyên tắc để viết code sạch. Nếu bạn còn lơ mơ, tôi sẽ giải thích ngay bây giờ.
Việc pass argument mang giá trị null với ngụ ý rằng không có giá trị của argument đó là một ý tưởng tồi bởi 2 lý do sau:
Bạn phải đọc kỹ phần implementation của một hàm (hàm mà ta định pass giá trị null) đồng thời đảm bảo các hàm phụ thuộc nó đã có các đoạn logic code xử lý NULL.
Mỗi khi chỉnh sửa hàm, ta sẽ phải để ý từng dòng code để xem giá trị null đã được chuyển đến đâu
Bằng việc áp dụng nguyên tắc "không bao giờ pass argument mang giá trị null", 2 vấn đề trên chỉ còn là dĩ vãng.
Đối với các hàm mang Optional parameter thì sao? Chỉ cần overload hàm đó với một tập các parameter khác.
void kill(){
kill(self);
}
void kill(Person person){
person.setDeathTime(now());
}
Bạn cần chú ý, 2 vấn đề gây phiền toái trên hầu như không xuất hiện trong phạm vi một vài class đơn lẻ. Vì vậy, chúng ta không nhất thiết phải áp dụng nguyên tắc này cho các private method (nhưng hãy chắc chắn bạn đã check null trước khi pass giá trị vào hàm)
#4 Validate public API arguments
Rõ ràng bạn không thể áp dụng nguyên tắc 3 cho một public API. Do đó, nguyên tắc thứ 4 này là giải pháp tương đối "chuẩn" cho việc xử lý NULL khi làm việc với public API. Thực ra thì nguyên tắc #4 cũng khá đơn giản: luôn kiểm tra argument trước khi pass nó vào các hàm trong API.
Một gợi ý nhỏ: bạn có thể sử dụng requireNonNull() từ Object class:
public Foo(Bar bar, Baz baz){
this.bar = Objects.requireNonNull(bar,"bar must not be null");
this.baz = Objects.requireNonNull(baz,"baz must not be null");
}
Làm quen và triển khai các kiến trúc xây dựng ứng dụng trong khóa thực tập NodeJS Full Stack
#5 Xem xét áp dụng Optional
Trước khi Java 8 xuất hiện, việc một method trả về NULL trong trường hợp không tìm được giá trị trả về là quá phổ biến (return value missing). Điều này tiềm ẩn nhiều lỗi và việc khắc phục cũng không mấy dễ chịu (lập trình viên sẽ phải tra cứu documentation, thậm chí là xem từng dòng code để phát hiện và đánh giá nguy cơ trả về giá trị NULL).
Java 8 đã mang đến một công cụ mới: Optional. Optional được thiết kế để áp dụng cho hoàn cảnh kể trên (return value missing). Vì vậy, bạn nên xem xét wrap return type của mình với Optional:
Optional<String> makingCheck(){
//code check null
}
makingCheck().orElseThrow(SomeKindOfException::new);
#6 Return collection trống nay vì return null
Khi xử lý NULL với Collections, mọi thứ lại khác đi một chút.
Collections có thể chứa số lượng phần tử bất kỳ, do đó, nó hoàn toàn có thể không chứa phần tử nào. Thật vậy, có hẳn method emptyXXX() trong Collections để trả về một Collection trống. Chúng ta hoàn toàn có thể tận dụng method này thay vì phức tạp hóa mọi thứ với Optional.
List<String> findSomething(){
if(someCondition){
return Collections.emptyList();
}
//code
}
#7 Optional không dành cho các field
Như tôi đã đề cập ở nguyên tắc số 5, Optional được thiết kế để xử lý return values bị bỏ trống. Điều này đồng nghĩa với viêc không nên áp dụng Optional cho các field trong một class.
#8 Throw Exception
Khi bạn đang code logic xử lý và muốn ám chỉ trường hợp "xấu" đang xảy ra, hãy throw exception thay vì return null.
#9 Kiểm thử?
Kiểm thử code không chỉ giới hạn trong việc phát hiện và xử lý null, nó là cả một quy trình khi phát triển ứng dụng. Hãy nhớ rằng không có giải pháp tối ưu nào không bao gồm quá trình kiểm thử.
#10 Kiểm tra nhiều hơn một lần
Cẩn thận không bao giờ là thừa, việc kiểm tra (nhiều lần) các tham chiếu để chắc chắn chúng không trỏ đến null sẽ tiết kiệm thời gian và công sức của bạn trong các dự án lớn.
Khi làm việc với NULL, cẩn thận là trên hết.
Techmaster via TidyJava
Bình luận