ตัวอย่างชีวิตจริงอะไรบ้างที่จะเข้าใจบทบาทสำคัญของการยืนยัน
ตัวอย่างชีวิตจริงอะไรบ้างที่จะเข้าใจบทบาทสำคัญของการยืนยัน
คำตอบ:
การยืนยัน (โดยวิธีของคีย์เวิร์ดassert ) ถูกเพิ่มใน Java 1.4 พวกเขาจะใช้ในการตรวจสอบความถูกต้องของค่าคงที่ในรหัส พวกเขาไม่ควรถูกเรียกใช้ในรหัสการผลิตและบ่งบอกถึงข้อผิดพลาดหรือการใช้ผิดเส้นทางของรหัส พวกเขาสามารถเปิดใช้งานในเวลาทำงานโดยวิธีการของ-ea
ตัวเลือกในjava
คำสั่ง แต่ไม่ได้เปิดตามค่าเริ่มต้น
ตัวอย่าง:
public Foo acquireFoo(int id) {
Foo result = null;
if (id > 50) {
result = fooService.read(id);
} else {
result = new Foo(id);
}
assert result != null;
return result;
}
assert
เพื่อตรวจสอบพารามิเตอร์วิธีสาธารณะ ( docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html ) ที่ควรจะโยนException
แทนที่จะฆ่าโปรแกรม
This convention is unaffected by the addition of the assert construct. Do not use assertions to check the parameters of a public method. An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. It can throw only an AssertionError.
docs.oracle.com/javase/8/docs/technotes/guides/language/ ......
สมมติว่าคุณควรจะเขียนโปรแกรมเพื่อควบคุมโรงไฟฟ้านิวเคลียร์ เป็นที่ชัดเจนว่าแม้ความผิดพลาดเล็กน้อยที่สุดอาจมีผลลัพธ์ที่เป็นความหายนะดังนั้นโค้ดของคุณจะต้องไม่มีข้อผิดพลาด (สมมติว่า JVM ปราศจากข้อผิดพลาดเพื่อการโต้แย้ง)
Java ไม่ใช่ภาษาที่สามารถตรวจสอบได้ซึ่งหมายความว่า: คุณไม่สามารถคำนวณได้ว่าผลลัพธ์ของการดำเนินการของคุณจะสมบูรณ์แบบ เหตุผลหลักสำหรับสิ่งนี้คือพอยน์เตอร์: พวกเขาสามารถชี้ไปที่ใดก็ได้หรือที่ไหนก็ได้ดังนั้นพวกเขาไม่สามารถคำนวณได้ว่าเป็นค่าที่แน่นอนอย่างน้อยที่สุดไม่อยู่ในช่วงที่เหมาะสมของรหัส เมื่อได้รับปัญหานี้จะไม่มีวิธีที่จะพิสูจน์ว่ารหัสของคุณถูกต้องทั้งหมด แต่สิ่งที่คุณสามารถทำได้คือการพิสูจน์ว่าอย่างน้อยคุณจะพบข้อผิดพลาดทุกครั้งที่มันเกิดขึ้น
แนวคิดนี้ขึ้นอยู่กับกระบวนทัศน์การออกแบบโดยสัญญา (DbC): ก่อนอื่นคุณต้องกำหนด (ด้วยความแม่นยำทางคณิตศาสตร์) สิ่งที่วิธีการของคุณควรจะทำแล้วตรวจสอบโดยการทดสอบในระหว่างการดำเนินการจริง ตัวอย่าง:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
return a + b;
}
ขณะนี้เห็นได้ชัดว่าทำงานได้ดีนักเขียนโปรแกรมส่วนใหญ่จะไม่เห็นข้อผิดพลาดที่ซ่อนอยู่ภายในอันนี้ (คำใบ้: Ariane V หยุดทำงานเนื่องจากข้อผิดพลาดที่คล้ายกัน) ตอนนี้ DbC กำหนดว่าคุณต้องเสมอตรวจสอบเข้าและส่งออกของฟังก์ชั่นเพื่อตรวจสอบว่ามันทำงานได้อย่างถูกต้อง Java สามารถทำได้ผ่านการยืนยัน:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
final int result = a + b;
assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
return result;
}
หากฟังก์ชั่นนี้ล้มเหลวคุณจะสังเกตเห็นได้ คุณจะรู้ว่ามีปัญหาในรหัสของคุณคุณรู้ว่ามันอยู่ที่ไหนและคุณรู้ว่าสิ่งที่ทำให้มัน (คล้ายกับข้อยกเว้น) และสิ่งที่สำคัญยิ่งกว่าคือ: คุณหยุดการทำงานที่ถูกต้องเมื่อมันเกิดขึ้นเพื่อป้องกันไม่ให้โค้ดเพิ่มเติมทำงานกับค่าที่ไม่ถูกต้องและอาจทำให้เกิดความเสียหายกับสิ่งที่มันควบคุม
ข้อยกเว้น Java เป็นแนวคิดที่คล้ายกัน แต่พวกเขาล้มเหลวในการตรวจสอบทุกอย่าง หากคุณต้องการการตรวจสอบมากขึ้น (ที่ความเร็วของการดำเนินการ) คุณต้องใช้การยืนยัน การทำเช่นนั้นจะทำให้โค้ดของคุณพองตัว แต่ในที่สุดคุณก็สามารถส่งมอบผลิตภัณฑ์ได้ในเวลาสั้น ๆ ในการพัฒนาอย่างน่าประหลาดใจ (ก่อนหน้านี้คุณแก้ไขข้อผิดพลาด และนอกจากนี้: หากมีข้อผิดพลาดภายในรหัสของคุณคุณจะตรวจพบมัน ไม่มีวิธีการในการเลื่อนจุดบกพร่องและทำให้เกิดปัญหาในภายหลัง
สิ่งนี้ยังไม่รับประกันสำหรับรหัสที่ปราศจากข้อบกพร่อง แต่มันใกล้กว่านั้นมากกว่าโปรแกรมปกติ
new IllegalArgumentException
ส่งต่อข้อความ? ฉันหมายถึงนอกเหนือจากการมี o เพิ่มthrows
การประกาศวิธีการและรหัสเพื่อจัดการข้อยกเว้นที่อื่น เหตุใดจึงต้องassert
ทิ้งการยกเว้นใหม่ หรือทำไมไม่ได้เป็นif
แทนassert
? ไม่สามารถรับสิ่งนี้ได้ :(
a
สามารถลบได้ การยืนยันครั้งที่สองนั้นไร้ประโยชน์ สำหรับค่า int จะเป็นกรณีที่ a + b - b == a การทดสอบนั้นจะล้มเหลวก็ต่อเมื่อคอมพิวเตอร์เสียขั้นพื้นฐานเท่านั้น เพื่อป้องกันสิ่งที่อาจเกิดขึ้นคุณต้องตรวจสอบความสอดคล้องของ CPU หลายตัว
การยืนยันเป็นเครื่องมือในการพัฒนาเพื่อดักจับบั๊กในโค้ดของคุณ พวกมันถูกออกแบบมาให้ลบออกได้ง่ายดังนั้นจึงไม่มีอยู่ในรหัสการผลิต ดังนั้นการยืนยันไม่ได้เป็นส่วนหนึ่งของ "โซลูชัน" ที่คุณส่งมอบให้กับลูกค้า เป็นการตรวจสอบภายในเพื่อให้แน่ใจว่าสมมติฐานที่คุณทำนั้นถูกต้อง ตัวอย่างที่พบบ่อยที่สุดคือการทดสอบหาค่าว่าง มีวิธีการมากมายที่เขียนเช่นนี้:
void doSomething(Widget widget) {
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
บ่อยครั้งในวิธีเช่นนี้วิดเจ็ตไม่ควรเป็นโมฆะ ดังนั้นถ้ามันเป็นโมฆะมีข้อผิดพลาดในรหัสของคุณที่ไหนสักแห่งที่คุณต้องติดตาม แต่โค้ดด้านบนจะไม่บอกคุณ ดังนั้นในความพยายามอย่างดีที่จะเขียนรหัส "ปลอดภัย" คุณยังซ่อนข้อบกพร่องด้วย มันดีกว่าที่จะเขียนโค้ดแบบนี้:
/**
* @param Widget widget Should never be null
*/
void doSomething(Widget widget) {
assert widget != null;
widget.someMethod(); // ...
... // do more stuff with this widget
}
ด้วยวิธีนี้คุณจะต้องแน่ใจว่าได้ตรวจพบข้อผิดพลาดนี้ก่อน (นอกจากนี้ยังมีประโยชน์ในการระบุในสัญญาว่าพารามิเตอร์นี้ไม่ควรเป็นโมฆะ) ตรวจสอบให้แน่ใจว่าได้เปิดการยืนยันเมื่อคุณทดสอบรหัสของคุณในระหว่างการพัฒนา (และชักชวนเพื่อนร่วมงานของคุณให้ทำเช่นนี้บ่อยครั้งก็เป็นเรื่องยากซึ่งฉันคิดว่าน่ารำคาญมาก)
ตอนนี้เพื่อนร่วมงานของคุณบางคนจะคัดค้านรหัสนี้โดยยืนยันว่าคุณควรใส่เช็คว่างเพื่อป้องกันข้อยกเว้นในการผลิต ในกรณีนั้นการยืนยันยังมีประโยชน์ คุณสามารถเขียนแบบนี้:
void doSomething(Widget widget) {
assert widget != null;
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
ด้วยวิธีนี้เพื่อนร่วมงานของคุณจะมีความสุขที่การตรวจสอบโมฆะมีรหัสการผลิต แต่ในระหว่างการพัฒนาคุณไม่ได้ซ่อนข้อผิดพลาดเมื่อวิดเจ็ตเป็นโมฆะ
นี่คือตัวอย่างในโลกแห่งความจริง: ฉันเคยเขียนวิธีที่เปรียบเทียบสองค่าโดยพลการเพื่อความเท่าเทียมกันโดยที่ค่าใดค่าหนึ่งอาจเป็นโมฆะ:
/**
* Compare two values using equals(), after checking for null.
* @param thisValue (may be null)
* @param otherValue (may be null)
* @return True if they are both null or if equals() returns true
*/
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = thisValue.equals(otherValue);
}
return result;
}
รหัสนี้มอบหมายงานของequals()
วิธีการในกรณีที่ค่านี้ไม่เป็นโมฆะ แต่จะถือว่าequals()
วิธีการปฏิบัติตามสัญญาequals()
อย่างถูกต้องโดยการจัดการพารามิเตอร์ว่าง
เพื่อนร่วมงานคัดค้านรหัสของฉันบอกฉันว่าหลายชั้นเรียนของเรามีequals()
วิธีการบั๊กกี้ที่ไม่ได้ทดสอบว่าเป็นโมฆะดังนั้นฉันควรนำการตรวจสอบนั้นมาใช้ในวิธีนี้ เป็นที่ถกเถียงกันว่าเป็นสิ่งที่ฉลาดหรือหากเราควรบังคับให้เกิดข้อผิดพลาดดังนั้นเราจึงสามารถตรวจพบและแก้ไขได้ แต่ฉันรอการอนุมัติจากเพื่อนร่วมงานของฉันและทำการตรวจสอบว่างซึ่งฉันได้ทำเครื่องหมายไว้ด้วยความคิดเห็น:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = otherValue != null && thisValue.equals(otherValue); // questionable null check
}
return result;
}
การตรวจสอบเพิ่มเติมที่นี่other != null
มีความจำเป็นหากequals()
วิธีการล้มเหลวในการตรวจสอบเป็นโมฆะตามที่กำหนดไว้ในสัญญา
แทนที่จะมีส่วนร่วมในการอภิปรายที่ไร้ผลกับเพื่อนร่วมงานของฉันเกี่ยวกับภูมิปัญญาของการให้รหัสรถ buggy อยู่ในฐานรหัสของเราฉันเพียงแค่ใส่สองยืนยันในรหัส การยืนยันเหล่านี้จะแจ้งให้เราทราบในระหว่างขั้นตอนการพัฒนาหากหนึ่งในคลาสของเราไม่สามารถใช้งานequals()
ได้อย่างถูกต้องดังนั้นฉันสามารถแก้ไขได้:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
assert otherValue == null || otherValue.equals(null) == false;
} else {
result = otherValue != null && thisValue.equals(otherValue);
assert thisValue.equals(null) == false;
}
return result;
}
ประเด็นสำคัญที่ควรคำนึงถึงคือ:
การยืนยันเป็นเครื่องมือในการพัฒนาเท่านั้น
จุดการยืนยันคือการแจ้งให้คุณทราบหากมีข้อผิดพลาดที่ไม่เพียง แต่ในรหัสของคุณ แต่คุณในฐานรหัส (การยืนยันที่นี่จะทำเครื่องหมายข้อบกพร่องในคลาสอื่น ๆ จริง ๆ )
แม้ว่าเพื่อนร่วมงานของฉันจะมั่นใจว่าชั้นเรียนของเราเขียนอย่างถูกต้องการยืนยันที่นี่จะยังคงมีประโยชน์ คลาสใหม่จะถูกเพิ่มที่อาจล้มเหลวในการทดสอบเป็นโมฆะและวิธีนี้สามารถตั้งค่าสถานะข้อบกพร่องเหล่านั้นสำหรับเรา
ในการพัฒนาคุณควรเปิดใช้การยืนยันแม้ว่ารหัสที่คุณเขียนไม่ได้ใช้การยืนยัน IDE ของฉันถูกตั้งค่าให้ทำเช่นนี้โดยค่าเริ่มต้นสำหรับการปฏิบัติการใหม่ใด ๆ
คำยืนยันไม่เปลี่ยนพฤติกรรมของรหัสในการผลิตดังนั้นเพื่อนร่วมงานของฉันมีความสุขที่การตรวจสอบว่างอยู่ที่นั่นและวิธีการนี้จะทำงานได้อย่างถูกต้องแม้ว่าequals()
วิธีการนี้จะบั๊ก ฉันมีความสุขเพราะฉันจะequals()
ใช้วิธีการบั๊กกี้ในการพัฒนา
นอกจากนี้คุณควรทดสอบนโยบายการยืนยันของคุณโดยใส่การยืนยันชั่วคราวที่จะล้มเหลวดังนั้นคุณสามารถมั่นใจได้ว่าคุณได้รับแจ้งไม่ว่าจะผ่านไฟล์บันทึกหรือการติดตามสแต็กในเอาต์พุตสตรีม
คำตอบที่ดีมากมายที่อธิบายว่าassert
คำหลักทำอะไร แต่มีเพียงไม่กี่คำตอบสำหรับคำถามจริง "ควรใช้assert
คำหลักในชีวิตจริงเมื่อใด"
คำตอบ: แทบไม่เคยเลย
การยืนยันเป็นแนวคิดที่ยอดเยี่ยม รหัสที่ดีมีข้อความมากมายif (...) throw ...
(และญาติของพวกเขาชอบObjects.requireNonNull
และMath.addExact
) อย่างไรก็ตามการตัดสินใจการออกแบบบางอย่างได้ จำกัด ยูทิลิตี้ของassert
คำหลักอย่างมาก
แนวคิดการขับขี่ที่อยู่เบื้องหลังassert
คำหลักคือการปรับให้เหมาะสมก่อนกำหนดและคุณลักษณะหลักคือสามารถปิดการตรวจสอบทั้งหมดได้อย่างง่ายดาย ในความเป็นจริงการassert
ตรวจสอบจะถูกปิดโดยค่าเริ่มต้น
อย่างไรก็ตามเป็นเรื่องสำคัญอย่างยิ่งที่การตรวจสอบคงที่จะดำเนินการในการผลิตต่อไป เนื่องจากการครอบคลุมการทดสอบที่สมบูรณ์แบบนั้นเป็นไปไม่ได้และรหัสการผลิตทั้งหมดจะมีข้อบกพร่องซึ่งการยืนยันจะช่วยในการวินิจฉัยและบรรเทา
ดังนั้นการใช้ควรเป็นที่ต้องการเช่นเดียวกับที่มันเป็นสิ่งจำเป็นสำหรับการตรวจสอบค่าพารามิเตอร์ของวิธีการสาธารณะและสำหรับการขว้างปาif (...) throw ...
IllegalArgumentException
บางครั้งอาจถูกล่อลวงให้เขียนเช็คที่ไม่แปรเปลี่ยนซึ่งใช้เวลานานในการประมวลผลที่ไม่พึงประสงค์ อย่างไรก็ตามการตรวจสอบดังกล่าวจะทำให้การทดสอบช้าลงซึ่งไม่พึงประสงค์เช่นกัน การตรวจสอบที่ใช้เวลานานดังกล่าวมักจะเขียนเป็นการทดสอบหน่วย อย่างไรก็ตามบางครั้งมันก็สมเหตุสมผลที่จะใช้assert
ด้วยเหตุนี้
อย่าใช้assert
เพียงเพราะมันสะอาดและน่ารักกว่าif (...) throw ...
(และฉันบอกว่าด้วยความเจ็บปวดที่ยิ่งใหญ่เพราะฉันชอบที่สะอาดและสวย) หากคุณไม่สามารถช่วยเหลือตัวเองได้และสามารถควบคุมวิธีเปิดใช้งานแอปพลิเคชันของคุณได้อย่าลังเลที่จะใช้assert
แต่เปิดใช้งานการยืนยันในการผลิตอยู่เสมอ เป็นที่ยอมรับว่าเป็นสิ่งที่ฉันมักจะทำ ฉันกำลังผลักดันให้มีคำอธิบายประกอบลอมบอกที่จะทำให้การทำหน้าที่มากขึ้นเช่นassert
โหวตให้ที่นี่if (...) throw ...
(Rant: JVM devs เป็นกลุ่มของตัวแปลงสัญญาณที่น่ากลัวและปรับให้เหมาะสมก่อนกำหนดนั่นคือเหตุผลที่คุณได้ยินเกี่ยวกับปัญหาด้านความปลอดภัยจำนวนมากในปลั๊กอิน Java และ JVM พวกเขาปฏิเสธที่จะรวมการตรวจสอบขั้นพื้นฐานและรหัสยืนยันในรหัสการผลิต จ่ายราคา)
catch (Throwable t)
จับทุกข้อคือ ไม่มีเหตุผลที่จะไม่พยายามดักบันทึกหรือลองใหม่ / กู้คืนจาก OutOfMemoryError, AssertionError ฯลฯ
assert
คำหลักที่ไม่ดี ฉันจะแก้ไขคำตอบของฉันเพื่อให้ชัดเจนยิ่งขึ้นว่าฉันอ้างอิงถึงคำหลักไม่ใช่แนวคิด
นี่คือกรณีการใช้งานที่พบบ่อยที่สุด สมมติว่าคุณเปลี่ยนค่า enum:
switch (fruit) {
case apple:
// do something
break;
case pear:
// do something
break;
case banana:
// do something
break;
}
ตราบใดที่คุณจัดการกับทุกกรณีคุณก็สบายดี แต่สักวันใครบางคนจะเพิ่มรูปใน enum ของคุณและลืมที่จะเพิ่มลงในคำสั่งเปลี่ยนของคุณ สิ่งนี้จะสร้างบั๊กที่อาจจับได้ยากเพราะเอฟเฟกต์จะไม่รู้สึกจนกว่าคุณจะออกจากคำสั่งสวิตช์ แต่ถ้าคุณเขียนสวิตช์แบบนี้คุณสามารถจับได้ทันที:
switch (fruit) {
case apple:
// do something
break;
case pear:
// do something
break;
case banana:
// do something
break;
default:
assert false : "Missing enum value: " + fruit;
}
AssertionError
ถ้าเปิดใช้งานการยืนยัน ( -ea
) พฤติกรรมที่ต้องการในการผลิตคืออะไร? ภัยเงียบแบบไม่มี op และความเป็นไปได้ที่จะเกิดขึ้นภายหลังในการดำเนินการ อาจจะไม่. throw new AssertionError("Missing enum value: " + fruit);
ฉันขอแนะนำให้ชัดเจน
default
เพื่อให้คอมไพเลอร์สามารถเตือนคุณในกรณีที่ขาดหายไป คุณสามารถreturn
แทนbreak
(ซึ่งอาจต้องมีการแยกวิธีการ) แล้วจัดการกรณีที่ขาดหายไปหลังจากเปลี่ยน assert
วิธีนี้คุณจะได้รับทั้งคำเตือนและโอกาสต่อไปยัง
การยืนยันจะถูกใช้เพื่อตรวจสอบเงื่อนไขหลังและเงื่อนไขล่วงหน้า "ไม่ควรล้มเหลว" รหัสที่ถูกต้องไม่ควรล้มเหลวในการยืนยัน เมื่อพวกเขาเรียกพวกเขาควรระบุข้อผิดพลาด (หวังว่าจะอยู่ในสถานที่ที่ใกล้เคียงกับตำแหน่งที่แท้จริงของปัญหาคือ)
ตัวอย่างของการยืนยันอาจเป็นการตรวจสอบว่ามีการเรียกวิธีการเฉพาะกลุ่มตามลำดับที่ถูกต้อง (เช่นที่hasNext()
ถูกเรียกมาก่อนnext()
ในIterator
)
คำหลักยืนยันใน Java ทำอะไร
ลองดูโค้ดไบต์ที่คอมไพล์แล้ว
เราจะสรุปได้ว่า:
public class Assert {
public static void main(String[] args) {
assert System.currentTimeMillis() == 0L;
}
}
สร้างรหัสไบต์เดียวกันเกือบแน่นอนเป็น:
public class Assert {
static final boolean $assertionsDisabled =
!Assert.class.desiredAssertionStatus();
public static void main(String[] args) {
if (!$assertionsDisabled) {
if (System.currentTimeMillis() != 0L) {
throw new AssertionError();
}
}
}
}
ที่Assert.class.desiredAssertionStatus()
เป็นtrue
เมื่อ-ea
มีการส่งผ่านในบรรทัดคำสั่งและเท็จอย่างอื่น
เราใช้System.currentTimeMillis()
เพื่อให้แน่ใจว่าจะไม่ได้รับการเพิ่มประสิทธิภาพ ( assert true;
ทำ)
ฟิลด์สังเคราะห์ถูกสร้างขึ้นเพื่อให้ Java จำเป็นต้องโทรAssert.class.desiredAssertionStatus()
เพียงครั้งเดียวในขณะโหลดและจากนั้นแคชจะเก็บผลลัพธ์ไว้ที่นั่น ดูเพิ่มเติม: ความหมายของ "คงสังเคราะห์" คืออะไร?
เราสามารถตรวจสอบได้ด้วย:
javac Assert.java
javap -c -constants -private -verbose Assert.class
ด้วย Oracle JDK 1.8.0_45 ฟิลด์สแตติกสังเคราะห์ถูกสร้างขึ้น (ดูเพิ่มเติมที่: ความหมายของ "สแตติกสังเคราะห์" คืออะไร ):
static final boolean $assertionsDisabled;
descriptor: Z
flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
พร้อมกับ initializer คงที่:
0: ldc #6 // class Assert
2: invokevirtual #7 // Method java/lang Class.desiredAssertionStatus:()Z
5: ifne 12
8: iconst_1
9: goto 13
12: iconst_0
13: putstatic #2 // Field $assertionsDisabled:Z
16: return
และวิธีการหลักคือ:
0: getstatic #2 // Field $assertionsDisabled:Z
3: ifne 22
6: invokestatic #3 // Method java/lang/System.currentTimeMillis:()J
9: lconst_0
10: lcmp
11: ifeq 22
14: new #4 // class java/lang/AssertionError
17: dup
18: invokespecial #5 // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return
เราสรุปได้ว่า:
assert
: มันเป็นแนวคิดภาษาจาวาassert
จะได้รับการเทิดทูนสวยดีด้วยคุณสมบัติของระบบ-Pcom.me.assert=true
ที่จะมาแทนที่ในบรรทัดคำสั่งและ-ea
throw new AssertionError()
catch (Throwable t)
ข้อสามารถที่จะจับการละเมิดการยืนยันด้วยหรือไม่ สำหรับฉันที่ จำกัด ยูทิลิตี้ของพวกเขาเฉพาะกับกรณีที่เนื้อหาของการยืนยันใช้เวลานานซึ่งหายาก
AssertionError
แรกและ rethrow
ตัวอย่างโลกแห่งความจริงจากสแต็กคลาส (จากAssertion ในบทความ Java )
public int pop() {
// precondition
assert !isEmpty() : "Stack is empty";
return stack[--num];
}
การยืนยันอนุญาตให้ตรวจจับข้อบกพร่องในรหัส คุณสามารถเปิดการยืนยันสำหรับการทดสอบและการดีบักในขณะที่ปิดพวกเขาเมื่อโปรแกรมของคุณกำลังการผลิต
ทำไมต้องยืนยันบางสิ่งเมื่อคุณรู้ว่ามันเป็นเรื่องจริง เป็นจริงเฉพาะเมื่อทุกอย่างทำงานอย่างถูกต้อง หากโปรแกรมมีข้อบกพร่องอาจเป็นจริงได้ การตรวจจับสิ่งนี้ก่อนหน้าในกระบวนการช่วยให้คุณรู้ว่ามีบางอย่างผิดปกติ
assert
คำสั่งมีคำสั่งนี้พร้อมกับตัวเลือกString
ข้อความ
ไวยากรณ์สำหรับคำสั่ง assert มีสองรูปแบบ:
assert boolean_expression;
assert boolean_expression: error_message;
ต่อไปนี้เป็นกฎพื้นฐานบางประการที่ควบคุมว่าควรใช้การยืนยันที่ใดและไม่ควรใช้การยืนยันใด ควรใช้การยืนยันสำหรับ:
การตรวจสอบพารามิเตอร์อินพุตของวิธีไพรเวต ไม่ใช่สำหรับวิธีการสาธารณะ public
วิธีการควรโยนข้อยกเว้นปกติเมื่อผ่านพารามิเตอร์ที่ไม่ดี
ที่ใดก็ได้ในโปรแกรมเพื่อรับรองความถูกต้องของข้อเท็จจริงซึ่งเกือบจะเป็นจริงอย่างแน่นอน
ตัวอย่างเช่นหากคุณแน่ใจว่าเป็นเพียง 1 หรือ 2 คุณสามารถใช้การยืนยันดังนี้:
...
if (i == 1) {
...
}
else if (i == 2) {
...
} else {
assert false : "cannot happen. i is " + i;
}
...
ไม่ควรใช้คำยืนยันใน:
การตรวจสอบพารามิเตอร์อินพุตของวิธีการสาธารณะ เนื่องจากอาจไม่สามารถยืนยันได้เสมอควรใช้กลไกการยกเว้นปกติ
การตรวจสอบข้อ จำกัด เกี่ยวกับสิ่งที่ป้อนโดยผู้ใช้ เช่นเดียวกับข้างต้น
ไม่ควรใช้สำหรับผลข้างเคียง
ตัวอย่างเช่นนี่ไม่ใช่การใช้ที่เหมาะสมเพราะนี่คือการยืนยันที่ใช้สำหรับผลข้างเคียงของการเรียกใช้doSomething()
เมธอด
public boolean doSomething() {
...
}
public void someMethod() {
assert doSomething();
}
กรณีเดียวที่สามารถพิสูจน์ได้คือเมื่อคุณพยายามค้นหาว่ามีการเปิดใช้งานการยืนยันในรหัสของคุณหรือไม่:
boolean enabled = false;
assert enabled = true;
if (enabled) {
System.out.println("Assertions are enabled");
} else {
System.out.println("Assertions are disabled");
}
นอกจากนี้ทุกคำตอบที่ดีให้ที่นี่อย่างเป็นทางการ Java SE 7 คู่มือการเขียนโปรแกรมมีคู่มือการกระชับสวยเกี่ยวกับการใช้assert
; มีตัวอย่างหลายจุดที่เป็นแนวคิดที่ดี (และที่สำคัญคือแย่) ในการใช้การยืนยันและแตกต่างจากการโยนข้อยกเว้นอย่างไร
Assert มีประโยชน์มากเมื่อพัฒนา คุณใช้เมื่อบางสิ่งไม่สามารถเกิดขึ้นได้หากรหัสของคุณทำงานอย่างถูกต้อง มันใช้งานง่ายและสามารถอยู่ในรหัสตลอดกาลเพราะมันจะถูกปิดในชีวิตจริง
หากมีโอกาสที่เงื่อนไขสามารถเกิดขึ้นได้ในชีวิตจริงคุณต้องจัดการกับมัน
ฉันชอบ แต่ไม่ทราบวิธีเปิดใช้งานใน Eclipse / Android / ADT ดูเหมือนว่าจะปิดแม้ในขณะที่การแก้จุดบกพร่อง (มีเธรดเกี่ยวกับสิ่งนี้ แต่มันอ้างถึง 'Java vm' ซึ่งไม่ปรากฏใน ADT Run Configuration)
นี่คือคำยืนยันที่ฉันเขียนในเซิร์ฟเวอร์สำหรับโครงการ Hibernate / SQL เอนทิตี bean มีคุณสมบัติบูลีนที่ได้ผลสองอย่างที่เรียกว่า isActive และ isDefault แต่ละคนสามารถมีค่าเป็น "Y" หรือ "N" หรือ null ซึ่งถือว่าเป็น "N" เราต้องการให้แน่ใจว่าไคลเอนต์เบราว์เซอร์ถูก จำกัด เพียงสามค่า ดังนั้นใน setters ของฉันสำหรับคุณสมบัติทั้งสองนี้ฉันได้เพิ่มการยืนยันนี้:
assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;
แจ้งให้ทราบดังต่อไปนี้
การยืนยันนี้ใช้สำหรับขั้นตอนการพัฒนาเท่านั้น หากลูกค้าส่งค่าที่ไม่ดีเราจะตรวจสอบก่อนและแก้ไขก่อนที่จะถึงการผลิต การยืนยันเป็นข้อบกพร่องที่คุณสามารถตรวจได้ แต่เนิ่นๆ
การยืนยันนี้ช้าและไม่มีประสิทธิภาพ ไม่เป็นไร. ยืนยันได้ฟรีที่จะช้า เราไม่สนใจเพราะเป็นเครื่องมือสำหรับการพัฒนาเท่านั้น สิ่งนี้จะไม่ทำให้รหัสการผลิตช้าลงเพราะการยืนยันจะถูกปิดใช้งาน (มีความขัดแย้งในประเด็นนี้ซึ่งฉันจะไปในภายหลัง) สิ่งนี้นำไปสู่ประเด็นต่อไปของฉัน
การยืนยันนี้ไม่มีผลข้างเคียง ฉันสามารถทดสอบค่าของฉันกับชุดสุดท้ายคงที่ซึ่งเปลี่ยนแปลงไม่ได้ แต่ชุดนั้นจะคงอยู่ในการผลิตซึ่งจะไม่มีการใช้
การยืนยันนี้มีอยู่เพื่อตรวจสอบการทำงานที่เหมาะสมของลูกค้า ดังนั้นเมื่อถึงเวลาผลิตเราจะมั่นใจว่าลูกค้าทำงานอย่างถูกต้องเพื่อให้เราสามารถปิดการยืนยันได้อย่างปลอดภัย
บางคนถามสิ่งนี้: หากไม่ต้องการการยืนยันในการผลิตทำไมไม่นำพวกเขาออกไปเมื่อคุณทำเสร็จแล้ว? เพราะคุณยังคงต้องการมันเมื่อคุณเริ่มทำงานกับเวอร์ชั่นถัดไป
บางคนแย้งว่าคุณไม่ควรใช้การยืนยันเพราะคุณไม่สามารถมั่นใจได้ว่าข้อบกพร่องทั้งหมดจะหายไปดังนั้นคุณต้องทำให้พวกเขารอบแม้ในการผลิต ดังนั้นจึงไม่มีประโยชน์ในการใช้คำสั่ง assert เนื่องจากข้อดีเพียงอย่างเดียวของการยืนยันคือคุณสามารถปิดมันได้ ดังนั้นตามความคิดนี้คุณไม่ควรใช้คำยืนยัน (เกือบ) ฉันไม่เห็นด้วย. เป็นจริงอย่างแน่นอนว่าหากการทดสอบอยู่ในการผลิตคุณไม่ควรใช้การยืนยัน แต่การทดสอบนี้ไม่ได้อยู่ในการผลิต อันนี้สำหรับจับข้อบกพร่องที่ไม่น่าจะถึงการผลิตดังนั้นมันอาจถูกปิดอย่างปลอดภัยเมื่อคุณทำเสร็จแล้ว
BTW ฉันสามารถเขียนได้เช่นนี้
assert value == null || value.equals("Y") || value.equals("N") : value;
สิ่งนี้ใช้ได้สำหรับค่าสามค่าเท่านั้น แต่ถ้าจำนวนค่าที่เป็นไปได้มากขึ้นรุ่น HashSet จะสะดวกกว่า ฉันเลือกรุ่น HashSet เพื่อให้ประเด็นของฉันมีประสิทธิภาพ
HashSet
ArrayList
นอกจากนี้การสร้างรายการและชุดครองเวลาค้นหา พวกเขาต้องการได้ดีเมื่อใช้ค่าคงที่ ทั้งหมดบอกว่า +1
โดยทั่วไปการยืนยันจะใช้ในการดีบักแอปพลิเคชันหรือใช้แทนการจัดการข้อยกเว้นสำหรับบางแอปพลิเคชันเพื่อตรวจสอบความถูกต้องของแอปพลิเคชัน
การยืนยันทำงานในเวลาทำงาน ตัวอย่างง่ายๆที่สามารถอธิบายแนวคิดทั้งหมดได้อย่างง่ายมากอยู่ในที่นี้ - คำหลักยืนยันทำอะไรใน Java (WikiAnswers)
การยืนยันถูกปิดใช้งานตามค่าเริ่มต้น ในการเปิดใช้งานเราต้องเรียกใช้โปรแกรมด้วย-ea
ตัวเลือก (ความหลากหลายสามารถเปลี่ยนแปลงได้) ตัวอย่างเช่นjava -ea AssertionsDemo
.
มีสองรูปแบบสำหรับการใช้การยืนยัน:
assert 1==2; // This will raise an AssertionError
.assert 1==2: "no way.. 1 is not equal to 2";
สิ่งนี้จะยกระดับ AssertionError ด้วยข้อความที่แสดงให้เห็นด้วยและดีกว่า แม้ว่าไวยากรณ์ที่แท้จริงคือassert expr1:expr2
ที่ที่ expr2 สามารถเป็นนิพจน์ใด ๆ ที่ส่งคืนค่าฉันได้ใช้มันบ่อยขึ้นเพียงเพื่อพิมพ์ข้อความในการสรุป(และนี่เป็นความจริงในหลายภาษาไม่ใช่เฉพาะ Java):
"assert" ส่วนใหญ่จะใช้เป็นตัวช่วยในการดีบักโดยนักพัฒนาซอฟต์แวร์ในระหว่างกระบวนการดีบั๊ก ข้อความยืนยันไม่ควรปรากฏขึ้น หลายภาษามีตัวเลือกเวลารวบรวมที่จะทำให้ละเว้น "asserts" ทั้งหมดเพื่อใช้ในการสร้างรหัส "การผลิต"
"ข้อยกเว้น" เป็นวิธีที่สะดวกในการจัดการกับเงื่อนไขข้อผิดพลาดทุกชนิดไม่ว่าจะเป็นข้อผิดพลาดเชิงตรรกะหรือไม่เพราะถ้าคุณพบข้อผิดพลาดที่ไม่สามารถดำเนินการต่อได้คุณสามารถ "โยนพวกเขาขึ้นไปในอากาศได้ "จากทุกที่ที่คุณอยู่คาดหวังว่ามีคนอื่นอยู่ที่นั่นพร้อมที่จะ" จับ "พวกเขา การควบคุมจะถูกถ่ายโอนในขั้นตอนเดียวตรงจากรหัสที่โยนข้อยกเว้นตรงไปยังนวมจับ (และผู้จับสามารถเห็นย้อนหลังที่สมบูรณ์ของการโทรที่เกิดขึ้น)
ยิ่งกว่านั้นผู้โทรของรูทีนย่อยนั้นไม่ต้องตรวจสอบเพื่อดูว่ารูทีนย่อยประสบความสำเร็จ: "ถ้าเราอยู่ที่นี่ตอนนี้มันจะต้องประสบความสำเร็จเพราะไม่เช่นนั้นจะมีข้อยกเว้นเกิดขึ้น กลยุทธ์ง่ายๆนี้ทำให้การออกแบบรหัสและการดีบักง่ายขึ้นมาก
ข้อยกเว้นช่วยให้เงื่อนไขข้อผิดพลาดร้ายแรงสะดวกสบายคือ: "ข้อยกเว้นของกฎ" และสำหรับพวกเขาที่จะได้รับการจัดการโดย code-path ที่เป็น "ข้อยกเว้นของกฎ ... " fly ball!
การยืนยันคือการตรวจสอบที่อาจถูกปิด พวกเขาไม่ค่อยได้ใช้ ทำไม?
result != null
เช่นการตรวจสอบดังกล่าวนั้นรวดเร็วมากและแทบไม่มีอะไรจะบันทึกดังนั้นมีอะไรเหลือ ตรวจสอบราคาแพงสำหรับเงื่อนไขที่คาดว่าจะเป็นจริง ตัวอย่างที่ดีคือค่าคงที่ของโครงสร้างข้อมูลเช่น RB-tree ที่จริงในConcurrentHashMap
ของ JDK8 TreeNodes
มีไม่กี่ที่มีความหมายดังกล่าวอ้างสำหรับ
บางครั้งการตรวจสอบไม่แพงเลย แต่ในเวลาเดียวกันคุณค่อนข้างแน่ใจว่ามันจะผ่าน ในรหัสของฉันมีเช่น
assert Sets.newHashSet(userIds).size() == userIds.size();
ที่ฉันค่อนข้างแน่ใจว่ารายการที่ฉันเพิ่งสร้างมีองค์ประกอบที่ไม่ซ้ำกัน แต่ฉันต้องการเอกสารและตรวจสอบอีกครั้ง
โดยพื้นฐานแล้ว "การยืนยันที่แท้จริง" จะผ่านไปและ "การยืนยันที่ผิดพลาด" จะล้มเหลว มาดูกันว่ามันจะทำงานอย่างไร:
public static void main(String[] args)
{
String s1 = "Hello";
assert checkInteger(s1);
}
private static boolean checkInteger(String s)
{
try {
Integer.parseInt(s);
return true;
}
catch(Exception e)
{
return false;
}
}
assert
เป็นคำหลัก มันถูกนำมาใช้ใน JDK 1.4 มีสองประเภทของassert
s
assert
คำสั่งที่ง่ายมากassert
คำสั่งง่ายๆโดยค่าเริ่มต้นassert
งบทั้งหมดจะไม่ถูกดำเนินการ หากassert
คำสั่งได้รับเท็จมันจะเพิ่มข้อผิดพลาดในการยืนยันโดยอัตโนมัติ