GOTO ยังถือว่าเป็นอันตรายหรือไม่? [ปิด]


282

ทุกคนตระหนักถึงจดหมายถึงบรรณาธิการของ Dijkstra : ไปที่ข้อความที่ถือว่าเป็นอันตราย (เช่นที่นี่ . html การถอดเสียงและที่นี่ . pdf) และมีการผลักดันที่น่ากลัวตั้งแต่นั้นมา ในขณะที่มันเป็นไปได้ที่จะใช้ในการผลิตกลับไปข้าง unmaintainable รหัสแผ่กิ่งก้านสาขา แต่กระนั้นมันก็ยังคงอยู่ในการเขียนโปรแกรมภาษาที่ทันสมัย แม้แต่โครงสร้างการควบคุมความต่อเนื่องขั้นสูงใน Scheme ก็สามารถอธิบายได้ว่าเป็น goto ที่ซับซ้อน

มีกรณีใดบ้างที่รับประกันการใช้ goto? ควรหลีกเลี่ยงเมื่อใด

ตามคำถามการติดตาม: C มีฟังก์ชั่นคู่หนึ่งคือ setjmp และ longjmp ที่ให้ความสามารถในการข้ามไปไม่ได้อยู่ในกรอบสแต็กปัจจุบันเท่านั้น แต่อยู่ในกรอบการเรียกใด ๆ สิ่งเหล่านี้ควรได้รับการพิจารณาว่าอันตรายหรือไม่? อันตรายมาก?


Dijkstra เองรู้สึกเสียใจกับตำแหน่งนั้นซึ่งเขาไม่รับผิดชอบ ในตอนท้ายของEWD1308 (เช่นกันที่นี่ . pdf) เขาเขียนว่า:

ในที่สุดเรื่องสั้นสำหรับการบันทึก ในปี 1968 การสื่อสารของ ACM ตีพิมพ์ข้อความของฉันภายใต้ชื่อ " คำสั่ง goto ถือว่าเป็นอันตราย " ซึ่งในปีต่อมาจะมีการอ้างอิงบ่อยที่สุดอย่างน่าเศร้า แต่บ่อยครั้งโดยผู้เขียนที่ไม่เห็นมันมากไปกว่า ชื่อซึ่งกลายเป็นรากฐานที่สำคัญของชื่อเสียงของฉันโดยกลายเป็นเทมเพลต: เราจะเห็นบทความทุกประเภทภายใต้ชื่อ "X ถือว่าเป็นอันตราย" สำหรับเกือบทุก X รวมทั้งชื่อ "Dijkstra ถือว่าเป็นอันตราย" แต่เกิดอะไรขึ้น ฉันส่งบทความภายใต้ชื่อ " คดีกับคำสั่ง goto"ซึ่งเพื่อเพิ่มความเร็วในการพิมพ์บรรณาธิการได้เปลี่ยนเป็น" จดหมายถึงบรรณาธิการ "และในกระบวนการที่เขาได้รับมันเป็นชื่อใหม่ของการประดิษฐ์ของเขาเองบรรณาธิการคือ Niklaus Wirth

บทความคลาสสิกที่มีความคิดที่ดีเกี่ยวกับหัวข้อนี้ที่จะจับคู่กับของ Dijkstra คือการเขียนโปรแกรมแบบมีโครงสร้างโดยไปที่คำชี้แจงโดย Donald E. Knuth การอ่านทั้งสองช่วยในการสร้างบริบทใหม่และความเข้าใจที่ไม่เชื่อในเรื่องนี้ ในบทความนี้ความเห็นของ Dijkstra ในคดีนี้มีการรายงานและมีความแข็งแกร่งยิ่งขึ้น:

Donald E. Knuth:ฉันเชื่อว่าโดยการนำเสนอมุมมองดังกล่าวฉันไม่เห็นด้วยอย่างยิ่งกับความคิดของ Dijkstra เนื่องจากเขาเพิ่งเขียนสิ่งต่อไปนี้: "ได้โปรดอย่าตกหลุมพรางของความเชื่อที่ว่า ไปที่คำสั่ง]. ฉันมีความรู้สึกอึดอัดว่าคนอื่น ๆ จะทำให้ศาสนาออกจากมันเช่นถ้าปัญหาแนวความคิดของการเขียนโปรแกรมสามารถแก้ไขได้โดยเคล็ดลับเดียวโดยรูปแบบที่เรียบง่ายของการเข้ารหัสวินัย! "


1
การข้ามไปของ C # นั้นไม่เหมือนกับการข้ามไปที่ Dijkstra กำลังพูดถึงด้วยเหตุผลที่ Dijkstra พูดถึงอย่างแม่นยำ ที่ข้ามไปเป็นทั้งที่เป็นอันตรายในขณะที่มันกลับมาแล้ว แต่ยังมีจำนวนมากที่จำเป็นน้อยลงเพราะภาษาสมัยใหม่ให้โครงสร้างการควบคุมทางเลือก C # goto มีข้อ จำกัด อย่างมาก
jalf

25
Gotos นั้นดีเมื่อเพิ่มความชัดเจน หากคุณมีลูปซ้อนกันเป็นเวลานานจะดีกว่าที่จะออกไปจากมันได้ดีกว่าการตั้งค่าตัวแปร "หยุด" และแตกจนกว่าคุณจะออก
simendsjo

28
หากคุณมีการวนซ้ำซ้อนกันในระดับความลึก 4 (ไม่ใช่ว่ามันเป็นเรื่องดี) การแยกออกจากทั้งหมดต้องตั้งค่าชั่วคราว goto ที่นี่ชัดเจนกว่าสำหรับฉันมากและ IDE ควรแสดงให้เห็นว่า goto นั้นอยู่ที่ไหน ที่กล่าวว่าการใช้ข้ามไปใช้ควรจะเบาบางและในความคิดของฉันเพียงเลื่อนลงไปที่ข้ามรหัส
simendsjo

7
ผมแนะนำให้คุณไปอ่าน 9001 gotoหัวข้อแท็ก
Matti Virkkunen

5
มีสิ่งหนึ่งที่เห็นได้ชัดที่เลวร้ายยิ่งกว่าการใช้goto: gotoแฮ็คเครื่องมือการเขียนโปรแกรมโครงสร้างร่วมกันในการดำเนินการ

คำตอบ:


184

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

  1. การใช้ที่อยู่หน่วยความจำแบบไม่ จำกัด (ทั้ง GOTO หรือพอยน์เตอร์พอยต์) ให้โอกาสมากมายเกินกว่าที่จะทำผิดพลาดที่หลีกเลี่ยงได้ง่าย
  2. ยิ่งมีวิธีการมากมายที่จะไปถึง "สถานที่" โดยเฉพาะในโค้ดผู้ที่มีความมั่นใจน้อยกว่าก็สามารถรู้ได้ว่าสถานะของระบบอยู่ที่จุดนั้น (ดูด้านล่าง)
  3. การเขียนโปรแกรมแบบมีโครงสร้าง IMHO นั้นเกี่ยวกับ "การหลีกเลี่ยง GOTO" และอื่น ๆ เกี่ยวกับการทำให้โครงสร้างของรหัสตรงกับโครงสร้างของข้อมูล ตัวอย่างเช่นโครงสร้างข้อมูลที่ทำซ้ำ (เช่นอาร์เรย์, ไฟล์เรียงลำดับเป็นต้น) จะถูกประมวลผลตามปกติโดยหน่วยของรหัสซ้ำ การมีโครงสร้างแบบบิวด์อิน (เช่นในขณะสำหรับจนกระทั่งจนถึงสำหรับแต่ละเป็นต้น) อนุญาตให้โปรแกรมเมอร์หลีกเลี่ยงความเบื่อหน่ายในการทำซ้ำรูปแบบโค้ดที่ซ้ำซ้อนเดิม
  4. แม้ว่า GOTO จะมีรายละเอียดการใช้งานในระดับต่ำ (ไม่ใช่ในทุกกรณี!) มันต่ำกว่าระดับที่โปรแกรมเมอร์ควรคิด มีโปรแกรมเมอร์จำนวนกี่คนที่ทำสมดุลกับสมุดเช็คส่วนตัวในแบบไบนารีดิบ มีโปรแกรมเมอร์กี่คนที่กังวลว่าเซ็กเตอร์ใดในดิสก์ที่มีเร็กคอร์ดเฉพาะแทนที่จะเป็นเพียงการมอบกุญแจให้เอ็นจิ้นฐานข้อมูล

