อะไรคือสิ่งที่เลวร้ายมากเมื่อเทียบกับ goto เมื่อใช้กับกรณีที่ชัดเจนและเกี่ยวข้อง


40

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

ฉันมี IP ที่ฉันต้องตรวจสอบว่าอยู่ในรายการ IP หรือไม่จากนั้นดำเนินการกับรหัสต่อไปมิฉะนั้นจะเกิดข้อยกเว้น

<?php

$ip = '192.168.1.5';
$ips = [
    '192.168.1.3',
    '192.168.1.4',
    '192.168.1.5',
];

foreach ($ips as $i) {
    if ($ip === $i) {
        goto allowed;
    }
}

throw new Exception('Not allowed');

allowed:

...

ถ้าฉันไม่ได้ใช้gotoฉันก็ต้องใช้ตัวแปรบางอย่างเช่น

$allowed = false;

foreach ($ips as $i) {
    if ($ip === $i) {
        $allowed = true;
        break;
    }
}

if (!$allowed) {
    throw new Exception('Not allowed');
}

คำถามของฉันคือสิ่งที่แย่มากgotoเมื่อใช้กับกรณีที่เกี่ยวข้องและ IMO ที่ชัดเจนเช่นนั้น


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

คำตอบ:


120

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

if (!contains($ips, $ip)) throw new Exception('Not allowed');

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

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

$list_contains_ip = undef;        # STATE: we don't know yet

foreach ($ips as $i) {
  if ($ip === $i) {
      $list_contains_ip = true;   # STATE: positive
      break;
  }
                                  # STATE: we still don't know yet, huh?                                                          
                                  # Well, then...
  $list_contains_ip = false;      # STATE: negative
}

if (!$list_contains_ip) {
  throw new Exception('Not allowed');
}

ที่$list_contains_ipเป็นตัวแปรของรัฐเท่านั้นหรือโดยปริยาย:

                             # STATE: unknown
foreach ($ips as $i) {       # What are we checking here anyway?
  if ($ip === $i) {
    goto allowed;            # STATE: positive
  }
                             # STATE: unknown
}
                             # guess this means STATE: negative
throw new Exception('Not allowed');

allowed:                     # Guess we jumped over the trap door

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

