Điều gì là xấu với goto khi nó được sử dụng cho các trường hợp rõ ràng và có liên quan?


40

Tôi đã luôn biết rằng đó gotolà một cái gì đó xấu, bị nhốt trong tầng hầm ở một nơi nào đó không bao giờ được nhìn thấy cho tốt nhưng tôi đã chạy vào một ví dụ mã ngày hôm nay có ý nghĩa hoàn hảo để sử dụng goto.

Tôi có một IP nơi tôi cần kiểm tra xem có nằm trong danh sách IP không và sau đó tiến hành mã, nếu không sẽ ném ngoại lệ.

<?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:

...

Nếu tôi không sử dụng gotothì tôi phải sử dụng một số biến như

$allowed = false;

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

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

Câu hỏi của tôi là những gì rất tồi tệ gotokhi nó được sử dụng cho các trường hợp liên quan rõ ràng và imo như vậy?


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
maple_shaft

Câu trả lời:


120

Bản thân GOTO không phải là một vấn đề tức thời, đó là những cỗ máy trạng thái tiềm ẩn mà mọi người có xu hướng thực hiện với nó. Trong trường hợp của bạn, bạn muốn mã kiểm tra xem địa chỉ IP có trong danh sách địa chỉ được phép hay không, do đó

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

vì vậy mã của bạn muốn kiểm tra một điều kiện. Thuật toán để thực hiện kiểm tra này không phải là vấn đề đáng lo ngại ở đây, trong không gian tinh thần của chương trình chính của bạn, kiểm tra là nguyên tử. Đó là cách nó phải như vậy.

Nhưng nếu bạn đặt mã kiểm tra vào chương trình chính của mình, bạn sẽ mất nó. Bạn giới thiệu trạng thái có thể thay đổi, hoặc rõ ràng:

$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_ipbiến trạng thái duy nhất của bạn ở đâu , hoặc ngầm định:

                             # 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

Như bạn thấy, có một biến trạng thái chưa được khai báo trong cấu trúc GOTO. Đó không phải là vấn đề, nhưng các biến trạng thái này giống như đá cuội: mang một cái không khó, mang theo một túi đầy chúng sẽ khiến bạn đổ mồ hôi. Mã của bạn sẽ không giữ nguyên: tháng tới bạn sẽ được yêu cầu phân biệt giữa địa chỉ riêng tư và công khai. Một tháng sau đó, mã của bạn sẽ cần hỗ trợ các dải IP. Năm tới, sẽ có người yêu cầu bạn hỗ trợ các địa chỉ IPv6. Không có thời gian, mã của bạn sẽ trông như thế này:

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.

Và bất cứ ai phải gỡ lỗi mã đó sẽ nguyền rủa bạn và con bạn.

Dijkstra đặt nó như thế này:

Việc sử dụng không được kiểm soát của câu lệnh go to đã dẫn đến hậu quả tức thời là nó trở nên cực kỳ khó khăn để tìm ra một bộ tọa độ có ý nghĩa để mô tả tiến trình xử lý.

Và đó là lý do GOTO bị coi là có hại.


22
Câu $list_contains_ip = false;nói đó dường như bị đặt nhầm chỗ
Bergi

1
Một túi đầy sỏi rất dễ, tôi đã có một túi tiện lợi để đựng chúng. : p
whatsisname

13
@Bergi: Bạn nói đúng. Điều đó cho thấy thật dễ dàng để làm hỏng những điều này.
wallenborn

6
@whatsisname Túi đó được gọi là hàm / lớp / gói để giữ chúng. sử dụng gotos giống như tung hứng chúng và ném cái túi đi.
Falco

5
Tôi thích trích dẫn từ Dijkstra, tôi đã không nghe nó nói như vậy nhưng đó là một điểm thực sự tốt. Thay vì có một luồng khá nhiều từ trên xuống dưới, đột nhiên bạn có thứ gì đó có thể nhảy ngẫu nhiên trên khắp trang. Đặt nó ngắn gọn là khá ấn tượng.
Bill K

41