เชิงอรรถข้างต้น:

เกี่ยวกับจุดที่ 2 พิจารณารหัสต่อไปนี้:

a = b + 1
/* do something with a */

ที่จุด "ทำอะไร" ในรหัสเราสามารถระบุด้วยความมั่นใจสูงที่มีค่ามากกว่าa b(ใช่ฉันไม่สนใจความเป็นไปได้ของจำนวนเต็มล้นที่ไม่ได้ใส่กันเราไม่ต้องยกตัวอย่างง่ายๆ)

ในทางกลับกันหากรหัสได้อ่านด้วยวิธีนี้:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

ความหลากหลายของวิธีที่จะได้รับฉลาก 10 หมายความว่าเราต้องทำงานหนักมากขึ้นเพื่อให้มั่นใจเกี่ยวกับความสัมพันธ์ระหว่างaและbณ จุดนั้น (ในความเป็นจริงในกรณีทั่วไปมันไม่สามารถตัดสินใจได้!)

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

if (some condition) {
  action-1
} else {
  action-2
}

ถูกนำไปใช้บนเครื่องเสมือนโดยรวบรวม action-1 และ action-2 เป็นรูทีนแบบไม่มีพารามิเตอร์แบบ out-of-line จากนั้นใช้ opcode VM สองอาร์กิวเมนต์เดียวซึ่งใช้ค่าบูลีนของเงื่อนไขเพื่อเรียกใช้หนึ่งหรืออย่างอื่น แนวคิดคือ "เลือกสิ่งที่จะเรียกใช้ตอนนี้" แทนที่จะ "ไปที่นี่หรือไปที่นั่น" อีกครั้งเพียงแค่การเปลี่ยนคำอุปมา


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

2
Postscript ทำงานเหมือนจุด 4 ของคุณ
luser droog

Smalltalk ทำงานคล้ายกับตัวอย่างของจุดที่ 4 หาก "คล้ายกัน" คุณหมายถึง "ไม่มีอะไรที่เหมือนกับภาษาเชิงดำเนินการ" : P ไม่มีการควบคุมการไหลในภาษา; การตัดสินใจทั้งหมดได้รับการจัดการผ่าน polymorphism ( trueและfalseมีประเภทแตกต่างกัน) และแต่ละสาขาของ if / else เป็นแลมบ์ดา
cHao

จุดเหล่านี้เป็นจุดที่ถูกต้อง แต่ในที่สุดพวกเขาก็ย้ำว่า goto ที่ไม่ดีสามารถเป็น หยุดพัก, ดำเนินการต่อ, ออก, ส่งคืน, gosub, settimeout, global, include ฯลฯ เป็นเทคนิคที่ทันสมัยทั้งหมดที่ต้องใช้การติดตามการไหลของสิ่งต่าง ๆ ทางจิตใจและสามารถนำไปใช้ในทางที่ผิดเพื่อสร้างรหัสปาเก็ตตี้ เพื่อความเป็นธรรมถึงแม้ว่าฉันจะไม่เคยเห็นการใช้ goto โดยตรงมาก่อน แต่ฉันก็เคยเห็นมันใช้ครั้งเดียวหรือสองครั้ง ที่พูดถึงคำแถลงว่ามีสิ่งที่ดีกว่าเสมอที่จะใช้
Beejor

gotoในการเขียนโปรแกรมภาษาที่ทันสมัย (Go) stackoverflow.com/a/11065563/3309046
nishanths

245

XKCD ของ GOTO การ์ตูน

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

ฉันคิดว่าการ์ตูนนี้แสดงให้เห็นว่าสวยงาม "ฉันจะปรับโครงสร้างการไหลของโปรแกรมหรือใช้น้อย 'โกโตะ' แทน." GOTO เป็นวิธีที่อ่อนแอออกมาเมื่อคุณมีการออกแบบที่อ่อนแอ Velociraptors เหยื่ออ่อนแอ


41
GOTO สามารถ 'กระโดด' จากจุดหนึ่งไปยังอีกจุดหนึ่งโดยพลการ ศาสนกระโดดไปที่นี่จากที่ไหนเลย!
rpattabi

29
ฉันไม่พบเรื่องตลกเลยเพราะทุกคนรู้ว่าคุณต้องเชื่อมโยงก่อนจึงจะสามารถดำเนินการได้
Kinjal Dixit

31
ผู้ร่วมงานของคุณผิดและเห็นได้ชัดว่าไม่ได้อ่านเอกสารของ Knuth
จิม Balter

27
ฉันไม่เห็นจุดจบของโค้ดที่ยุ่งเหยิงและเป็นอย่างอื่นในช่วงหลายปีที่ผ่านมาเพื่อหลีกเลี่ยงการข้าม @jimmcKeeth, การ์ตูน xkcd ข้างต้นไม่ได้สร้างข้ามไปที่อ่อนแอ มันทำให้ความสนุกของฮิสทีเรียรอบ ๆ ใช้มัน

4
คุณตระหนักดีว่าซอร์สโค้ดของ Delphi มีคำสั่ง goto และนี่คือการใช้งานที่เหมาะสมของ goto
David Heffernan

131

บางครั้งการใช้ GOTO เป็นทางเลือกแทนการจัดการข้อยกเว้นภายในฟังก์ชันเดียว:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

รหัส COM ดูเหมือนจะตกอยู่ในรูปแบบนี้ค่อนข้างบ่อย


29
ผมเห็นมีกรณีการใช้งานถูกต้องตามกฎหมายที่ข้ามไปสามารถลดความซับซ้อนรหัสและทำให้มันอ่านได้มากขึ้น / การบำรุงรักษา แต่ดูเหมือนว่าจะมีอะไรบางอย่างกลับไปข้าง-หวาดกลัวลอยรอบ ...
ป๊อป Catalin

48
@Bob: มันยากที่จะย้ายรหัส err_cleanup ไปยังรูทีนย่อยถ้ามันล้างตัวแปรท้องถิ่น
Niki

4
จริงๆแล้วฉันใช้มันใน COM / VB6 เพียงเพราะฉันไม่มีทางเลือกไม่ใช่เพราะมันเป็นทางเลือก ทุกวันนี้ฉันมีความสุขแค่ไหนลอง / จับ / ในที่สุด
Rui Craveiro

10
@ user4891 วิธีสำนวนภาษา C ++ ไม่ได้ลอง {} catch () {ทำความสะอาด; } แต่แทนที่จะเป็น RAII ซึ่งทรัพยากรที่จำเป็นต้องมีการล้างข้อมูลให้ทำใน destructors Constructor / destructor ทุกตัวจัดการทรัพยากรเดียวอย่างแน่นอน
เดวิดสโตน

5
มีสองวิธีในการเขียนนี้ใน C โดยไม่ต้องข้ามไป; และทั้งคู่สั้นกว่ามาก ถ้า: (f ()) ถ้า (g ()) ถ้า (h ()) กลับมาประสบความสำเร็จ; ทำความสะอาด(); ส่งคืนความล้มเหลว; หรือ: ถ้า (f () && g () && h ()) กลับมาประสบความสำเร็จ; ทำความสะอาด(); ส่งคืนความล้มเหลว;
LHP

124

ฉันสามารถจำใช้ข้ามไปครั้งเดียว ผมมีชุดของห้าลูปนับซ้อนกันและฉันต้องการที่จะสามารถที่จะแยกออกจากโครงสร้างทั้งหมดจากภายในตามในช่วงต้นของเงื่อนไขบางอย่าง:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

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

ศาสนไม่มีการโจมตีฉัน