if ($ip =~ /:/) goto IP_V6;
if ($ip =~ /\//) goto IP_RANGE;
if ($ip =~ /^10\./) goto IP_IS_PRIVATE;

foreach ($ips as $i) { ... }

IP_IS_PRIVATE:
   foreach ($ip_priv as $i) { ... }

IP_V6:
   foreach ($ipv6 as $i) { ... }

IP_RANGE:
   # i don't even want to know how you'd implement that

ALLOWED:
   # Wait, is this code even correct?
   # There seems to be a bug in here.

และใครก็ตามที่ต้องแก้จุดบกพร่องรหัสนั้นจะสาปแช่งคุณและลูก ๆ ของคุณ

Dijkstraทำให้มันเป็นอย่างนี้:

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

และนั่นเป็นสาเหตุที่ GOTO ถือเป็นอันตราย


22
$list_contains_ip = false;คำแถลงดังกล่าวดูเหมือนจะถูกวางผิดที่
Bergi

1
กระเป๋าที่เต็มไปด้วยก้อนกรวดเป็นเรื่องง่ายฉันมีกระเป๋าที่สะดวกต่อการถือ : p
whatsisname

13
@Bergi: ถูกต้อง นั่นแสดงให้เห็นว่าการทำสิ่งเหล่านี้เป็นเรื่องง่ายเพียงใด
wallenborn

6
@whatsisname กระเป๋าใบนั้นเรียกว่า function / class / package เพื่อถือมัน การใช้ gotos ก็เหมือนกับการเล่นปาหี่และโยนกระเป๋าออกไป
Falco

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

41

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

if (!in_array($ip, $ips)) throw new Exception('Not allowed');

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

แน่นอนว่าถ้าคุณไม่มีทางเลือกอื่นคุณก็สามารถใช้GOTOเอฟเฟกต์และโครงสร้างการควบคุมได้เช่นกัน


5
@james large it's easier to write good optimizing compilers for "structured" languages.สนใจที่จะทำอย่างละเอียด? เป็นตัวอย่างที่เคาน์เตอร์ Rust folks แนะนำMIRซึ่งเป็นตัวแทนระดับกลางในคอมไพเลอร์ซึ่งแทนที่ลูป, ดำเนินการต่อ, หยุดพัก, และเช่นเดียวกันกับ gotos โดยเฉพาะเพราะมันง่ายต่อการตรวจสอบและเพิ่มประสิทธิภาพ
8bittree

6
"ถ้าคุณไม่มีทางเลือกอื่นที่คุณใช้ GOTO" นี่จะหายากเหลือเกิน ฉันไม่สามารถนึกถึงกรณีที่คุณไม่มีทางเลือกอื่น ๆ ได้อย่างสุจริตนอกขอบเขต จำกัด ที่น่าหัวเราะอย่างสมบูรณ์เพื่อป้องกันการปรับรหัสที่มีอยู่หรือเรื่องไร้สาระที่คล้ายกัน
jpmc26

8
นอกจากนี้ IMO, การลอกเลียนแบบgotoกับรังหนูของธงและเงื่อนไข (ในขณะที่ตัวอย่างที่สองของ OP) มีมากน้อยกว่าอ่าน juts gotoใช้

3
@ 8bittree: การเขียนโปรแกรมโครงสร้างมีไว้สำหรับภาษาต้นฉบับไม่ใช่ภาษาเป้าหมายของคอมไพเลอร์ ภาษาเป้าหมายสำหรับคอมไพเลอร์เนทีฟคือคำสั่ง CPU ซึ่งไม่ใช่ "โครงสร้าง" ในแง่ภาษาการเขียนโปรแกรม
Robert Harvey

4
@ 8bittree คำตอบสำหรับคำถามของคุณเกี่ยวกับ MIR อยู่ในลิงก์ที่คุณให้ไว้: "แต่ก็ดีที่มีโครงสร้างใน MIR เพราะเรารู้ว่ามันจะถูกใช้ในรูปแบบเฉพาะเช่นการวนซ้ำหรือการหยุดพัก ." กล่าวอีกนัยหนึ่งเนื่องจาก goto ไม่ได้รับอนุญาตใน Rust พวกเขารู้ว่ามีโครงสร้างและรุ่นกลางที่มี gotos ก็เช่นกัน ในที่สุดรหัสต้องลงไปที่ goto เหมือนคำสั่งเครื่อง สิ่งที่ฉันได้รับจากลิงก์ของคุณคือพวกเขาเพียงแค่เพิ่มขั้นตอนที่เพิ่มขึ้นในการแปลงจากภาษาระดับสูงถึงระดับต่ำ
JimmyJames

17

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

สมมติตัวอย่างรหัสต่อไปนี้:

       i = 4;
label: printf( "%d\n", i );

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

ย้อนกลับไปในช่วงต้นทศวรรษ 90 เราได้รับโค้ด C จำนวนมากซึ่งผลักดันการแสดงผลกราฟิก 3 มิติและบอกให้ทำงานเร็วขึ้น มันเป็นโค้ดประมาณ 5,000 บรรทัดเท่านั้น แต่ทั้งหมดนั้นอยู่ในmainนั้นและผู้เขียนใช้ประมาณ 15 หรือมากกว่านั้นgotoในการแยกทั้งสองทิศทาง นี่คือรหัสที่ไม่ดีที่จะเริ่มต้นด้วย แต่การปรากฏตัวของเหล่านั้นgotoทำให้มันแย่ลงมาก เพื่อนร่วมงานของฉันใช้เวลาประมาณ 2 สัปดาห์เพื่อไขปริศนาเรื่องการควบคุม ยิ่งไปกว่านั้นgotoผลลัพธ์เหล่านั้นส่งผลให้เกิดโค้ดควบคู่ไปกับตัวเองอย่างแน่นหนาซึ่งเราไม่สามารถเปลี่ยนแปลงได้หากไม่ทำอะไรเลย

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

ในที่สุดเราให้ลูกค้าสองตัวเลือก - ให้เราเขียนสิ่งใหม่ทั้งหมดตั้งแต่เริ่มต้นหรือซื้อฮาร์ดแวร์ที่เร็วขึ้น

พวกเขาซื้อฮาร์ดแวร์เร็วขึ้น

กฎของ Bode สำหรับการใช้งานgoto:

  1. สาขาไปข้างหน้าเท่านั้น
  2. อย่าบายพาสโครงสร้างการควบคุม (เช่นไม่สาขาเข้าไปในร่างกายของนั้นifหรือforหรือwhileคำสั่ง);
  3. อย่าใช้gotoแทนโครงสร้างการควบคุม

มีหลายกรณีที่มีgoto เป็นคำตอบที่ถูก แต่จะหายาก (ทำลายออกจากวงซ้อนกันอย่างลึกซึ้งเป็นเรื่องเกี่ยวกับสถานที่เดียวที่ฉันต้องการใช้)

แก้ไข

gotoขยายตัวในคำสั่งสุดท้ายที่นี่เป็นหนึ่งในไม่กี่กรณีการใช้งานที่ถูกต้องสำหรับ สมมติว่าเรามีฟังก์ชั่นต่อไปนี้:

T ***myalloc( size_t N, size_t M, size_t P )
{
  size_t i, j, k;

  T ***arr = malloc( sizeof *arr * N );
  for ( i = 0; i < N; i ++ )
  {
    arr[i] = malloc( sizeof *arr[i] * M );
    for ( j = 0; j < M; j++ )
    {
      arr[i][j] = malloc( sizeof *arr[i][j] * P );
      for ( k = 0; k < P; k++ )
        arr[i][j][k] = initial_value();
    }
  }
  return arr;
}

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

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

T ***myalloc( size_t N, size_t M, size_t P )
{
  size_t i, j, k;

  T ***arr = malloc( sizeof *arr * N );
  if ( arr )
  {
    for ( i = 0; i < N; i ++ )
    {
      if ( !(arr[i] = malloc( sizeof *arr[i] * M )) )
        goto cleanup_1;

      for ( j = 0; j < M; j++ )
      {
        if ( !(arr[i][j] = malloc( sizeof *arr[i][j] * P )) )
          goto cleanup_2;

        for ( k = 0; k < P; k++ )
          arr[i][j][k] = initial_value();
      }
    }
  }
  goto done;

  cleanup_2:
    // We failed while allocating arr[i][j]; clean up the previously allocated arr[i][j]
    while ( j-- )
      free( arr[i][j] );
    free( arr[i] );
    // fall through

  cleanup_1:
    // We failed while allocating arr[i]; free up all previously allocated arr[i][j]
    while ( i-- )
    {
      for ( j = 0; j < M; j++ )
        free( arr[i][j] );
      free( arr[i] );
    }

    free( arr );
    arr = NULL;

  done:
    return arr;
}

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


ฉันชอบกฎเหล่านี้ ฉันยังคงไม่ใช้gotoแม้จะเป็นไปตามกฎเหล่านี้ - ฉันจะไม่ใช้เลยยกเว้นว่ามันจะเป็นรูปแบบเดียวของการแตกแขนงที่มีอยู่ในภาษา - แต่ฉันสามารถเห็นได้ว่ามันจะไม่เป็นฝันร้ายที่จะ debug มากเกินไปgotoถ้าพวกเขาถูกเขียนตามกฎเหล่านี้
Wildcard

หากคุณต้องการแยกวงที่ซ้อนกันอย่างลึกล้ำคุณเพียงแค่ทำให้วงนั้นเป็นอีกฟังก์ชันและกลับมา
bunyaCloven

6
เมื่อมีคนสรุปมันให้ฉัน: "มันไม่ใช่ปัญหาที่เกิดขึ้นจริงมันเป็นปัญหาที่เกิดขึ้นมา" ในฐานะผู้เชี่ยวชาญด้านการเพิ่มประสิทธิภาพซอฟต์แวร์ฉันเห็นด้วยว่าขั้นตอนการควบคุมที่ไม่มีโครงสร้างสามารถส่งคอมไพเลอร์ (และมนุษย์!) ให้เป็นลูป ฉันเคยใช้เวลาหลายชั่วโมงในการถอดรหัสเครื่องสถานะเรียบง่าย (สี่ในห้ารัฐ) ในฟังก์ชัน BLAS ซึ่งใช้รูปแบบต่าง ๆGOTOรวมทั้ง Fortran's (ใน) ที่มีชื่อเสียงที่ได้รับมอบหมายและ gotos เลขคณิตถ้าฉันจำได้ถูกต้อง
njuffa

@njuffa "ในฐานะผู้เชี่ยวชาญการเพิ่มประสิทธิภาพซอฟต์แวร์ฉันเห็นด้วยว่าโฟลว์การควบคุมที่ไม่มีโครงสร้างสามารถส่งคอมไพเลอร์ (และมนุษย์!) ไปเป็นลูป" - รายละเอียดเพิ่มเติม? ฉันคิดว่าคอมไพเลอร์การเพิ่มประสิทธิภาพทุกวันนี้ใช้ SSA เป็น IR ซึ่งหมายความว่ามันใช้โค้ดที่เหมือน goto ภายใต้
Maciej Piechotka

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

12

return, break, continueและthrow/ catchทุกคนเป็นหลัก gotos - พวกเขาทั้งหมดควบคุมถ่ายโอนไปยังชิ้นส่วนของรหัสอื่นและทุกคนสามารถดำเนินการกับ gotos - ในความเป็นจริงฉันไม่ได้ครั้งเดียวในโครงการโรงเรียน, ครูสอนภาษาปาสคาลบอกว่าวิธีที่ดีมากปาสคาลเป็นกว่า พื้นฐานเพราะโครงสร้าง ... ดังนั้นฉันต้องตรงกันข้าม ...

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

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

เมื่อเทียบกับงบควบคุมการไหลที่คล้ายกัน GOTO สามารถติดตามได้อย่างง่ายดายที่สุด (มีการใช้งานข้ามไปหนึ่งครั้งในกรณีที่คุณแนะนำ) และฝันร้ายเมื่อถูกทารุณกรรม - และถูกทำร้ายอย่างง่ายดาย ...

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

โครงสร้างอื่น ๆ จำนวนมากได้รับการพัฒนาเพื่อให้โค้ดของคุณเข้าใจได้มากขึ้น: ฟังก์ชัน, วัตถุ, การกำหนดขอบเขต, การห่อหุ้ม, ความคิดเห็น (!) ... รวมถึงรูปแบบ / กระบวนการที่สำคัญกว่าเช่น "DRY" (ป้องกันการซ้ำซ้อน) และ "YAGNI" (การลดความซับซ้อน / ความซับซ้อนของรหัส) - ทั้งหมดนำเข้าจริงๆเท่านั้นสำหรับคนต่อไปที่จะอ่านรหัสของคุณ (ใครจะเป็นคุณ - หลังจากที่คุณลืมสิ่งที่คุณทำในตอนแรก!)


3
ฉัน upvoting นี้เพียงประโยค"สิ่งที่สำคัญที่สุด ... คือการทำให้โค้ดอ่านได้; ทำให้มันเกือบจะเป็นเรื่องรอง" ฉันจะบอกว่ามันแตกต่างกันเล็กน้อย มันไม่มากทำให้รหัสที่อ่านได้เป็นทำให้รหัสง่าย แต่วิธีการอย่างใดอย่างหนึ่งก็คือโอ้ดังที่ความจริงที่ทำให้มันทำอะไรบางอย่างที่เป็นเรื่องรอง
Wildcard

" , และ/ ทุกคนเป็นหลัก gotos" ไม่เห็นด้วยอดีตเคารพหลักการของการเขียนโปรแกรมโครงสร้าง (แม้ว่าคุณอาจโต้แย้งเกี่ยวกับข้อยกเว้น) ไปสู่แน่นอนไม่ได้ ในแง่ของคำถามนี้คำพูดนี้ได้ไม่มากนัก returnbreakcontinuethrowcatch
Eric

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

7

GOTOเป็นเครื่องมือ มันสามารถใช้เพื่อดีหรือชั่ว

ในวันที่ไม่ดีกับ FORTRAN และ BASIC มันเกือบเป็นเครื่องมือเดียว

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

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

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

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

แต่คุณพูดว่า " GOTOไม่ดีGOTOชั่วร้าย GOTOสอบตก" นักเรียนจะเข้าใจว่า !


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

4

ด้วยข้อยกเว้นการสร้างโฟลว์gotoทั้งหมดใน PHP (และภาษาส่วนใหญ่) จะถูกกำหนดตามลำดับชั้น

ลองนึกภาพโค้ดที่ตรวจสอบด้วยตาที่รู้แล้ว:

a;
foo {
    b;
}
c;

โดยไม่คำนึงถึงสิ่งที่ควบคุมการสร้างfooคือ ( if, whileฯลฯ ) มีเพียงคำสั่งที่ได้รับอนุญาตในบางa, และbc

คุณสามารถมีa- b- cหรือa- cหรือแม้กระทั่งa- b- b- -b cแต่คุณอาจจะไม่เคยมีb- cหรือa- b- -ac

... gotoถ้าคุณมี

$a = 1;
first:
echo 'a';
if ($a === 1) {
    echo 'b';
    $a = 2;
    goto first;
}
echo 'c'; 

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

gotoมีสถานที่ แต่ส่วนใหญ่เป็นการเพิ่มประสิทธิภาพขนาดเล็กในภาษาระดับต่ำ IMO, ไม่มีที่ดีสำหรับมันใน PHP


FYI, โค้ดตัวอย่างสามารถเขียนได้ดีกว่าคำแนะนำของคุณ

if(!in_array($ip, $ips, true)) {
    throw new Exception('Not allowed');
}

2

ในภาษาระดับต่ำ GOTO คงหนีไม่พ้น แต่ในระดับสูงควรหลีกเลี่ยง (ในกรณีที่ภาษารองรับ) เพราะทำให้โปรแกรมอ่านยากขึ้น

ทุกอย่างจะทำให้การอ่านยากขึ้น ภาษาระดับสูงจะถูกแทนที่ o ทำให้การอ่านรหัสง่ายกว่าภาษาระดับต่ำเช่น, พูด, แอสเซมเบลอร์หรือซี

GOTO ไม่ก่อให้เกิดภาวะโลกร้อนหรือก่อให้เกิดความยากจนในโลกที่สาม มันแค่ทำให้อ่านรหัสยากขึ้น

ภาษาสมัยใหม่ส่วนใหญ่มีโครงสร้างการควบคุมที่ทำให้ GOTO ไม่จำเป็น บางคนเช่น Java ไม่ได้มี

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


6
gotoสามารถอ่านโค้ด eaiser ได้ เมื่อคุณสร้างตัวแปรเพิ่มเติมและกิ่งที่ซ้อนกันกับการกระโดดครั้งเดียวรหัสจะยากต่อการอ่านและทำความเข้าใจ
Matthew Whited

@ MatthewWhited บางทีถ้าคุณมี goto เดี่ยวในสิบวิธี แต่วิธีการส่วนใหญ่ไม่ยาวสิบบรรทัดและส่วนใหญ่เวลาที่คนใช้ GOTO พวกเขาไม่ใช้ "เพียงแค่นี้ครั้งเดียว"
Tulains Córdova

2
@MatthewWhited ในความเป็นจริงแล้วรหัส spaguettiนั้นมาจากการที่ซับซ้อนและยากที่จะทำตามรหัสที่เกิดจากโครงสร้างการแตกแขนงที่ไม่มีโครงสร้าง ตอนนี้รหัส spaguetti เป็นสีแดงง่ายกว่าไหม? อาจจะ. ไวนิลกลับมาทำไม GOTO ถึงไม่ยอม
Tulains Córdova

3
@Tulains ฉันต้องเชื่อว่าสิ่งเลวร้ายนั้นมีอยู่เพราะผู้คนบ่นgotoกันไปเรื่อย ๆแต่ส่วนใหญ่ที่ฉันเห็นขึ้นมาไม่ใช่ตัวอย่างของรหัสสปาเก็ตตี้ที่ซับซ้อน แต่สิ่งที่ค่อนข้างตรงไปตรงมามักจะเลียนแบบรูปแบบของการควบคุมภาษา 't มีไวยากรณ์สำหรับมันสองตัวอย่างพบมากที่สุดคือจะแยกออกจากลูปหลายและตัวอย่างใน OP ที่gotoถูกนำมาใช้ในการดำเนินการ-for else

1
รหัสสปาเก็ตตี้ที่ไม่มีโครงสร้างมาจากภาษาที่ไม่มีฟังก์ชั่น / วิธีการ gotoยังยอดเยี่ยมสำหรับสิ่งต่าง ๆ เช่นลองใหม่อีกครั้งยกเว้นย้อนกลับเครื่องรัฐ
Matthew Whited

1

ไม่มีอะไรผิดปกติกับgotoงบตัวเอง ความผิดนั้นเกิดขึ้นกับคนบางกลุ่มที่ใช้ข้อความนี้อย่างไม่เหมาะสม

นอกจากสิ่งที่กล่าว JacquesB (จัดการข้อผิดพลาดใน C), คุณกำลังใช้เพื่อออกจากวงซ้อนกันไม่ใช่สิ่งที่คุณสามารถทำได้โดยใช้goto ในกรณีนี้คุณใช้ดีกว่าbreakbreak

แต่ถ้าคุณมีสถานการณ์ลูปซ้อนกันการใช้gotoจะง่ายกว่า / เรียบง่ายกว่า ตัวอย่างเช่น:

$allowed = false;

foreach ($ips as $i) {
    foreach ($ips2 as $i2) {
        if ($ip === $i && $ip === $i2) {
            $allowed = true;
            goto out;
        }
    }
}

out:

if (!$allowed) {
    throw new Exception('Not allowed');
}

ตัวอย่างข้างต้นไม่สมเหตุสมผลในปัญหาเฉพาะของคุณเนื่องจากคุณไม่จำเป็นต้องวนซ้ำซ้อนกัน แต่ฉันหวังว่าคุณจะเห็นเฉพาะส่วนที่ซ้อนกันของมัน

จุด Bonous:หากรายการ IP ของคุณมีขนาดเล็กวิธีการของคุณก็ใช้ได้ แต่ถ้ารายการที่เติบโตขึ้นรู้ว่าวิธีการของคุณมีความซับซ้อนเวลาทำงาน asymptotic เลวร้ายที่สุดของO (n) เมื่อรายการของคุณเติบโตขึ้นคุณอาจต้องการใช้วิธีการอื่นที่ได้รับO (log n) (เช่นโครงสร้างแบบต้นไม้) หรือO (1) (ตารางแฮชที่ไม่มีการชนกัน)


6
ฉันไม่แน่ใจเกี่ยวกับ PHP แต่หลายภาษาจะสนับสนุนการใช้breakและcontinueด้วยตัวเลข (เพื่อแยก / ต่อเลเยอร์หลายเลเยอร์) หรือเลเบล (เพื่อแยก / ต่อลูปกับป้ายกำกับนั้น) ซึ่งโดยทั่วไปควรเป็น ดีกว่าที่จะใช้gotoสำหรับลูปซ้อนกัน
8bittree

@ 8bittree จุดดี ฉันไม่รู้ว่าการแตก PHP เป็นสิ่งที่แฟนซี ฉันรู้เพียงว่าการแบ่งของ C นั้นไม่น่าประหลาดใจเลย การหยุดพักของ Perl (พวกเขาเรียกมันว่าครั้งสุดท้าย ) ก็เคยคล้ายกับของ C (เช่นไม่แฟนซีเหมือนของ PHP) แต่มัน -too- กลายเป็นแฟนซีหลังจากบางจุด ผมคิดว่าคำตอบของฉันจะได้รับน้อยลงและน้อยมีประโยชน์เป็นภาษาอื่น ๆ จะแนะนำรุ่นจินตนาการของการแบ่ง :)
ถ้ำ

