เคยมีข้อได้เปรียบในการใช้ 'goto' ในภาษาที่สนับสนุนลูปและฟังก์ชั่นหรือไม่? ถ้าเป็นเช่นนั้นทำไม


201

ฉันอยู่ภายใต้การแสดงผลที่gotoไม่ควรใช้ถ้าเป็นไปได้ ในขณะที่อ่าน libavcodec (ซึ่งเขียนเป็นภาษา C) เมื่อวันก่อนฉันสังเกตเห็นการใช้งานหลายอย่าง มันเคยเป็นประโยชน์ที่จะใช้gotoในภาษาที่รองรับลูปและฟังก์ชั่นหรือไม่? ถ้าเป็นเช่นนั้นทำไม

คำตอบ:


242

มีเหตุผลสองสามข้อในการใช้คำสั่ง "goto" ที่ฉันรู้ (บางคนพูดสิ่งนี้แล้ว):

ออกจากฟังก์ชันอย่างหมดจด

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

กำลังออกจากลูปซ้อนกัน

หากคุณอยู่ในลูปซ้อนกันและต้องการแยกลูปออกทั้งหมดลูปสามารถทำให้สิ่งนี้สะอาดและง่ายกว่าการใช้คำสั่ง break และ if-checks

การปรับปรุงประสิทธิภาพระดับต่ำ

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

โปรดทราบว่าในตัวอย่างเหล่านี้ทั้งหมด gotos ถูก จำกัด ขอบเขตของฟังก์ชั่นเดียว


15
วิธีที่ถูกต้องในการออกจากลูปซ้อนกันคือการปรับโครงสร้างลูปด้านในให้เป็นวิธีแยกต่างหาก
สัน

129
@Jason - Bah นั่นเป็นภาระของวัว แทนที่gotoด้วยreturnเป็นเพียงโง่ มันไม่ได้เป็น "refactoring" อะไรมันก็แค่ "เปลี่ยนชื่อ" เพื่อให้ผู้คนที่เติบโตขึ้นมาในgotoสภาพแวดล้อมที่ -suppressed (เช่นเราทุกคน) gotoรู้สึกดีขึ้นเกี่ยวกับการใช้สิ่งที่มีคุณธรรมจะมีจำนวน ผมชอบมากที่จะเห็นวงที่ผมใช้มันและดูเล็ก ๆ น้อย ๆgotoซึ่งโดยตัวเองเป็นเพียงเครื่องมือgotoกว่าคนเห็นต้องย้ายวงที่ไหนสักแห่งที่ไม่เกี่ยวข้องเพียงเพื่อหลีกเลี่ยงการ
Chris Lutz

18
มีไซต์ที่ gotos สำคัญ: ตัวอย่างเช่นสภาพแวดล้อม C ++ ที่มีข้อยกเว้น ในแหล่ง Silverlight เรามีคำสั่ง goto สำหรับฟังก์ชั่นที่ปลอดภัยที่มีอยู่นับหมื่นรายการ (หรือมากกว่า) ผ่านการใช้มาโคร - ตัวแปลงสัญญาณสื่อสำคัญและไลบรารีมักทำงานผ่านค่าส่งคืนและไม่มีข้อยกเว้นและยากที่จะรวมกลไกการจัดการข้อผิดพลาดเหล่านี้ วิธีการแสดงเดี่ยว
Jeff Wilcox

79
มันน่าสังเกตว่าทั้งหมดbreak, continue, returnมีพื้นgotoเพียงในบรรจุภัณฑ์ที่ดี
el.pescado

16
ฉันไม่เห็นวิธีที่do{....}while(0)ควรจะเป็นความคิดที่ดีกว่า goto ยกเว้นความจริงมันใช้งานได้ใน Java
รายการเจเรมี

906

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

ลองใส่กระดาษของ Dijkstra เข้ากับบริบทเพื่อฉายแสงให้กับวัตถุ

เมื่อ Dijkstra เขียนบทความของเขาภาษายอดนิยมในเวลานั้นเป็นภาษาที่ไม่มีโครงสร้างเช่น BASIC, FORTRAN (ภาษาถิ่นก่อนหน้า) และภาษาแอสเซมบลีที่หลากหลาย เป็นเรื่องปกติสำหรับผู้ที่ใช้ภาษาระดับสูงกว่าในการกระโดดข้ามฐานรหัสของพวกเขาในเกลียวที่บิดเบี้ยวและมีการประมวลผลที่บิดเบี้ยวซึ่งก่อให้เกิดคำว่า "สปาเก็ตตี้โค้ด" คุณสามารถเห็นสิ่งนี้ได้โดยกระโดดข้ามไปที่เกม Trek คลาสสิกที่เขียนโดย Mike Mayfield และพยายามหาวิธีการทำงานของสิ่งต่าง ๆ ใช้เวลาสักครู่เพื่อดูว่า

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

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

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

ไม่ใช่สิ่งที่gotoอยู่ในสายตาดังนั้นจะต้องอ่านง่ายใช่มั้ย หรือวิธีการเกี่ยวกับสิ่งนี้:

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

ไม่มีgotoทั้ง มันจะต้องสามารถอ่านได้

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

ตอนนี้เพื่อความเป็นธรรมโครงสร้างภาษาบางอย่างง่ายต่อการถูกทารุณมากกว่าคนอื่น หากคุณเป็นโปรแกรมเมอร์ซีฉันจะลองใช้อีกนานกว่านี้ประมาณ 50% ของการใช้#defineงานนานก่อนที่ฉันจะทำสงครามต่อต้านgoto!