83
"Refactor เป็นฟังก์ชันและแทนที่ goto ด้วย return :)" และความแตกต่างคืออะไร? ความแตกต่างคืออะไร? ยังไม่กลับไปอีกหรือ ส่งคืนยังเบรกการไหลของโครงสร้างเหมือนที่ goto ทำและในกรณีนี้พวกเขาทำแบบเดียวกัน (แม้ว่า goto สามารถใช้สำหรับสิ่งที่มีความหมายต่ำกว่า) ได้
Pop Catalin

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

5
การแทนที่ด้วยการส่งคืนจะใช้ได้เฉพาะเมื่อคุณใช้ภาษาที่รองรับการคืนสินค้า
Loren Pechtel

31
@ leppie: รุ่นที่ขัดขืนgotoและทำให้เราเขียนโปรแกรมที่มีโครงสร้างก็ปฏิเสธผลตอบแทนในช่วงต้นด้วยเหตุผลเดียวกัน มันลงมาถึงวิธีการอ่านรหัสเป็นวิธีการอย่างชัดเจนว่ามันแสดงโปรแกรมเมอร์เจตนา การสร้างฟังก์ชั่นเพื่อวัตถุประสงค์อื่น ๆ กว่าที่จะหลีกเลี่ยงการใช้คำหลักผลร้ายในการทำงานร่วมกันที่ไม่ดี: การรักษาจะเลวร้ายยิ่งกว่าโรค
โคลน

21
@ButtleButkus: ตรงไปตรงมามันก็ไม่ดีถ้าไม่แย่ อย่างน้อยด้วยgotoหนึ่งสามารถระบุเป้าหมายได้อย่างชัดเจน ด้วยbreak 5;(1) ฉันต้องนับการปิดวงที่จะหาปลายทาง; และ (2) หากโครงสร้างลูปเปลี่ยนแปลงตลอดเวลาอาจต้องเปลี่ยนหมายเลขนั้นเพื่อให้ปลายทางถูกต้อง ถ้าฉันจะหลีกเลี่ยงgotoการได้กำไรนั้นไม่ควรติดตามสิ่งต่างๆด้วยตนเอง
เจ้าพระยา

93

แล้วเรามีนี้การอภิปรายและฉันยืนตามจุดของฉัน

นอกจากนี้ฉันยังเบื่อหน่ายกับคนที่อธิบายโครงสร้างภาษาระดับสูงว่า“ gotoแฝง” เพราะเห็นได้ชัดว่าไม่ได้มีจุดประสงค์อะไรเลย ตัวอย่างเช่น:

แม้แต่โครงสร้างการควบคุมความต่อเนื่องขั้นสูงใน Scheme ก็สามารถอธิบายได้ว่าเป็น goto ที่มีความซับซ้อน

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

ในทำนองเดียวกันการพูดว่า“ GOTO เป็นเครื่องมือและเป็นเครื่องมือทั้งหมดที่สามารถใช้และถูกทารุณกรรม” ได้โดยสมบูรณ์ ไม่มีคนงานก่อสร้างสมัยใหม่ที่จะใช้หินและอ้างว่า "เป็นเครื่องมือ" หินถูกแทนที่ด้วยค้อน gotoถูกแทนที่ด้วยโครงสร้างการควบคุม หากคนงานก่อสร้างติดอยู่ในป่าโดยไม่มีค้อนแน่นอนเขาจะใช้หินแทน หากโปรแกรมเมอร์ต้องใช้ภาษาการเขียนโปรแกรมที่ด้อยกว่าที่ไม่มีฟีเจอร์ X แน่นอนว่าเธออาจต้องใช้gotoแทน แต่ถ้าเธอใช้ที่อื่นแทนที่จะใช้คุณสมบัติภาษาที่เหมาะสมเธอจะไม่เข้าใจภาษาอย่างถูกต้องและใช้ผิด จริงๆมันเป็นง่ายๆเป็นว่า


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

10
ดังนั้นการใช้ Goto ที่เหมาะสมคืออะไร? สำหรับทุกกรณีเท่าที่มีเครื่องมืออื่นที่ดีเหมาะ และแม้แต่หินเจียรของคุณก็ถูกแทนที่ด้วยเครื่องมือไฮเทคทุกวันนี้แม้ว่าพวกเขาจะยังคงทำจากหินอยู่ มีความแตกต่างใหญ่ระหว่างวัตถุดิบและเครื่องมือ
Konrad Rudolph

8
@jalf: ไปแน่นอนที่สุดจะอยู่ใน C # ดูstackoverflow.com/questions/359436/…
Jon Skeet

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

10
@ การเข้ารหัส: ไม่คุณพลาดส่วนสำคัญของการโพสต์ มันเป็นriposteมากกว่าอาร์กิวเมนต์ที่แยกและสมบูรณ์ ฉันเพียง gotoแต่ชี้ให้เห็นความล้มเหลวในเหตุผลหลัก“สำหรับการที่” คุณพูดถูกเพราะฉันไม่ได้โต้แย้งอะไรgotoเลย - ฉันไม่ได้ตั้งใจ - ดังนั้นจึงไม่มีการถามคำถาม
Konrad Rudolph

91

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

ไปที่ดีสำหรับเครื่องจักรของรัฐ คำสั่ง switch ในลูปคือ (ตามลำดับความสำคัญโดยทั่วไป): (a) ไม่ได้เป็นตัวแทนของโฟลว์ควบคุม, (b) น่าเกลียด, (c) อาจไม่มีประสิทธิภาพขึ้นอยู่กับภาษาและคอมไพเลอร์ ดังนั้นคุณจะต้องเขียนหนึ่งฟังก์ชันต่อรัฐและทำสิ่งต่าง ๆ เช่น "return NEXT_STATE;" ซึ่งดูเหมือนว่าข้ามไป

ได้รับมันเป็นเรื่องยากที่จะรหัสเครื่องรัฐในทางที่ทำให้พวกเขาเข้าใจง่าย อย่างไรก็ตามไม่มีปัญหาใด ๆ เกี่ยวกับการใช้ goto และไม่สามารถลดได้โดยใช้โครงสร้างการควบคุมทางเลือก หากภาษาของคุณไม่มีโครงสร้างเครื่อง เหมืองแร่ไม่ได้

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

setjmp / longjmp สามารถที่ดีสำหรับการดำเนินการข้อยกเว้นหรือข้อยกเว้นเช่นพฤติกรรม ขณะที่ไม่ได้รับการยกย่องในระดับสากลข้อยกเว้นทั่วไปมักจะคิดว่า "ถูกต้อง" โครงสร้างการควบคุม

setjmp / longjmp นั้นอันตรายยิ่งกว่า goto ในแง่ที่ว่ามันยากที่จะใช้อย่างถูกต้อง

ไม่เคยมีหรือจะไม่เคยมีภาษาใด ๆ ที่มันเป็นการยากที่จะเขียนโค้ดไม่ดี - Donald Knuth

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

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


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

2
คุณสามารถใช้คำสั่ง switch ภายในลูป (หรือตัวจัดการเหตุการณ์) เพื่อทำเครื่องจักรสถานะได้ดี ฉันได้ทำเครื่องรัฐจำนวนมากโดยไม่ต้องใช้ jmp หรือ goto
สกอตต์วิทล็อค

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

2
ฉันสามารถพูดคุณในวรรคสุดท้ายหรือไม่
คริสลัทซ์

2
และที่นี่ในปี 2013 เราได้ตี (และผ่านมาแล้ว) ระยะ "ตัวชี้ถือว่าเป็นอันตราย"
Isiah Meadows

68

ในLinux: การใช้ goto ใน Kernel Codeบน Kernel Trap มีการสนทนากับ Linus Torvalds และ "คนใหม่" เกี่ยวกับการใช้ GOTO ในรหัส Linux มีบางจุดที่ดีมากและ Linus แต่งกายด้วยความเย่อหยิ่งตามปกติ :)

บางตอน:

Linus: "ไม่คุณถูกล้างสมองโดยคน CS ที่คิดว่า Niklaus Wirth รู้จริง ๆ ว่าเขากำลังพูดถึงอะไรเขาไม่เขาไม่ได้มีร่องรอยที่เยือกเย็นเลย"

-

Linus: "ฉันคิดว่าของ goto นั้นใช้ได้และพวกเขามักจะอ่านได้ดีกว่าการเยื้องจำนวนมาก"

