วิธีใช้การยืนยันใน Java

การเขียนโปรแกรมที่ทำงานอย่างถูกต้องในรันไทม์อาจเป็นเรื่องท้าทาย เนื่องจากสมมติฐานของเราเกี่ยวกับการทำงานของโค้ดของเราเมื่อดำเนินการมักจะผิด การใช้คุณสมบัติการยืนยันของ Java เป็นวิธีหนึ่งในการตรวจสอบว่าตรรกะการเขียนโปรแกรมของคุณนั้นถูกต้อง

บทช่วยสอนนี้แนะนำการยืนยัน Java ก่อนอื่นคุณจะได้เรียนรู้ว่าการยืนยันคืออะไรและวิธีระบุและใช้ในโค้ดของคุณ จากนั้นคุณจะพบวิธีใช้การยืนยันเพื่อบังคับใช้เงื่อนไขเบื้องต้นและเงื่อนไขภายหลัง สุดท้ายคุณจะเปรียบเทียบการยืนยันกับข้อยกเว้นและค้นหาสาเหตุที่คุณต้องการทั้งสองอย่างในรหัสของคุณ

ดาวน์โหลดรับโค้ดดาวน์โหลดซอร์สโค้ดเพื่อดูตัวอย่างในบทช่วยสอนนี้ สร้างโดย Jeff Friesen สำหรับ JavaWorld

Java Assertions คืออะไร?

ก่อน JDK 1.4 นักพัฒนามักใช้ข้อคิดเห็นเพื่อบันทึกสมมติฐานเกี่ยวกับความถูกต้องของโปรแกรม อย่างไรก็ตามความคิดเห็นไม่มีประโยชน์ในการใช้เป็นกลไกในการทดสอบและแก้ไขข้อสันนิษฐาน คอมไพลเลอร์ไม่สนใจความคิดเห็นดังนั้นจึงไม่มีวิธีใดที่จะใช้เพื่อตรวจหาจุดบกพร่อง นักพัฒนามักจะไม่อัปเดตความคิดเห็นเมื่อเปลี่ยนรหัส  

ใน JDK 1.4 การยืนยันถูกนำมาใช้เป็นกลไกใหม่สำหรับการทดสอบและแก้ไขข้อสันนิษฐานเกี่ยวกับรหัสของเรา โดยพื้นฐานแล้วการยืนยัน  เป็นเอนทิตีที่รวบรวมได้ซึ่งดำเนินการในรันไทม์โดยสมมติว่าคุณได้เปิดใช้งานสำหรับการทดสอบโปรแกรม คุณสามารถตั้งโปรแกรมการยืนยันเพื่อแจ้งให้คุณทราบถึงจุดบกพร่องที่เกิดข้อบกพร่องซึ่งจะช่วยลดระยะเวลาที่คุณจะใช้ในการแก้ไขข้อบกพร่องของโปรแกรมที่ล้มเหลวได้อย่างมาก

การยืนยันใช้เพื่อกำหนดรหัสข้อกำหนดที่ทำให้โปรแกรมถูกต้องหรือไม่โดยการทดสอบเงื่อนไข (นิพจน์บูลีน) สำหรับค่าที่แท้จริงและแจ้งให้ผู้พัฒนาทราบเมื่อเงื่อนไขดังกล่าวเป็นเท็จ การใช้การยืนยันสามารถเพิ่มความมั่นใจในความถูกต้องของรหัสได้อย่างมาก

วิธีเขียนคำยืนยันใน Java

การยืนยันจะดำเนินการผ่านassertคำสั่งและjava.lang.AssertionErrorชั้นเรียน คำสั่งนี้เริ่มต้นด้วยคีย์เวิร์ดassertและต่อด้วยนิพจน์บูลีน มันแสดงออกทางวากยสัมพันธ์ดังนี้:

ยืนยันBooleanExpr ;