Có một số trường hợp sử dụng hợp pháp cho GOTO. Ví dụ để xử lý lỗi và dọn dẹp trong C hoặc để thực hiện một số dạng máy trạng thái. Nhưng đây không phải là một trong những trường hợp này. Ví dụ thứ hai là IMHO dễ đọc hơn, nhưng thậm chí còn dễ đọc hơn sẽ trích xuất vòng lặp sang một hàm riêng biệt và sau đó quay lại khi bạn tìm thấy kết quả khớp. Thậm chí tốt hơn sẽ là (trong mã giả, tôi không biết cú pháp chính xác):

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

Vì vậy, những gì là xấu về GOTO? Lập trình có cấu trúc sử dụng các hàm và các cấu trúc điều khiển để tổ chức mã để cấu trúc cú pháp phản ánh cấu trúc logic. Nếu một cái gì đó chỉ được thực hiện có điều kiện, nó sẽ xuất hiện trong một khối câu lệnh có điều kiện. Nếu một cái gì đó được thực thi trong một vòng lặp, nó sẽ xuất hiện trong một khối vòng lặp. GOTOcho phép bạn phá vỡ cấu trúc cú pháp bằng cách nhảy xung quanh một cách tùy tiện, do đó làm cho mã khó theo dõi hơn nhiều.

Tất nhiên nếu bạn không có lựa chọn nào khác mà bạn sử dụng GOTO, nhưng nếu hiệu quả tương tự có thể đạt được với các chức năng và cấu trúc điều khiển, thì tốt hơn là nên sử dụng.


5
@jameslarge it's easier to write good optimizing compilers for "structured" languages.Chăm sóc công phu? Để làm ví dụ ngược lại, nhóm Rust giới thiệu MIR , một đại diện trung gian trong trình biên dịch, đặc biệt thay thế các vòng lặp, tiếp tục, ngắt và như vậy với gotos, vì việc kiểm tra và tối ưu hóa đơn giản hơn.
8bittree

6
"Nếu bạn không có lựa chọn nào khác, bạn sử dụng GOTO" Điều này sẽ cực kỳ hiếm. Tôi thực sự không thể nghĩ đến một trường hợp mà bạn không có lựa chọn nào khác, ngoài những hạn chế hoàn toàn lố bịch ngăn chặn việc tái cấu trúc mã hiện có hoặc vô nghĩa tương tự.
jpmc26

8
Ngoài ra, IMO, mô phỏng a gotovới một tổ chuột và cờ có điều kiện (như trong ví dụ thứ hai của OP) khó đọc hơn nhiều so với các juts sử dụng a goto.

3
@ 8bittree: Lập trình có cấu trúc dành cho ngôn ngữ nguồn, không phải ngôn ngữ đích của trình biên dịch. Ngôn ngữ đích cho trình biên dịch gốc là các hướng dẫn CPU, được quyết định không "có cấu trúc", theo nghĩa ngôn ngữ lập trình.
Robert Harvey

4
@ 8bree . " Nói cách khác, vì goto không được phép trong Rust, nên họ biết nó có cấu trúc và phiên bản trung gian với gotos cũng vậy. Cuối cùng, mã phải xuống để goto như các lệnh máy. Những gì tôi nhận được từ liên kết của bạn là họ chỉ cần thêm một bước tăng dần trong quá trình chuyển đổi từ ngôn ngữ cấp cao sang cấp thấp.
JimmyJames

17

Như những người khác đã nói, vấn đề không nằm ở gotochính nó; vấn đề là ở cách mọi người sử dụng gotovà cách nó có thể làm cho mã khó hiểu và duy trì hơn.

Giả sử đoạn mã sau:

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

Giá trị nào được in cho i? Khi nào nó được in? Cho đến khi bạn tính đến mọi trường hợp goto labeltrong chức năng của mình, bạn không thể biết. Sự hiện diện đơn giản của nhãn đó sẽ phá hủy khả năng gỡ lỗi mã của bạn bằng cách kiểm tra đơn giản. Đối với các hàm nhỏ có một hoặc hai nhánh, không có vấn đề gì nhiều. Đối với các chức năng không nhỏ ...