-

Linus: "แน่นอนในภาษาที่โง่เขลาอย่าง Pascal ที่ฉลากไม่สามารถสื่อความหมายได้ข้ามไปได้ก็ไม่ดี"


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

5
ว้าว. มันน่าผิดหวังที่อ่าน คุณจะคิดว่าเป็นผู้ชายที่ OS ใหญ่เช่น Linus Torvalds จะรู้ดีกว่าที่จะบอกว่า Pascal (ปาสกาลโรงเรียนเก่าไม่ใช่รุ่น Object สมัยใหม่) เป็นสิ่งที่ Mac OS Classic เขียนในช่วงยุค 68k และเป็นระบบปฏิบัติการที่ทันสมัยที่สุดในยุคนั้น
เมสันล้อ

6
@mason Classic Mac OS มีไลบรารี Pascal บางอัน (ในที่สุด - Pascal runtime ใช้หน่วยความจำมากเกินไปใน Macs รุ่นแรก) แต่ส่วนใหญ่ของรหัสแกนหลักถูกเขียนใน Assembler โดยเฉพาะกราฟิกและ UI ประจำ
จิม Dovey

30
ไลนัสให้เหตุผลเท่านั้น (โดยชัดแจ้งเช่น Rik van Riel ในการอภิปราย) สำหรับการจัดการสถานะทางออกและกลับไปบนพื้นฐานของความซับซ้อนที่โครงสร้างทางเลือกของ C จะนำมาหากพวกเขาถูกนำมาใช้แทน
Charles Stewart

21
IMHO Linus พูดถูกเรื่องนี้ ประเด็นของเขาคือรหัสเคอร์เนลที่เขียนด้วย C ซึ่งต้องใช้สิ่งที่คล้ายกับการจัดการข้อยกเว้นนั้นชัดเจนที่สุดและเขียนโดยใช้ goto สำนวนgoto cleanup_and_exitเป็นหนึ่งในไม่กี่คนที่ "ดี" การใช้ประโยชน์จากกลับไปข้างซ้ายตอนนี้ที่เรามีfor, whileและifการจัดการการควบคุมการไหลของเรา ดูเพิ่มเติมที่: programmers.stackexchange.com/a/154980
steveha

50

ใน C gotoทำงานได้เฉพาะภายในขอบเขตของฟังก์ชันปัจจุบันซึ่งมีแนวโน้มที่จะ จำกัด วงบั๊กที่อาจเกิดขึ้น setjmpและlongjmpเป็นอันตรายมากกว่าอยู่นอกท้องถิ่นซับซ้อนและขึ้นอยู่กับการนำไปใช้ อย่างไรก็ตามในทางปฏิบัติมันไม่ชัดเจนและผิดปกติเกินไปที่จะทำให้เกิดปัญหามากมาย

ฉันเชื่อว่าอันตรายของgotoใน C นั้นเกินจริงอย่างมาก โปรดจำไว้ว่าgotoข้อโต้แย้งดั้งเดิมเกิดขึ้นในยุคของภาษาอย่าง BASIC ที่ผู้เริ่มต้นจะเขียนโค้ดสปาเก็ตตี้เช่นนี้:

3420 IF A > 2 THEN GOTO 1430

ที่นี่ Linus อธิบายการใช้งานที่เหมาะสมของgoto: http://www.kernel.org/doc/Documentation/CodingStyle (บทที่ 7)


7
เมื่อ BASIC เปิดให้ใช้งานเป็นครั้งแรกไม่มีทางเลือกอื่นสำหรับ GOTO nnnn และ GOSUB mmmm เพื่อเป็นวิธีในการกระโดดไปมา โครงสร้างที่มีโครงสร้างถูกเพิ่มเข้ามาในภายหลัง
Jonathan Leffler

7
คุณหายไปจากจุด ... ถึงแม้คุณจะไม่ต้องเขียนสปาเก็ตตี้ ... GOTOs ของคุณสามารถใช้ในลักษณะที่มีระเบียบวินัยได้เสมอ
JoelFan

นอกจากนี้ยังเป็นที่น่าสังเกตว่าพฤติกรรมของsetjmp/ longjmpถูกระบุเฉพาะเมื่อใช้เป็นวิธีการกระโดดไปยังจุดหนึ่งภายในขอบเขตจากสถานที่อื่นภายในขอบเขตเดียวกันนั้น เมื่อการควบคุมออกจากขอบเขตที่setjmpจะถูกดำเนินการความพยายามใด ๆ ที่จะใช้longjmpกับโครงสร้างที่สร้างโดยsetjmpจะส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนด
SuperCat

9
GOTO A * 40 + B * 200 + 30บางรุ่นพื้นฐานจะช่วยให้คุณทำ ไม่ยากเลยที่จะเห็นว่าสิ่งนี้มีประโยชน์มากและอันตรายมากเพียงใด
Jon Hanna

1
@Hjulle มันจะคำนวณการแสดงออกและจากนั้นไปที่บรรทัดของรหัสที่มีตัวเลขที่ (หมายเลขบรรทัดอย่างชัดเจนเป็นความต้องการของท้องถิ่นก่อนหน้านี้มากที่สุด) ZX Spectrum Basic เป็นสิ่งหนึ่งที่ยอมรับได้
Jon Hanna

48

วันนี้มันยากที่จะเห็นเป็นเรื่องใหญ่เกี่ยวกับGOTOคำสั่งเพราะ "การเขียนโปรแกรมโครงสร้าง" GOTOคนส่วนใหญ่ได้รับรางวัลการอภิปรายและภาษาของวันนี้มีโครงสร้างการควบคุมการไหลเพียงพอที่จะหลีกเลี่ยง

นับจำนวนของgotoในโปรแกรม C ที่ทันสมัย ตอนนี้เพิ่มจำนวนbreak, continueและreturnงบ นอกจากนี้เพิ่มจำนวนครั้งที่คุณใช้if, else, while, หรือswitch caseเกี่ยวกับวิธีการหลายอย่างที่GOTOs โปรแกรมของคุณจะมีถ้าคุณเขียนใน FORTRAN หรือพื้นฐานในปี 1968 เมื่อ Dijkstra เขียนหนังสือของเขา

ภาษาการเขียนโปรแกรมในเวลานั้นขาดการควบคุมโฟลว์ ยกตัวอย่างเช่นในต้นฉบับดาร์ทเมาท์พื้นฐาน:

  • IFELSEงบไม่มี หากคุณอยากหนึ่งคุณจะต้องเขียน:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • แม้ว่าคุณIFงบไม่จำเป็นต้องมีELSEมันก็ยังคง จำกัด GOTOอยู่ในบรรทัดเดียวซึ่งมักจะประกอบไปด้วย

  • ไม่มีDO...LOOPคำสั่ง สำหรับ non- FORloops คุณต้องจบการวนซ้ำอย่างชัดเจนGOTOหรือIF...GOTOย้อนกลับไปที่จุดเริ่มต้น

  • SELECT CASEไม่มี ON...GOTOคุณต้องใช้

ดังนั้นคุณจะจบลงด้วยการที่มีจำนวนมากของGOTOในโปรแกรมของคุณ และคุณไม่ได้ขึ้นอยู่กับข้อ จำกัด ของGOTOเพื่อภายใน subroutine เดียว (เพราะGOSUB...RETURNเป็นเช่นแนวคิดที่อ่อนแอของซับรูทีน) ดังนั้นเหล่านี้GOTOs สามารถไปได้ทุกที่ เห็นได้ชัดว่าสิ่งนี้ทำให้การควบคุมเป็นเรื่องยาก

นี่คือที่GOTOมาของการเคลื่อนไหวต่อต้าน