ดังนั้นสำหรับผู้ที่ใส่ใจที่จะอ่านสิ่งนี้ไปไกลมีประเด็นสำคัญหลายประการที่ควรทราบ

  1. กระดาษ Dijkstra ในgotoงบถูกเขียนขึ้นสำหรับสภาพแวดล้อมการเขียนโปรแกรมที่gotoเป็นจำนวนมาก มากขึ้นอาจเป็นอันตรายมากกว่าที่เป็นอยู่ในภาษาที่ทันสมัยที่สุดที่ไม่ได้ประกอบ
  2. ทิ้งการใช้งานทั้งหมดโดยอัตโนมัติgotoเพราะสิ่งนี้มีเหตุผลมากพอ ๆ กับการพูดว่า "ฉันพยายามที่จะสนุกครั้งเดียว แต่ไม่ชอบเลยตอนนี้ฉันเลยสู้แล้ว"
  3. มีการใช้งานที่ถูกต้องตามกฎหมายของคำสั่งที่ทันสมัย ​​(โลหิตจาง) gotoในรหัสที่ไม่สามารถแทนที่อย่างเพียงพอโดยการก่อสร้างอื่น ๆ
  4. แน่นอนว่ามีการใช้ข้อความที่ผิดกฎหมายในลักษณะเดียวกัน
  5. มีมากเกินไปใช้นอกกฎหมายของงบการควบคุมที่ทันสมัยเช่น " godo" สิ่งที่น่ารังเกียจที่เสมอเท็จdoห่วงเสียจากการใช้ในสถานที่ของbreak เหล่านี้มักจะเลวร้ายยิ่งกว่าการใช้เหตุผลของgotogoto

42
@pocjoc: การเขียนการเพิ่มประสิทธิภาพหางแบบวนซ้ำใน C เช่น สิ่งนี้เกิดขึ้นในการใช้งานล่ามแปลภาษาที่ใช้งาน / รันไทม์ แต่เป็นตัวแทนของปัญหาที่น่าสนใจ คอมไพเลอร์ส่วนใหญ่ที่เขียนใน C ใช้ประโยชน์จาก goto ด้วยเหตุนี้ การใช้งานเฉพาะของตนที่ไม่ได้เกิดขึ้นในสถานการณ์ส่วนใหญ่แน่นอน แต่ในสถานการณ์ที่จะเรียกมันทำให้จำนวนมากของความรู้สึกในการใช้งาน
zxq9

66
ทำไมทุกคนถึงพูดถึงกระดาษ "ไปที่พิจารณาว่าเป็นอันตราย" ของ Dijkstra โดยไม่พูดถึงการตอบของ Knuth ต่อมัน "การเขียนโปรแกรมแบบมีโครงสร้างพร้อมคำสั่ง"
Oblomov

22
@ Nietzche-jou "นี่เป็นชายฟางหน้าซีดที่น่ารัก [... ]" เขาพูดประชดประชันโดยใช้ขั้วต่อเท็จเพื่อแสดงว่าทำไมความอัปยศจึงไร้เหตุผล Strawmanคือการเข้าใจผิดอย่างมีเหตุผลที่คุณจงใจบิดเบือนตำแหน่งของฝ่ายตรงข้ามโดยเจตนาเพื่อให้ปรากฏว่าตำแหน่งของพวกเขาพ่ายแพ้ได้ง่าย เช่น "ผู้ที่ไม่เชื่อว่ามีพระเจ้าไม่เชื่อในพระเจ้าเพราะพวกเขาเกลียดพระเจ้า" การแบ่งขั้วที่ผิดพลาดคือเมื่อคุณสมมติว่าขั้วตรงข้ามสุดขั้วเป็นจริงเมื่อมีความเป็นไปได้เพิ่มเติมอย่างน้อยหนึ่งรายการ (เช่นขาวดำ) เช่น "พระเจ้าเกลียดกระเทยดังนั้นเขาจึงรัก heteros"
Braden ที่ดีที่สุด

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

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

154

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

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


36

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

กฎกับ goto ที่เราใช้คือ goto ก็โอเคสำหรับการกระโดดไปข้างหน้าไปยังจุดเดียวในการทำความสะอาด

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

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

นอกเหนือจากนั้นgotoเป็นสัญญาณที่ความคิดไม่มากพอที่จะเข้าไปในส่วนของรหัส


1ภาษาสมัยใหม่ซึ่งรองรับgotoการใช้งานข้อ จำกัด บางอย่าง (เช่นgotoอาจไม่เข้าหรือออกจากฟังก์ชั่น) แต่ปัญหาพื้นฐานยังคงเหมือนเดิม

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


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

37
"โดยทั่วไปแล้วเนื่องจากลักษณะที่มีข้อบกพร่องของ goto (และฉันเชื่อว่านี่เป็นสิ่งที่ไม่มีข้อโต้แย้ง)" -1 และฉันหยุดอ่านที่นั่น
o0 '

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

5
finally? ดังนั้นการใช้ข้อยกเว้นสำหรับสิ่งอื่นนอกเหนือจากการจัดการข้อผิดพลาดนั้นดี แต่การใช้gotoนั้นไม่ดี? ฉันคิดว่ามีข้อยกเว้นค่อนข้างเหมาะเจาะ
คริสเตียน

3
@Mud: ในกรณีที่ฉันพบ (ในชีวิตประจำวัน) การสร้างฟังก์ชั่น "ที่ไม่มีเหตุผลจริงที่มีอยู่" เป็นทางออกที่ดีที่สุด เหตุผล: ลบรายละเอียดจากฟังก์ชันระดับบนสุดเช่นผลลัพธ์ที่มีการควบคุมการไหลที่อ่านง่าย ฉันไม่ได้เจอกับสถานการณ์ที่ goto สร้างโค้ดที่อ่านได้มากที่สุด ในทางกลับกันฉันใช้การส่งคืนหลายครั้งในฟังก์ชั่นง่าย ๆ ซึ่งคนอื่นอาจต้องการข้ามจุดทางออกเดียว ฉันชอบที่จะเห็นตัวอย่างเคาน์เตอร์ที่ goto สามารถอ่านได้มากกว่า refactoring ในฟังก์ชั่นที่มีชื่อดี
ToolmakerSteve

35

มีสิ่งหนึ่งที่เลวร้ายยิ่งกว่าgoto's; การใช้ตัวดำเนินการ programflow อื่น ๆ อย่างแปลก ๆ เพื่อหลีกเลี่ยงการข้ามไป:

ตัวอย่าง:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

2
do{}while(false)ฉันคิดว่าถือได้ว่าเป็นสำนวน คุณไม่ได้รับอนุญาตให้ไม่เห็นด้วย: D
Thomas Eding

37
@trinithis: ถ้าเป็น "สำนวน" นั่นเป็นเพียงเพราะลัทธิต่อต้านข้ามไป หากคุณมองอย่างใกล้ชิดคุณจะรู้ว่ามันเป็นเพียงวิธีการพูดgoto after_do_block;โดยไม่พูดอย่างนั้น มิฉะนั้น ... "ลูป" ที่ทำงานหนึ่งครั้งใช่ไหม ฉันจะเรียกการใช้โครงสร้างการควบคุมในทางที่ผิด
cHao