Quay trở lại vào đầu những năm 90, chúng tôi đã nhận được một đống mã C điều khiển màn hình đồ họa 3d và được yêu cầu làm cho nó chạy nhanh hơn. Nó chỉ có khoảng 5000 dòng mã, nhưng tất cả đều nằm trong đó main, và tác giả đã sử dụng khoảng 15 hoặc hơn gotonhánh của cả hai hướng. Đây là mã xấu để bắt đầu, nhưng sự hiện diện của những cái đó gotolàm cho nó tồi tệ hơn nhiều. Đồng nghiệp của tôi mất khoảng 2 tuần để giải quyết vấn đề kiểm soát. Thậm chí tốt hơn, những gotokết quả đó đã tạo ra mã kết hợp chặt chẽ với chính nó đến mức chúng ta không thể thực hiện bất kỳ thay đổi nào mà không phá vỡ một cái gì đó.

Chúng tôi đã thử biên dịch với tối ưu hóa cấp 1 và trình biên dịch đã ăn hết RAM có sẵn, sau đó tất cả trao đổi có sẵn, rồi hoảng loạn hệ thống (có lẽ không liên quan gì đến gotobản thân chúng, nhưng tôi thích ném giai thoại đó ra).

Cuối cùng, chúng tôi đã cung cấp cho khách hàng hai tùy chọn - chúng ta hãy viết lại toàn bộ từ đầu hoặc mua phần cứng nhanh hơn.

Họ đã mua phần cứng nhanh hơn.

Quy tắc của Bode khi sử dụng goto:

  1. Chi nhánh chỉ chuyển tiếp;
  2. Không bỏ qua các cấu trúc điều khiển (nghĩa là không phân nhánh vào phần thân của một ifhoặc forhoặc whilecâu lệnh);
  3. Không sử dụng gotothay cho cấu trúc điều khiển

Có những trường hợp a goto câu trả lời đúng, nhưng chúng rất hiếm (thoát ra khỏi một vòng lặp được lồng sâu là về nơi duy nhất tôi sử dụng nó).

CHỈNH SỬA

Mở rộng về tuyên bố cuối cùng đó, đây là một trong số ít trường hợp sử dụng hợp lệ cho goto. Giả sử chúng ta có chức năng sau:

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;
}

Bây giờ, chúng ta có một vấn đề - nếu một trong những malloccuộc gọi thất bại giữa chừng thì sao? Không chắc là một sự kiện có thể xảy ra, chúng tôi không muốn trả về một mảng được phân bổ một phần, chúng tôi cũng không muốn bảo lãnh ra khỏi hàm có lỗi; chúng tôi muốn dọn dẹp sau khi chính mình và giải phóng bất kỳ bộ nhớ được phân bổ một phần. Trong một ngôn ngữ đưa ra một ngoại lệ đối với phân bổ xấu, điều đó khá đơn giản - bạn chỉ cần viết một trình xử lý ngoại lệ để giải phóng những gì đã được phân bổ.

Trong C, bạn không có cấu trúc xử lý ngoại lệ; bạn phải kiểm tra giá trị trả về của mỗi malloccuộc gọi và thực hiện hành động thích hợp.

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;
}

Chúng ta có thể làm điều này mà không cần sử dụng goto? Tất nhiên chúng ta có thể - nó chỉ cần thêm một chút sổ sách kế toán (và, trong thực tế, đó là con đường tôi đi). Nhưng, nếu bạn đang tìm kiếm những nơi sử dụng gotokhông phải là ngay lập tức một dấu hiệu của sự thực hành xấu hoặc thiết kế, đây là một trong số ít.


Tôi thích những quy tắc này. Tôi vẫn sẽ không sử dụng gotothậm chí phù hợp với những quy tắc-tôi sẽ không sử dụng nó ở tất cả trừ khi đó là hình thức duy nhất của nhánh có sẵn trong ngôn ngữ nhưng tôi có thể thấy rằng nó sẽ không phải là quá nhiều của một cơn ác mộng để gỡ lỗi gotoNếu chúng được viết theo các quy tắc này.
tự đại diện

nếu bạn cần thoát ra khỏi một vòng lặp được lồng sâu, bạn chỉ cần làm cho vòng lặp đó trở thành một chức năng khác và quay trở lại
bunyaCoppy

6
Như ai đó đã từng tóm tắt cho tôi: "Đây không phải là vấn đề, đó là vấn đề đến từ". Là một chuyên gia tối ưu hóa phần mềm, tôi đồng tình rằng luồng điều khiển không có cấu trúc có thể ném các trình biên dịch (và con người!) Cho một vòng lặp. Có lần tôi đã dành hàng giờ để giải mã một máy trạng thái đơn giản (bốn trong năm trạng thái) trong hàm BLAS, sử dụng nhiều hình thức khác nhau GOTO, bao gồm cả hình ảnh số học được gán và số học nổi tiếng của Fortran, nếu tôi nhớ lại chính xác.
njuffa