สิ่งที่ควรทราบก็คือว่าวิธีที่ต้องการของการเขียนรหัสถ้าใครมีโค้ดบางส่วนในวงซึ่งจะมีการดำเนินการที่ไม่ค่อยจะเป็น420 if (rare_condition) then 3000// 430 and onward: rest of loop and other main-line code// //3000 [code for rare condition] 3230 goto 430การเขียนโค้ดเพื่อหลีกเลี่ยงการแตกกิ่งก้านสาขาหรือการกระโดดในกรณีสายหลักทั่วไป แต่มันทำให้ยากต่อการติดตาม หลีกเลี่ยงสาขาในรหัสการชุมนุมอาจจะเลวร้ายยิ่งถ้าบางสาขาจะถูก จำกัด ให้เช่น +/- 128 ไบต์และบางครั้งก็ไม่ได้มีคู่ที่สมบูรณ์ (เช่น "cjne" มีอยู่ แต่ไม่ได้ "cje")
supercat

ฉันเคยเขียนโค้ดบางส่วนสำหรับ 8x51 ซึ่งมีการขัดจังหวะที่รันทุกๆ 128 รอบ ทุกรอบพิเศษที่ใช้ในกรณีทั่วไปของ ISR นั้นจะลดความเร็วในการประมวลผลของ mainline code มากกว่า 1% (ฉันคิดว่าประมาณ 90 รอบจาก 128 นั้นมักจะใช้กับสายหลัก) และคำสั่งการแยกใด ๆ จะใช้สองรอบ ( ทั้งในการแตกแขนงและกรณีล้ม) รหัสมีการเปรียบเทียบสองแบบ - แบบหนึ่งซึ่งมักจะรายงานว่าเท่ากัน อื่น ๆ ที่ไม่เท่ากัน ในทั้งสองกรณีรหัสตัวพิมพ์หายากมีขนาดเกิน 128 ไบต์ ดังนั้น ...
supercat

cjne r0,expected_value,first_comp_springboard/.../ cjne r1,unexpected_value,second_comp_fallthrough// `ajmp second_comp_target` // //first_comp_springboard: ajmp first_comp_target second_comp_fallthrough: ...ไม่ใช่รูปแบบการเข้ารหัสที่ดีมาก แต่เมื่อรอบแต่ละรอบนับหนึ่งจะทำสิ่งนั้น แน่นอนว่าในช่วงทศวรรษที่ 1960 การเพิ่มประสิทธิภาพในระดับดังกล่าวมีความสำคัญมากกว่าในปัจจุบันโดยเฉพาะอย่างยิ่งเนื่องจากซีพียูสมัยใหม่มักต้องการการปรับแต่งที่แปลกใหม่และระบบการคอมไพล์แบบทันเวลาอาจสามารถใช้โค้ดเพิ่มประสิทธิภาพสำหรับซีพียูที่ไม่มีอยู่ เขียนรหัสคำถาม
SuperCat

34

Go To สามารถจัดเรียง stand-in สำหรับการจัดการข้อยกเว้น "ของจริง" ในบางกรณี พิจารณา:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

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

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

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

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


1
แม้ว่าฉันมักจะเห็นด้วยกับสิ่งที่คุณพูดที่นี่ความจริงที่ว่าbreakงบอยู่ในโครงสร้างการควบคุมชัดเจนว่าพวกเขาทำอะไร จากgotoตัวอย่างผู้ที่อ่านรหัสจะต้องมองหารหัสเพื่อค้นหาฉลากซึ่งในทางทฤษฎีอาจอยู่ก่อนโครงสร้างการควบคุม ฉันไม่ได้มีประสบการณ์มากพอกับ C แบบเก่าที่จะตัดสินว่าคนหนึ่งดีกว่าคนอื่นอย่างแน่นอน แต่ก็มีการแลกเปลี่ยนกันทั้งสองทาง
Vivian River

5
@DanielAllenLangdon: ความจริงที่ว่าbreaks อยู่ภายในวงทำให้เห็นได้ชัดว่าที่พวกเขาออกจากวง นั่นไม่ใช่ "สิ่งที่พวกเขาทำ" เนื่องจากในความเป็นจริงแล้วมันไม่มีการวนซ้ำเลย! ไม่มีสิ่งใดในนั้นที่จะมีโอกาสเกิดซ้ำ แต่นั่นไม่ชัดเจนจนกว่าจะสิ้นสุด ความจริงที่ว่าคุณมี "ลูป" ที่ไม่ทำงานมากกว่าหนึ่งครั้งหมายความว่าโครงสร้างการควบคุมกำลังถูกใช้งานในทางที่ผิด ด้วยgotoตัวอย่างโปรแกรมเมอร์สามารถพูดgoto error_handler;ได้ มันชัดเจนมากขึ้นและยากที่จะติดตาม (Ctrl + F, "error_handler:" เพื่อค้นหาเป้าหมายลองทำด้วย "}")
cHao

1
ฉันเคยเห็นโค้ดที่คล้ายกับตัวอย่างที่สองของคุณในระบบควบคุมการจราจรทางอากาศ - เนื่องจาก 'goto ไม่ได้อยู่ในพจนานุกรมของเรา'
QED

30

Donald E. Knuth ตอบคำถามนี้ในหนังสือ "Literate Programming", 1992 CSLI บนหน้า 17 มีการเขียนเรียงความ " โครงสร้างการเขียนโปรแกรมด้วยงบโกโตะ " (PDF) ฉันคิดว่าบทความนี้อาจตีพิมพ์ในหนังสือเล่มอื่นด้วย

บทความอธิบายข้อเสนอแนะของ Dijkstra และอธิบายถึงสถานการณ์ที่สิ่งนี้ถูกต้อง แต่เขายังให้ตัวอย่างเคาน์เตอร์จำนวนมาก (ปัญหาและอัลกอริธึม) ซึ่งไม่สามารถทำซ้ำได้ง่ายโดยใช้ลูปที่มีโครงสร้างเท่านั้น

บทความนี้มีคำอธิบายที่สมบูรณ์ของปัญหาประวัติศาสตร์ตัวอย่างและตัวอย่างเคาน์เตอร์


25

ไปที่ถือว่าเป็นประโยชน์

ผมเริ่มเขียนโปรแกรมในปี 1975 ในการเขียนโปรแกรม 1970 ยุคคำว่า "โกโตะถือว่าเป็นอันตราย" กล่าวว่ามากหรือน้อยกว่าที่การเขียนโปรแกรมภาษาใหม่ที่มีโครงสร้างการควบคุมที่ทันสมัยถูกคุ้มค่าพยายาม เราลองใช้ภาษาใหม่ เราแปลงอย่างรวดเร็ว เราไม่เคยกลับไป

เราไม่เคยกลับไป แต่ถ้าคุณอายุน้อยกว่าคุณจะไม่เคยไปที่นั่นตั้งแต่แรก

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

สโลแกนที่ไม่เข้าใจไม่สว่างมาก มันอาจจะเป็นที่ดีที่สุดที่จะลืมคำขวัญดังกล่าว คำขวัญดังกล่าวไม่ได้ช่วย

สโลแกนนี้โดยเฉพาะอย่างไรก็ตาม "ไปถือว่าอันตราย" ได้ดำเนินการในชีวิตที่ตายของตัวเอง

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

ในทางตรงกันข้ามฉันจำไม่ได้ว่าได้พบกับอินสแตนซ์ที่แท้จริงของการละเมิดการใช้ข้ามไปตั้งแต่ปี 1990

ปัญหาที่ใหญ่ที่สุดของ goto อาจไม่ใช่ทางด้านเทคนิค แต่เป็นเรื่องของสังคม โปรแกรมเมอร์ที่ไม่รู้มากบางครั้งดูเหมือนจะรู้สึกว่าการข้ามไปแล้วทำให้พวกเขาฟังดูดี คุณอาจต้องพึงพอใจโปรแกรมเมอร์ดังกล่าวเป็นครั้งคราว นั่นคือชีวิต.

สิ่งที่แย่ที่สุดเกี่ยวกับ goto ในวันนี้คือมันไม่ได้ใช้งานเพียงพอ


24

ดึงดูดโดย Jay Ballou เพิ่มคำตอบฉันจะเพิ่ม£ 0.02 ของฉัน หาก Bruno Ranschaert ยังไม่ได้ทำเช่นนั้นฉันได้กล่าวถึงบทความ "โครงสร้างการเขียนโปรแกรมด้วยงบ GOTO" ของ Knuth