3
ตัวแบ่งที่มีค่าความลึกจะไม่สามารถแก้ไขปัญหาได้หากไม่มีตัวแปรเพิ่มเติม ตัวแปรในตัวมันเองที่สร้างระดับความซับซ้อนเพิ่มขึ้น
Matthew Whited

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

@ NPSF3000 คุณช่วยยกตัวอย่างของการวนซ้ำซ้อนกันที่คุณสามารถออกได้โดยไม่ต้องใช้คำสั่งgotoในขณะที่ใช้ภาษาที่ไม่มีตัวแบ่งรุ่นแฟนซีที่ระบุความลึกหรือไม่?
มนุษย์ถ้ำ

0

ด้วย goto ฉันสามารถเขียนรหัสได้เร็วขึ้น!

จริง ไม่แคร์

ไปที่การประชุม! พวกเขาแค่เรียกมันว่า jmp

จริง ไม่แคร์

ไปที่แก้ปัญหาได้ง่ายขึ้น

จริง ไม่แคร์

ในมือของรหัสผู้พัฒนาที่มีระเบียบวินัยซึ่งใช้ goto สามารถอ่านได้ง่ายขึ้น

จริง อย่างไรก็ตามฉันเป็น coder ที่มีระเบียบวินัย ฉันเห็นสิ่งที่เกิดขึ้นกับรหัสเมื่อเวลาผ่านไป Gotoเริ่มออกมาดี จากนั้นกระตุ้นให้ใช้ชุดรหัสซ้ำอีกครั้งในไม่ช้าฉันก็พบว่าตัวเองอยู่ที่จุดพักที่ไม่มีเงื่อนงำอะไรเลยที่เกิดขึ้นแม้หลังจากดูสถานะโปรแกรมแล้ว Gotoทำให้มันยากที่จะเหตุผลเกี่ยวกับรหัส เราได้ทำงานสร้างยากจริงๆwhile, do while, for, for each switch, subroutines, functions, และอื่น ๆ ทั้งหมดเพราะการทำสิ่งนี้ด้วยifและgotoเป็นเรื่องยากในสมอง