@njuffa "Là một chuyên gia tối ưu hóa phần mềm, tôi đồng tình rằng luồng điều khiển không có cấu trúc có thể ném các trình biên dịch (và con người!) cho một vòng lặp." - có thêm chi tiết nào không? Tôi nghĩ rằng hầu như mọi trình biên dịch tối ưu hóa hiện nay đều sử dụng SSA là IR, điều đó có nghĩa là nó sử dụng mã giống như goto bên dưới.
Maciej Piechotka

@MaciejPiechotka Tôi không phải là kỹ sư biên dịch. Có, tất cả các trình biên dịch hiện đại dường như sử dụng SSA trong biểu diễn trung gian của chúng. Trình biên dịch thích các cấu trúc điều khiển thoát một lần duy nhất, không chắc cách sử dụng SSA đóng vai trò như thế nào? Bằng cách quan sát mã máy được sử dụng và sử dụng tài nguyên trình biên dịch, và thảo luận với các kỹ sư trình biên dịch luồng điều khiển không cấu trúc cản trở việc tối ưu hóa các biến đổi mã, có lẽ là do vấn đề "đến từ". Việc sử dụng tài nguyên (thời gian, bộ nhớ) có thể có tác động tiêu cực đến hiệu suất mã nếu trình biên dịch quyết định bỏ qua việc tối ưu hóa vì quá tốn kém.
njuffa

12

return, break, continuethrow/ catchlà tất cả về cơ bản gotos - tất cả họ đều kiểm soát chuyển đến một đoạn mã và tất cả có thể được thực hiện với gotos - trong thực tế tôi đã làm như vậy một lần trong một dự án trường học, một giảng viên PASCAL đã nói như thế nào tốt hơn nhiều Pascal là hơn cơ bản vì cấu trúc ... nên tôi phải trái ngược ...

Điều quan trọng nhất về Kỹ thuật phần mềm (Tôi sẽ sử dụng thuật ngữ này trên Mã hóa để chỉ tình huống bạn được ai đó trả tiền để tạo một cơ sở mã cùng với các kỹ sư khác cần cải tiến và bảo trì liên tục) là tạo mã có thể đọc được- - cho phép nó làm một cái gì đó gần như là thứ yếu. Mã của bạn sẽ chỉ được viết một lần, nhưng trong hầu hết các trường hợp, mọi người sẽ dành nhiều ngày và tuần để xem lại / học lại, cải thiện và sửa nó - và mỗi khi họ (hoặc bạn) sẽ phải bắt đầu lại từ đầu và cố gắng nhớ / tìm ra ma cua ban.

Hầu hết các tính năng đã được thêm vào ngôn ngữ trong những năm qua là để làm cho phần mềm dễ bảo trì hơn, không dễ viết hơn (mặc dù một số ngôn ngữ đi theo hướng đó - chúng thường gây ra các vấn đề dài hạn ...).

So với các tuyên bố kiểm soát dòng chảy tương tự, GOTO có thể dễ dàng theo dõi tốt nhất (Một goto duy nhất được sử dụng trong trường hợp như bạn đề xuất) và cơn ác mộng khi bị lạm dụng - và rất dễ bị lạm dụng ...

Vì vậy, sau khi đối phó với những cơn ác mộng spaghetti trong một vài năm, chúng tôi chỉ nói "Không", vì một cộng đồng chúng tôi sẽ không chấp nhận điều này - quá nhiều người làm rối tung nó nếu có một chút chậm trễ - đó thực sự là vấn đề duy nhất với họ. Bạn có thể sử dụng chúng ... nhưng ngay cả khi đó là trường hợp hoàn hảo, anh chàng tiếp theo sẽ cho rằng bạn là một lập trình viên tồi tệ vì bạn không hiểu lịch sử của cộng đồng.

