Java Helpful NullPointerExceptions: อ่าน Chain ที่ขาดให้เจอจุด null เร็วขึ้น
บรรทัดเดียวอย่าง order.customer().address().city() มีจุดที่เป็น null ได้หลายแห่ง NPE แบบเดิมบอกแค่เลขบรรทัด ทำให้คนเฝ้าระบบต้องเปิด debugger หรือเดาจากข้อมูล JEP 358 ให้ JVM อธิบาย operation ที่ล้มและส่วนของ chain ที่เป็น null ได้ละเอียดขึ้นครับ
WHY: Diagnostic ที่ดีลดเวลา ไม่ได้แก้ Contract
ข้อความที่ละเอียดช่วย incident response แต่ไม่ได้ทำให้ null ปลอดภัยขึ้น ถ้า field ต้องมีจริง เราควร validate ตอนรับข้อมูล ถ้าค่าว่างเป็นสถานะปกติ ให้ model ด้วย Optional หรือผลลัพธ์ domain ที่ชัด ไม่ควรรอ NPE เพื่อควบคุม flow
HOW: ทดสอบ Behavior โดยไม่ผูกกับถ้อยคำ JVM
ข้อความ NPE อาจต่างตาม bytecode และข้อมูล debug ตัวอย่างจึงยืนยันว่าเส้นทางผิดโยน NPE จริง แต่ไม่ assert ประโยคเต็มที่อาจเปลี่ยนระหว่าง runtime
public class Main {
record Address(String city) {}
record Customer(Address address) {}
static String city(Customer customer) {
return customer.address().city().trim();
}
public static void main(String[] args) {
boolean caught = false;
try {
city(new Customer(null));
} catch (NullPointerException error) {
caught = true;
assert error.getMessage() != null;
assert !error.getMessage().isBlank();
}
assert caught;
}
}
ใน log production เก็บ stack trace และ runtime version คู่กัน แต่ตรวจข้อความก่อนส่งออก เพราะ expression อาจเผยชื่อ field หรือโครงสร้างภายใน อย่าใช้ message เป็น machine-readable API; ใช้ exception type, error code ของระบบเรา หรือ telemetry field ที่ควบคุม schema เอง
ลด Chain เมื่อ Boundary สำคัญ
ถ้า address มาจาก API ภายนอก ให้แยกตัวแปรและตรวจตรง boundary เพื่อส่ง error ที่ผู้เรียกแก้ได้ Helpful NPE เหมาะกับบั๊กที่หลุดจาก invariant ไม่ใช่ validation message สำหรับผู้ใช้
การเปิด diagnostic ควรมาคู่กับ symbol mapping และ source revision ที่ตรงกับ binary ไม่เช่นนั้นบรรทัดที่เห็นอาจไม่ตรง source ปัจจุบัน
Recap
- ใช้ Helpful NPE เพื่อลดเวลาหาจุด null ใน chain
- อย่าพึ่งถ้อยคำ exception เป็นสัญญาให้ระบบอื่น parse
- validate required data ที่ boundary และ model optional state ให้ชัด
- เก็บ runtime version กับ source revision ใน incident artifact
พรุ่งนี้ดู NPE ที่เกิดบ่อยหนึ่งรายการ แล้วตัดสินว่าควรเพิ่ม invariant check ตรงไหน แทนการเพิ่ม catch รอบกว้างครับ