ไม่เลย gotoเราไม่ต้องการที่จะมองไปที่ แน่นอนว่ามันยังมีชีวิตอยู่และดีในระบบเลขฐานสอง แต่เราไม่จำเป็นต้องเห็นสิ่งนั้นในแหล่งที่มา ในความเป็นจริงifเริ่มสั่นคลอนเล็กน้อย


1
"โกโตะแก้ปัญหาได้ง่ายขึ้น" / "จริงไม่ต้องห่วง" - ฉันเกลียดที่จะเห็นรหัสของคุณซึ่งคุณจงใจหลีกเลี่ยงการแก้ปัญหาอย่างง่าย ๆ เพื่อสนับสนุนสิ่งที่สามารถขยายได้มากที่สุด (ได้ยินของ YAGNI?)
253751

1
@ Wildcard หากและ goto เป็นวิธีที่ง่ายมากในการแก้ปัญหาที่แก้ไขได้ดีที่สุดในวิธีอื่น ๆ พวกเขาเป็นผลไม้แขวนต่ำ มันไม่เกี่ยวกับภาษาเฉพาะ Gotoช่วยให้คุณสามารถสรุปปัญหาได้โดยทำให้เกิดปัญหาอีกเล็กน้อย Ifให้คุณเลือกสิ่งที่เป็นนามธรรม มีวิธีที่ดีกว่าในการสรุปและวิธีที่ดีกว่าในการเลือกสิ่งที่เป็นนามธรรม ความแตกต่างสำหรับหนึ่ง
candied_orange

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