Nhiều cấu trúc khác đã được phát triển chỉ để làm cho mã của bạn dễ hiểu hơn: Hàm, Đối tượng, Phạm vi, Đóng gói, Nhận xét (!) ... cũng như các mẫu / quy trình quan trọng hơn như "DRY" (ngăn ngừa trùng lặp) và "YAGNI" (Giảm tổng quát hóa / phức tạp hóa mã) - tất cả thực sự chỉ nhập cho anh chàng NEXT để đọc mã của bạn (Ai có thể sẽ là bạn - sau khi bạn quên hầu hết những gì bạn đã làm ở nơi đầu tiên!)


3
Tôi ủng hộ điều này chỉ cho câu, "Điều quan trọng nhất ... là làm cho mã có thể đọc được, khiến nó làm điều gì đó gần như là thứ yếu." Tôi sẽ nói nó chỉ hơi khác một chút; Nó không quá nhiều làm cho mã có thể đọc được như làm cho mã đơn giản . Nhưng dù bằng cách nào, thật đúng là làm cho nó làm một cái gì đó chỉ là thứ yếu.
tự đại diện

" , Và / là tất cả về cơ bản gotos" không đồng ý, sự tôn trọng cựu những nguyên lý của lập trình cấu trúc (mặc dù bạn có thể tranh luận về trường hợp ngoại lệ), go-to chắc chắn không. Trong câu hỏi này, không có nhiều nhận được từ nhận xét này. returnbreakcontinuethrowcatch
Eric

@eric Bạn có thể mô hình hóa bất kỳ câu lệnh nào với GOTOs. Bạn có thể đảm bảo rằng tất cả các GOTO đều đồng ý với các nguyên lý lập trình có cấu trúc - chúng chỉ đơn giản có một cú pháp khác và phải, để sao chép chính xác chức năng, bao gồm các hoạt động khác như "nếu". Ưu điểm của các phiên bản có cấu trúc là chúng bị hạn chế chỉ hỗ trợ một số mục tiêu nhất định - bạn BIẾT khi bạn thấy tiếp tục xấp xỉ vị trí của mục tiêu. Lý do tôi đề cập đến là để chỉ ra rằng đó là vấn đề lạm dụng, không phải là nó không thể được sử dụng một cách chính xác.
Bill K

7

GOTOlà một công cụ. Nó có thể được sử dụng cho tốt hoặc xấu.

Trong những ngày xưa tồi tệ, với FORTRAN và BASIC, nó gần như là công cụ duy nhất .

Khi nhìn vào mã từ những ngày đó, khi bạn thấy một GOTObạn phải tìm ra lý do tại sao nó ở đó. Nó có thể là một phần của một thành ngữ tiêu chuẩn mà bạn có thể hiểu nhanh chóng ... hoặc nó có thể là một phần của cấu trúc kiểm soát ác mộng không bao giờ nên có. Bạn không biết cho đến khi bạn nhìn, và rất dễ bị nhầm lẫn.

Mọi người muốn một cái gì đó tốt hơn, và các cấu trúc điều khiển tiên tiến hơn đã được phát minh. Chúng bao gồm hầu hết các trường hợp sử dụng và những người bị đốt cháy xấu GOTOmuốn cấm hoàn toàn chúng.

Trớ trêu thay, GOTOnó không quá tệ khi nó hiếm. Khi bạn nhìn thấy một cái, bạn biết có một cái gì đó đặc biệt đang diễn ra, và thật dễ dàng để tìm thấy nhãn tương ứng vì nó là nhãn duy nhất gần đó.

Nhanh chóng chuyển tiếp đến ngày hôm nay. Bạn là một giảng viên giảng dạy lập trình. Bạn có thể nói "Trong hầu hết các trường hợp, bạn nên sử dụng các cấu trúc mới nâng cao, nhưng trong một số trường hợp, một đơn giản GOTOcó thể dễ đọc hơn." Học sinh sẽ không hiểu điều đó. Họ sẽ lạm dụng GOTOđể làm cho mã không thể đọc được.

Thay vào đó bạn nói " GOTOxấu. GOTOÁc. GOTOThất bại trong kỳ thi." Học sinh sẽ hiểu điều đó !


