Tối ưu source code sử dụng Optional trong java 8
Bài đăng này đã không được cập nhật trong 9 năm
Team làm service của dự án SPEED khách hàng review source code rất chặt chẽ. Dự án sử dụng Java 8, khách hàng không chỉ yêu cầu về performance cao mà còn về style, convention code text. Nói như một member của dự án là quan điểm của khác hàng "Viết code cũng như model thời trang". Dự án viết code theo TDD, nghĩa là viết Unit test trước rồi implement logic sau. Vì vậy logic càng đơn giản, gọn gàng bao nhiêu thì viết Unit test sẽ đơn giản bấy nhiêu.
Với một hàm có logic xửa lý phức tạp thì làm sao đơn giản hóa nó để dễ dàng viết Unit test? Dễ viết Unit test đồng nghĩa với việc quản lý chất lượng source code (tính đúng của logic) tốt hơn. Một giải pháp khá đơn giản mà mọi Dev đều sử dụng là "extract to method". Nhưng đôi khi việc extract method không đáp ứng được yêu cầu.
Ví dụ, một đoạn source code được extract ra method nhỏ, nhưng method đó có thể trả về giá trị null, có nghĩa là ta phải sẻ dụng if else khi kiểm tra null giá trị trả về. Việc sử dụng nhiều if else trong một method làm cho code text rất rối mắt, đôi khi dẫn tới nhầm lẫn.
Java 8 bổ sung wrapper class là Optinal, Optional sẽ hỗ trợ ta việc sử lý giá trị null một cách rất hiệu quả, ta không phải bận tâm đến NullPointerException nữa.
1. Optional là gì?
Optional trong Java 8 là một Container object, nó bao bọc (warpper) một object, object này có thể present hoặc không. Khi object là null thì Optional trả về empty. Đặc biệt Optional hỗ trợ lambda expression.
Construction của Optional
Optional<String> empty = Optional.empty();
Empty Optional.Optional<String> full = Optional.of("Framgia");
Not null Optional
Khi bạn không chắc tham số của nó có null hay không thì sử dụng method ofNullable()
Optional<String> halfFull = Optional.ofNullable(someOtherString);
2. Các method của Optional
filter(Lambdas)
Method lọc, sẽ trả về nếu giá trị thỏa mãn điều kiện lọc (Predicate), ngược lại trả về empty Optional.
ifPresent()
Trả về true
nếu giá trị khác empty, ngược lại trả về false
.
get()
Trả về giá trị nếu khác empty, ngược lại thì throw NoSuchElementException
.
orElse(T otherValue)
Trả về giá trị nếu khác empty, ngược lại thì trả về "otherValue".
orElseGet(Supplier otherMethod)
Trả về giá trị nếu giá trị khác empty, ngược lại thì trả về giá trị trả về của method "otherMethod" được gọi.
orElseThrow(Supplier exceptionSupplier)
Trả về giá trị nếu giá trị khác empty, ngược lại throw exception "exceptionSupplier".
map(Lambdas)
Trả về giá trị được map, convert tương ứng.
Ta có thể sử dụng biểu thức lambda trong hàm.
3. Tối ưu code text với Optional
Sau đây mình xin đưa ra một tip nhỏ việc tối ưu code text từ kinh nghiệm thực tế dự án. Vì quy định bảo mật nên mình sẽ không đưa source code của dự án làm ví dụ mà sẽ viết một ví dụ đơn giản khác.
Method sau sẽ tạo thông tin thống kê doanh số bán hàng của một nhân viên theo từng mặt hàng của siêu thị trong một tháng.
public void thongKeDoanThuThangNhanVien(String maNhanVien, Integer thangNam) {
// Lấy danh sách mặt hàng
List<HangHoa> dsHangHoa = layDanhSachHangHoa();
// Lặp danh sách hàng hóa để tính doanh số bán hàng theo từng mặt hàng.
dsHangHoa.foreEach(e -> {
// Lấy danh sách các hóa đơn bán lẻ của nv bán mặt hàng e này.
List<BanGhiHoaDon> hoaDonBanLes = layHoaDonBanLe(maNhanVien, e.ma_hang_hoa);
// Lưu thông tin doanh số của mặt hàng vào DB
insertInto(
doanh_thu_thang.id,
doanh_thu_thang.ma_nhan_vien,
doanh_thu_thang.thang_nam,,
doanh_thu_thang.ma_hang
doanh_thu_thang.doanh_thu)
.values(
null,
maNhanVien,
thangNam,
e.ma_hang_hoa
tinhDoanhSo(dsHoaDonBanLe)).execute();
});
}
// Method này lấy danh sách hóa đơn bán lẻ theo nhân viên, mặt hàng
List<BanGhiHoaDon> layHoaDonBanLe (String maNhanVien, String maHangHoa) {
return selectFrom(hoa_don_ban_le_chi_tiet)
.where(ma_nhan_vien.equal(maNhanVien))
.and(ma_hang_hoa.equal(maHangHoa))
.fetch();
}
// Method sau tính doanh số của 1 một mặt hàng cho nhân viên
Double tinhDoanhSo(List<BanGhiHoaDon> dsHoaDonBanLe) {
Double doanhSo = null;
for(BanGhiHoaDon hoaDon : dsHoaDonBanLe) {
doanhSo += hodaDon.thanhTien;
}
return doanhSo;
}
Ở ví dụ trên code text là tương đối tối ưu. Method layDanhSachHangHoa()
luôn trả về giá trị khác null, vì hàng hóa là dữ liệu master nên luôn tồn tại. Nhưng method layHoaDonBanLe()
thì không như vậy. Một nhân viên có thể không bán mặt hàng nào đó, do vậy method này sẽ trả về null. Để xử lý trường hợp null ta có thể kiểm tra điều kiện với if/else. Đây là ví dụ đơn giản nên thêm điều kiện if/else
text code sẽ không rối thêm nhiều, thực tế sẽ có trường hợp phải thêm nhiều if/else
để xử lý một kết quả null trả về từ 1 method.
Sau đây mình sẽ sử dụng Optional để xử lý kết quả trả về null của method layHoaDonBanLe()
và xử lý đầu vào null của method tinhDoanhSo()
.
Optional<List<BanGhiHoaDon>> layHoaDonBanLe (String maNhanVien, String maHangHoa) {
return Optional.ofNullable(selectFrom(hoa_don_ban_le_chi_tiet)
.where(ma_nhan_vien.equal(maNhanVien))
.and(ma_hang_hoa.equal(maHangHoa))
.fetch());
}
Double tinhDoanhSo(Optional<List<BanGhiHoaDon>> dsHoaDonBanLe) {
return dsHoaDonBanLe.map(e -> e.thanhTien).collect(Collectors.toList).sum().orElse(null);
}
Trên đây là một ví dụ đơn giản về việc sử dụng Optional trong việc xử lý giá trị null, làm cho code ngắn gọn, dễ đọc, dễ viết Unit test hơn.
Tài liệu tham khảo
http://blog.joda.org/2015/08/java-se-8-optional-pragmatic-approach.html
All rights reserved