5
@ThomasEding Eding มีข้อยกเว้นหนึ่งประการสำหรับคุณ หากคุณเคยเขียนโปรแกรม C / C ++ และต้องใช้ #define คุณจะรู้ว่านั่นคือ {} ในขณะที่ (0) เป็นหนึ่งในมาตรฐานสำหรับการห่อหุ้มโค้ดหลายบรรทัด ตัวอย่างเช่น: #define do {memcpy (a, b, 1); บางสิ่งบางอย่าง ++;} ขณะที่ (0)ปลอดภัยกว่าดีกว่า#define memcpy (a, b, 1); something ++
Ignas2526

10
@ Ignas2526 คุณได้แสดงให้เห็นอย่างชัดเจนว่า#defineมีมากน้อยกว่าการใช้gotoครั้งเดียว: D
Luaan

3
+1 สำหรับรายการที่ดีของวิธีที่ผู้คนพยายามหลีกเลี่ยงการข้ามไปมาก ฉันเคยเห็นการกล่าวถึงเทคนิคดังกล่าวที่อื่น แต่นี่เป็นรายการที่ยอดเยี่ยมรวบรัด ไตร่ตรองว่าทำไมในช่วง 15 ปีที่ผ่านมาทีมของฉันไม่เคยต้องการเทคนิคเหล่านั้นเลยหรือไม่ได้ใช้ gotos แม้แต่ในไคลเอนต์ / เซิร์ฟเวอร์แอปพลิเคชันที่มีโค้ดนับแสนบรรทัดโดยโปรแกรมเมอร์หลายคน C #, java, PHP, python, javascript ไคลเอนต์เซิร์ฟเวอร์และรหัสแอป ไม่โอ้อวดหรือทำให้เสื่อมเสียในตำแหน่งใดเพียงแค่อยากรู้อยากเห็นอย่างแท้จริงว่าทำไมบางคนพบสถานการณ์ที่ขอ gotos เป็นวิธีการแก้ปัญหาที่ชัดเจนและคนอื่น ๆ ไม่ได้ ...
ToolmakerSteve

28

ในC # สวิตช์งบทำไม่อนุญาตให้ฤดูใบไม้ร่วงผ่าน ดังนั้นgotoจึงถูกใช้เพื่อถ่ายโอนการควบคุมไปยังเลเบลตัวพิมพ์ใหญ่หรือเลเบลเริ่มต้น

ตัวอย่างเช่น:

switch(value)
{
  case 0:
    Console.Writeln("In case 0");
    goto case 1;
  case 1:
    Console.Writeln("In case 1");
    goto case 2;
  case 2:
    Console.Writeln("In case 2");
    goto default;
  default:
    Console.Writeln("In default");
    break;
}

แก้ไข: มีข้อยกเว้นหนึ่งข้อสำหรับกฎ "no fall-through" อนุญาตให้ใช้ Fall-through ได้หากคำสั่ง case ไม่มีรหัส


3
สวิทช์ฤดูใบไม้ร่วงผ่านการสนับสนุนใน NET 2.0 - msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii

9
เฉพาะในกรณีที่ไม่มีตัวรหัส หากมีรหัสคุณต้องใช้คำหลัก goto
Matthew Whited

26
คำตอบนี้สนุกมาก - ลบ C-fall-through เพราะหลายคนเห็นว่ามันเป็นอันตรายและตัวอย่างนี้ใช้ goto (หรือถูกมองว่าเป็นอันตรายโดยคนจำนวนมาก) เพื่อฟื้นฟูพฤติกรรมที่เป็นอันตรายตามที่คาดการณ์ไว้ แต่ผลลัพธ์โดยรวมนั้นเป็นอันตรายน้อยกว่า เพราะรหัสทำให้เห็นได้ชัดว่าการเข้าใจผิดนั้นเป็นเจตนา!)
thomasrutter

9
เพียงเพราะคำหลักเขียนด้วยตัวอักษร GOTO ไม่ได้เป็น goto กรณีที่นำเสนอนี้ไม่ใช่การข้ามไป มันเป็นคำสั่งสวิตช์ที่สร้างขึ้นสำหรับการตกหล่น จากนั้นอีกครั้งฉันไม่รู้ C # ดีมากดังนั้นฉันอาจผิด
Thomas Eding

1
ในกรณีนี้มันเป็นมากกว่าการตกหล่น (เพราะคุณสามารถพูดได้ว่าgoto case 5:ในกรณีที่ 1) ดูเหมือนว่าคำตอบของ Konrad Rudolph นั้นถูกต้องที่นี่: gotoกำลังชดเชยคุณสมบัติที่ขาดหายไป (และชัดเจนน้อยกว่าฟีเจอร์ที่แท้จริง) หากสิ่งที่เราต้องการจริงๆคือการล้มลงอาจเป็นค่าเริ่มต้นที่ดีที่สุดที่จะไม่ล้มเหลว แต่สิ่งที่ต้องการcontinueขออย่างชัดเจน
David Stone

14

#ifdef TONGUE_IN_CHEEK

Perl มีgotoที่ช่วยให้คุณสามารถเรียกหางของคนจน :-P

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

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

(ความคิดเห็นเดียวกันสามารถนำไปใช้กับlongjmpข้อยกเว้นcall/ccและสิ่งที่คล้ายกัน --- พวกเขามีการใช้งานที่ถูกกฎหมาย แต่สามารถถูกทำร้ายได้ง่ายตัวอย่างเช่นการโยนข้อยกเว้นอย่างหมดจดเพื่อหลบหนีโครงสร้างการควบคุมที่ซ้อนกันลึก ๆ .)


ฉันคิดว่านี่เป็นเหตุผลเดียวที่ใช้ goto ใน Perl
Brad Gilbert

12

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

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

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


1
บางทีคุณต้องการที่จะอ่านว่าทำไมฉันเชื่อว่านี่เป็นความผิดปกติที่stackoverflow.com/questions/46586/ …
Konrad Rudolph