หากBooleanExprประเมินเป็นจริงจะไม่มีอะไรเกิดขึ้นและการดำเนินการจะดำเนินต่อไป หากนิพจน์ประเมินว่าเป็นเท็จอย่างไรก็ตามAssertionErrorจะมีการสร้างอินสแตนซ์และส่งออกดังแสดงในรายการ 1

รายการ 1:AssertDemo.java (รุ่น 1)

คลาสสาธารณะ AssertDemo {public static void main (String [] args) {int x = -1; ยืนยัน x> = 0; }}

การยืนยันในรายการที่ 1 บ่งบอกถึงความเชื่อของผู้พัฒนาว่าตัวแปรxมีค่าที่มากกว่าหรือเท่ากับ 0 อย่างไรก็ตามนี่ไม่ใช่กรณีอย่างชัดเจน งบผลการดำเนินการในการโยนassertAssertionError

รวบรวมรายการ 1 ( javac AssertDemo.java) และเรียกใช้โดยเปิดใช้งานการยืนยัน ( java -ea AssertDemo) คุณควรสังเกตผลลัพธ์ต่อไปนี้:

ข้อยกเว้นในเธรด "main" java.lang.AssertionError ที่ AssertDemo.main (AssertDemo.java:6)

ข้อความนี้ค่อนข้างคลุมเครือเนื่องจากไม่ได้ระบุสิ่งที่ทำให้เกิดการAssertionErrorโยน หากคุณต้องการข้อความที่ให้ข้อมูลเพิ่มเติมให้ใช้assertข้อความที่แสดงด้านล่าง:

ยืนยันBooleanExpr : expr ;

นี่exprคือนิพจน์ใด ๆ (รวมถึงการเรียกใช้เมธอด) ที่สามารถส่งคืนค่า - คุณไม่สามารถเรียกใช้เมธอดด้วยvoidประเภทการส่งคืน นิพจน์ที่มีประโยชน์คือสตริงลิเทอรัลที่อธิบายสาเหตุของความล้มเหลวดังที่แสดงในรายการ 2

รายการ 2:AssertDemo.java (เวอร์ชัน 2)

คลาสสาธารณะ AssertDemo {public static void main (String [] args) {int x = -1; ยืนยัน x> = 0: "x <0"; }}

รวบรวมรายการ 2 ( javac AssertDemo.java) และเรียกใช้โดยเปิดใช้งานการยืนยัน ( java -ea AssertDemo) คราวนี้คุณควรสังเกตผลลัพธ์ที่ขยายเล็กน้อยต่อไปนี้ซึ่งรวมถึงสาเหตุของการโยนAssertionError:

ข้อยกเว้นในเธรด "main" java.lang.AssertionError: x <0 ที่ AssertDemo.main (AssertDemo.java:6)

ตัวอย่างเช่นการรันAssertDemoโดยไม่มี-eaอ็อพชัน (enable assertions) จะไม่มีผลลัพธ์ เมื่อไม่ได้เปิดใช้งานการยืนยันจะไม่ถูกดำเนินการแม้ว่าจะยังคงอยู่ในไฟล์คลาส

เงื่อนไขเบื้องต้นและเงื่อนไขภายหลัง

การยืนยันจะทดสอบสมมติฐานของโปรแกรมโดยการตรวจสอบว่าไม่มีการละเมิดเงื่อนไขเบื้องต้นและเงื่อนไขภายหลังต่างๆโดยจะแจ้งเตือนนักพัฒนาเมื่อเกิดการละเมิด:

  • เงื่อนไขเป็นเงื่อนไขที่จะต้องประเมินให้เป็นจริงก่อนที่จะดำเนินการตามลำดับโค้ดบางส่วนที่ เงื่อนไขเบื้องต้นช่วยให้แน่ใจว่าผู้โทรรักษาสัญญากับผู้โทร
  • postconditionเป็นเงื่อนไขที่จะต้องประเมินให้เป็นจริงหลังจากที่การกระทำของลำดับโค้ดบางส่วนที่ เงื่อนไขภายหลังทำให้แน่ใจว่าผู้โทรรักษาสัญญากับผู้โทร

