switch ที่ไม่มี default มี default อยู่ และมันโยน exception ที่คุณไม่เคยพิมพ์
คุณเขียน switch ที่ครอบคลุมทุกค่าของ enum แล้วไม่ใส่ default
javac ไม่บ่น เพราะมันพิสูจน์ได้ว่าคุณครอบคลุมครบจริง · นั่นคือทั้งหมดที่มันรู้ ณ ตอนที่แปล
การทดลอง
หัวข้อที่มีชื่อว่า “การทดลอง”library หนึ่งตัว สองรุ่น · รุ่นแรกมี 2 ค่า รุ่นสองมี 3 ค่า
เพิ่มมาค่าเดียวคือ BLUE
ผู้เรียกถูกแปล ครั้งเดียว เทียบกับรุ่นแรก แล้วไม่ถูกแตะอีกเลย
| library บน classpath | RED | BLUE |
|---|---|---|
| รุ่นแรก · 2 ค่า | ok | IllegalArgumentException |
| รุ่นสอง · 3 ค่า | ok | MatchException |
MatchException ไม่ได้อยู่ในซอร์สของผู้เรียกเลย ไม่มีใครพิมพ์คำนั้น
ไม่มี throw ไม่มี default
sha ของ file .class ก่อนการทดลองคือ 31c741d3dbd629d7 และหลังการทดลองคือ 31c741d3dbd629d7
· ไม่มีไบต์ไหนขยับ
สามช่องแรกคือช่องควบคุม ไม่ใช่ของแถม
หัวข้อที่มีชื่อว่า “สามช่องแรกคือช่องควบคุม ไม่ใช่ของแถม”ตารางนี้ถูกวางให้พิสูจน์ตัวเองได้ ก่อนจะพิสูจน์อะไรให้คุณ
| ช่อง | ผล | มันตัดคำอธิบายไหนทิ้ง |
|---|---|---|
รุ่นแรก · RED | ok | ตัด “การทดลองพังตั้งแต่ต้น” |
รุ่นแรก · BLUE | IllegalArgumentException | ตัด “classpath ไม่เคยเปลี่ยนจริง” — รุ่นแรกไม่มีค่าชื่อนี้ให้หาเจอ |
รุ่นสอง · RED | ok | ตัด “รุ่นสองพังทั้งใบ” |
ถ้าไม่มีสามช่องนี้ ช่องที่สี่พิสูจน์อะไรไม่ได้เลย
ทำไม javac ถึงใส่ทางออกที่คุณไม่ได้ขอ
หัวข้อที่มีชื่อว่า “ทำไม javac ถึงใส่ทางออกที่คุณไม่ได้ขอ”javac ตรวจความครอบคลุมของ switch ตอนแปล · ตอนนั้นมันเห็น library รุ่นแรก
และมันถูกต้องตามข้อมูลที่มี
แต่ bytecode ที่มันคายออกมาต้องอยู่รอดในโลกที่ library เปลี่ยนได้ · Java ยอมให้คุณ สลับ jar โดยไม่แปลผู้เรียกใหม่ นั่นคือทั้งจุดขายและต้นเหตุ
ถ้าไม่มีทางออกนั้น switch จะไหลทะลุไปเจอ อะไรก็ไม่รู้ — คืนค่าขยะ หรือกระโดดผิดที่
· javac จึงปิดรูด้วยการโยน exception ชนิดหนึ่งที่บอกว่า
“ค่าที่ได้ ไม่ตรงกับรูปแบบไหนเลยที่รู้จักตอนแปล”
switch ที่ไม่มี default ไม่ได้แปลว่า “ทุกค่าถูกจัดการ” · มันแปลว่า
“ทุกค่าที่มีอยู่ตอนแปล ถูกจัดการ”
ประโยคที่สองอ่อนกว่าประโยคแรกมาก และความต่างจะโผล่ก็ต่อเมื่อมีคนอัปเดต dependency · ซึ่งเป็นตอนที่ไม่มีใครกำลังมอง code ชิ้นนี้อยู่
ตัวการไม่ใช่ sealed และไม่ใช่ pattern matching
หัวข้อที่มีชื่อว่า “ตัวการไม่ใช่ sealed และไม่ใช่ pattern matching”ตัวอย่างข้างบนเป็น enum ธรรมดา ไม่มี sealed ไม่มี pattern · และมันก็ยังโยน
สิ่งที่จุดชนวนคือ javac พิสูจน์ความครอบคลุมให้ได้ โดยที่คุณไม่ได้เขียน default
· เมื่อไรที่เงื่อนไขนั้นเป็นจริง ทางออกสังเคราะห์จะถูกใส่เข้ามา
ตัววัดของหน้านี้พิสูจน์ทิศตรงข้ามไว้ด้วย · มี file ฝาแฝดที่เหมือนกันทุกอย่าง
ยกเว้นมี default · bytecode ของมันมีทางออกสังเคราะห์ไหม
คำตอบคือ ไม่มี และมันรอดทุกช่องของตาราง
แล้วเครื่องมือเตือนไหม
หัวข้อที่มีชื่อว่า “แล้วเครื่องมือเตือนไหม”ตอนแปล javac ปฏิเสธ switch ที่ไม่ครอบคลุมจริง ๆ · ตัววัดมี file ที่จงใจขาด case
และ javac คืนรหัส 1 พร้อม compiler.err.not.exhaustive
แต่นั่นคือคำเตือนสำหรับ code ที่ไม่ครอบคลุมตอนนี้ ไม่ใช่คำเตือนสำหรับ code ที่ครอบคลุมตอนนี้และจะไม่ครอบคลุมพรุ่งนี้
ไม่มีสวิตช์ไหนบอกคุณว่า switch ตัวนี้จะพังถ้า dependency เพิ่มค่า
เพราะตอนแปล ข้อมูลนั้นยังไม่มีอยู่บนโลก
เอาไปใช้ยังไง
หัวข้อที่มีชื่อว่า “เอาไปใช้ยังไง”ถ้า enum หรือ sealed อยู่ในโมดูลเดียวกับคุณ และคุณคุมการแก้ของมัน
ให้ละ default ไว้ · วันที่มีคนเติมค่าใหม่ javac จะแดงให้ทุกจุดที่ต้องแก้ ซึ่งเป็นสิ่งที่คุณต้องการ
ถ้ามันมาจาก library ของคนอื่น สมการกลับด้าน · คุณไม่ได้คุมว่าเมื่อไรจะมีค่าใหม่ และคุณไม่ได้แปลใหม่ตอนมันเพิ่ม · ใส่ทางเดินสำรองที่คุณเขียนเอง จะได้เลือกได้ว่า จะพังยังไง
หน้านี้ไม่ได้บอกให้ใส่ default ทุกที่ · การใส่ default ในกรณีแรกทำให้เสียการตรวจ
ที่มีค่าที่สุดของ javac ไป และเปลี่ยน “แดงตอนแปล” เป็น “เงียบตอนรัน”
คำถามที่ต้องตอบก่อนคือ ใครคุม enum ตัวนี้ ไม่ใช่ “ควรใส่ default ไหม”
สิ่งที่หน้านี้ไม่ได้วัด
หัวข้อที่มีชื่อว่า “สิ่งที่หน้านี้ไม่ได้วัด”- ไม่ได้วัดเวลา หน่วยของหน้านี้คือชื่อ class ของ exception, รหัสออกของ javac
และ sha ของ file
.class· ทั้งสามข้ามเครื่องได้ - ไม่ได้วัด JDK ที่เก่ากว่า 21 exception ตัวนี้เข้ามาที่ 21 · วัดบน 21, 25, 26 และได้ผลเดียวกันทั้งสามตัว
- ไม่ได้วัดคอมไพเลอร์ตัวอื่น ecj ของ Eclipse อาจคาย bytecode คนละแบบ
- ไม่ได้วัดกรณีที่ค่าใหม่ถูกลบ ไม่ใช่ถูกเพิ่ม ซึ่งพังคนละแบบ
ตัววัดอยู่ที่ scripts/synthetic-default/ รันด้วย npm run verify:synthetic-default
· มันอยู่ในเกตของ CI จึงมีคนรันทุกครั้งที่มีคน push
ข้ออ้างทั้งหน้าพิงอยู่บนประโยคเดียว: คำว่า MatchException ไม่ได้ถูกพิมพ์ลงใน code
· ด่านของ CI ถอดคอมเมนต์ออกจากซอร์สของผู้เรียกแล้วตรวจว่าไม่มีคำนั้นจริง
วัดได้ ไม่มี
ตัวถอดคอมเมนต์เองก็มีช่องควบคุม เพราะถ้ามันถอด code ทิ้งไปด้วย ข้อข้างบนจะเป็นจริง
โดยบังเอิญ · ด่านจึงบังคับว่าหลังถอดแล้ว ต้องยังเหลือ switch และ case อยู่ในมือ