15
ใครก็ตามที่เลื่อนขั้น "มันรวบรวมมาที่ JMP อยู่ดี!" ข้อโต้แย้งโดยทั่วไปไม่เข้าใจจุดที่อยู่เบื้องหลังการเขียนโปรแกรมในภาษาระดับที่สูงขึ้น
Nietzche-jou

7
สิ่งที่คุณต้องการจริงๆคือการลบและสาขา ทุกอย่างอื่นเพื่อความสะดวกหรือประสิทธิภาพ
David Stone

8

ดูเมื่อต้องการใช้ไปที่เมื่อเขียนโปรแกรมใน C :

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

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

ฉันหมายถึงอะไร คุณอาจมีรหัสที่มีลักษณะดังนี้:

int big_function()
{
    /* do some work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* clean up*/
    return [success];
}

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

โดยการใช้gotoงานก็จะเป็น

int big_function()
{
    int ret_val = [success];
    /* do some work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
end:
    /* clean up*/
    return ret_val;
}

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

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


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


7

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

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

สิ่งนี้จะสร้างความแปลกประหลาด แต่โครงสร้างการไหลของการควบคุมที่ถูกกฎหมายซึ่งเป็นลำดับเช่น (a, b, c, b, a, b, a, b, ... ) เป็นไปได้ซึ่งทำให้แฮกเกอร์คอมไพเลอร์ไม่มีความสุข เห็นได้ชัดว่ามีเทคนิคการเพิ่มประสิทธิภาพที่ชาญฉลาดจำนวนหนึ่งซึ่งพึ่งพาโครงสร้างประเภทนี้ซึ่งไม่เกิดขึ้น (ฉันควรตรวจสอบสำเนาหนังสือมังกรของฉัน ... ) ผลลัพธ์ของสิ่งนี้อาจ (โดยใช้คอมไพเลอร์บางตัว) ว่าการเพิ่มประสิทธิภาพอื่นไม่ได้ทำเพื่อรหัสที่มีgoto s

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


3
ดี ... พาฉันย้อนเวลากลับไปเขียนโปรแกรม FORTRAN ใน บริษัท ข้อมูลทางการเงิน ในปี 2007
Marcin

5
โครงสร้างภาษาใด ๆ สามารถถูกใช้ในทางที่ทำให้อ่านไม่ได้หรือมีประสิทธิภาพต่ำ
เพียงความคิดเห็นที่ถูกต้องของฉัน

@ เพียง: จุดนั้นไม่เกี่ยวกับความสามารถในการอ่านหรือประสิทธิภาพต่ำ แต่เป็นข้อสันนิษฐานและรับประกันเกี่ยวกับกราฟการไหลของการควบคุม การใช้ goto ในทางที่ผิดจะเป็นการเพิ่มประสิทธิภาพ (หรือสามารถอ่านได้)
Anders Eurenius

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

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

7

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

เพื่ออธิบายฉันจะให้ตัวอย่างที่ยังไม่มีใครแสดงได้ที่นี่:

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

ที่ฝากข้อมูลของตารางแฮชแต่ละสล็อตมี 4 สล็อตและฉันมีเกณฑ์มากมายในการตัดสินใจว่าองค์ประกอบใดที่จะเขียนทับเมื่อที่เก็บข้อมูลเต็ม ตอนนี้นี่หมายถึงการผ่านสามถังผ่านไปเช่นนี้

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    goto add;

// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
    goto add;

// Additional passes go here...

add:
// element is written to the hash table here

ตอนนี้ถ้าฉันไม่ได้ใช้ goto รหัสนี้จะเป็นอย่างไร

บางสิ่งเช่นนี้

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    break;

if (add_index >= ELEMENTS_PER_BUCKET) {
  // Otherwise, find first empty element
  for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
    if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
      break;
  if (add_index >= ELEMENTS_PER_BUCKET)
   // Additional passes go here (nested further)...
}

// element is written to the hash table here

มันจะดูแย่ลงและแย่ลงหากมีการเพิ่มการส่งผ่านมากขึ้นในขณะที่เวอร์ชันที่มี goto จะรักษาระดับการเยื้องไว้ตลอดเวลาและหลีกเลี่ยงการใช้เก๊

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


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

2
คุณยังไม่ได้อธิบายจริงๆว่าการเพิ่มฟังก์ชั่นอื่น ๆ จะกำจัด goto ได้อย่างไร, การเยื้องหลาย ๆ ระดับและเก๊หากคำสั่ง ...
Ricardo

มันจะมีลักษณะเช่นนี้โดยใช้สัญกรณ์คอนเทนเนอร์มาตรฐาน: container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();ฉันพบว่าโปรแกรมประเภทนั้นอ่านง่ายกว่ามาก แต่ละฟังก์ชั่นในกรณีนี้สามารถเขียนเป็นฟังก์ชั่นแท้ซึ่งง่ายกว่าที่จะให้เหตุผล ตอนนี้ฟังก์ชั่นหลักอธิบายว่าโค้ดทำหน้าที่อะไรเพียงแค่ชื่อของฟังก์ชั่นและถ้าคุณต้องการคุณสามารถดูคำจำกัดความของพวกเขาเพื่อดูว่ามันทำงานอย่างไร
David Stone

3
ความจริงที่ว่าทุกคนต้องยกตัวอย่างว่าอัลกอริธึมบางอย่างควรใช้ goto โดยธรรมชาติอย่างไร - เป็นภาพสะท้อนที่น่าเศร้าเกี่ยวกับการคิดอัลกอริทึมเล็กน้อยในวันนี้ !! แน่นอนตัวอย่างของ @ Ricardo คือ (หนึ่งในหลาย ๆ ตัวอย่าง) ที่สมบูรณ์แบบที่ไปที่โก้หรูและชัดเจน
Fattie

6

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


1
ฉันเห็นสิ่งที่เป็นประโยชน์ในภาษาอย่าง C อย่างไรก็ตามเมื่อคุณมีพลังของ C ++ constructors / destructors โดยทั่วไปแล้วมันไม่มีประโยชน์อะไร
David Stone

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

1
นี่คือจุดประสงค์สุดท้ายที่ฉันใช้ข้ามไป ฉันไม่พลาด goto ใน Java เพราะมันได้ลองแล้วในที่สุดที่สามารถทำงานเดียวกันได้
Patricia Shanahan

5