4
Điều này thực tế đúng cho tất cả những thứ không dùng nữa. Quả cầu. Tên biến một chữ cái. Tự sửa đổi mã. Eval. Thậm chí, tôi nghi ngờ, đầu vào của người dùng chưa được lọc. Một khi chúng ta đã tự điều chỉnh để tránh những thứ như bệnh dịch hạch, thì chúng ta sẽ ở một vị trí phù hợp để đánh giá liệu một việc sử dụng chúng, có dấu hiệu tốt, có thể là giải pháp chính xác cho một vấn đề cụ thể hay không. Trước đó, tốt nhất chúng ta nên coi họ là đơn giản bị cấm.
Dewi Morgan

4

Ngoại trừ goto, tất cả các cấu trúc luồng trong PHP (và hầu hết các ngôn ngữ) được sắp xếp theo thứ bậc.

Hãy tưởng tượng một số mã được kiểm tra qua đôi mắt nheo mắt:

a;
foo {
    b;
}
c;

Bất kể những gì kiểm soát xây dựng foolà ( if, while, vv), chỉ có một số đơn đặt hàng cho phép a, bc.

Bạn có thể có a- b- c, hoặc a- choặc thậm chí a- b- b- b- c. Nhưng bạn không bao giờ có thể có b- choặc a- b- a- c.

... Trừ khi bạn có goto.

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

goto(đặc biệt là ngược goto) có thể gây rắc rối đến mức tốt nhất là cứ để nó một mình và sử dụng các cấu trúc dòng bị chặn, phân cấp.

gotos có một vị trí, nhưng chủ yếu là tối ưu hóa vi mô trong các ngôn ngữ cấp thấp. IMO, không có chỗ nào tốt cho nó trong PHP.


FYI, mã ví dụ có thể được viết thậm chí tốt hơn so với các đề xuất của bạn.

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

2

Trong các ngôn ngữ cấp thấp, GOTO là không thể tránh khỏi. Nhưng ở cấp độ cao thì nên tránh (trong trường hợp ngôn ngữ hỗ trợ nó) vì nó làm cho các chương trình khó đọc hơn .

Tất cả mọi thứ sôi sục để làm cho mã khó đọc hơn. Các ngôn ngữ cấp cao là supossedt o làm cho mã dễ đọc hơn các ngôn ngữ cấp thấp như, nói, trình biên dịch hoặc C.

GOTO không gây ra sự nóng lên toàn cầu cũng như không gây ra nghèo đói trong thế giới thứ ba. Nó chỉ làm cho mã khó đọc hơn.

Hầu hết các ngôn ngữ hiện đại đều có cấu trúc điều khiển khiến GOTO không cần thiết. Một số người như Java thậm chí không có nó.

Trong thực tế, thuật ngữ mã spaguetti xuất phát từ nguyên nhân mã hóa, khó theo dõi bởi các cấu trúc phân nhánh không cấu trúc.


6
gotocó thể làm cho mã háo hức để đọc. Khi bạn tạo thêm các biến và các nhánh lồng nhau so với một bước nhảy, mã sẽ khó đọc và khó hiểu hơn nhiều.
Matthew Whited

@MatthewWhited Có lẽ nếu bạn có một goto duy nhất trong phương thức mười dòng. Nhưng hầu hết các phương thức không dài mười dòng và hầu hết mọi người khi sử dụng GOTO, họ không sử dụng "chỉ một lần này".
Tulains Córdova ngày 25/8/2016

2
@MatthewWhited Trên thực tế, thuật ngữ mã spaguetti xuất phát từ mã phức tạp, khó theo dõi mã gây ra bởi các cấu trúc phân nhánh không cấu trúc. Hoặc là mã spaguetti dễ dàng hơn để màu đỏ bây giờ? Có lẽ. Vinyl đang trở lại, tại sao GOTO sẽ không.
Tulains Córdova ngày 25/8/2016

3
@Tulains Tôi phải tin những điều tồi tệ như vậy tồn tại bởi vì mọi người cứ phàn nàn về điều đó, nhưng hầu hết những lần tôi thấy gotoxuất hiện không phải là những ví dụ về mã spaghetti phức tạp, mà thay vào đó là những điều khá đơn giản, thường mô phỏng các dòng kiểm soát trong các ngôn ngữ không có Không có cú pháp cho nó: hai ví dụ phổ biến nhất là thoát ra khỏi nhiều vòng lặp và ví dụ trong OP nơi gotođược sử dụng để thực hiện for- else.