1
@artelius ชุดรูปแบบ "ถูกต้องไม่ต้องสนใจ" ไม่น่าเชื่อ มันหมายถึงการแสดงให้เห็นถึงหลาย ๆ จุดที่ฉันจะยอมรับเพื่อข้ามไปและยังคงลงมากับมัน การโต้เถียงประเด็นเหล่านั้นกับฉันจึงไม่มีประโยชน์ เนื่องจากไม่ใช่จุดของฉัน รับจุดหรือไม่
candied_orange

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

-1

โดยทั่วไปแล้วภาษาแอสเซมบลีจะมีเพียงการกระโดดแบบมีเงื่อนไข / ไม่มีเงื่อนไข (เทียบเท่าของ GOTO การใช้งานแบบเก่าของ FORTRAN และ BASIC ไม่มีคำสั่งบล็อกควบคุมเกินการนับซ้ำ (การวนซ้ำ DO) ทำให้การควบคุมอื่น ๆ ทั้งหมดไปยัง IFs และ GOTO ภาษาเหล่านี้ถูกยกเลิกด้วยข้อความที่มีตัวเลขกำกับดังนั้นโค้ดที่เขียนขึ้นสำหรับภาษาเหล่านี้อาจเป็นไปได้และมักจะยากที่จะติดตามและมีแนวโน้มที่จะเกิดข้อผิดพลาด

ในการขีดจุดมีคำสั่ง" COME FROM " ที่คิดค้นขึ้นมาอย่างระมัดระวัง

ไม่จำเป็นต้องใช้ GOTO ในภาษาเช่น C, C ++, C #, PASCAL, Java, etc; สามารถใช้สิ่งก่อสร้างทางเลือกซึ่งเกือบจะแน่นอนว่ามีประสิทธิภาพและบำรุงรักษาได้มากกว่า เป็นความจริงที่ว่าหนึ่ง GOTO ในไฟล์ต้นฉบับจะไม่มีปัญหา ปัญหาคือไม่ต้องใช้หลายอย่างในการสร้างหน่วยของรหัสที่ยากต่อการติดตามและข้อผิดพลาดในการบำรุงรักษา นั่นเป็นเหตุผลที่ภูมิปัญญาที่ยอมรับได้คือการหลีกเลี่ยง GOTO ทุกครั้งที่ทำได้

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

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