สิ่งนี้แสดงให้เห็นถึงคำสั่ง goto หรือไม่?


15

ฉันเจอคำถามนี้เมื่อสองวินาทีก่อนและฉันดึงเนื้อหาบางส่วนออกจากที่นั่น: มีชื่อของโครงสร้าง 'break n' หรือไม่?

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

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

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

ฉันกำลังมองหาคำตอบที่จัดการกับ n-ซ้อนสำหรับลูป

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


2
หากคุณสามารถเขียนเงื่อนไขลูปภายในได้for (j = 10; j > 0 && !broken; j--)ดังนั้นคุณไม่จำเป็นต้องมีbreak;คำสั่ง หากคุณเลื่อนการประกาศbrokenไปยังก่อนที่ลูปด้านนอกจะเริ่มต้นถ้านั่นสามารถเขียนได้ตามfor (i = 0; i < 10 && !broken; i++)นั้นฉันคิดว่าไม่ break;จำเป็น
FrustratedWithFormsDesigner

8
รับตัวอย่างมาจาก C # - การอ้างอิง C # บนgoto: goto (การอ้างอิง C #) - "คำสั่ง goto ยังเป็นประโยชน์ในการออกจากลูปที่ซ้อนกันอย่างลึกซึ้ง"

OMG ฉันไม่เคยรู้เลยว่า c # มีคำสั่ง goto! สิ่งที่คุณเรียนรู้ใน StackExchange (ฉันจำไม่ได้ว่าครั้งสุดท้ายที่ฉันต้องการความสามารถในการออกแถลงการณ์ goto ดูเหมือนจะไม่เกิดขึ้น)
John Wu

6
IMHO มันขึ้นอยู่กับ "ศาสนาการเขียนโปรแกรม" และ moonphase ปัจจุบันของคุณ สำหรับฉัน (และคนส่วนใหญ่รอบ ๆ ฉัน) มันดีที่จะใช้ goto เพื่อออกจากรังลึกถ้าลูปมีขนาดค่อนข้างเล็กและทางเลือกอื่นคือการตรวจสอบฟุ่มเฟือยของตัวแปรบูลที่แนะนำเพื่อให้เกิดเงื่อนไขการวนซ้ำ สิ่งนี้มากขึ้นเมื่อคุณต้องการแยกตัวออกไปครึ่งทางหรือเมื่อหลังจากวนรอบด้านในสุดจะมีรหัสบางอย่างที่จะต้องตรวจสอบบูลด้วย
PlasmaHH

1
@JohnWu gotoจำเป็นต้องใช้จริงในสวิตช์ C # เพื่อเลื่อนผ่านไปยังอีกกรณีหนึ่งหลังจากรันโค้ดใด ๆ ภายในเคสแรก นั่นเป็นกรณีเดียวที่ฉันได้ใช้ข้ามไป
Dan Lyons

คำตอบ:


33

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

คุณรัฐที่คุณอยากนอกวงที่จะดำเนินการเป็นเวลานานและด้านในสุดหนึ่งที่จะยังคงเป็นเป็นเวลานานi < 10j > 0

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

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

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}

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

1
@pswg ฉันไม่เห็นการพิมพ์รหัสใด ๆiในตอนท้ายของลูปด้านนอกในคำถามของ OP
Tulains Córdova

OP อ้างถึงรหัสจากคำถามของฉันที่นี่แต่เหลือส่วนนั้น ฉันไม่ได้ลงคะแนนให้คุณ BTW คำตอบนั้นถูกต้องทั้งหมดเกี่ยวกับการเลือกเงื่อนไขการวนซ้ำอย่างถูกต้อง
pswg

3
@pswg ฉันเดาว่าถ้าคุณต้องการที่จะพิสูจน์ goto จริง ๆ คุณสามารถเขียนปัญหาที่ต้องใช้ในอีกทางหนึ่งฉันได้เขียนโปรแกรมอย่างมืออาชีพมานานกว่าสองทศวรรษแล้วและยังไม่มีปัญหาในโลกแห่งความจริงที่ต้องการหนึ่ง.
Tulains Córdova

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