สิ่งหนึ่งที่ฉันไม่เคยเห็นมาพูดถึงคือการเรียงลำดับของรหัสที่ถูกสอนในหนังสือเรียนของ Fortran สิ่งต่าง ๆ เช่นการขยายขอบเขตของ DO loop และรูทีนย่อย open-coded (จำไว้ว่านี่คือ Fortran II หรือ Fortran IV หรือ Fortran 66 หรือ Fortran 66 - ไม่ใช่ Fortran 77 หรือ 90) อย่างน้อยก็มีโอกาสที่รายละเอียดวากยสัมพันธ์นั้นไม่แน่นอน แต่แนวคิดควรมีความแม่นยำเพียงพอ ข้อมูลโค้ดในแต่ละกรณีอยู่ในฟังก์ชั่นเดียว

โปรดทราบว่าหนังสือ ' The Elements of Programming Style, 2nd Edn ' ที่ยอดเยี่ยม แต่เก่าและล้าสมัยโดย Kernighan & Plauger มีตัวอย่างชีวิตจริงของการละเมิด GOTO จากการเขียนโปรแกรมหนังสือข้อความในยุค (ปลายยุค 70) อย่างไรก็ตามเนื้อหาด้านล่างไม่ได้มาจากหนังสือเล่มนั้น

ขยายช่วงสำหรับลูป DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

เหตุผลข้อหนึ่งสำหรับเรื่องไร้สาระเช่นนั้นก็คือบัตรตอกที่ดีสมัยเก่า คุณอาจสังเกตว่าป้ายกำกับ (เรียงตามลำดับเนื่องจากเป็นรูปแบบมาตรฐาน!) อยู่ในคอลัมน์ 1 (จริง ๆ แล้วต้องอยู่ในคอลัมน์ 1-5) และรหัสอยู่ในคอลัมน์ 7-72 ​​(คอลัมน์ 6 เป็นความต่อเนื่อง คอลัมน์เครื่องหมาย) คอลัมน์ 73-80 จะได้รับหมายเลขลำดับและมีเครื่องที่จะจัดเรียงชั้นบัตรหมัดเป็นใบสั่งหมายเลขลำดับได้ หากคุณมีโปรแกรมของคุณบนการ์ดที่เรียงลำดับและจำเป็นต้องเพิ่มการ์ด (เส้น) สองสามอันลงในวงกลางของวงคุณจะต้องคืนเงินทุกอย่างหลังจากผ่านสายพิเศษเหล่านั้น แต่ถ้าคุณแทนที่บัตรเดียวกับสิ่งที่ GOTO คุณสามารถหลีกเลี่ยง resequencing บัตรทั้งหมด - คุณเพียงแค่ซุกบัตรใหม่ในตอนท้ายของงานประจำที่มีหมายเลขลำดับใหม่ ถือว่าเป็นความพยายามครั้งแรกที่ 'การคำนวณสีเขียว'

โอ้คุณอาจจะทราบว่าฉันกำลังโกงและไม่ตะโกน - Fortran IV เขียนด้วยตัวพิมพ์ใหญ่ทั้งหมดตามปกติ

รูทีนย่อยเปิดรหัส

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

GOTO ระหว่างเลเบล 76 และ 54 เป็นเวอร์ชันของ goto ที่คำนวณ หากตัวแปร i มีค่า 1 ให้ไปที่ป้ายกำกับแรกในรายการ (123); ถ้ามันมีค่าเป็น 2 ให้ข้ามวินาทีแล้วเรื่อย ๆ แฟรกเมนต์จาก 76 ไปยัง goto ที่คำนวณแล้วเป็นรูทีนย่อย open-coded มันเป็นรหัสชิ้นหนึ่งที่ถูกประหารชีวิตแทนที่จะเป็นรูทีนย่อย แต่เขียนออกมาในส่วนของฟังก์ชั่น (Fortran ยังมีฟังก์ชั่นคำสั่ง - ซึ่งเป็นฟังก์ชั่นแบบฝังที่ติดตั้งอยู่ในบรรทัดเดียว)

มีโครงสร้างที่แย่กว่า goto ที่คำนวณ - คุณสามารถกำหนดป้ายกำกับให้กับตัวแปรแล้วใช้ goto ที่กำหนด Googling ที่ได้รับมอบหมาย gotoบอกฉันว่ามันถูกลบออกจาก Fortran 95 ชอล์กหนึ่งสำหรับการปฏิวัติการเขียนโปรแกรมที่มีโครงสร้างซึ่งอาจกล่าวได้ว่าเริ่มต้นในที่สาธารณะด้วยจดหมายหรือบทความ "GOTO Considered Harmful อันตราย" ของ Dijkstra

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


2
หากคุณต้องการดูตัวอย่างของรหัสบางส่วนที่ใช้goto'in the wild' คำถามที่ต้องการ: อัลกอริทึมการเรียงลำดับ Bose-Hibbardแสดงรหัส (Algol 60) ที่เผยแพร่ในปี 1963 บางส่วนเค้าโครงดั้งเดิมไม่ได้เปรียบเทียบกับมาตรฐานการเข้ารหัสที่ทันสมัย รหัสที่ชัดเจน (เยื้อง) ยังคงไม่สามารถพิสูจน์ได้ชัดเจน gotoงบมีไม่ทำให้มัน (มาก) ยากที่จะเข้าใจในสิ่งที่อัลกอริทึมจะขึ้นอยู่กับ
Jonathan Leffler

1
เป็นเด็กเกินไปที่จะมีอะไรที่ใกล้มีประสบการณ์ในการชกต่อยบัตรมันก็ enlightening อ่านเกี่ยวกับปัญหาเรื่องการเจาะ +1
Qix - MONICA ถูกยกเลิก

20

ไม่มีสิ่งดังกล่าวเป็นGOTO ถือว่าเป็นอันตราย

GOTO เป็นเครื่องมือและเป็นเครื่องมือที่ทุกคนก็สามารถนำมาใช้และถูกทารุณกรรม

อย่างไรก็ตามมีเครื่องมือมากมายในโลกแห่งการเขียนโปรแกรมที่มีแนวโน้มที่จะถูกทำร้ายมากกว่าการใช้งานและ GOTO เป็นหนึ่งในนั้น กับงบ Delphi เป็นอีกหนึ่ง

โดยส่วนตัวแล้วฉันไม่ได้ใช้ทั้งในรหัสทั่วไปแต่ฉันมีการใช้งานที่แปลกทั้งGOTOและด้วยที่ได้รับการรับประกันและวิธีการแก้ปัญหาอื่นจะมีรหัสเพิ่มเติม

ทางออกที่ดีที่สุดสำหรับคอมไพเลอร์เพียงแค่เตือนคุณว่าคำหลักนั้นเสียและคุณจะต้องสั่งคำสั่ง pragma สองสามคำรอบคำสั่งเพื่อกำจัดคำเตือน

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


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

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

แม้ว่าgotoคำหลักจะถูกขัดจาก C # แต่แนวคิดของ "กระโดดที่นี่" ยังคงมีอยู่ใน IL การวนซ้ำไม่สิ้นสุดสามารถสร้างได้ง่ายโดยไม่ต้องใช้คำหลัก goto หากการขาดการรับประกันว่ารหัสที่เรียกจะกลับมาในความคิดของคุณหมายถึง "ไม่สามารถให้เหตุผลเกี่ยวกับรหัส" ได้ฉันก็จะบอกว่าเราไม่เคยมีความสามารถนั้น
Lasse V. Karlsen

อ๊ะคุณมีความรู้ลึกซึ้งพบที่มาของการสื่อสารผิดพลาดของเรา gotoใน C # ไม่ได้เป็นจริง "โกโตะ" ในความหมายเดิมของคำว่า เป็นรุ่นที่อ่อนแอกว่ามากที่อนุญาตให้กระโดดภายในฟังก์ชันเท่านั้น ความรู้สึกที่ฉันใช้ "goto" ช่วยให้สามารถกระโดดได้ทุกที่ในกระบวนการ ดังนั้นถึงแม้ว่า C # จะมีคำหลัก "goto" แต่ก็ไม่เคยมีคำตอบที่เป็นไปได้ ใช่ goto ที่แท้จริงนั้นมีให้ที่ระดับ IL ในลักษณะเดียวกับที่เป็นเมื่อมีการรวบรวมภาษาใด ๆ เพื่อประกอบเข้าด้วยกัน แต่โปรแกรมเมอร์ได้รับการปกป้องจากสิ่งนั้นไม่สามารถใช้งานได้ภายใต้สถานการณ์ปกติดังนั้นจึงไม่นับ
Jonathan Hartley