ส่วนใหญ่อภิปรายรอบคอบและอย่างละเอียดของงบโกโตะใช้ถูกต้องตามกฎหมายของพวกเขาและสร้างทางเลือกที่สามารถนำมาใช้ในสถานที่ของ "งบกลับไปข้างคุณงามความดี" แต่สามารถทำร้ายเป็น "ได้อย่างง่ายดายเช่นงบข้ามไปเป็นโดนัลด์ Knuth บทความการเขียนโปรแกรมที่มีโครงสร้างที่มีงบโกโตะ " ในการสำรวจคอมพิวเตอร์เดือนธันวาคม 2517 (เล่มที่ 6 หมายเลข 4 หน้า 261 - 301)

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


3

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

นี่คือตัวอย่างรวดเร็ว:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

คุณยังสามารถใช้รูปแบบนี้gotoเพื่อให้รูปแบบพื้นฐานของการเพิ่มประสิทธิภาพการโทรหาง

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

(ในPerl 5 เวอร์ชัน 16ที่น่าจะเขียนได้ดีกว่าgoto __SUB__;)

มีโมดูลที่จะนำเข้าtailตัวดัดแปลงและหนึ่งที่จะนำเข้าrecurหากคุณไม่ต้องการใช้รูปแบบgotoนี้

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

เหตุผลอื่น ๆ ส่วนใหญ่ที่ใช้gotoจะทำได้ดีกว่ากับคำหลักอื่น ๆ

เช่นเดียวกับredoรหัสเล็กน้อย:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

หรือไปที่lastโค้ดจากหลาย ๆ ที่:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

2

ถ้าเป็นเช่นนั้นทำไม

C ไม่มีการแบ่งหลายระดับ / ป้ายกำกับและกระแสการควบคุมทั้งหมดไม่สามารถจำลองได้อย่างง่ายดายด้วยการวนซ้ำและการตัดสินใจเบื้องต้น gotos ไปไกลเพื่อแก้ไขข้อบกพร่องเหล่านี้

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

libavcodec เป็นโค้ดที่ไวต่อประสิทธิภาพ การแสดงออกโดยตรงของโฟลว์ควบคุมน่าจะเป็นลำดับความสำคัญเพราะมันจะมีแนวโน้มที่จะทำงานได้ดีขึ้น


2

เช่นเดียวกับที่ไม่มีใครใช้คำสั่ง "COME FROM" ....


8
หรือใน C ++, C #, Java, JS, Python, Ruby .... ฯลฯ และอื่น ๆ พวกเขาเท่านั้นเรียกมันว่า "ข้อยกเว้น"
cHao

2

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

หากคุณต้องทำวนซ้ำเช่นนี้ทำไมไม่ทำให้การพึ่งพาตัวแปรแฟล็กชัดเจน?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

ไม่ควร/*empty*/จะเป็นอย่างนั้นstepfailed = 1? ไม่ว่าในกรณีใดมันจะดีกว่าdo{}while(0)อย่างไร? ในทั้งสองคุณต้องbreakออกจากมัน (หรือในของคุณstepfailed = 1; continue;) ดูเหมือนไม่จำเป็นสำหรับฉัน
Thomas Eding

2

1) การใช้ข้ามไปที่พบบ่อยที่สุดที่ฉันรู้คือเลียนแบบการจัดการข้อยกเว้นในภาษาที่ไม่ได้นำเสนอคือใน C. (รหัสที่ได้รับจากนิวเคลียร์ข้างต้นเป็นเพียงแค่นั้น) ดูที่รหัสแหล่ง Linux และคุณ ' จะเห็นล้านล้าน gotos ใช้วิธีนั้น มีอยู่ประมาณ 100,000 gotos ในรหัสลินุกซ์ตามการสำรวจอย่างรวดเร็วดำเนินการในปี 2013: http://blog.regehr.org/archives/894 การใช้งานไปที่ถูกกล่าวถึงแม้จะอยู่ในคู่มือสไตล์ลินุกซ์การเข้ารหัส: https://www.kernel.org/doc/Documentation/CodingStyle เช่นเดียวกับการเขียนโปรแกรมเชิงวัตถุจำลองโดยใช้ structs ที่มีตัวชี้ฟังก์ชั่น goto มีสถานที่ในการเขียนโปรแกรม C ดังนั้นใครถูก: Dijkstra หรือ Linus (และเคอร์เนล Linux ทั้งหมด) ทฤษฎีกับการฝึกฝนโดยทั่วไป