เงื่อนไขเบื้องต้น

คุณสามารถบังคับใช้เงื่อนไขเบื้องต้นเกี่ยวกับตัวสร้างและวิธีการสาธารณะโดยทำการตรวจสอบอย่างชัดเจนและทิ้งข้อยกเว้นเมื่อจำเป็น สำหรับวิธีการช่วยเหลือส่วนตัวคุณสามารถบังคับใช้เงื่อนไขเบื้องต้นได้โดยระบุการยืนยัน พิจารณารายชื่อ 3.

รายการ 3:AssertDemo.java (รุ่น 3)

นำเข้า java.io.FileInputStream; นำเข้า java.io.InputStream; นำเข้า java.io.IOException; คลาส PNG {/ ** * สร้างอินสแตนซ์ PNG อ่านไฟล์ PNG ที่ระบุและถอดรหัส * เป็นโครงสร้างที่เหมาะสม * * @param filespec path และชื่อของไฟล์ PNG ที่จะอ่าน * * @throws NullPointerException เมื่อfilespecเป็น *null* / PNG (String filespec) พ่น IOException {// บังคับใช้เงื่อนไขเบื้องต้นในตัวสร้างและ // วิธีการที่ไม่เป็นส่วนตัว ถ้า (filespec == null) โยน NullPointerException ใหม่ ("filespec เป็น null"); ลอง (FileInputStream fis = FileInputStream ใหม่ (filespec)) {readHeader (fis); }} โมฆะส่วนตัว readHeader (InputStream is) พ่น IOException {// ยืนยันว่าเงื่อนไขเบื้องต้นเป็นที่พอใจในเมธอดส่วนตัว // ตัวช่วย ยืนยันคือ! = null: "null ผ่านไปคือ"; }} คลาสสาธารณะ AssertDemo {public static void main (String [] args) พ่น IOException {PNG png = PNG ใหม่ ((args.length == 0)? null: args [0]); }}

PNGชั้นในรายชื่อ 3 เป็นจุดเริ่มต้นที่น้อยที่สุดของห้องสมุดสำหรับการอ่านและถอดรหัส PNG (กราฟิกเครือข่ายแบบพกพา) ไฟล์ภาพ ตัวสร้างอย่างชัดเจนเปรียบเทียบfilespecกับnullการขว้างปาเมื่อพารามิเตอร์นี้มีNullPointerException nullประเด็นก็คือการบังคับใช้เงื่อนไขที่ไม่ประกอบด้วยfilespecnull

ไม่เหมาะสมที่จะระบุassert filespec != null;เนื่องจากเงื่อนไขเบื้องต้นที่กล่าวถึงใน Javadoc ของผู้สร้างจะไม่ได้รับเกียรติ (ในทางเทคนิค) เมื่อการยืนยันถูกปิดใช้งาน (อันที่จริงมันน่าจะเป็นเกียรติเพราะFileInputStream()จะขว้างNullPointerExceptionแต่คุณไม่ควรขึ้นอยู่กับพฤติกรรมที่ไม่มีเอกสาร)

อย่างไรก็ตามassertมีความเหมาะสมในบริบทของreadHeader()วิธีการผู้ช่วยส่วนตัวซึ่งจะอ่านและถอดรหัสส่วนหัวขนาด 8 ไบต์ของไฟล์ PNG ได้ในที่สุด เงื่อนไขเบื้องต้นที่isส่งผ่านค่าที่ไม่ใช่ค่าว่างจะถือไว้เสมอ

เงื่อนไขภายหลัง

โดยทั่วไปเงื่อนไขภายหลังจะระบุผ่านการยืนยันไม่ว่าวิธีการ (หรือตัวสร้าง) จะเป็นแบบสาธารณะหรือไม่ก็ตาม พิจารณารายชื่อ 4.

รายการ 4:AssertDemo.java (เวอร์ชัน 4)

คลาสสาธารณะ AssertDemo {public static void main (String [] args) {int [] array = {20, 91, -6, 16, 0, 7, 51, 42, 3, 1}; เรียงลำดับ (อาร์เรย์); สำหรับ (int element: array) System.out.printf ("% d", element); System.out.println (); } isSorted บูลีนคงที่ส่วนตัว (int [] x) {สำหรับ (int i = 0; ix [i + 1]) ส่งคืนเท็จ กลับจริง; } private static void sort (int [] x) {int j, a; // สำหรับค่าจำนวนเต็มทั้งหมดยกเว้นค่าซ้ายสุด ... สำหรับ (int i = 1; i 0 && x [j - 1]> a) {// Shift left value - x [j - 1] - หนึ่งตำแหน่ง ทางขวา - // x [j] x [ญ] = x [ญ - 1]; // อัปเดตตำแหน่งแทรกเพื่อเลื่อนตำแหน่งเดิมของค่า // (ตำแหน่งหนึ่งไปทางซ้าย) ญ -; } // แทรกตำแหน่งที่แทรก (ซึ่งเป็นตำแหน่งแทรกเริ่มต้น // หรือตำแหน่งแทรกสุดท้าย) โดยที่ a มีค่ามากกว่า // หรือเท่ากับค่าทั้งหมดทางด้านซ้าย x [ญ] = ก;} assert isSorted (x): "array not sorted"; }}

รายการ 4 แสดงsort()วิธีการช่วยเหลือที่ใช้อัลกอริทึมการเรียงลำดับการแทรกเพื่อเรียงลำดับอาร์เรย์ของค่าจำนวนเต็ม ฉันเคยassertตรวจสอบเงื่อนไขภายหลังของxการจัดเรียงก่อนที่จะsort()ส่งกลับไปยังผู้โทร

ตัวอย่างในรายการที่ 4 แสดงให้เห็นถึงลักษณะสำคัญของการยืนยันซึ่งโดยปกติจะมีราคาแพงในการดำเนินการ ด้วยเหตุนี้การยืนยันจึงมักปิดใช้งานในรหัสการผลิต ในรายการ 4 isSorted()ต้องสแกนผ่านอาร์เรย์ทั้งหมดซึ่งอาจใช้เวลานานในกรณีของอาร์เรย์ที่มีความยาว

การยืนยันเทียบกับข้อยกเว้นใน Java

นักพัฒนาใช้การยืนยันเพื่อบันทึกสถานการณ์ที่เป็นไปไม่ได้อย่างมีเหตุผลและตรวจจับข้อผิดพลาดในตรรกะการเขียนโปรแกรมของตน ในรันไทม์การยืนยันที่เปิดใช้งานจะแจ้งเตือนนักพัฒนาถึงข้อผิดพลาดทางตรรกะ ผู้พัฒนา refactors ซอร์สโค้ดเพื่อแก้ไขข้อผิดพลาดตรรกะจากนั้นคอมไพล์โค้ดนี้ใหม่

นักพัฒนาใช้กลไกการยกเว้นของ Java เพื่อตอบสนองต่อข้อผิดพลาดรันไทม์ที่ไม่ร้ายแรง (เช่นหน่วยความจำหมด) ซึ่งอาจเกิดจากปัจจัยแวดล้อมเช่นไฟล์ที่ไม่มีอยู่หรือโดยรหัสที่เขียนไม่ดีเช่นความพยายามที่จะหารด้วย 0. ตัวจัดการข้อยกเว้นมักถูกเขียนขึ้นเพื่อกู้คืนจากข้อผิดพลาดอย่างสง่างามเพื่อให้โปรแกรมสามารถทำงานต่อไปได้

การยืนยันไม่สามารถทดแทนข้อยกเว้นได้ ไม่เหมือนกับข้อยกเว้นการยืนยันไม่สนับสนุนการกู้คืนข้อผิดพลาด (โดยทั่วไปการยืนยันจะหยุดการทำงานของโปรแกรมทันที -  AssertionErrorไม่ได้หมายถึงการถูกจับ มักจะถูกปิดใช้งานในรหัสการผลิต และโดยทั่วไปจะไม่แสดงข้อความแสดงข้อผิดพลาดที่ใช้งานง่าย (แม้ว่าจะไม่ใช่ปัญหาassertก็ตาม) สิ่งสำคัญคือต้องรู้ว่าเมื่อใดควรใช้ข้อยกเว้นมากกว่าการยืนยัน

เมื่อใดควรใช้ข้อยกเว้น

สมมติว่าคุณได้เขียนsqrt()วิธีการคำนวณรากที่สองของอาร์กิวเมนต์ ในบริบทจำนวนที่ไม่ซับซ้อนมันเป็นไปไม่ได้ที่จะหารากที่สองของจำนวนลบ ดังนั้นคุณใช้การยืนยันเพื่อล้มเหลวเมธอดหากอาร์กิวเมนต์เป็นลบ พิจารณาส่วนของรหัสต่อไปนี้:

สาธารณะ double sqrt (x คู่) {assert x> = 0: "x is negative"; // ... }

ไม่เหมาะสมที่จะใช้การยืนยันเพื่อตรวจสอบข้อโต้แย้งในpublicวิธีนี้ การยืนยันมีจุดมุ่งหมายเพื่อตรวจหาข้อผิดพลาดในตรรกะการเขียนโปรแกรมและไม่ป้องกันวิธีการจากข้อโต้แย้งที่ผิดพลาด นอกจากนี้หากการยืนยันถูกปิดใช้งานจะไม่มีวิธีจัดการกับปัญหาของการโต้แย้งเชิงลบ เป็นการดีกว่าที่จะโยนข้อยกเว้นดังนี้:

สาธารณะ double sqrt (x คู่) {if (x <0) โยน IllegalArgumentException ใหม่ ("x เป็นค่าลบ"); // ... }

ผู้พัฒนาอาจเลือกที่จะให้โปรแกรมจัดการกับข้อยกเว้นอาร์กิวเมนต์ที่ผิดกฎหมายหรือเพียงแค่เผยแพร่ออกจากโปรแกรมซึ่งข้อความแสดงข้อผิดพลาดจะปรากฏขึ้นโดยเครื่องมือที่รันโปรแกรม เมื่ออ่านข้อความแสดงข้อผิดพลาดนักพัฒนาสามารถแก้ไขโค้ดที่นำไปสู่ข้อยกเว้นได้

คุณอาจสังเกตเห็นความแตกต่างเล็กน้อยระหว่างการยืนยันและตรรกะการตรวจจับข้อผิดพลาด การทดสอบยืนยันในขณะที่การทดสอบตรรกะข้อผิดพลาดการตรวจสอบx >= 0 x < 0การยืนยันเป็นแง่ดี: เราถือว่าการโต้แย้งนั้นโอเค ในทางตรงกันข้ามตรรกะการตรวจจับข้อผิดพลาดนั้นมองในแง่ร้าย: เราถือว่าอาร์กิวเมนต์ไม่เป็นที่ยอมรับ เอกสารยืนยันตรรกะที่ถูกต้องในขณะที่ข้อยกเว้นแสดงพฤติกรรมรันไทม์ที่ไม่ถูกต้อง

ในบทช่วยสอนนี้คุณได้เรียนรู้วิธีใช้การยืนยันเพื่อบันทึกตรรกะของโปรแกรมที่ถูกต้อง คุณยังได้เรียนรู้ว่าเหตุใดการยืนยันจึงไม่สามารถทดแทนข้อยกเว้นได้และคุณได้เห็นตัวอย่างที่การใช้ข้อยกเว้นจะมีประสิทธิภาพมากกว่า

เรื่องนี้ "วิธีใช้การยืนยันใน Java" เผยแพร่ครั้งแรกโดย JavaWorld