ความเห็นที่ฉันอธิบายที่นี่ไม่ได้เป็นของฉัน แต่เป็นหัวใจสำคัญของกระดาษต้นฉบับของ Dijkstra จากปี 1967 ฉันคิดว่าเขามีบรรดาศักดิ์อีกครั้งโดยบรรณาธิการของเขา "โกโตะถือว่าเป็นอันตราย" ปีอย่างแม่นยำเพราะมันเป็นการปฏิวัติที่เฉียบแหลมและเป็นที่ยอมรับในระดับสากล
Jonathan Hartley


17

ตั้งแต่ฉันเริ่มทำบางสิ่งในเคอร์เนล linux, gotos ไม่ได้รบกวนฉันเท่าที่พวกเขาเคยทำ ตอนแรกผมก็จัดเรียงของหวาดกลัวที่จะเห็นพวกเขา (พวกเคอร์เนล) เพิ่ม gotos เป็นรหัสของฉัน ฉันคุ้นเคยกับการใช้ gotos ในบางบริบทที่ จำกัด และตอนนี้ฉันจะใช้มันเป็นครั้งคราว โดยทั่วไปแล้วจะเป็น goto ที่ข้ามไปยังจุดสิ้นสุดของฟังก์ชันเพื่อล้างข้อมูลและประกันตัวออกไปแทนที่จะทำซ้ำการล้างข้อมูลและ bailout เดียวกันในหลาย ๆ ที่ของฟังก์ชัน และโดยทั่วไปแล้วมันไม่ได้มีขนาดใหญ่พอที่จะส่งมอบให้กับฟังก์ชั่นอื่น - เช่นการปล่อยตัวแปร malloc'ed ในตัวเครื่องเป็นกรณีทั่วไป

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

setjmp / longjmp มีสถานที่ของพวกเขา - แต่สถานที่นั้นเป็นสถานที่ที่คุณจะไม่ได้ไปเยี่ยมชม แต่นาน ๆ ครั้ง

แก้ไข: ฉันเพิ่งดูรหัส ที่จริงแล้วมันเป็น siglongjmp () ที่ฉันใช้ไม่ใช่ longjmp (ไม่ใช่ว่ามันเป็นเรื่องใหญ่ แต่ฉันลืมไปว่า siglongjmp ยังมีอยู่)


15

หากคุณกำลังเขียน VM ใน C ปรากฎว่าการใช้ (gcc's) ที่คำนวณได้ gotos เช่นนี้:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

ทำงานได้เร็วกว่าสวิตช์ทั่วไปภายในลูป


ปัญหาเฉพาะที่ไม่ได้มาตรฐานใช่ไหม
Calmarius

&&op_incแน่นอนไม่ได้รวบรวมเพราะ (ซ้าย) &คาดว่า lvalue แต่ (ขวา) &ผลผลิต rvalue
fredoverflow

7
@FredO: เป็นผู้ให้บริการ GCC แบบพิเศษ อย่างไรก็ตามฉันจะปฏิเสธรหัสนี้ในทุกกรณียกเว้นสถานการณ์ที่เลวร้ายที่สุดเพราะฉันส่วนใหญ่ไม่สามารถเข้าใจ wtf ที่เกิดขึ้นได้อย่างแน่นอน
ลูกสุนัข

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

14

เพราะgotoสามารถนำมาใช้เพื่อสร้างความสับสน metaprogramming

Gotoเป็นทั้งนิพจน์การควบคุมระดับสูงและระดับต่ำและดังนั้นจึงไม่มีรูปแบบการออกแบบที่เหมาะสมสำหรับปัญหาส่วนใหญ่

มันเป็นระดับต่ำในแง่ที่ว่าข้ามไปคือการดำเนินการแบบดั้งเดิมที่ดำเนินการบางสิ่งบางอย่างที่สูงขึ้นเช่นwhileหรือforeachหรือบางสิ่งบางอย่าง

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

ดังนั้นมีความเป็นธรรมดาและความชั่วร้ายgotoทางด้าน

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

ด้านชั่วร้ายที่เกี่ยวข้องกับชีวิตประจำไม่ได้ใช้ข้ามไปในขณะที่แบ่งหรือผลตอบแทน แต่ใช้มันสำหรับสิ่งที่เรียกว่าตรรกะปาเก็ตตี้ ในกรณีนี้นักพัฒนาที่มีความสุขของ goto กำลังสร้างโค้ดจากเขาวงกตของ goto และวิธีเดียวที่จะเข้าใจว่ามันคือการจำลองมันโดยรวมทางจิตใจซึ่งเป็นงานที่น่าเบื่อหน่ายอย่างมากเมื่อมีจำนวนมากของ goto ฉันหมายถึงลองนึกภาพปัญหาของการประเมินโค้ดที่elseไม่ตรงข้ามกับifที่ที่ซ้อนกันifอาจอนุญาตในบางสิ่งที่ถูกปฏิเสธจากภายนอกifฯลฯ

สุดท้ายจริงๆครอบคลุมเรื่องที่เราควรทราบว่าหลักทุกภาษาในช่วงต้นยกเว้น Algol if-then-elseทำครั้งแรกเพียงงบเรื่องเดียวกับรุ่นของพวกเขา ดังนั้นวิธีเดียวที่จะทำบล็อกแบบมีเงื่อนไขได้คือการgotoใช้บล็อกแบบมีเงื่อนไข บ้าฉันรู้ แต่ฉันอ่านสเป็คเก่า ๆ แล้ว โปรดจำไว้ว่าคอมพิวเตอร์เครื่องแรกนั้นถูกตั้งโปรแกรมในรหัสเครื่องไบนารี่ดังนั้นฉันคิดว่า HLL ชนิดใดก็ได้ที่เป็นเครื่องช่วยชีวิต ฉันเดาว่าพวกเขาไม่ได้พิถีพิถันเกินไปเกี่ยวกับคุณสมบัติของ HLL ที่พวกเขาได้รับ

ต้องบอกทุกสิ่งที่ผมใช้ในการติดหนึ่งgotoในโปรแกรมที่ผมเขียนทุก"เพียงเพื่อรบกวนครูสอน"


4
+1 สำหรับผู้สอนที่น่ารำคาญ! :-)
Lumi

10

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

นี่เป็นเพียงตัวอย่างเล็ก ๆ ของรหัสที่ฉันพบ

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------หรือ----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

3
CRT 'ถูกต้องไหม (Y / N): ': ป้อน YORN ถึง YORN =' Y 'หรือ YORN =' N '; ฯลฯ
joel.neely

5
แท้จริง แต่ที่สำคัญกว่าโปรแกรมเมอร์จริงรู้เมื่อไม่ได้ใช้goto- และรู้ว่าทำไม หลีกเลี่ยงการสร้างภาษาต้องห้ามเพราะ $ programming_guru พูดอย่างนั้นนั่นคือคำจำกัดความของการเขียนโปรแกรมสินค้า - ลัทธิ
Piskvor ออกจากอาคารใน

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


7

บทความต้นฉบับควรถูกมองว่าเป็น "GOTO ที่ไม่มีเงื่อนไขถือว่าเป็นอันตราย" โดยเฉพาะอย่างยิ่งการสนับสนุนรูปแบบของการเขียนโปรแกรมตามเงื่อนไข ( if) และซ้ำ ( while) สร้างมากกว่าการทดสอบและกระโดดร่วมกับรหัสแรก gotoยังคงมีประโยชน์ในบางภาษาหรือสถานการณ์ที่ไม่มีโครงสร้างการควบคุมที่เหมาะสมอยู่


6

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

ตัวอย่างเช่นหากคุณกำลังคว้าทรัพยากรและใช้เซมาฟอร์หรือ mutexes คุณต้องคว้ามันตามลำดับและคุณควรปล่อยมันในลักษณะตรงกันข้าม

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

