คอมไพเลอร์ควรรายงานข้อผิดพลาดและคำเตือนอย่างไร


11

ฉันไม่ได้วางแผนที่จะเขียนคอมไพเลอร์ในอนาคตอันใกล้นี้ ยังฉันค่อนข้างสนใจกับเทคโนโลยีคอมไพเลอร์และสิ่งนี้จะทำให้ดีขึ้นได้อย่างไร

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

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

C # ไม่มีปัญหาเดียวกัน แต่มีเพียงเล็กน้อย ดูเหมือนว่าการรวบรวมเกิดขึ้นในหลายรอบและการส่งผ่านที่ล้มเหลวจะป้องกันการส่งต่อจากการดำเนินการต่อไป ด้วยเหตุนี้ข้อผิดพลาดที่คุณได้รับเมื่องานสร้างล้มเหลวมักจะประเมินค่าต่ำเกินไป ในการรันครั้งเดียวอาจกล่าวได้ว่าคุณมีข้อผิดพลาดสองข้อ แต่เมื่อคุณแก้ไขข้อผิดพลาดบางทีคุณอาจได้รับ 26 ข้อใหม่

การขุดไปที่ C และ C ++ เพียงแสดงชุดค่าผสมที่ไม่ดีในจุดอ่อนการวินิจฉัยการคอมไพล์ของ Java และ C # (แม้ว่าอาจจะมีความแม่นยำมากกว่าที่จะกล่าวว่า Java และ C # เพิ่งไปได้ด้วยปัญหาครึ่งหนึ่ง) คำเตือนบางอย่างควรเป็นข้อผิดพลาด (เช่นเมื่อเส้นทางโค้ดไม่คืนค่าทั้งหมด) และยังคงเป็นคำเตือนเพราะฉันคิดว่าในเวลาที่พวกเขาเขียนมาตรฐานเทคโนโลยีคอมไพเลอร์ก็ไม่ดีพอที่จะทำให้เป็นแบบนี้ ตรวจสอบที่จำเป็น ในหลอดเลือดดำเดียวกันคอมไพเลอร์มักตรวจสอบมากกว่ามาตรฐานกล่าว แต่ยังคงใช้ระดับข้อผิดพลาดคำเตือน "มาตรฐาน" สำหรับการค้นพบเพิ่มเติม และบ่อยครั้งที่คอมไพเลอร์จะไม่รายงานข้อผิดพลาดทั้งหมดที่พบได้ทันที อาจต้องใช้คอมไพล์เล็กน้อยเพื่อกำจัดพวกมันทั้งหมด ไม่ต้องพูดถึงข้อผิดพลาด cryptic คอมไพเลอร์ C ++ ชอบถ่มน้ำลาย

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

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

PHP อยู่ด้านข้างมีระดับข้อผิดพลาดและข้อยกเว้นที่สำคัญไม่มากก็น้อย ข้อผิดพลาดในการแยกวิเคราะห์มีการรายงานทีละครั้งคำเตือนมักจะแย่มากซึ่งควรยกเลิกสคริปต์ของคุณ (แต่ไม่ใช่โดยค่าเริ่มต้น) การแจ้งเตือนมักแสดงปัญหาตรรกะที่ร้ายแรงและข้อผิดพลาดบางอย่างไม่ดีพอที่จะหยุดสคริปต์ของคุณ ทำและตามปกติด้วย PHP มีบางสิ่งที่แปลกจริง ๆ อยู่ที่นั่น (ทำไมนรกเราต้องมีระดับข้อผิดพลาดสำหรับข้อผิดพลาดร้ายแรงที่ไม่ร้ายแรงมาก? E_RECOVERABLE_E_ERRORฉันกำลังพูดกับคุณ)

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

คุณคิดว่าควรเป็นวิธีที่ถูกต้องในการรายงานข้อผิดพลาดของคอมไพเลอร์?


-1: "ภาษาที่ไม่ได้รวบรวมยังคงมีส่วนร่วมในการรายงานข้อผิดพลาดเส็งเคร็ง" อัตนัยและโต้แย้ง ไม่ช่วยเหลือจริงๆ นี่เป็นคำถามหรือข้อร้องเรียนหรือไม่?
S.Lott

2
@ S.Lott ฉันคิดว่าคุณเป็นคนที่ขอบที่นี่ ฉันพบว่าฉันรวบรวมภาษาได้ยากขึ้นมากและดูเหมือนจะไม่รบกวนคุณ
zneak

@zneak: ข้อความอื่น ๆ ใกล้จะเป็นจริงและยากที่จะแยกวิเคราะห์ คำแถลงดังกล่าวแสดงให้เห็นได้อย่างง่ายดายที่สุดว่าเป็นเรื่องส่วนตัว
S.Lott

1
@ S.Lott ฉันผิดที่ระบุว่า Python ระบุข้อผิดพลาดทีละหนึ่งหรือไม่
zneak