1
Mã spaghetti không cấu trúc đến từ các ngôn ngữ nơi các hàm / phương thức không tồn tại. gotocũng tuyệt vời cho những thứ như thử lại ngoại lệ, rollback, máy trạng thái.
Matthew Whited

1

Không có gì sai với gotobản thân tuyên bố. Những sai trái là với một số người sử dụng không đúng cách tuyên bố.

Ngoài những gì JacquesB đã nói (xử lý lỗi trong C), bạn đang sử dụng gotođể thoát khỏi một vòng lặp không lồng nhau, một việc mà bạn có thể làm bằng cách sử dụng break. Trong trường hợp này bạn sử dụng tốt hơn break.

Nhưng nếu bạn có một kịch bản vòng lặp lồng nhau, thì việc sử dụng gotosẽ thanh lịch / đơn giản hơn. Ví dụ:

$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');
}

Ví dụ trên không có ý nghĩa trong vấn đề cụ thể của bạn, bởi vì bạn không cần một vòng lặp lồng nhau. Nhưng tôi hy vọng rằng bạn chỉ thấy phần vòng lặp lồng nhau của nó.

Điểm quan trọng: nếu danh sách IP của bạn nhỏ, phương pháp của bạn vẫn ổn. Nhưng nếu danh sách phát triển, hãy biết rằng cách tiếp cận của bạn có độ phức tạp thời gian chạy tồi tệ nhất của O (n) . Khi danh sách của bạn phát triển, bạn có thể muốn sử dụng một phương pháp khác để đạt được O (log n) (chẳng hạn như cấu trúc cây) hoặc O (1) (bảng băm không có va chạm).


6
Tôi không chắc chắn về PHP, nhưng nhiều ngôn ngữ sẽ hỗ trợ sử dụng breakcontinuevới một số (để phá vỡ / tiếp tục nhiều lớp vòng lặp) hoặc nhãn (để phá vỡ / tiếp tục vòng lặp với nhãn đó), một trong hai ngôn ngữ thường là tốt hơn để sử dụng gotocho các vòng lặp lồng nhau.
8bittree

@ 8 điểm tốt. Tôi không biết rằng sự phá vỡ của PHP là tuyệt vời . Tôi chỉ biết nghỉ của C không phải là ưa thích. Sự phá vỡ của Perl (họ gọi nó là lần cuối ) cũng được sử dụng tương tự như C (nghĩa là không ưa thích như PHP) nhưng nó lại trở nên lạ mắt sau một thời điểm. Tôi đoán câu trả lời của tôi ngày càng ít hữu ích hơn vì nhiều ngôn ngữ đang giới thiệu các phiên bản phá vỡ lạ mắt :)
caveman

3
nghỉ với giá trị độ sâu sẽ không giải quyết được vấn đề nếu không có biến phụ. Một biến trong bản thân nó tạo ra một mức độ phức tạp thêm.
Matthew Whited

Tất nhiên, chỉ vì bạn có các vòng lặp lồng nhau không có nghĩa là bạn cần một chiếc goto, có nhiều cách khác để xử lý tình huống đó một cách tao nhã.
NPSF3000

@ NPSF3000 Bạn có thể vui lòng cung cấp một ví dụ về vòng lặp lồng nhau mà bạn có thể thoát mà không cần sử dụng câu lệnh goto , đồng thời sử dụng ngôn ngữ không có phiên bản ngắt ưa thích chỉ định độ sâu không?
thượng cổ

0

Với goto tôi có thể viết mã nhanh hơn!

Thật. Đừng quan tâm.

Goto tồn tại trong lắp ráp! Họ chỉ gọi nó là jmp.

Thật. Đừng quan tâm.

Goto giải quyết vấn đề đơn giản hơn.

Thật. Đừng quan tâm.

Trong tay một mã nhà phát triển có kỷ luật sử dụng goto có thể dễ đọc hơn.

Thật. Tuy nhiên, tôi đã là một lập trình viên kỷ luật. Tôi đã thấy những gì xảy ra với mã theo thời gian. Gotokhởi đầu tốt Sau đó, mong muốn sử dụng lại bộ mã. Khá sớm, tôi thấy mình ở một điểm dừng không có manh mối gì xảy ra ngay cả sau khi nhìn vào trạng thái chương trình. Gotolàm cho nó khó để lý luận về mã. Chúng tôi đã làm việc việc tạo thực sự khó khăn while, do while, for, for each switch, subroutines, functions, và nhiều hơn nữa tất cả bởi vì làm công cụ này với ifgotorất khó đối với não.