เป็นไปได้เสมอที่จะทำถูกต้องโดยไม่มี goto แต่ในกรณีนี้และอีกไม่กี่คนที่ Goto เป็นทางออกที่ดีกว่าสำหรับการอ่านและการบำรุงรักษาเป็นหลัก

อดัม


5

การใช้งาน GOTO สมัยใหม่อย่างหนึ่งคือคอมไพเลอร์ C # เพื่อสร้างเครื่องสถานะสำหรับผู้แจงนับที่กำหนดโดยผลตอบแทน

GOTO เป็นสิ่งที่ควรใช้โดยคอมไพเลอร์ไม่ใช่โปรแกรมเมอร์


5
คุณคิดว่าใครเป็นผู้สร้างคอมไพเลอร์?
tloach

10
คอมไพเลอร์แน่นอน!
Seiti

3
ฉันคิดว่าเขาหมายถึง "GOTO เป็นสิ่งที่ควรใช้รหัสที่คอมไพเลอร์ปล่อยเท่านั้น"
Simon Buchan

gotoเป็นกรณีนี้กับ ที่ซึ่งเราอาจใช้gotoในเครื่องรัฐที่เขียนด้วยมือเพื่อใช้งานตัวแจงนับเราสามารถใช้yieldแทนได้
Jon Hanna

เปิดสตริงจำนวนมาก (เพื่อป้องกันการคอมไพล์เป็น if-else) ด้วยคอมไพล์เคสดีฟอลต์เพื่อสลับด้วยคำสั่ง goto
M.kazem Akhgary

5

จนกว่า C และ C ++ (ในบรรดาผู้ร้ายคนอื่น ๆ ) จะมีป้ายกำกับการหยุดพักและดำเนินการต่อไป goto จะยังคงมีบทบาทต่อไป


ดังนั้นการหยุดหรือดำเนินการติดป้ายกำกับจะแตกต่างจากการข้ามไปอย่างไร
Matthew Whited

3
พวกเขาไม่อนุญาตให้มีการกระโดดโดยพลการในการควบคุม
DrPizza

5

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

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

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

ฉันแน่ใจว่านี่ไม่ใช่เรื่องใหม่ แต่วิธีที่ฉันจัดการใน C (++) คือการกำหนดมาโครบางตัว:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

จากนั้น (สมมติว่าสถานะเป็นเริ่มแรก 0) เครื่องสถานะที่มีโครงสร้างด้านบนจะเปลี่ยนเป็นรหัสที่มีโครงสร้าง:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

ด้วยรูปแบบที่หลากหลายนี้สามารถมีการเรียกและผลตอบแทนดังนั้นเครื่องรัฐบางอย่างสามารถทำหน้าที่เหมือนรูทีนย่อยของเครื่องรัฐอื่น ๆ

มันผิดปกติหรือไม่? ใช่. ต้องเรียนรู้ในส่วนของผู้ดูแลหรือไม่ ใช่. การเรียนรู้นั้นจ่ายผลตอบแทนหรือไม่? ฉันคิดอย่างนั้น ทำได้หรือไม่ถ้าไม่มี GOTO ที่กระโดดลงบล็อก? Nope


1
การหลีกเลี่ยงการใช้ภาษาด้วยความกลัวเป็นสัญญาณของความเสียหายของสมอง นี่มันช่างฉลาดกว่านรกด้วย
Vector Gorgoth

4

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

มันไม่คุ้มค่า.


สิ่งที่ดีเกี่ยวกับ Try ... Catch blocks ใน C # คือการดูแลการล้างสแต็กและทรัพยากรที่จัดสรรอื่น ๆ (เรียกว่าการคลายสแต็ก) เป็นข้อยกเว้นทำให้เกิดข้อยกเว้นขึ้นกับตัวจัดการข้อยกเว้น สิ่งนี้ทำให้ลอง ... จับได้ดีกว่า Goto ดังนั้นถ้าคุณมีลอง ... จับให้ใช้
Scott Whitlock

ฉันมีทางออกที่ดีกว่า: ห่อโค้ดที่คุณต้องการให้ปรากฏใน 'do {... } ขณะที่ (0);' ห่วง ด้วยวิธีนี้คุณสามารถกระโดดออกมาในลักษณะเดียวกับ goto โดยไม่มีค่าใช้จ่ายในการลอง / catch loop (ฉันไม่รู้เกี่ยวกับ C # แต่ใน C ++ ลองใช้ราคาถูก & จับได้ราคาสูงดังนั้นดูเหมือนว่า มากเกินไปที่จะโยนข้อยกเว้นที่การกระโดดอย่างง่ายจะพอเพียง)
Jim Dovey

10
จิมปัญหาที่เกิดขึ้นก็คือมันไม่มีอะไรมากไปกว่าวิธีการที่จะได้รับการข้ามไปอย่างโง่เขลา
เข้ารหัสด้วยสไตล์

4

ฉันพบว่าตัวเองถูกบังคับให้ใช้ goto เพราะฉันไม่สามารถคิดวิธีที่ดีกว่า (เร็วกว่า) ในการเขียนรหัสนี้:

ฉันมีวัตถุที่ซับซ้อนและฉันต้องดำเนินการบางอย่างกับมัน หากวัตถุอยู่ในสถานะเดียวฉันสามารถทำเวอร์ชันด่วนของการดำเนินการได้มิฉะนั้นฉันต้องทำเวอร์ชันช้าของการดำเนินการ ในบางกรณีในช่วงกลางของการทำงานช้ามันเป็นไปได้ที่จะตระหนักว่าสิ่งนี้สามารถทำได้ด้วยการทำงานที่รวดเร็ว

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

นี่เป็นส่วนสำคัญของรหัสเรียลไทม์ UI ดังนั้นฉันจึงคิดว่า GOTO เป็นธรรมที่นี่

ฮิวโก้


วิธีที่ไม่ใช่ GOTO จะใช้ฟังก์ชัน fast_calculations ซึ่งเกิดค่าใช้จ่าย อาจไม่เห็นได้ชัดในสถานการณ์ส่วนใหญ่ แต่อย่างที่คุณบอกว่านี่เป็นความเร็วที่สำคัญ
Kyle Cronin

1
ดีที่ไม่น่าแปลกใจ รหัสทั้งหมดที่มีแนวทางปฏิบัติที่ไม่สมบูรณ์จะทำลายแนวทางปฏิบัติที่ดีที่สุดทั้งหมด แนวทางปฏิบัติที่ดีที่สุดสำหรับการเข้าถึงและการบำรุงรักษาไม่ใช่สำหรับการบีบออกอีก 10 มิลลิวินาทีหรือประหยัด RAM อีก 5 ไบต์
Jonathon

1
@ JonathonWisnoski การใช้งานที่ถูกต้องของ goto ยังกำจัดจำนวนของโค้ด spagetti ที่ยุ่งเหยิงด้วยรังของตัวแปรหนูเพื่อติดตามการที่เรากำลังไป
vonbrand

4

เกือบทุกสถานการณ์ที่สามารถใช้ goto ได้คุณสามารถทำสิ่งเดียวกันโดยใช้โครงสร้างอื่น คอมไพเลอร์จะใช้งานไปแล้ว

ฉันเองไม่เคยใช้มันอย่างชัดเจนไม่จำเป็นต้องเคย


4

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

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

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

ตอนนี้พื้นที่โค้ดการใช้สแต็คและเวลาดำเนินการอาจไม่เพียงพอในหลาย ๆ สถานการณ์สำหรับโปรแกรมเมอร์จำนวนมาก แต่เมื่อคุณอยู่ในสภาพแวดล้อมแบบฝังที่มีโค้ดพื้นที่ทำงานเพียง 2KB เท่านั้นคำแนะนำพิเศษ 50 ไบต์เพื่อหลีกเลี่ยงการกำหนดไว้อย่างชัดเจน 'goto' นั้นน่าหัวเราะและนี่ไม่ใช่สถานการณ์ที่หายากอย่างที่โปรแกรมเมอร์ระดับสูงหลายคนเชื่อ

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


3

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.