1
@ S.Lott จากนั้นทุกอย่างจะต้องเปลี่ยนไปเพราะครั้งสุดท้ายที่ฉันพยายามข้อผิดพลาดทางไวยากรณ์ใด ๆ ที่จะทำให้งูหลามหยุดพยายามที่จะ "รวบรวม" และข้อผิดพลาดชื่อจะโยนข้อยกเว้นและไม่ตรวจสอบส่วนที่เหลือของฟังก์ชั่น ห้องสำหรับรายงานข้อผิดพลาดหนึ่งข้อต่อหน่วยที่ทดสอบได้) คำพูดเชิงอัตนัยและข้อโต้แย้งของฉันเป็นการแนะนำสิ่งที่ฉันเชื่อว่าเป็นความจริง แต่ถ้ามันไม่เป็นความจริงอีกต่อไปฉันจะไปและแก้ไขคำถามของฉัน ตอนนี้มันทำงานยังไง?
zneak

คำตอบ:


6

คำถามของคุณดูเหมือนจะไม่เกี่ยวกับวิธีที่เรารายงานข้อผิดพลาดของคอมไพเลอร์ แต่มันเกี่ยวกับการจำแนกปัญหาและจะทำอย่างไรกับพวกเขา

ถ้าเราเริ่มต้นด้วยการสมมติว่าในขณะนี้การแบ่งขั้วเตือน / ข้อผิดพลาดนั้นถูกต้องเรามาดูกันว่าเราสามารถสร้างได้ดีเพียงใด ความคิดบางอย่าง:

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

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

ตอนนี้สิ่งที่ฉันไม่เห็นด้วยกับคุณใน:

  1. พยายามรายงานปัญหาทุกเรื่องเป็นพิเศษ หากมีข้อผิดพลาดนั่นจะเป็นการทำลายการสร้าง งานสร้างเสียหาย บิลด์จะไม่ทำงานจนกว่าข้อผิดพลาดนั้นจะได้รับการแก้ไข ดังนั้นจึงเป็นการดีกว่าที่จะรายงานข้อผิดพลาดนั้นทันทีแทนที่จะ "ดำเนินการ" เพื่อลองและระบุทุกสิ่ง "ผิด" ด้วยรหัส โดยเฉพาะอย่างยิ่งเมื่อสิ่งเหล่านี้จำนวนมากอาจเกิดจากข้อผิดพลาดเริ่มต้นอยู่แล้ว

  2. ตัวอย่างเฉพาะของคำเตือนที่ควรเป็นข้อผิดพลาด ใช่มันอาจเป็นความผิดพลาดของโปรแกรมเมอร์ ไม่ไม่ควรทำลายโครงสร้าง หากฉันรู้ว่าอินพุตไปยังฟังก์ชันนั้นจะส่งคืนค่าเสมอฉันควรจะสามารถรัน build และทำการทดสอบบางอย่างโดยไม่ต้องเพิ่มการตรวจสอบพิเศษเหล่านั้น ใช่มันควรจะเป็นคำเตือน และสิ่งหนึ่งที่มีความรุนแรงสูงที่นั่น แต่มันไม่ควรทำลายโครงสร้างภายในและของตัวเองเว้นแต่จะรวบรวมด้วยคำเตือนเป็นข้อผิดพลาด

คิด?


ฉันเห็นด้วยกับคุณยกเว้นจุดที่เราไม่เห็นด้วย (duh) ดังนั้น +1 จากฉัน ฉันคิดว่ามันง่ายพอที่จะทำให้ทุกเส้นทางของโค้ดคืนค่าหรือยกเลิกโปรแกรมของคุณโดยพิจารณาว่ามันเลวร้ายเพียงใดเมื่อคุณล้มลงในกรณีของพฤติกรรมที่ไม่ได้กำหนด
zneak

7

ปัญหาหนึ่งที่คุณนำมาใช้คือการรายงานข้อผิดพลาดที่ไม่สมบูรณ์ - เช่นการรายงานข้อผิดพลาด 2 ครั้งและเมื่อคุณแก้ไขข้อผิดพลาดคุณจะได้รับมากขึ้น

นี่คือ (ส่วนใหญ่) ประนีประนอมในส่วนของนักเขียนคอมไพเลอร์ ขึ้นอยู่กับข้อผิดพลาดที่คุณทำมันเป็นเรื่องง่ายมากที่คอมไพเลอร์จะเริ่มเข้าใจผิดในสิ่งที่คุณทำไม่ดีพอที่จะเริ่มรายงานข้อผิดพลาดที่มีน้อยมากเกี่ยวกับความเป็นจริง เพียงแค่ยกตัวอย่างเช่นพิจารณา typo ง่ายๆที่คุณมีสิ่งที่ต้องการแทนitn x; int x;หากคุณไม่ได้ทำอย่างอื่นที่ทำให้มีความitnหมายสิ่งนี้จะถูกรายงานว่าเป็นข้อผิดพลาด ไม่เป็นไร แต่ตอนนี้ให้พิจารณาว่าจะเกิดอะไรขึ้นต่อไป - คอมไพเลอร์ดูรหัสจำนวนมากที่พยายามใช้ xเป็นตัวแปร A) ควรหยุดและให้คุณแก้ไขหรือ B) spew 2000 ข้อผิดพลาดเกี่ยวกับerror: "x": undeclared identifierหรือบางสิ่งบางอย่างในการสั่งซื้อ? พิจารณาความเป็นไปได้อื่น:

int main()[

นี้เป็นอีกหนึ่งพิมพ์ผิดชัดเจนสวย - เห็นได้ชัดว่ามันควรจะเป็นแทน{ [คอมไพเลอร์สามารถบอกคุณได้ว่าส่วนนั้นค่อนข้างง่าย - แต่ถ้าเป็นเช่นนั้นควรรายงานข้อผิดพลาดสำหรับสิ่งที่ชอบx=1;พูดอะไรบางอย่างerror: statement only allowed inside a function?

โปรดทราบว่าสิ่งเหล่านี้เป็นปัญหาที่ค่อนข้างเล็กน้อย - ปัญหาที่แย่กว่านั้นหาง่ายมาก (โดยเฉพาะอย่างยิ่งพวกเราส่วนใหญ่รู้เมื่อคุณเข้าสู่เทมเพลต C ++) บรรทัดล่างคือผู้เขียนคอมไพเลอร์มักจะติดอยู่กับการพยายามประนีประนอมระหว่างการรายงานข้อผิดพลาดที่ผิดพลาด (เช่นการรายงานบางสิ่งบางอย่างเป็นข้อผิดพลาดแม้ว่ามันจะดี) และล้มเหลวในการรายงานข้อผิดพลาดจริง มีกฎบางอย่างของหัวแม่มือที่สุดพยายามที่จะป้องกันไม่ให้ไปผิดมากเกินไปในทิศทางใด แต่เกือบจะไม่มีพวกเขาอยู่ใกล้สมบูรณ์แบบ

หนึ่งปัญหาอื่น ๆ ที่คุณกล่าวถึงเป็น Java @SupressWarningและ สิ่งนี้ค่อนข้างแตกต่างจากข้างบน - มันค่อนข้างจะแก้ไขได้เล็กน้อย เหตุผลเดียวที่ไม่ได้รับการแก้ไขคือการทำเช่นนั้นไม่สอดคล้องกับ "ตัวละคร" พื้นฐานของ Java - กล่าวคือในความเห็นของพวกเขา "นั่นไม่ใช่ข้อผิดพลาด แต่เป็นคุณลักษณะ" แม้ว่าปกติแล้วจะเป็นเรื่องตลก แต่ในกรณีนี้ผู้คนที่เกี่ยวข้องมีความเข้าใจผิดว่าพวกเขาเชื่อว่าเป็นเรื่องจริง

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


3
นอกเหนือจากประเด็นของคุณเกี่ยวกับข้อผิดพลาดก่อนกำหนดที่ทำให้คนอื่น ๆ อีกมากมายนอกจากนี้ยังมีความจริงที่ว่าการส่งผ่านในภายหลังมักจะสร้างขึ้นเพื่อให้การส่งผ่านก่อนหน้านี้เสร็จสมบูรณ์ ตัวอย่างเช่นหนึ่งในการส่งผ่านต้นในการตรวจสอบคอมไพเลอร์ C # เพื่อให้แน่ใจว่าไม่มีรอบในกราฟการสืบทอด - คุณไม่มี A สืบทอดจาก B ซึ่งสืบทอดมาจาก A หากคุณต้องการที่จะดำเนินการและสร้างรายการ ของข้อผิดพลาดทั้งหมดหลังจากนั้นแต่ละรอบที่ผ่านมาจะต้องสามารถรับมือกับรอบได้ทำให้ช้าลงอย่างมากแม้ในคอมไพล์ "ดี"
อานนท์

@Anon คอมไพเลอร์ Java ใช้ความพยายามดีกว่าในการมีชีวิตรอดในช่วงแรก ๆ และฉันไม่พบว่ามันช้าลงอย่างมีนัยสำคัญ สำหรับฉันมันค่อนข้างน่ารำคาญที่cscจะยอมแพ้อย่างรวดเร็ว
zneak

@zneak: ตามที่เจอร์รี่พูดมันเป็นการประนีประนอมในส่วนของนักพัฒนาคอมไพเลอร์ การเขียนการวินิจฉัยข้อผิดพลาดที่ดีนั้นเป็นปัญหาที่ยากมาก (ดูเสียงดังกราวสำหรับตัวอย่างว่าคุณสามารถรับมันไปได้ไกลแค่ไหน) ดูที่นี่สำหรับการสนทนาที่ดีของเฟสและคอมไพเลอร์ของ C #
Dean Harding
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.