Vì vậy, không. Chúng tôi không muốn nhìn vào goto. Chắc chắn nó còn sống và tốt trong hệ nhị phân nhưng chúng ta không cần phải thấy nó trong nguồn. Trong thực tế, ifđang bắt đầu trông hơi run rẩy.


1
"Goto giải quyết vấn đề đơn giản hơn." / "Đúng. Đừng quan tâm." - Tôi ghét nhìn thấy mã của bạn khi bạn cố tình tránh các giải pháp đơn giản có lợi cho những gì có thể mở rộng nhất. (Nghe nói về YAGNI?)
user253751

1
@Wildcard if và goto là những cách rất đơn giản để giải quyết vấn đề tốt nhất giải quyết các cách khác. Chúng là những quả treo thấp. Đó không phải là về ngôn ngữ cụ thể. Gotocho phép bạn trừu tượng hóa một vấn đề bằng cách làm cho nó một số vấn đề về mã khác. Ifcho phép bạn chọn sự trừu tượng đó. Có nhiều cách tốt hơn để trừu tượng và tốt hơn để chọn sự trừu tượng. Đa hình cho một.
candied_orange

2
Mặc dù tôi đồng ý với bạn, đây là một câu trả lời kém. "Đúng, không quan tâm" không phải là một lý lẽ thuyết phục cho bất cứ điều gì. Tôi nghĩ thông điệp chính của bạn là thế này: mặc dù goto hấp dẫn những người thích sử dụng các công cụ mạnh mẽ, hiệu quả, nhưng nó lại dẫn đến một lượng lớn thời gian lãng phí đáng kinh ngạc sau này, ngay cả khi được chăm sóc cẩn thận.
Artelius

1
@artelius Chủ đề "đúng, không quan tâm" không có nghĩa là có sức thuyết phục. Nó có nghĩa là để minh họa cho nhiều điểm tôi sẽ thừa nhận để đi đến và vẫn chống lại nó. Do đó tranh luận những điểm đó với tôi là vô nghĩa. Vì đó không phải là quan điểm của tôi. Đạt mục đích?
candied_orange

1
Tôi chỉ tin rằng một câu trả lời hay không chỉ nêu ý kiến ​​của bạn, mà còn giải thích nó, và cho phép người đọc tự đưa ra đánh giá. Như hiện tại, không rõ tại sao điểm tốt của goto không đủ để vượt qua các vấn đề của nó. Hầu hết chúng ta đã dành hàng giờ để cố gắng tìm ra những con bọ không liên quan đến goto. Đây không phải là công trình duy nhất khó lý giải. Một số người hỏi câu hỏi này có lẽ ít kinh nghiệm hơn và cần những điều được đặt trong quan điểm.
Artelius

-1

Các ngôn ngữ hội thường chỉ có các bước nhảy có điều kiện / vô điều kiện (tương đương với GOTO. Các triển khai FORTRAN và BASIC cũ hơn không có câu lệnh chặn điều khiển nào ngoài vòng lặp được tính (vòng lặp DO), để lại tất cả các luồng điều khiển khác cho IF và GOTO. những ngôn ngữ này đã bị chấm dứt bởi một tuyên bố được gắn nhãn bằng số. Do đó, mã được viết cho các ngôn ngữ này có thể, và thường là, khó theo dõi và dễ bị lỗi.

Để nhấn mạnh điểm này, có câu lệnh " COME TỪ " được phát minh một cách ngẫu nhiên .

Thực tế không cần sử dụng GOTO trong các ngôn ngữ như C, C ++, C #, PASCAL, Java, v.v.; công trình thay thế có thể được sử dụng mà gần như chắc chắn sẽ hiệu quả và dễ bảo trì hơn nhiều. Đúng là một GOTO trong tệp nguồn sẽ không thành vấn đề. Vấn đề là không cần nhiều người để làm cho một đơn vị mã khó theo dõi và dễ bị lỗi. Đó là lý do tại sao sự khôn ngoan được chấp nhận là tránh GOTO bất cứ khi nào có thể.

Bài viết trên wikipedia về tuyên bố goto này có thể hữu ích

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.