34

ในตัวอย่างนี้คุณสามารถ refactor รหัสลงในรูทีนแยกต่างหากและreturnจากวงภายใน ที่จะลบล้างความต้องการที่จะแยกออกจากวงรอบนอก

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}

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

2
แค่ข้อความด้านข้าง: ในโค้ดต้นฉบับมันจะพิมพ์iหลังจากสิ้นสุดลูปด้านนอก ดังนั้นเพื่อสะท้อนให้เห็นถึงจริงๆที่iเป็นค่าที่สำคัญที่นี่return true;และทั้งสองควรจะreturn false; return i;
pswg

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

6

ไม่ฉันไม่คิดว่าgotoจำเป็น ถ้าคุณเขียนแบบนี้:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

การมี&&!brokenลูปทั้งคู่ช่วยให้คุณสามารถแยกลูปทั้งสองออกi==jได้

สำหรับฉันแล้ววิธีนี้เป็นที่นิยมมากกว่า i<10 && !brokenเงื่อนไขห่วงมีความชัดเจนมาก:

รุ่นการทำงานสามารถดูได้ที่นี่: http://rextester.com/KFMH48414

รหัส:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

เอาท์พุท:

วนซ้ำเมื่อ i == j == 1
2
0

ทำไมคุณถึงได้ใช้brokenในตัวอย่างแรกของคุณที่ทั้งหมด ? เพียงใช้ตัวแบ่งบนลูปด้านใน นอกจากนี้หากคุณต้องใช้อย่างแน่นอนbrokenทำไมต้องประกาศนอกลูป เพียงแค่ประกาศว่าภายในiห่วง สำหรับwhileวงโปรดอย่าใช้มัน ฉันคิดว่ามันสามารถอ่านได้น้อยกว่ารุ่น goto
luiscubal

@luiscubal: มีการใช้ตัวแปรชื่อbrokenเพราะฉันไม่ชอบการใช้breakคำสั่ง (ยกเว้นในกรณีที่เปลี่ยน แต่ที่เกี่ยวกับมัน) มันประกาศนอกนอกวงในกรณีที่คุณต้องการที่จะทำลายทั้งหมด loops i==jเมื่อ โดยทั่วไปแล้วคุณbrokenจะต้องประกาศก่อนวงนอกสุดที่จะทำลาย
FrustratedWithFormsDesigner

ในการอัปเดตของคุณi==2ในตอนท้ายของลูป เงื่อนไขความสำเร็จควรลัดวงจรเพิ่มขึ้นถ้ามันคือการเป็น% break 2เทียบเท่ากับ
pswg

2
ฉันเองชอบbroken = (j == i)มากกว่าถ้าและถ้าภาษาอนุญาตให้วางประกาศที่ขาดอยู่ในการเริ่มต้นวนรอบนอก แม้ว่ามันจะเป็นเรื่องยากที่จะบอกว่าสิ่งเหล่านี้เช่นนี้โดยเฉพาะเป็นห่วงทั้งเป็น no-op (มันกลับไม่มีอะไรและไม่มีผลข้างเคียง) และถ้าไม่บางสิ่งบางอย่างมันอาจจะไม่ว่าภายในถ้า ( แต่ผมยังคงชอบbroken = (j ==i); if broken { something(); }ดีกว่า เป็นการส่วนตัว)
Martijn

@Martijn ในรหัสเดิมของฉันมันพิมพ์iหลังจากสิ้นสุดวงรอบนอก กล่าวอีกนัยหนึ่งที่สำคัญจริง ๆคิดว่าเมื่อมันแยกออกจากวงรอบนอก
pswg

3