อย่างไรก็ตามมี gotcha ตามปกติสำหรับไม่มีการสนับสนุนระดับคอมไพเลอร์และตรวจสอบการสร้าง / รูปแบบทั่วไป: มันง่ายกว่าที่จะใช้พวกเขาผิดและแนะนำข้อบกพร่องโดยไม่ต้องตรวจสอบเวลารวบรวม Windows และ Visual C ++ แต่ในโหมด C ให้การจัดการข้อยกเว้นผ่าน SEH / VEH ด้วยเหตุผลนี้: ข้อยกเว้นมีประโยชน์แม้จะอยู่นอกภาษา OOP เช่นในภาษาขั้นตอน แต่คอมไพเลอร์ไม่สามารถบันทึกเบคอนของคุณได้เสมอแม้ว่าจะให้การสนับสนุนทางไวยากรณ์สำหรับข้อยกเว้นในภาษา ลองพิจารณาเป็นตัวอย่างของข้อผิดพลาดที่มีชื่อเสียงของ Apple SSL "goto fail" ซึ่งทำซ้ำ goto หนึ่งรายการที่มีผลร้าย ( https://www.imperialviolet.org/2014/02/22/applebug.html ):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

คุณสามารถมีข้อผิดพลาดเดียวกันโดยใช้ข้อยกเว้นสนับสนุนคอมไพเลอร์เช่นใน C ++:

struct Fail {};

try {
  if (something())
    throw Fail();
    throw Fail(); // copypasta bug
  printf("Never reached\n");
}
catch (Fail&) {
  // control jumps here
}

แต่ทั้งสองสายพันธุ์ของข้อผิดพลาดสามารถหลีกเลี่ยงได้หากคอมไพเลอร์วิเคราะห์และเตือนคุณเกี่ยวกับรหัสที่เข้าไม่ถึง ตัวอย่างเช่นการคอมไพล์ด้วย Visual C ++ ที่ระดับคำเตือน / W4 ค้นหาข้อบกพร่องในทั้งสองกรณี Java เช่นห้ามรหัสที่ไม่สามารถเข้าถึงได้ (ซึ่งสามารถหาได้!) ด้วยเหตุผลที่ดี: มันน่าจะเป็นข้อผิดพลาดในรหัสเฉลี่ยของ Joe ตราบใดที่การสร้าง goto ไม่อนุญาตให้เป้าหมายที่คอมไพเลอร์ไม่สามารถคิดออกได้ง่าย ๆ เช่น gotos ไปยังที่อยู่ที่คำนวณ (**) มันไม่ได้ยากสำหรับคอมไพเลอร์ในการค้นหาโค้ดที่เข้าไม่ถึงภายในฟังก์ชันที่มี gotos กว่าการใช้ Dijkstra - อนุมัติรหัส

(**) เชิงอรรถ: Gotos ไปยังหมายเลขบรรทัดที่คำนวณได้เป็นไปได้ใน Basic บางเวอร์ชันเช่น GOTO 10 * x โดยที่ x เป็นตัวแปร ค่อนข้างสับสนใน Fortran "computed goto" หมายถึงโครงสร้างที่เทียบเท่ากับคำสั่ง switch ใน C. Standard C ไม่อนุญาตให้มีการคำนวณ gotos ในภาษา แต่กลับไปที่ป้ายประกาศแบบคงที่ / syntactically อย่างไรก็ตาม GNU C มีส่วนขยายเพื่อรับที่อยู่ของป้ายกำกับ (ตัวดำเนินการนำหน้า &&) และยังอนุญาตให้ข้ามไปเป็นตัวแปรประเภทโมฆะ * ดูhttps://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.htmlสำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อย่อยที่ไม่ชัดเจนนี้ ส่วนที่เหลือของโพสต์นี้ไม่เกี่ยวข้องกับคุณลักษณะ GNU C ที่ปิดบัง

มาตรฐาน C (เช่นไม่คำนวณ) gotos มักจะไม่ได้เป็นเหตุผลที่รหัสไม่สามารถเข้าถึงได้ในเวลารวบรวม เหตุผลปกติคือรหัสตรรกะดังต่อไปนี้ ป.ร. ให้ไว้

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

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

void tough1() {
  if (computation1() != computation2())
    printf("Unreachable\n");
}

void tough2() {
  if (computation1() == computation2())
    goto out;
  printf("Unreachable\n");
out:;
}

struct Out{};

void tough3() {
  try {
    if (computation1() == computation2())
      throw Out();
    printf("Unreachable\n");
  }
  catch (Out&) {
  }
}

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

Visual C ++ / W4 (แม้จะมี / Ox) ล้มเหลวในการค้นหารหัสที่ไม่สามารถเข้าถึงได้ในสิ่งเหล่านี้และเนื่องจากคุณอาจรู้ว่าปัญหาในการค้นหารหัสที่เข้าไม่ถึงนั้นไม่สามารถเข้าถึงได้โดยทั่วไป (หากคุณไม่เชื่อฉันเกี่ยวกับสิ่งนั้น: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )

ในฐานะที่เป็นปัญหาที่เกี่ยวข้อง C goto สามารถใช้เพื่อจำลองข้อยกเว้นเฉพาะภายในเนื้อหาของฟังก์ชัน ไลบรารี C มาตรฐานนำเสนอฟังก์ชัน setjmp () และ longjmp () สำหรับการจำลองการออก / ยกเว้นที่ไม่ใช่โลคอล แต่มีข้อเสียร้ายแรงบางประการเมื่อเทียบกับภาษาอื่น ๆ บทความ Wikipedia http://en.wikipedia.org/wiki/Setjmp.hอธิบายปัญหาหลังนี้ได้ค่อนข้างดี คู่ฟังก์ชั่นนี้ยังใช้งานได้บน Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ) แต่แทบจะไม่มีใครใช้พวกเขาที่นั่นเพราะ SEH / VEH เหนือกว่า แม้กระทั่ง Unix ฉันคิดว่า setjmp และ longjmp นั้นไม่ค่อยมีคนใช้

2) ฉันคิดว่าการใช้ข้ามครั้งที่สองที่ใช้กันทั่วไปของ goto ใน C คือการใช้การแบ่งหลายระดับหรือการดำเนินการหลายระดับต่อไปซึ่งเป็นกรณีการใช้งานที่ไม่มีข้อโต้แย้ง เรียกคืนได้ว่า Java ไม่อนุญาตให้ใช้ป้ายข้ามไป แต่อนุญาตให้แบ่งป้ายกำกับหรือทำป้ายกำกับต่อ ตามhttp://www.oracle.com/technetwork/java/simple-142616.htmlนี่เป็นกรณีการใช้งานทั่วไปของ gotos ใน C (90% พวกเขาพูด) แต่ในประสบการณ์ส่วนตัวของฉันรหัสระบบมีแนวโน้ม ใช้ gotos เพื่อจัดการข้อผิดพลาดบ่อยขึ้น บางทีในรหัสทางวิทยาศาสตร์หรือที่ระบบปฏิบัติการมีข้อยกเว้นการจัดการ (Windows) การออกหลายระดับเป็นกรณีการใช้งานที่โดดเด่น พวกเขาไม่ได้ให้รายละเอียดใด ๆ เกี่ยวกับบริบทของการสำรวจ

แก้ไขเพื่อเพิ่ม: ปรากฎว่ารูปแบบการใช้งานทั้งสองนี้พบได้ในหนังสือ C ของ Kernighan และ Ritchie รอบ ๆ หน้า 60 (ขึ้นอยู่กับรุ่น) สิ่งที่ควรทราบอีกประการหนึ่งคือกรณีใช้งานทั้งสองเกี่ยวข้องกับการส่งต่อไปข้างหน้าเท่านั้น และปรากฎว่า MISRA C 2012 edition (ต่างจากฉบับที่ 2004) อนุญาตให้ใช้งาน gotos ได้ตราบใดที่พวกมันเป็นรุ่นต่อไปเท่านั้น


ขวา. "elephant in the room" คือเคอร์เนล Linux เพื่อประโยชน์ของความดีซึ่งเป็นตัวอย่างหนึ่งของฐานรหัสที่สำคัญระดับโลกซึ่งเต็มไปด้วย goto แน่นอนมันเป็น อย่างชัดเจน "anti-goto-meme" เป็นเพียงความอยากรู้จากทศวรรษที่ผ่านมา แน่นอนมีหลายสิ่งในการเขียนโปรแกรม (สะดุดตา "สถิตยศาสตร์" แน่นอน "กลม" และตัวอย่างเช่น "elseif") ซึ่งสามารถถูกทารุณกรรมโดยไม่ใช่มืออาชีพ ดังนั้นหากคุณเป็นลูกพี่ลูกน้องคือ Learnin 2 โปรแกรมคุณจะบอกพวกเขาว่า "โอ้ไม่ใช้ globals" และ "ไม่เคยใช้อย่างอื่น"
Fattie

ข้อผิดพลาดข้ามไปของ goto ไม่เกี่ยวกับ goto ปัญหาเกิดจากคำสั่ง if โดยไม่มีเครื่องหมายวงเล็บปีกกาในภายหลัง การคัดลอกข้อความใด ๆ ที่วางสองครั้งจะทำให้เกิดปัญหา ฉันคิดว่าประเภทของ braceless เปล่าหากเป็นอันตรายมากกว่า goto
muusbolla

2

บางคนบอกว่าไม่มีเหตุผลสำหรับการข้ามไปใน C ++ บางคนบอกว่าในกรณี 99% มีทางเลือกที่ดีกว่า นี่ไม่ใช่เหตุผลเพียงแค่การแสดงผลที่ไม่สมเหตุสมผล นี่เป็นตัวอย่างที่ดีที่ goto นำไปสู่รหัสที่ดีบางอย่างเช่นห่วง do-while ที่ปรับปรุงแล้ว:

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

เปรียบเทียบกับรหัสฟรีข้ามไป:

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

ฉันเห็นความแตกต่างเหล่านี้:

  • {}จำเป็นต้องใช้บล็อกที่ซ้อนกัน(แม้ว่าจะdo {...} whileคุ้นเคยมากกว่า)
  • ต้องการloopตัวแปรพิเศษใช้ในสี่แห่ง
  • มันใช้เวลาอ่านและทำความเข้าใจกับ loop
  • loopไม่เก็บข้อมูลใด ๆ ก็แค่ควบคุมการไหลของการดำเนินการซึ่งเป็นที่เข้าใจได้น้อยกว่าฉลากที่เรียบง่าย

มีอีกตัวอย่างหนึ่งคือ

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

ทีนี้ลองกำจัด goto ที่ "ชั่วร้าย":

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

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

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

ประเด็นก็คือว่า goto สามารถนำไปใช้ในทางที่ผิดได้ง่าย แต่ตัวของ goto นั้นไม่ควรตำหนิ โปรดทราบว่าฉลากมีขอบเขตฟังก์ชั่นใน C ++ ดังนั้นจึงไม่ก่อให้เกิดมลภาวะระดับโลกเช่นเดียวกับในแอสเซมบลีบริสุทธิ์ซึ่งลูปที่ซ้อนทับกันมีตำแหน่งอยู่และเป็นเรื่องธรรมดามาก - เช่นในรหัสต่อไปนี้สำหรับ 8051 โปรแกรมวนรอบส่วนฟ้าผ่า:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

มีข้อได้เปรียบอีกประการหนึ่ง: goto สามารถใช้เป็นลูปที่มีชื่อเงื่อนไขและการไหลอื่น ๆ :

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

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

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;

1

ใน Perl ให้ใช้ label เพื่อ "goto" จาก loop - ใช้คำสั่ง "last" ซึ่งคล้ายกับการแตก

ทำให้สามารถควบคุมลูปซ้อนได้ดีขึ้น

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


ผมคิดว่ารูปแบบเฉพาะของโกโตะว่าคุณจะเคยใช้ใน Perl goto &subroutineคือ ซึ่งเริ่มต้นรูทีนย่อยด้วย @_ ปัจจุบันขณะที่แทนที่รูทีนย่อยปัจจุบันในสแต็ก
Brad Gilbert

1

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


11
การพูดว่า "ฉันไม่สามารถคิดถึงเหตุผลใด ๆ ก็ได้" ในการโต้แย้งของคุณคือทางการen.wikipedia.org/wiki/Argument_from_ignoranceแม้ว่าฉันจะชอบคำศัพท์ "การพิสูจน์ด้วยการขาดจินตนาการ"
เพียงความคิดเห็นที่ถูกต้องของฉัน

2
@ เพียงแค่ความคิดเห็นที่ถูกต้องของฉัน: มันเป็นความเข้าใจผิดอย่างมีเหตุผลเฉพาะในกรณีที่มีการโพสต์เฉพาะกิจ จากมุมมองของนักออกแบบภาษาอาจเป็นอาร์กิวเมนต์ที่ถูกต้องในการชั่งน้ำหนักค่าใช้จ่ายในการรวมคุณลักษณะ ( goto) การใช้งานของ @ cschol คล้ายกัน: แม้ว่าอาจจะไม่ได้ออกแบบภาษาในขณะนี้ (s) เขากำลังประเมินความพยายามของนักออกแบบโดยทั่วไป
Konrad Rudolph

1
@ KonradRudolph: IMHO การอนุญาตให้ใช้ภาษาgotoยกเว้นในบริบทที่จะนำตัวแปรเข้ามามีอยู่นั้นมีราคาถูกกว่าการพยายามสนับสนุนโครงสร้างการควบคุมทุกชนิดที่บางคนอาจต้องการ การเขียนโค้ดด้วยgotoอาจไม่ดีเท่าการใช้โครงสร้างอื่น แต่การสามารถเขียนโค้ดด้วยgotoจะช่วยหลีกเลี่ยง "ช่องโหว่ในความหมาย" - โครงสร้างที่ภาษาไม่สามารถเขียนโค้ดที่มีประสิทธิภาพได้
supercat

1
@supercat ฉันกลัวว่าเรามาจากโรงเรียนสอนภาษาต่าง ๆ ฉันไม่เห็นด้วยกับภาษาที่แสดงออกอย่างที่สุดในราคาที่สามารถเข้าใจได้ ฉันอยากมีข้อ จำกัด มากกว่าภาษาที่อนุญาต
Konrad Rudolph