คำตอบสั้น ๆ คือ "มันขึ้นอยู่กับ" ฉันแนะนำให้เลือกเกี่ยวกับความถูกต้องและความเข้าใจในโค้ดของคุณ วิธีข้ามไปอาจชัดเจนกว่าการปรับสภาพหรือกลับกัน คุณอาจคำนึงถึงหลักการแปลกใจน้อยที่สุดแนวทางที่ใช้ในร้านค้า / โครงการและความสอดคล้องของรหัสฐาน ตัวอย่างเช่นการข้ามไปใช้สวิตช์หรือการจัดการข้อผิดพลาดเป็นวิธีปฏิบัติทั่วไปในฐานรหัสเคอร์เนล Linux นอกจากนี้โปรดทราบว่าโปรแกรมเมอร์บางคนอาจมีปัญหาในการอ่านโค้ดของคุณ (ยกขึ้นด้วยมนต์ "goto is evil" หรือไม่ได้เรียนรู้เพราะครูคิดเช่นนั้น) และบางคนอาจ "ตัดสินคุณ"

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


2

ในกรณีของคุณการข้ามไปนั้นเป็นการปรับปรุงที่ดูน่าดึงดูด แต่ก็ไม่ได้เป็นการปรับปรุงที่คุ้มค่าที่จะละเมิดกฎกับการใช้มันในฐานรหัสของคุณ

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

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

ที่ถูกกล่าวว่าใช่คุณผลิตหนึ่งในกรณีที่มีหนามเล็กน้อย คุณอาจ:

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

มันยากมากสำหรับฉันที่จะสร้างกรณีที่การวนซ้ำไม่ติดกันจนไม่ควรทำเป็นกิจวัตรของตัวเอง

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


1
มันเป็นโปรแกรมเมอร์งานของเราในการอ่านและทำความเข้าใจโค้ด ไม่ใช่แค่รหัสแบบง่าย ๆ เว้นแต่ว่าเป็นลักษณะของ codebase ของคุณ มีและจะเป็นข้อยกเว้นสำหรับความนิยม (ไม่ใช่กฎ) เสมอกับการใช้ gotos เนื่องจากค่าใช้จ่ายที่เข้าใจ เมื่อใดก็ตามที่ผลประโยชน์มีมากกว่าค่าใช้จ่ายคือเมื่อใดควรใช้ข้ามไป; เป็นกรณีสำหรับการตัดสินใจเข้ารหัสทั้งหมดในวิศวกรรมซอฟต์แวร์
dietbuddha

1
@dietbuddha มีค่าใช้จ่ายในการละเมิดข้อตกลงการเข้ารหัสซอฟต์แวร์ที่ยอมรับซึ่งควรนำมาพิจารณาอย่างเพียงพอ มีค่าใช้จ่ายในการเพิ่มความหลากหลายของโครงสร้างการควบคุมที่ใช้ในโปรแกรมแม้ว่าโครงสร้างการควบคุมนี้อาจเหมาะสมกับสถานการณ์ มีค่าใช้จ่ายในการทำลายการวิเคราะห์รหัสคงที่ ถ้าสิ่งนี้ยังคงมีค่าควรแก่การโต้เถียง
djechlin

1

TLD; DR:ไม่เฉพาะเจาะจงใช่โดยทั่วไป

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

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

ประเด็นคือจะมีข้อยกเว้นเสมอเมื่อ a gotoเป็นธรรม แต่สำหรับบางคนเท่านั้นเนื่องจากการประเมินค่าที่แตกต่างกัน น่าเสียดายที่วิศวกรหลายคนไม่รวมเทคนิคต่าง ๆ โดยไม่รู้ตัวเพราะพวกเขาอ่านบนอินเทอร์เน็ตแทนที่จะสละเวลาเพื่อทำความเข้าใจว่าทำไม


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

-1

ฉันไม่คิดว่ากรณีนี้แสดงถึงข้อยกเว้นเนื่องจากสามารถเขียนเป็น:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.