1
@ thb ใช่แน่นอนฉันทำ มันมักจะทำให้รหัสมากยากที่จะอ่านและเหตุผลเกี่ยวกับ แทบทุกครั้งที่มีคนโพสต์โค้ดที่มีgotoอยู่ในเว็บไซต์ตรวจสอบโค้ดการกำจัดgotoลอจิกที่ลดความซับซ้อนของโค้ด
Konrad Rudolph

1

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

ตัวอย่างเช่นดูตัวอย่างโค้ดสองรายการต่อไปนี้:

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

รหัสเทียบเท่ากับ GOTO

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

สิ่งแรกที่เราคิดคือผลลัพธ์ของโค้ดทั้งสองจะเป็น "ค่าของ A: 0" (เราสมมติว่าการประมวลผลโดยไม่มีการขนานกันแน่นอน)

ไม่ถูกต้อง: ในตัวอย่างแรก A จะเป็น 0 เสมอ แต่ในตัวอย่างที่สอง (ด้วยคำสั่ง GOTO) A อาจไม่ใช่ 0 ทำไม

เหตุผลก็เพราะจากอีกจุดหนึ่งของโปรแกรมฉันสามารถแทรก a GOTO FINALโดยไม่ต้องควบคุมค่าของ A

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

วัสดุที่เกี่ยวข้องสามารถพบได้ในบทความที่มีชื่อเสียงจากนาย Dijkstra "คดีกับคำสั่งไปที่"


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

1

ฉันใช้ goto ในกรณีต่อไปนี้: เมื่อจำเป็นต้องกลับจาก funcions ในสถานที่ต่าง ๆ และก่อนที่จะส่งคืนบางสิ่งบางอย่างจำเป็นต้องทำ

ไม่ใช่รุ่น goto:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

รุ่น goto:

int doSomething_goto (struct my_complicated_stuff *ctx)
{
    int ret=0;
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
            ret=-1;
           goto exit_db;   
          }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        ret=-2;
        goto exit_freetmp;      
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        ret=-3;
        goto exit;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
        ret=-4;
        goto exit_freekey; 
    }

    if (ctx->something_else->additional_check) {
        ret=-5;
        goto exit_freekey;  
    }

exit_freekey:
    rsa_free(key);
exit:    
    pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
    free(temp_data);        
exit_db:
    db_disconnect(conn);    
    return ret;     
}

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


3
นี่คือเหตุผลที่เรามีfinallyบล็อกใน C #
John Saunders

^ like @JohnSaunders พูดว่า นี่คือตัวอย่างของ "using goto เพราะภาษาไม่มีโครงสร้างการควบคุมที่เหมาะสม" อย่างไรก็ตามมันเป็นกลิ่นรหัสที่ต้องใช้คะแนนข้ามหลาย ๆ จุดใกล้การกลับมา มีรูปแบบการเขียนโปรแกรมที่ปลอดภัยยิ่งขึ้น (ง่ายกว่าไม่พลาด) และไม่ต้องใช้ gotos ซึ่งใช้งานได้ดีแม้ในภาษาที่ไม่มี "ที่สุด": ออกแบบการโทร "ล้างข้อมูล" เพื่อไม่ให้ทำอันตรายเมื่อไม่สะอาด จำเป็นต้องมี แยกปัจจัยทุกอย่างยกเว้นการล้างข้อมูลเป็นวิธีซึ่งใช้การออกแบบแบบหลายคืน เรียกเมธอดนั้นจากนั้นทำการโทรแบบล้างค่า
ToolmakerSteve

โปรดทราบว่าวิธีที่ฉันอธิบายต้องใช้การเรียกใช้เมธอดระดับพิเศษ (แต่เป็นภาษาที่ขาดfinallyเท่านั้น) ในฐานะที่เป็นทางเลือกในการใช้gotos แต่ไปร่วมกันออกจากจุดที่เสมอไม่ทั้งหมดขึ้นสะอาด แต่วิธีการล้างข้อมูลแต่ละวิธีสามารถจัดการค่าที่เป็นโมฆะหรือสะอาดอยู่แล้วหรือได้รับการปกป้องโดยการทดสอบตามเงื่อนไขดังนั้นจึงข้ามเมื่อไม่เหมาะสม
ToolmakerSteve

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

@muusbolla - 1) "สร้าง 5 วิธี" - ไม่ฉันขอแนะนำวิธีการเดียวที่ล้างทรัพยากรใด ๆ ที่มีค่าไม่เป็นโมฆะ หรือดูทางเลือกของฉันซึ่งใช้gotos ที่ทั้งหมดไปที่จุดออกเดียวกันซึ่งมีตรรกะเดียวกัน (ซึ่งต้องเพิ่มพิเศษถ้าต่อทรัพยากรตามที่คุณพูด) แต่ไม่เป็นไรเมื่อใช้Cคุณถูกต้อง - ไม่ว่าด้วยเหตุผลใดก็ตามที่รหัสอยู่ใน C มันก็เกือบจะแน่นอนว่าเป็นการแลกเปลี่ยนที่โปรดปรานรหัส "โดยตรง" ที่สุด (ข้อเสนอแนะของฉันจัดการกับสถานการณ์ที่ซับซ้อนซึ่งทรัพยากรที่ให้ไว้อาจมีหรือไม่มีการจัดสรร แต่ใช่ overkill ในกรณีนี้)
ToolmakerSteve

0

Edsger Dijkstra นักวิทยาศาสตร์คอมพิวเตอร์ที่มีคุณูปการสำคัญในสนามก็มีชื่อเสียงในการวิจารณ์การใช้ GoTo มีบทความสั้น ๆ เกี่ยวกับข้อโต้แย้งของเขาในการเป็นวิกิพีเดีย


0

มันมีประโยชน์สำหรับการประมวลผลสายอักขระที่ชาญฉลาดเป็นครั้งคราว

ลองนึกภาพตัวอย่างเช่น printf-esque:

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

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

ฉันยังใช้ gotos อย่างรอบคอบใน lexers โดยทั่วไปแล้วสำหรับกรณีที่คล้ายกัน คุณไม่ต้องการเวลาส่วนใหญ่ แต่พวกมันก็ดีสำหรับกรณีแปลก ๆ เหล่านั้น

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