GOTO vẫn bị coi là có hại? [đóng cửa]


282

Mọi người đều biết về Thư của Dijkstra gửi cho biên tập viên: đi đến tuyên bố được coi là có hại (cũng ở đây .html bảng điểm và ở đây .pdf) và đã có một sự thúc đẩy ghê gớm kể từ thời điểm đó để tránh tuyên bố goto bất cứ khi nào có thể. Mặc dù có thể sử dụng goto để tạo ra mã không thể nhầm lẫn, ngổn ngang, tuy nhiên nó vẫn còn trong các ngôn ngữ lập trình hiện đại . Ngay cả cấu trúc điều khiển tiếp tục tiên tiến trong Scheme có thể được mô tả như một goto tinh vi.

Những trường hợp nào đảm bảo việc sử dụng goto? Khi nào là tốt nhất để tránh?

Như một câu hỏi tiếp theo: C cung cấp một cặp hàm, setjmp và longjmp, cung cấp khả năng goto không chỉ trong khung ngăn xếp hiện tại mà trong bất kỳ khung gọi nào. Những thứ này có nên được coi là nguy hiểm như goto? Nguy hiểm hơn?


Chính Dijkstra đã hối hận vì danh hiệu đó mà anh không chịu trách nhiệm. Vào cuối EWD1308 (cũng ở đây .pdf), ông đã viết:

Cuối cùng là một câu chuyện ngắn cho hồ sơ. Vào năm 1968, Truyền thông của ACM đã xuất bản một văn bản của tôi với tiêu đề " Tuyên bố goto được coi là có hại ", trong những năm sau đó, nó thường được nhắc đến nhiều nhất, đáng tiếc, thường là bởi các tác giả không nhìn thấy nó nhiều hơn nó tiêu đề, đã trở thành nền tảng cho sự nổi tiếng của tôi bằng cách trở thành một khuôn mẫu: chúng ta sẽ thấy tất cả các loại bài viết dưới tiêu đề "X được coi là có hại" cho hầu hết mọi X, bao gồm một tiêu đề "Dijkstra coi là có hại". Nhưng chuyện gì đã xảy ra? Tôi đã gửi một bài báo dưới tiêu đề " Một trường hợp chống lại tuyên bố goto", để tăng tốc độ xuất bản, biên tập viên đã đổi thành" thư gửi cho biên tập viên ", và trong quá trình đó, ông đã đặt cho nó một tiêu đề mới về phát minh của chính mình! Biên tập viên là Niklaus Wirth.

Một bài viết kinh điển về chủ đề này, phù hợp với chủ đề của Dijkstra, là Lập trình có cấu trúc với các Tuyên bố , bởi Donald E. Knuth. Đọc cả hai giúp thiết lập lại bối cảnh và sự hiểu biết không giáo điều về chủ đề này. Trong bài báo này, ý kiến ​​của Dijkstra về trường hợp này được báo cáo và thậm chí còn mạnh mẽ hơn:

Donald E. Knuth: Tôi tin rằng bằng cách trình bày một quan điểm như vậy trên thực tế tôi không đồng ý gay gắt với ý tưởng của Dijkstra, vì gần đây ông đã viết như sau: "Xin đừng rơi vào cái bẫy tin rằng tôi vô cùng giáo điều về [ đi đến tuyên bố]. Tôi có cảm giác không thoải mái khi những người khác đang tạo ra một tôn giáo từ đó, như thể các vấn đề khái niệm về lập trình có thể được giải quyết bằng một thủ thuật duy nhất, bằng một hình thức kỷ luật mã hóa đơn giản! "


1
Goto của C # không giống với goto mà Dijkstra đang nói đến, vì những lý do chính xác mà Dijkstra đã nói đến. Goto đó vừa có hại như hồi đó, nhưng cũng ít cần thiết hơn, bởi vì các ngôn ngữ hiện đại cung cấp các cấu trúc điều khiển thay thế. C # goto là vô cùng hạn chế.
jalf

25
Gotos là tốt khi họ thêm rõ ràng. Nếu bạn có một vòng lặp lồng nhau dài, có thể tốt hơn để thoát khỏi nó hơn là đặt các biến "break" và phá vỡ cho đến khi bạn thoát ra.
simendsjo

28
Nếu bạn có một vòng lặp lồng nhau trên 4 độ sâu (không phải đó là một điều tốt), việc thoát ra khỏi tất cả yêu cầu thiết lập các giá trị tạm thời. Một goto ở đây rõ ràng hơn nhiều đối với tôi và IDE sẽ dễ dàng hiển thị goto ở đâu. Điều đó nói rằng, việc sử dụng goto nên thưa thớt và theo tôi chỉ di chuyển xuống để bỏ qua mã
simendsjo

7
Tôi đề nghị bạn nên đọc chín nghìn và một chủ đề được gắn thẻ goto.
Matti Virkkunen

5
Có một điều rõ ràng tồi tệ hơn việc sử dụng goto: hack các công cụ lập trình có cấu trúc cùng nhau để thực hiện a goto.

Câu trả lời:


184

Các tuyên bố sau đây là khái quát; mặc dù luôn luôn có thể biện hộ ngoại lệ, nhưng thông thường (theo kinh nghiệm của tôi và ý kiến ​​khiêm tốn) không đáng để mạo hiểm.

  1. Việc sử dụng không giới hạn các địa chỉ bộ nhớ (GOTO hoặc con trỏ thô) cung cấp quá nhiều cơ hội để dễ dàng mắc phải những sai lầm có thể tránh được.
  2. Càng có nhiều cách để đến một "vị trí" cụ thể trong mã, người ta càng không tin tưởng về trạng thái của hệ thống tại thời điểm đó. (Xem bên dưới.)
  3. Lập trình có cấu trúc IMHO ít nói về "tránh các GOTO" và nhiều hơn về việc làm cho cấu trúc của mã khớp với cấu trúc của dữ liệu. Ví dụ, cấu trúc dữ liệu lặp lại (ví dụ mảng, tệp tuần tự, v.v.) được xử lý tự nhiên bởi một đơn vị mã lặp lại. Có các cấu trúc dựng sẵn (ví dụ: while, for, cho đến, for-every, v.v.) cho phép lập trình viên tránh sự nhàm chán khi lặp lại các mẫu mã giống nhau.
  4. Ngay cả khi GOTO là chi tiết triển khai cấp thấp (không phải luôn luôn như vậy!) Nó vẫn ở dưới mức mà lập trình viên nên nghĩ. Có bao nhiêu lập trình viên cân bằng sổ séc cá nhân của họ trong nhị phân thô? Có bao nhiêu lập trình viên lo lắng về lĩnh vực nào trên đĩa chứa một bản ghi cụ thể, thay vì chỉ cung cấp một khóa cho công cụ cơ sở dữ liệu (và có bao nhiêu cách có thể xảy ra nếu chúng ta thực sự viết tất cả các chương trình của mình về các lĩnh vực đĩa vật lý)?

Chú thích ở trên:

Về điểm 2, hãy xem xét đoạn mã sau:

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

Tại điểm "làm một cái gì đó" trong mã, chúng ta có thể tuyên bố với độ tin cậy acao lớn hơn b. (Vâng, tôi đang bỏ qua khả năng tràn số nguyên chưa được xử lý. Chúng ta đừng bỏ qua một ví dụ đơn giản.)

Mặt khác, nếu mã đã đọc theo cách này:

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

Sự đa dạng của các cách để có được nhãn 10 có nghĩa là chúng ta phải làm việc chăm chỉ hơn nhiều để tự tin về các mối quan hệ giữa abtại thời điểm đó. (Trong thực tế, trong trường hợp chung, điều đó là không thể giải quyết được!)

Về điểm 4, toàn bộ khái niệm "đi đâu đó" trong mã chỉ là một phép ẩn dụ. Không có gì thực sự "đi" ở bất cứ đâu trong CPU ngoại trừ electron và photon (đối với nhiệt thải). Đôi khi chúng ta từ bỏ một phép ẩn dụ cho một cái khác, hữu ích hơn, một. Tôi nhớ lại đã gặp (vài thập kỷ trước!) Một ngôn ngữ

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

đã được triển khai trên một máy ảo bằng cách biên dịch hành động-1 và hành động-2 thành các thói quen không tham số ngoài dòng, sau đó sử dụng một mã op VM hai đối số duy nhất sử dụng giá trị boolean của điều kiện để gọi cái này hoặc cái kia. Khái niệm này chỉ đơn giản là "chọn những gì để gọi ngay bây giờ" chứ không phải "đến đây hoặc đi đến đó". Một lần nữa, chỉ là một sự thay đổi của ẩn dụ.


2
Một điểm tốt. Trong các ngôn ngữ cấp cao hơn, goto thậm chí không thực sự có ý nghĩa gì (xem xét việc nhảy giữa các phương thức trong Java). Hàm Haskell có thể bao gồm một biểu thức; Hãy thử nhảy ra khỏi đó với một goto!
Ốc cơ khí

2
Postcript hoạt động như ví dụ điểm 4 của bạn.
kẻ lừa đảo rủ rê

Smalltalk hoạt động tương tự như ví dụ điểm 4, nếu "tương tự" bạn có nghĩa là "không có gì giống như ngôn ngữ thủ tục làm". : P Không có kiểm soát dòng chảy trong ngôn ngữ; tất cả các quyết định được xử lý thông qua đa hình ( truefalsethuộc các loại khác nhau) và mỗi nhánh của if / other về cơ bản là một lambda.
cHao

Đây là những điểm hợp lệ, nhưng cuối cùng họ chỉ nhắc lại việc goto xấu có thể "nếu bị lạm dụng" như thế nào. Phá vỡ, tiếp tục, thoát, trở lại, gosub, giải quyết, toàn cầu, bao gồm, v.v ... là tất cả các kỹ thuật hiện đại đòi hỏi phải theo dõi tinh thần dòng chảy của mọi thứ và tất cả có thể bị lạm dụng để tạo mã spaghetti và bỏ qua xung quanh để tạo ra sự không chắc chắn của các trạng thái khác nhau. Công bằng mà nói, mặc dù tôi chưa bao giờ thấy việc sử dụng goto không tốt, tôi cũng chỉ từng thấy nó được sử dụng một hoặc hai lần. Điều đó nói lên tuyên bố rằng hầu hết luôn có những thứ tốt hơn để sử dụng.
Beejor

gototrong một ngôn ngữ lập trình hiện đại (Go) stackoverflow.com/a/11065563/3309046 .
nishanths

245

Truyện tranh GOTO của XKCD

Một đồng nghiệp của tôi cho biết lý do duy nhất để sử dụng GOTO là nếu bạn tự lập trình cho đến một góc mà đó là lối thoát duy nhất. Nói cách khác, thiết kế phù hợp trước thời hạn và bạn sẽ không cần sử dụng GOTO sau này.

Tôi nghĩ truyện tranh này minh họa rất hay rằng "Tôi có thể cấu trúc lại dòng chảy của chương trình, hoặc sử dụng một chút 'GOTO' thay thế." GOTO là một lối thoát yếu khi bạn có thiết kế yếu. Velociraptors con mồi trên kẻ yếu .


41
GOTO có thể thực hiện 'nhảy' từ một điểm tùy ý sang một điểm tùy ý khác. Velociraptor nhảy đến đây từ hư không!
rpattabi

29
Tôi không thấy trò đùa nào buồn cười cả vì mọi người đều biết rằng bạn cũng phải liên kết trước khi bạn có thể thực thi.
Kinjal Dixit

31
Đồng nghiệp của bạn đã sai và rõ ràng là không đọc bài viết của Knuth.
Jim Balter

27
Tôi đã thấy không có kết thúc của mã bị xáo trộn và mặt khác bị méo mó trong nhiều năm được đưa ra chỉ để tránh một goto. @jimmcKeeth, Phim hoạt hình xkcd ở trên không cho thấy goto yếu. Nó làm cho niềm vui của sự cuồng loạn xung quanh việc sử dụng nó.

4
Bạn có nhận ra rằng mã nguồn thư viện Delphi chứa các câu lệnh goto. Và đây là những cách sử dụng thích hợp của goto.
David Heffernan

131

Đôi khi, sử dụng GOTO thay thế cho xử lý ngoại lệ trong một chức năng duy nhất là hợp lệ:

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

return;

err_cleanup:
...

Mã COM dường như rơi vào mô hình này khá thường xuyên.


29
Tôi đồng ý, có những trường hợp sử dụng hợp pháp trong đó, goto có thể đơn giản hóa mã và làm cho nó dễ đọc / dễ bảo trì hơn, nhưng dường như có một số loại goto-phobia trôi nổi xung quanh ...
Pop Catalin

48
@Bob: Thật khó để chuyển mã err_cleanup vào chương trình con nếu nó dọn sạch các biến cục bộ.
Niki

4
Trên thực tế, tôi đã sử dụng nó trong COM / VB6 chỉ vì tôi không có sự thay thế, không phải vì nó là một sự thay thế. Hôm nay tôi hạnh phúc biết bao với thử / bắt / cuối cùng.
Rui Craveiro

10
@ user4891 Cách C ++ thành ngữ không thử {} Catch () {dọn dẹp; }, nhưng đúng hơn, RAII, nơi các tài nguyên cần được dọn sạch được thực hiện trong các hàm hủy. Mỗi hàm tạo / hàm hủy quản lý chính xác một tài nguyên.
David Stone

5
Có hai cách viết này trong C mà không cần goto; và cả hai đều ngắn hơn nhiều. Hoặc: if (f ()) if (g ()) if (h ()) trả về thành công; dọn dẹp(); trở về thất bại; hoặc: if (f () && g () && h ()) trả lại thành công; dọn dẹp(); trở về thất bại;
LHP

124

Tôi chỉ có thể nhớ lại bằng cách sử dụng một goto một lần. Tôi đã có một loạt năm vòng lặp được đếm lồng nhau và tôi cần có thể thoát ra khỏi toàn bộ cấu trúc từ bên trong sớm dựa trên các điều kiện nhất định:

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

ENDOFLOOPS:

Tôi có thể dễ dàng khai báo một biến boolean và sử dụng nó như một phần của điều kiện cho mỗi vòng lặp, nhưng trong trường hợp này tôi đã quyết định một GOTO vừa thực tế vừa dễ đọc.

Không có Velociraptors tấn công tôi.


83
"Tái cấu trúc nó thành một hàm và thay thế goto bằng return :)", và sự khác biệt là? thực sự có gì khác biệt? không trở lại cũng đi? Trả về cũng hãm dòng chảy có cấu trúc như goto, và trong trường hợp này họ cũng làm theo cách tương tự (ngay cả khi goto có thể được sử dụng cho những thứ có ý nghĩa hơn)
Pop Catalin

38
Nesting rất nhiều vòng lặp thường là một mã mùi tất cả của riêng nó. Trừ khi bạn đang thực hiện, như phép nhân mảng 5 chiều, thật khó để hình dung một tình huống trong đó một số vòng lặp bên trong không thể được trích xuất một cách hữu ích thành các hàm nhỏ hơn. Giống như tất cả các quy tắc của ngón tay cái, có một số trường hợp ngoại lệ tôi cho rằng.
Doug McClean

5
Thay thế nó bằng trả về chỉ hoạt động nếu bạn đang sử dụng ngôn ngữ hỗ trợ trả về.
Loren Pechtel

31
@leppie: Thế hệ nổi loạn chống lại gotovà cho chúng tôi lập trình có cấu trúc cũng từ chối trả lại sớm, vì lý do tương tự. Nó liên quan đến việc mã có thể đọc được như thế nào, nó thể hiện rõ ràng ý định của lập trình viên như thế nào. Tạo một chức năng không nhằm mục đích nào khác ngoài việc tránh sử dụng một từ khóa sai dẫn đến sự gắn kết xấu: phương pháp chữa trị còn tệ hơn cả căn bệnh này.
Bùn

21
@BriptButkus: Thành thật mà nói, điều đó cũng tệ như vậy, nếu không nói là tồi tệ hơn. Ít nhất với một goto, người ta có thể chỉ định rõ ràng mục tiêu. Với break 5;, (1) tôi phải đếm số lần đóng vòng để tìm đích; và (2) nếu cấu trúc vòng lặp thay đổi, nó có thể yêu cầu thay đổi số đó để giữ đúng đích. Nếu tôi sẽ tránh goto, thì lợi ích sẽ không phải theo dõi thủ công như thế.
cHao

93

Chúng tôi đã có cuộc thảo luận này và tôi đứng theo quan điểm của tôi .

Hơn nữa, tôi đã chán ngấy với những người mô tả các cấu trúc ngôn ngữ cấp cao hơn như là một cách gotongụy trang bởi vì họ rõ ràng không có điểm nào cả . Ví dụ:

Ngay cả cấu trúc điều khiển tiếp tục tiên tiến trong Scheme có thể được mô tả như một goto tinh vi.

Điều đó là hoàn toàn vô nghĩa. Mọi cấu trúc điều khiển có thể được thực hiện theo các khía cạnh gotonhưng quan sát này hoàn toàn tầm thường và vô dụng. gotokhông được coi là có hại vì những tác động tích cực của nó nhưng vì những hậu quả tiêu cực của nó và những điều này đã bị loại bỏ bởi lập trình có cấu trúc.

Tương tự như vậy, nói rằng GOTO là một công cụ, và như tất cả các công cụ, nó có thể được sử dụng và lạm dụng, hoàn toàn không phù hợp. Không một công nhân xây dựng hiện đại nào sẽ sử dụng một tảng đá và khẳng định nó là một công cụ. Đá đã được thay thế bằng búa. gotođã được thay thế bởi các cấu trúc điều khiển. Nếu công nhân xây dựng bị mắc kẹt trong tự nhiên mà không có búa, tất nhiên anh ta sẽ sử dụng đá thay thế. Nếu một lập trình viên phải sử dụng ngôn ngữ lập trình kém hơn không có tính năng X, dĩ nhiên, cô ấy có thể phải sử dụng gotothay thế. Nhưng nếu cô ấy sử dụng nó ở bất cứ nơi nào khác thay vì tính năng ngôn ngữ phù hợp, rõ ràng cô ấy đã không hiểu đúng ngôn ngữ và sử dụng sai ngôn ngữ. Nó thực sự đơn giản như vậy.


82
Tất nhiên, việc sử dụng đá đúng cách không phải là một cái búa. Một trong những công dụng thích hợp của nó là đá mài, hoặc để mài các công cụ khác. Ngay cả đá thấp, khi được sử dụng đúng cách là một công cụ tốt. Bạn chỉ cần tìm cách sử dụng thích hợp. Tương tự với goto.
Kibbee

10
Vậy sử dụng Goto đúng cách là gì? Đối với mọi trường hợp có thể tưởng tượng được có một công cụ khác phù hợp hơn. Và ngay cả đá mài của bạn là thực sự thay thế bởi các công cụ công nghệ cao hiện nay, ngay cả khi họ vẫn được thực hiện của rock. Có một sự khác biệt lớn giữa nguyên liệu thô và công cụ.
Konrad Rudolph

8
@jalf: Chuyển chắc chắn nhất không tồn tại trong C #. Xem stackoverflow.com/questions/359436/
Jon Skeet

51
Tôi mất tinh thần rất nhiều người tán thành bài đăng này. Bài đăng của bạn chỉ có vẻ hiệu quả vì bạn không bao giờ bận tâm đặt câu hỏi logic nào bạn thực sự thực hiện, do đó bạn không nhận thấy sai lầm của mình. Cho phép tôi diễn giải toàn bộ bài đăng của bạn: "Có một công cụ ưu việt cho một goto trong mọi tình huống, vì vậy không bao giờ nên sử dụng gotos." Đây là một điều kiện nhị phân hợp lý và như vậy toàn bộ bài đăng của bạn về cơ bản đang cầu xin câu hỏi "Làm thế nào để bạn biết có một công cụ ưu việt cho một goto trong mọi tình huống?"
Mã hóa với phong cách

10
@ Mã hóa: Không, bạn hoàn toàn bỏ lỡ ý chính của bài đăng. Đó là một riposte chứ không phải là một đối số hoàn toàn bị cô lập. Tôi chỉ đơn thuần chỉ ra sự ngụy biện trong lập luận chính là cho for goto. Bạn đã đúng khi tôi không đưa ra một lập luận chống lại mọi gotongười - tôi không có ý định - vì vậy không có câu hỏi nào.
Konrad Rudolph

91

Goto cực kỳ thấp trong danh sách những thứ tôi đưa vào một chương trình chỉ vì lợi ích của nó. Điều đó không có nghĩa là không thể chấp nhận được.

Goto có thể tốt đẹp cho các máy trạng thái. Một câu lệnh chuyển đổi trong một vòng lặp là (theo thứ tự quan trọng điển hình): (a) không thực sự đại diện cho luồng điều khiển, (b) xấu, (c) có khả năng không hiệu quả tùy thuộc vào ngôn ngữ và trình biên dịch. Vì vậy, cuối cùng bạn viết một hàm cho mỗi trạng thái và thực hiện những việc như "return NEXT_STATE;" mà thậm chí trông giống như goto.

Cấp, rất khó để mã hóa các máy trạng thái theo cách làm cho chúng dễ hiểu. Tuy nhiên, không có khó khăn nào trong việc sử dụng goto và không ai có thể giảm được bằng cách sử dụng các cấu trúc điều khiển thay thế. Trừ khi ngôn ngữ của bạn có cấu trúc 'máy trạng thái'. Của tôi không.

Trong những trường hợp hiếm hoi khi thuật toán của bạn thực sự dễ hiểu nhất về mặt đường dẫn thông qua một chuỗi các nút (trạng thái) được kết nối bằng một tập hợp chuyển đổi giới hạn cho phép (gotos), thay vì bất kỳ luồng điều khiển cụ thể nào hơn (vòng lặp, điều kiện, không chú ý ), sau đó nên được rõ ràng trong mã. Và bạn nên vẽ một sơ đồ đẹp.

setjmp / longjmp có thể tốt cho việc thực hiện các ngoại lệ hoặc hành vi giống như ngoại lệ. Mặc dù không được ca ngợi trên toàn cầu, các trường hợp ngoại lệ thường được coi là một cấu trúc kiểm soát "hợp lệ".

setjmp / longjmp là 'nguy hiểm' hơn goto theo nghĩa là chúng khó sử dụng chính xác hơn, không bao giờ để tâm một cách toàn diện.

Chưa bao giờ có, cũng sẽ không bao giờ có bất kỳ ngôn ngữ nào trong đó khó viết mã xấu nhất. - Donald Knuth.

Lấy goto ra khỏi C sẽ không dễ dàng hơn để viết mã tốt trong C. Trên thực tế, nó sẽ bỏ lỡ điểm C được cho là có khả năng hoạt động như một ngôn ngữ trình biên dịch được tôn vinh.

Tiếp theo, nó sẽ là "con trỏ được coi là có hại", sau đó "gõ vịt được coi là có hại". Sau đó, ai sẽ là người bảo vệ bạn khi họ đến để lấy đi cấu trúc lập trình không an toàn của bạn? Hở?


14
Cá nhân, đây là nhận xét tôi sẽ đưa ra kiểm tra. Một điều tôi muốn chỉ ra cho độc giả là thuật ngữ bí truyền "máy trạng thái" bao gồm những thứ hàng ngày như máy phân tích từ vựng. Kiểm tra đầu ra của lex somtime. Toàn gotos.
TED

2
Bạn có thể sử dụng câu lệnh chuyển đổi bên trong vòng lặp (hoặc trình xử lý sự kiện) để làm tốt các máy trạng thái. Tôi đã thực hiện rất nhiều máy trạng thái mà không bao giờ phải sử dụng jmp hoặc goto.
Scott Whitlock

11
+1 Những mũi tên trên máy trạng thái ánh xạ tới 'goto' chặt chẽ hơn bất kỳ cấu trúc điều khiển nào khác. Chắc chắn, bạn có thể sử dụng một công tắc bên trong một vòng lặp - giống như bạn có thể sử dụng một loạt các hình ảnh thay vì một lúc cho các vấn đề khác, nhưng đó thường là một ý tưởng; đó là toàn bộ điểm của cuộc thảo luận này.
Edmund

2
Tôi có thể trích dẫn bạn về đoạn cuối đó không?
Chris Lutz

2
Và, tại đây vào năm 2013, chúng ta đã đạt được (và đã vượt qua) giai đoạn "con trỏ được coi là có hại".
Isiah Meadows

68

Trong Linux: Sử dụng goto Trong Kernel Code trên Kernel Trap, có một cuộc thảo luận với Linus Torvalds và một "anh chàng mới" về việc sử dụng GOTO trong mã Linux. Có một số điểm rất tốt ở đó và Linus mặc trang phục kiêu ngạo thường thấy đó :)

Một số đoạn:

Linus: "Không, bạn đã bị những người CS tẩy não vì nghĩ rằng Niklaus Wirth thực sự biết những gì anh ta đang nói. Anh ta đã không làm thế. Anh ta không có manh mối gì cả."

-

Linus: "Tôi nghĩ rằng goto là tốt, và chúng thường dễ đọc hơn số lượng lớn thụt vào."

-

Linus: "Tất nhiên, trong các ngôn ngữ ngu ngốc như Pascal, nơi các nhãn không thể mô tả, goto có thể xấu."


9
Đó là một điểm tốt như thế nào? Họ đang thảo luận về việc sử dụng nó trong một ngôn ngữ không có gì khác. Khi bạn đang lập trình lắp ráp, tất cả các nhánh và bước nhảy của goto. Và C là, và là một "ngôn ngữ lắp ráp di động". Hơn nữa, những đoạn bạn trích dẫn không nói về lý do tại sao anh ấy nghĩ goto là tốt.
jalf

5
Ồ Thật đáng thất vọng khi đọc. Bạn sẽ nghĩ rằng một anh chàng hệ điều hành lớn như Linus Torvalds sẽ biết nhiều hơn là nói điều đó. Pascal (pascal trường học cũ, không phải phiên bản Object hiện đại) là những gì Mac OS Classic được viết trong thời kỳ 68 nghìn và nó là hệ điều hành tiên tiến nhất thời bấy giờ.
Mason Wheeler

6
@mason Classic Mac OS có một số thư viện Pascal (cuối cùng - thời gian chạy Pascal chiếm quá nhiều bộ nhớ trong các máy Mac đầu tiên) nhưng phần lớn mã lõi được viết bằng Trình biên dịch, đặc biệt là các thói quen đồ họa và giao diện người dùng.
Jim Dovey

30
Linus chỉ lập luận (rõ ràng, như Rik van Riel trong cuộc thảo luận đó) cho goto để xử lý trạng thái thoát, và anh ta làm như vậy trên cơ sở sự phức tạp mà các cấu trúc thay thế của C sẽ mang lại nếu chúng được sử dụng thay thế.
Charles Stewart

21
IMHO Linus đúng về vấn đề này. Quan điểm của ông là mã hạt nhân, được viết bằng C, cần thực hiện một cái gì đó tương tự như xử lý ngoại lệ, được viết rõ ràng và đơn giản nhất bằng cách sử dụng một goto. Các thành ngữ goto cleanup_and_exitlà một trong số ít sử dụng "tốt" của goto trái bây giờ mà chúng tôi có for, whileifđể quản lý kiểm soát dòng chảy của chúng tôi. Xem thêm: lập trình
viên.stackexchange.com / a / 154980

50

Trong C, gotochỉ hoạt động trong phạm vi của hàm hiện tại, có xu hướng nội địa hóa bất kỳ lỗi tiềm ẩn nào. setjmplongjmpnguy hiểm hơn nhiều, không mang tính địa phương, phức tạp và phụ thuộc vào việc thực hiện. Tuy nhiên, trên thực tế, chúng quá tối nghĩa và không phổ biến để gây ra nhiều vấn đề.

Tôi tin rằng sự nguy hiểm của gotoC là rất lớn. Hãy nhớ rằng các gotođối số ban đầu đã diễn ra vào thời của các ngôn ngữ như BASIC lỗi thời, nơi những người mới bắt đầu sẽ viết mã spaghetti như thế này:

3420 IF A > 2 THEN GOTO 1430

Ở đây Linus mô tả một cách sử dụng thích hợp của goto: http://www.kernel.org/doc/Documentation/CodingStyle (chương 7).


7
Khi BASIC lần đầu tiên xuất hiện, không có bất kỳ sự thay thế nào cho GOTO nnnn và GOSUB mmmm như những cách để nhảy xung quanh. Cấu trúc cấu trúc đã được thêm vào sau đó.
Jonathan Leffler

7
Bạn đang thiếu điểm ... ngay cả khi đó bạn không phải viết spaghetti ... GOTO của bạn luôn có thể được sử dụng một cách có kỷ luật
JoelFan

Cũng đáng lưu ý rằng hành vi của setjmp/ longjmpchỉ được chỉ định khi chúng được sử dụng như một phương tiện để nhảy đến một điểm trong phạm vi từ các địa điểm khác trong cùng phạm vi đó. Khi điều khiển rời khỏi phạm vi setjmpđược thực thi, mọi nỗ lực sử dụng longjmptrên cấu trúc được tạo bởi setjmpsẽ dẫn đến hành vi không xác định.
supercat

9
Một số phiên bản của BASIC sẽ cho phép bạn làm GOTO A * 40 + B * 200 + 30. Không khó để thấy điều này rất tiện dụng và rất nguy hiểm.
Jon Hanna

1
@Hjulle nó sẽ tính toán biểu thức và sau đó đi đến dòng mã với số đó (số dòng rõ ràng là một yêu cầu của hầu hết các phương ngữ trước đó). ZX Spectrum Basic là một thứ sẽ chấp nhận điều đó
Jon Hanna

48

Ngày nay, thật khó để thấy vấn đề lớn về GOTOtuyên bố này bởi vì những người "lập trình có cấu trúc" chủ yếu giành chiến thắng trong cuộc tranh luận và các ngôn ngữ ngày nay có đủ cấu trúc dòng kiểm soát để tránh GOTO.

Đếm số lượng gotos trong một chương trình C hiện đại. Bây giờ thêm số lượng break, continuereturntuyên bố. Hơn nữa, thêm số lần bạn sử dụng if, else, while, switchhoặc case. Đó là về GOTOchương trình của bạn sẽ có bao nhiêu nếu bạn viết bằng FORTRAN hoặc BASIC vào năm 1968 khi Dijkstra viết thư.

Ngôn ngữ lập trình tại thời điểm đó đang thiếu dòng điều khiển. Ví dụ: trong bản gốc của Gordmouth BASIC:

  • IFbáo cáo không có ELSE. Nếu bạn muốn một, bạn phải viết:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Ngay cả khi IFtuyên bố của bạn không cần ELSE, nó vẫn bị giới hạn trong một dòng duy nhất, thường bao gồm một GOTO.

  • Không có DO...LOOPtuyên bố. Đối với các FORvòng không , bạn phải kết thúc vòng lặp với một vòng lặp rõ ràng GOTOhoặc IF...GOTOquay lại từ đầu.

  • Không có SELECT CASE. Bạn đã phải sử dụng ON...GOTO.

Vì vậy, bạn đã kết thúc với một nhiều của GOTOs trong chương trình của bạn. Và bạn không thể phụ thuộc vào sự hạn chế của GOTOs trong một chương trình con duy nhất (vì đó GOSUB...RETURNlà một khái niệm yếu về chương trình con), vì vậy những điều này GOTOcó thể đi bất cứ đâu . Rõ ràng, điều này làm cho dòng điều khiển khó theo dõi.

Đây là nơi mà GOTOphong trào chống lại bắt nguồn.


Một điều cần lưu ý là cách viết mã ưa thích nếu một người có một số mã trong một vòng lặp sẽ phải thực thi hiếm khi sẽ là 420 if (rare_condition) then 3000// 430 and onward: rest of loop and other main-line code// 3000 [code for rare condition]// 3230 goto 430. Viết mã theo cách đó để tránh bất kỳ nhánh hoặc bước nhảy nào trong trường hợp dòng chính phổ biến, nhưng nó làm cho mọi thứ khó theo dõi. Việc tránh nhánh trong mã lắp ráp có thể tồi tệ hơn, nếu một số nhánh bị giới hạn ở ví dụ +/- 128 byte và đôi khi không có cặp bổ sung (ví dụ: "cjne" tồn tại nhưng không có "cje").
supercat

Tôi đã từng viết một số mã cho 8x51 có một ngắt chạy cứ sau 128 chu kỳ. Mỗi chu kỳ bổ sung được sử dụng trong trường hợp chung của ISR sẽ làm giảm hơn 1% tốc độ thực thi của mã chính tuyến (tôi nghĩ rằng khoảng 90 chu kỳ trong số 128 thường có sẵn cho dòng chính) và bất kỳ hướng dẫn phân nhánh nào cũng sẽ mất hai chu kỳ ( trong cả hai trường hợp phân nhánh và rơi qua). Mã này có hai so sánh - một so sánh thường sẽ báo cáo bằng nhau; cái khác, không bằng nhau. Trong cả hai trường hợp, mã trường hợp hiếm hoi cách xa hơn 128 byte. Vì vậy ...
supercat

cjne r0,expected_value,first_comp_springboard/.../ cjne r1,unexpected_value,second_comp_fallthrough// `ajmp second_comp_target` // first_comp_springboard: ajmp first_comp_target// second_comp_fallthrough: .... Không phải là một mẫu mã hóa rất đẹp, nhưng khi các chu kỳ riêng lẻ được tính, người ta sẽ làm những việc như vậy. Tất nhiên, vào những năm 1960, mức độ tối ưu hóa như vậy quan trọng hơn ngày nay, đặc biệt là khi các CPU hiện đại thường yêu cầu tối ưu hóa kỳ lạ và các hệ thống biên dịch đúng lúc có thể có thể áp dụng mã tối ưu hóa cho CPU không tồn tại khi mã trong câu hỏi đã được viết.
supercat

34

Go To có thể cung cấp một loại dự phòng cho xử lý ngoại lệ "thực" trong một số trường hợp nhất định. Xem xét:

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

Rõ ràng mã này đã được đơn giản hóa để chiếm ít không gian hơn, vì vậy đừng quá bận tâm vào các chi tiết. Nhưng hãy xem xét một giải pháp thay thế mà tôi đã thấy quá nhiều lần trong mã sản xuất bởi các lập trình viên sẽ có độ dài vô lý để tránh sử dụng goto:

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

Bây giờ chức năng mã này làm điều tương tự chính xác. Trong thực tế, mã được tạo bởi trình biên dịch gần như giống hệt nhau. Tuy nhiên, trong sự nhiệt tình của lập trình viên để xoa dịu Nogoto (vị thần đáng sợ của quở trách hàn lâm), lập trình viên này đã phá vỡ hoàn toàn thành ngữ cơ bản mà whilevòng lặp thể hiện và đã thực sự hiểu được tính dễ đọc của mã. Điều này không tốt hơn.

Vì vậy, đạo đức của câu chuyện là, nếu bạn thấy mình đang dùng đến một thứ gì đó thực sự ngu ngốc để tránh sử dụng goto, thì đừng.


1
Mặc dù tôi có xu hướng đồng ý với những gì bạn đang nói ở đây, nhưng thực tế là các breaktuyên bố nằm trong cấu trúc kiểm soát cho thấy chính xác những gì họ làm. Với gotoví dụ, người đọc mã phải xem qua mã để tìm nhãn, theo lý thuyết, thực tế có thể là trước cấu trúc điều khiển. Tôi không đủ kinh nghiệm với C kiểu cũ để đưa ra đánh giá rằng cái này chắc chắn tốt hơn cái kia, nhưng cũng có sự đánh đổi.
Sông Vivian

5
@Daniel ALLenLangdon: Thực tế là các breaks nằm trong một vòng lặp cho thấy chính xác rằng họ thoát khỏi vòng lặp . Đó không phải là "chính xác những gì họ làm", vì trong thực tế, không có một vòng lặp nào cả! Không có gì trong đó có cơ hội lặp lại, nhưng điều đó không rõ ràng cho đến khi kết thúc. Việc bạn có một "vòng lặp" không bao giờ chạy nhiều hơn một lần, có nghĩa là các cấu trúc điều khiển đang bị lạm dụng. Với gotoví dụ, lập trình viên có thể nói goto error_handler;. Nó rõ ràng hơn, và thậm chí ít khó khăn hơn để làm theo. (Ctrl + F, "error_handler:" để tìm mục tiêu. Hãy thử làm điều đó với "}".)
cHao

1
Tôi đã từng thấy mã tương tự như ví dụ thứ hai của bạn trong một hệ thống kiểm soát không lưu - bởi vì 'goto không có trong từ vựng của chúng tôi'.
QED

30

Donald E. Knuth đã trả lời câu hỏi này trong cuốn sách "Lập trình văn học", 1992 CSLI. Trên P. 17 có một bài tiểu luận " Lập trình có cấu trúc với các câu lệnh goto " (PDF). Tôi nghĩ rằng bài báo có thể đã được xuất bản trong các cuốn sách khác là tốt.

Bài viết mô tả đề xuất của Dijkstra và mô tả các trường hợp điều này hợp lệ. Nhưng ông cũng đưa ra một số ví dụ ngược (các vấn đề và thuật toán) không thể được sao chép dễ dàng chỉ bằng các vòng có cấu trúc.

Bài viết chứa một mô tả đầy đủ về vấn đề, lịch sử, ví dụ và ví dụ truy cập.


25

Goto coi hữu ích.

Tôi bắt đầu lập trình từ năm 1975. Đến những năm lập trình viên từ những năm 1970, những từ "goto coi là có hại" đã nói ít nhiều rằng các ngôn ngữ lập trình mới với cấu trúc điều khiển hiện đại rất đáng để thử. Chúng tôi đã thử các ngôn ngữ mới. Chúng tôi nhanh chóng chuyển đổi. Chúng tôi không bao giờ quay trở lại.

Chúng tôi không bao giờ quay lại, nhưng, nếu bạn trẻ hơn, thì bạn chưa bao giờ đến đó ngay từ đầu.

Bây giờ, một nền tảng trong các ngôn ngữ lập trình cổ có thể không hữu ích lắm ngoại trừ một chỉ số về tuổi của lập trình viên. Mặc dù vậy, các lập trình viên trẻ tuổi thiếu nền tảng này, vì vậy họ không còn hiểu được thông điệp mà khẩu hiệu "goto coi là có hại" được truyền đến đối tượng dự định của nó tại thời điểm nó được giới thiệu.

Những khẩu hiệu không hiểu thì không sáng sủa lắm. Có lẽ tốt nhất là quên những khẩu hiệu như vậy. Những khẩu hiệu như vậy không giúp được gì.

Tuy nhiên, khẩu hiệu đặc biệt này, "Goto được coi là có hại", đã mang đến một cuộc sống bất tử của chính nó.

Goto không thể bị lạm dụng? Trả lời: chắc chắn, nhưng vậy thì sao? Thực tế mọi yếu tố lập trình đều có thể bị lạm dụng. boolVí dụ, người khiêm tốn bị lạm dụng thường xuyên hơn một số người trong chúng ta muốn tin.

Ngược lại, tôi không thể nhớ đã gặp một trường hợp lạm dụng goto duy nhất, thực tế kể từ năm 1990.

Vấn đề lớn nhất với goto có lẽ không phải là kỹ thuật mà là xã hội. Các lập trình viên không biết nhiều đôi khi dường như cảm thấy rằng goto phản cảm làm cho họ nghe thông minh. Thỉnh thoảng bạn có thể phải làm hài lòng những lập trình viên như vậy. Cuộc sống là thế.

Điều tồi tệ nhất về goto ngày nay là nó không được sử dụng đủ.


24

Bị thu hút bởi Jay Ballou khi thêm câu trả lời, tôi sẽ thêm 0,02 bảng của mình. Nếu Bruno Ranschaert chưa làm như vậy, tôi đã đề cập đến bài viết "Lập trình có cấu trúc với các tuyên bố GOTO" của Knuth.

Một điều mà tôi chưa thấy thảo luận là loại mã, trong khi không chính xác phổ biến, đã được dạy trong sách giáo khoa của Fortran. Những thứ như phạm vi mở rộng của một vòng lặp DO và các chương trình con được mã hóa mở (hãy nhớ, đây sẽ là Fortran II, hoặc Fortran IV hoặc Fortran 66 - không phải Fortran 77 hoặc 90). Ít nhất có khả năng các chi tiết cú pháp là không chính xác, nhưng các khái niệm phải đủ chính xác. Các đoạn trong mỗi trường hợp là bên trong một chức năng.

Lưu ý rằng cuốn sách xuất sắc nhưng lỗi thời (và không còn xuất bản) ' Các yếu tố của Phong cách lập trình, Edn 2 ' của Kernighan & Plauger bao gồm một số ví dụ thực tế về việc lạm dụng GOTO từ sách giáo khoa lập trình trong thời đại của nó (cuối thập niên 70). Các tài liệu dưới đây không phải là từ cuốn sách đó, tuy nhiên.

Phạm vi mở rộng cho vòng lặp DO

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

Một lý do cho những điều vô nghĩa đó là thẻ đục lỗ lỗi thời. Bạn có thể nhận thấy rằng các nhãn (nằm ngoài chuỗi vì đó là kiểu chính tắc!) Nằm trong cột 1 (thực ra, chúng phải ở các cột 1-5) và mã nằm trong các cột 7-72 ​​(cột 6 là phần tiếp theo cột đánh dấu). Các cột 73-80 sẽ được cấp một số thứ tự và có những máy sắp xếp các cỗ bài thẻ theo thứ tự số thứ tự. Nếu bạn có chương trình của mình trên các thẻ được giải trình tự và cần thêm một vài thẻ (dòng) vào giữa vòng lặp, bạn sẽ phải lặp lại mọi thứ sau những dòng bổ sung đó. Tuy nhiên, nếu bạn thay thế một thẻ bằng công cụ GOTO, bạn có thể tránh sắp xếp lại tất cả các thẻ - bạn chỉ cần nhét các thẻ mới vào cuối thói quen với số thứ tự mới. Hãy coi đó là nỗ lực đầu tiên tại 'điện toán xanh'

Ồ, bạn cũng có thể lưu ý rằng tôi gian lận và không la hét - Fortran IV được viết bằng tất cả chữ hoa thông thường.

Chương trình con mã hóa mở

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

GOTO giữa các nhãn 76 và 54 là một phiên bản của goto được tính toán. Nếu biến i có giá trị 1, goto nhãn đầu tiên trong danh sách (123); nếu nó có giá trị 2, hãy chọn cái thứ hai, v.v. Đoạn từ 76 đến goto được tính toán là chương trình con mã hóa mở. Đó là một đoạn mã được thực thi giống như một chương trình con, nhưng được viết ra trong phần thân của hàm. (Fortran cũng có các hàm câu lệnh - là các hàm nhúng được trang bị trên một dòng.)

Có các cấu trúc tồi tệ hơn goto được tính toán - bạn có thể gán nhãn cho các biến và sau đó sử dụng một goto được gán. Googling được giao goto nói với tôi rằng nó đã bị xóa khỏi Fortran 95. Phấn đấu cho cuộc cách mạng lập trình có cấu trúc mà có thể nói là đã bắt đầu trước công chúng với thư hoặc bài báo "GOTO được coi là có hại" của Dijkstra.

Không có kiến ​​thức về các loại việc đã được thực hiện ở Fortran (và trong các ngôn ngữ khác, hầu hết trong số đó đã rơi đúng cách), những người mới đến chúng tôi khó hiểu được phạm vi của vấn đề mà Dijkstra đang giải quyết. Heck, tôi đã không bắt đầu lập trình cho đến mười năm sau khi bức thư đó được xuất bản (nhưng tôi đã không may lập trình trong Fortran IV một thời gian).


2
Nếu bạn muốn xem ví dụ về một số mã bằng cách sử dụng goto'trong tự nhiên', câu hỏi Muốn: Thuật toán sắp xếp Bose-Hibbard cho thấy một số mã (Algol 60) được xuất bản năm 1963. Bố cục ban đầu không so sánh với các tiêu chuẩn mã hóa hiện đại. Mã được làm rõ (thụt lề) vẫn còn khá khó hiểu. Các gototuyên bố ở đó làm cho nó (rất) khó hiểu thuật toán là gì.
Jonathan Leffler

1
Còn quá trẻ để trải nghiệm bất cứ điều gì gần với thẻ đục lỗ, thật tuyệt vời khi đọc về vấn đề đấm lại. +1
Qix - MONICA ĐƯỢC PHÂN PHỐI

20

Không có những thứ như GOTO coi là có hại .

GOTO là một công cụ, và như tất cả các công cụ, nó có thể được sử dụng và lạm dụng .

Tuy nhiên, có nhiều công cụ trong thế giới lập trình có xu hướng bị lạm dụng nhiều hơn là được sử dụng và GOTO là một trong số đó. các VỚI tuyên bố của Delphi là khác.

Cá nhân tôi không sử dụng mã thông thường , nhưng tôi đã sử dụng cả GOTOVỚI được bảo hành và một giải pháp thay thế sẽ chứa nhiều mã hơn.

Giải pháp tốt nhất là trình biên dịch sẽ cảnh báo bạn rằng từ khóa đã bị nhiễm độc và bạn phải nhét một vài chỉ thị pragma xung quanh câu lệnh để loại bỏ các cảnh báo.

Nó giống như bảo con bạn không chạy bằng kéo . Kéo không phải là xấu, nhưng một số cách sử dụng chúng có lẽ không phải là cách tốt nhất để giữ sức khỏe của bạn.


Vấn đề với GOTO là nó phá vỡ những bất biến quan trọng mà chúng ta coi là ngôn ngữ lập trình hiện đại. Để lấy một ví dụ, nếu tôi gọi một hàm, chúng tôi giả sử rằng khi hàm hoàn thành, nó sẽ trả lại quyền điều khiển cho người gọi, thông thường hoặc thông qua việc giải nén ngăn xếp đặc biệt. Nếu chức năng đó sử dụng sai GOTO, thì tất nhiên điều này không còn đúng nữa. Điều này làm cho nó rất khó để lý do về mã của chúng tôi. Sẽ không đủ để cẩn thận tránh việc sử dụng sai GOTO. Vấn đề vẫn xảy ra nếu GOTO bị lạm dụng bởi những người phụ thuộc của chúng tôi ...
Jonathan Hartley

... Vì vậy, để biết liệu chúng tôi có thể suy luận về các cuộc gọi chức năng của mình hay không, chúng tôi cần kiểm tra từng dòng mã nguồn của mọi phụ thuộc bắc cầu của chúng tôi, kiểm tra xem chúng có lạm dụng GOTO không. Vì vậy, sự tồn tại của GOTO trong ngôn ngữ đã phá vỡ khả năng tự tin suy luận về mã của chúng ta, ngay cả khi chúng ta sử dụng nó một cách hoàn hảo (hoặc không bao giờ sử dụng nó). Vì lý do này, GOTO không chỉ là một công cụ được sử dụng cẩn thận. Nó bị phá vỡ một cách có hệ thống và sự tồn tại của nó trong một ngôn ngữ được đơn phương coi là có hại.
Jonathan Hartley

Ngay cả khi gototừ khóa bị loại bỏ khỏi C #, khái niệm "nhảy vào đây" vẫn tồn tại trong IL. Một vòng lặp vô hạn có thể dễ dàng được xây dựng mà không cần từ khóa goto. Nếu điều này không đảm bảo rằng mã được gọi sẽ trả về, theo ý kiến ​​của bạn, có nghĩa là "không thể suy luận về mã", thì tôi sẽ nói rằng chúng ta không bao giờ có khả năng đó.
Lasse V. Karlsen

Ah, bạn có cái nhìn sâu sắc tìm thấy nguồn gốc của thông tin sai lệch của chúng tôi. Chữ gotoC # không phải là "goto" thực sự theo nghĩa gốc của từ này. Đây là một phiên bản yếu hơn nhiều chỉ cho phép nhảy trong một chức năng. Ý nghĩa trong đó tôi đang sử dụng "goto" cho phép nhảy bất cứ nơi nào trong quá trình. Vì vậy, mặc dù C # có một từ khóa "goto", nhưng nó được cho là chưa bao giờ có, một goto thực sự. Có, một goto thực có sẵn ở cấp IL, giống như khi có bất kỳ ngôn ngữ nào được biên dịch để lắp ráp. Nhưng lập trình viên được bảo vệ khỏi điều đó, không thể sử dụng nó trong các trường hợp bình thường, vì vậy nó không được tính.
Jonathan Hartley

Ngẫu nhiên, ý kiến ​​tôi mô tả ở đây ban đầu không phải là của tôi, mà là cốt lõi của bài báo gốc của Dijkstra từ năm 1967 năm chính xác bởi vì nó rất cách mạng, sâu sắc và được chấp nhận rộng rãi.
Jonathan Hartley

17

Nó chưa bao giờ, miễn là bạn có thể nghĩ cho chính mình.


17

Kể từ khi tôi bắt đầu làm một vài thứ trong kernel linux, gotos không làm phiền tôi nhiều như trước đây. Lúc đầu, tôi rất kinh hoàng khi thấy họ (những kẻ hạt nhân) đã thêm gotos vào mã của tôi. Tôi đã quen với việc sử dụng gotos, trong một số bối cảnh hạn chế và đôi khi sẽ tự mình sử dụng chúng. Thông thường, đó là một goto nhảy đến cuối chức năng để thực hiện một số loại dọn dẹp và bảo lãnh, thay vì sao chép cùng một dọn dẹp và cứu trợ ở một số nơi trong chức năng. Và thông thường, nó không phải là một cái gì đó đủ lớn để chuyển sang một chức năng khác - ví dụ: giải phóng một số biến malloc'ed cục bộ (k) là một trường hợp điển hình.

Tôi đã viết mã chỉ sử dụng setjmp / longjmp một lần. Đó là trong một chương trình tuần tự trống MIDI. Phát lại đã xảy ra trong một quy trình riêng biệt với tất cả các tương tác của người dùng và quá trình phát lại đã sử dụng bộ nhớ dùng chung với quy trình UI để có được thông tin hạn chế cần thiết để thực hiện phát lại. Khi người dùng muốn dừng phát lại, quá trình phát lại chỉ thực hiện một "long trở lại từ đầu" để bắt đầu lại, thay vì một vài thao tác phức tạp về bất cứ nơi nào nó xảy ra khi người dùng muốn dừng lại. Nó hoạt động rất tốt, đơn giản và tôi không bao giờ có bất kỳ vấn đề hoặc lỗi nào liên quan đến nó trong trường hợp đó.

setjmp / longjmp có vị trí của họ - nhưng địa điểm đó là nơi bạn sẽ không ghé thăm mà chỉ một lần trong một thời gian rất dài.

Chỉnh sửa: Tôi chỉ nhìn vào mã. Nó thực sự là siglongjmp () mà tôi đã sử dụng, không phải longjmp (không phải là vấn đề lớn, nhưng tôi đã quên rằng siglongjmp thậm chí còn tồn tại.)


15

Nếu bạn đang viết VM bằng C, thì hóa ra là sử dụng gotos tính toán (gcc) như thế này:

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

hoạt động nhanh hơn nhiều so với công tắc thông thường bên trong một vòng lặp.


Chỉ có vấn đề, đó không phải là tiêu chuẩn, phải không?
Calmarius

&&op_incchắc chắn không biên dịch, bởi vì (bên trái) &mong đợi một giá trị, nhưng (bên phải) &mang lại một giá trị.
dòng chảy

7
@FredO: Đó là một nhà điều hành GCC đặc biệt. Tuy nhiên, tôi sẽ từ chối mã này trong tất cả các trường hợp ngoại trừ hoàn cảnh, bởi vì tôi chắc chắn không thể hiểu được wtf đang diễn ra.
Cún con

Mặc dù đây là một ví dụ (tối ưu hóa tối đa cho tốc độ) biện minh cho cả việc sử dụng mã goto và mã hóa, nhưng nó vẫn nên được nhận xét cao để giải thích nhu cầu tối ưu hóa, về cách thức hoạt động và cách thức hoạt động tốt nhất ý kiến ​​có thể được thực hiện. Vì vậy, nó vẫn thất bại, nhưng vì nó khiến các lập trình viên bảo trì trong bóng tối. Nhưng tôi thích ví dụ VM. Cảm ơn.
microsOnOnD

14

Bởi vì gotocó thể được sử dụng để gây nhầm lẫn siêu lập trình

Gotovừa là biểu thức điều khiển cấp caocấp thấp , và kết quả là nó không có mẫu thiết kế phù hợp phù hợp với hầu hết các vấn đề.

Đó là cấp độ thấp theo nghĩa là một goto là một hoạt động nguyên thủy thực hiện một cái gì đó cao hơn như whilehoặc foreachhoặc một cái gì đó.

ở cấp độ cao theo nghĩa là khi được sử dụng theo một số cách nhất định, nó sẽ lấy mã thực thi theo một trình tự rõ ràng, theo kiểu không bị gián đoạn, ngoại trừ các vòng có cấu trúc, và nó thay đổi nó thành các phần logic, có đủ gotos, lấy - túi logic được tự động lắp ráp lại.

Vì vậy, có một prosaic và một mặt xấu xa để goto.

Mặt prosaic là một goto hướng lên có thể thực hiện một vòng lặp hoàn toàn hợp lý và một goto hướng xuống có thể làm một điều hoàn toàn hợp lý breakhoặc return. Tất nhiên, một thực tế while, breakhoặc returnsẽ dễ đọc hơn rất nhiều, vì người nghèo sẽ không phải mô phỏng hiệu ứng của gotođể có được bức tranh lớn. Vì vậy, một ý tưởng tồi nói chung.

Mặt xấu liên quan đến một thói quen không sử dụng goto trong một thời gian, phá vỡ hoặc quay trở lại, nhưng sử dụng nó cho cái được gọi là logic spaghetti . Trong trường hợp này, nhà phát triển goto-happy đang xây dựng các đoạn mã ra khỏi mê cung của goto và cách duy nhất để hiểu nó là mô phỏng nó một cách tổng thể, một nhiệm vụ cực kỳ mệt mỏi khi có nhiều goto. Ý tôi là, hãy tưởng tượng những rắc rối của việc đánh giá mã trong đó elsekhông chính xác là nghịch đảo của if, trong đó các lồng nhau ifcó thể cho phép trong một số thứ bị từ chối bên ngoài if, v.v., v.v.

Cuối cùng, để thực sự bao quát chủ đề, chúng ta nên lưu ý rằng về cơ bản tất cả các ngôn ngữ ban đầu ngoại trừ Algol ban đầu chỉ đưa ra các tuyên bố duy nhất tuân theo các phiên bản của chúng if-then-else. Vì vậy, cách duy nhất để thực hiện một khối có điều kiện là gotoxung quanh nó bằng cách sử dụng một điều kiện nghịch đảo. Mất trí, tôi biết, nhưng tôi đã đọc một số thông số kỹ thuật cũ. Hãy nhớ rằng các máy tính đầu tiên được lập trình theo mã máy nhị phân nên tôi cho rằng bất kỳ loại HLL nào cũng là phao cứu sinh; Tôi đoán họ không quá cầu kỳ về chính xác những tính năng HLL họ có.

Đã nói tất cả những gì tôi từng gắn bó gotovào mỗi chương trình tôi đã viết "chỉ để làm phiền những người theo chủ nghĩa thuần túy" .


4
+1 cho làm phiền những người theo chủ nghĩa thuần túy! :-)
Lumi

10

Từ chối việc sử dụng câu lệnh GOTO cho các lập trình viên cũng giống như nói với một người thợ mộc không sử dụng búa vì nó có thể làm hỏng bức tường trong khi anh ta đang đóng đinh. Một lập trình viên thực sự biết cách và thời điểm sử dụng GOTO. Tôi đã theo dõi một số cái gọi là 'Chương trình có cấu trúc' Tôi đã thấy mã Horrid như vậy chỉ để tránh sử dụng GOTO, để tôi có thể bắn lập trình viên. Ok, để bảo vệ phía bên kia, tôi cũng đã thấy một số mã spaghetti thực sự, những lập trình viên đó cũng nên bị bắn.

Đây chỉ là một ví dụ nhỏ về mã tôi đã tìm thấy.

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

-----------------------HOẶC LÀ----------------------

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

     INPUT YORN

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

3
DO CRT 'Điều này có đúng không? (Y / N): ': INPUT YORN UNTIL YORN =' Y 'HOẶC YORN =' N '; v.v.
joel.neely

5
Thật vậy, nhưng quan trọng hơn, một lập trình viên thực sự biết khi nào không nên sử dụng một goto- và biết tại sao . Tránh xây dựng ngôn ngữ cấm kỵ vì $ lập trình_guru đã nói như vậy, đó là định nghĩa của lập trình sùng bái hàng hóa.
Piskvor rời khỏi tòa nhà

1
Đó là một sự tương tự tốt. Để lái một chiếc đinh mà không làm hỏng lớp nền, bạn không loại bỏ búa. Thay vào đó, bạn sử dụng một công cụ đơn giản được gọi là cú đấm móng tay. Đây là một chốt kim loại với một đầu thon có một vết lõm rỗng ở đầu của nó, để ghép chắc chắn với đầu đinh (những dụng cụ này có các kích cỡ khác nhau cho các móng khác nhau). Mặt khác, đầu cùn của cú đấm móng tay được đập bằng búa.
Kaz


7

Bài báo gốc nên được coi là "GOTO vô điều kiện được coi là có hại". Nó đặc biệt ủng hộ một hình thức lập trình dựa trên các cấu trúc có điều kiện ( if) và lặp ( while), thay vì phổ biến thử nghiệm và nhảy đối với mã sớm. gotovẫn hữu ích trong một số ngôn ngữ hoặc hoàn cảnh, trong đó không có cấu trúc điều khiển thích hợp tồn tại.


6

Về nơi duy nhất tôi đồng ý Goto có thể được sử dụng là khi bạn cần xử lý lỗi và mỗi điểm cụ thể xảy ra lỗi yêu cầu xử lý đặc biệt.

Ví dụ: nếu bạn đang lấy tài nguyên và sử dụng semaphores hoặc mutexes, bạn phải lấy chúng theo thứ tự và bạn phải luôn giải phóng chúng theo cách ngược lại.

Một số mã yêu cầu một mô hình rất kỳ lạ khi lấy các tài nguyên này và bạn không thể chỉ viết một cấu trúc điều khiển dễ hiểu và được duy trì để xử lý chính xác cả việc lấy và giải phóng các tài nguyên này để tránh bế tắc.

Luôn luôn có thể làm điều đó ngay mà không cần goto, nhưng trong trường hợp này và một vài người khác, Goto thực sự là giải pháp tốt hơn chủ yếu cho khả năng đọc và bảo trì.

-Adam


5

Một trình sử dụng GOTO hiện đại là bởi trình biên dịch C # để tạo các máy trạng thái cho các điều tra viên được xác định bởi lợi nhuận.

GOTO là thứ nên được sử dụng bởi trình biên dịch chứ không phải lập trình viên.


5
Chính xác thì bạn nghĩ ai tạo ra trình biên dịch?
tloach

10
Trình biên dịch, tất nhiên!
Seiti

3
Tôi nghĩ rằng anh ta có nghĩa là "GOTO là thứ chỉ nên được sử dụng bởi mã được phát ra bởi trình biên dịch".
Simon Buchan

Đây là một trường hợp chống lại goto. Trường hợp chúng ta có thể sử dụng gototrong một máy trạng thái được mã hóa bằng tay để thực hiện một điều tra viên, chúng ta chỉ có thể sử dụng yieldthay thế.
Jon Hanna

bật nhiều chuỗi (để ngăn biên dịch sang if-other) với trường hợp biên dịch mặc định để chuyển sang câu lệnh goto.
M.kazem Akhÿ

5

Cho đến khi C và C ++ (trong số các thủ phạm khác) đã dán nhãn phá vỡ và tiếp tục, goto sẽ tiếp tục có vai trò.


Vì vậy, dán nhãn phá vỡ hoặc tiếp tục sẽ khác với goto như thế nào?
Matthew Whited 17/03/2016

3
Họ không cho phép nhảy hoàn toàn tùy ý trong dòng kiểm soát.
DrPizza

5

Nếu bản thân GOTO là ác, trình biên dịch sẽ là ác, vì chúng tạo ra JMP. Nếu nhảy vào một khối mã, đặc biệt là theo một con trỏ, vốn đã xấu, thì lệnh RETurn sẽ là xấu. Thay vào đó, cái ác nằm trong khả năng lạm dụng.

Đôi khi tôi đã phải viết các ứng dụng phải theo dõi một số đối tượng trong đó mỗi đối tượng phải tuân theo một chuỗi các trạng thái phức tạp để đáp ứng với các sự kiện, nhưng toàn bộ điều này chắc chắn là một luồng. Một chuỗi các trạng thái điển hình, nếu được biểu thị bằng mã giả sẽ là:

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

Tôi chắc chắn điều này không mới, nhưng cách tôi xử lý nó trong C (++) là xác định một số macro:

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

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

Sau đó (giả sử trạng thái ban đầu là 0) máy trạng thái có cấu trúc ở trên biến thành mã có cấu trúc:

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

Với một biến thể về điều này, có thể có GỌI và TRẢ LẠI, vì vậy một số máy trạng thái có thể hoạt động như chương trình con của các máy trạng thái khác.

Có bất thường không? Đúng. Có phải học một số phần của người bảo trì? Đúng. Việc học đó có được đền đáp không? Tôi nghĩ vậy. Nó có thể được thực hiện mà không có GOTO nhảy vào khối không? Không.


1
Tránh một tính năng ngôn ngữ vì sợ hãi là một dấu hiệu của tổn thương não. Điều này cũng nhanh hơn địa ngục.
Vector Gorgoth

4

Tôi tránh nó vì đồng nghiệp / người quản lý chắc chắn sẽ đặt câu hỏi về việc sử dụng nó trong đánh giá mã hoặc khi họ vấp phải nó. Trong khi tôi nghĩ rằng nó đã sử dụng (ví dụ trường hợp xử lý lỗi) - bạn sẽ chạy afoul của một số nhà phát triển khác, những người sẽ gặp một số loại vấn đề với nó.

Nó không đáng.


Điều thú vị về Thử ... Bắt các khối trong C # là chúng đảm nhiệm việc dọn sạch ngăn xếp và các tài nguyên được phân bổ khác (được gọi là giải nén ngăn xếp) khi ngoại lệ tạo ra một trình xử lý ngoại lệ. Điều này làm cho Thử ... Bắt tốt hơn nhiều so với Goto, vì vậy nếu bạn có Thử ... Bắt, hãy sử dụng nó.
Scott Whitlock

Tôi có một giải pháp tốt hơn: bọc đoạn mã bạn muốn bật ra trong 'do {...} while (0);' vòng. Bằng cách đó, bạn có thể nhảy ra giống như một goto mà không cần vòng lặp thử / bắt (tôi không biết về C #, nhưng trong C ++, thử là chi phí thấp và bắt là chi phí cao, vì vậy có vẻ như quá mức để ném một ngoại lệ trong đó một bước nhảy đơn giản sẽ đủ).
Jim Dovey

10
Jim, vấn đề với điều đó là không có gì khác hơn là cách đi vòng quanh ngu ngốc để có được một chiếc goto.
Mã hóa với phong cách

4

Tôi thực sự thấy mình bị buộc phải sử dụng một goto, bởi vì tôi thực sự không thể nghĩ ra cách nào tốt hơn (nhanh hơn) để viết mã này:

Tôi đã có một đối tượng phức tạp và tôi cần thực hiện một số thao tác trên nó. Nếu đối tượng ở một trạng thái, thì tôi có thể thực hiện một phiên bản nhanh của thao tác, nếu không tôi phải thực hiện một phiên bản hoạt động chậm. Vấn đề là trong một số trường hợp, ở giữa hoạt động chậm, có thể nhận ra rằng điều này có thể được thực hiện với thao tác nhanh.

SomeObject someObject;    

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

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

// object is simple, no calculations needed.

Đây là một phần quan trọng về tốc độ của mã UI thời gian thực, vì vậy tôi thành thật nghĩ rằng một GOTO đã được chứng minh ở đây.

Hugo


Cách không phải GOTO sẽ là sử dụng hàm fast_calculations, phát sinh một số chi phí. Có lẽ không đáng chú ý trong hầu hết các trường hợp, nhưng như bạn đã nói đây là tốc độ quan trọng.
Kyle Cronin

1
Vâng, điều đó hầu như không đáng ngạc nhiên. Tất cả các mã có hướng dẫn hiệu suất hoàn toàn điên rồ sẽ luôn phá vỡ khá nhiều tất cả các thực tiễn tốt nhất. Thực hành tốt nhất là khả năng tiếp cận và khả năng bảo trì, không phải để vắt kiệt thêm 10 mili giây hoặc tiết kiệm thêm 5 byte ram.
Jonathon

1
@JonathonWisnoski, việc sử dụng goto hợp pháp cũng giúp loại bỏ số lượng mã spagetti điên rồ kết hợp với tổ của các biến chuột để theo dõi nơi chúng ta sẽ đến.
vonbrand

4

Hầu như tất cả các tình huống mà một goto có thể được sử dụng, bạn có thể làm tương tự bằng cách sử dụng các cấu trúc khác. Goto được sử dụng bởi trình biên dịch nào.

Cá nhân tôi không bao giờ sử dụng nó một cách rõ ràng, không bao giờ cần.


4

Một điều tôi chưa từng thấy trong bất kỳ câu trả lời nào ở đây là giải pháp 'goto' thường hiệu quả hơn một trong những giải pháp lập trình có cấu trúc thường được đề cập.

Hãy xem xét trường hợp nhiều vòng lặp lồng nhau, trong đó sử dụng 'goto' thay vì một loạt các if(breakVariable)phần rõ ràng là hiệu quả hơn. Giải pháp "Đặt các vòng lặp của bạn vào một hàm và sử dụng return" thường hoàn toàn không hợp lý. Trong trường hợp có khả năng các vòng lặp đang sử dụng các biến cục bộ, bây giờ bạn phải chuyển tất cả chúng thông qua các tham số chức năng, có khả năng xử lý vô số các cơn đau đầu phát sinh từ đó.

Bây giờ hãy xem xét trường hợp dọn dẹp mà tôi đã sử dụng bản thân khá thường xuyên và phổ biến đến mức có lẽ phải chịu trách nhiệm cho cấu trúc {{Catch {} thử không có sẵn bằng nhiều ngôn ngữ. Số lượng kiểm tra và các biến bổ sung được yêu cầu để thực hiện cùng một điều tồi tệ hơn nhiều so với một hoặc hai hướng dẫn để thực hiện bước nhảy, và một lần nữa, giải pháp chức năng bổ sung hoàn toàn không phải là một giải pháp. Bạn không thể nói với tôi rằng nó dễ quản lý hơn hoặc dễ đọc hơn.

Bây giờ không gian mã, sử dụng ngăn xếp và thời gian thực hiện có thể không đủ quan trọng trong nhiều tình huống đối với nhiều lập trình viên, nhưng khi bạn ở trong một môi trường nhúng chỉ có 2KB không gian mã để làm việc, 50 byte hướng dẫn bổ sung để tránh một hướng dẫn rõ ràng 'goto' chỉ đáng cười, và đây không phải là tình huống hiếm gặp như nhiều lập trình viên cấp cao tin tưởng.

Tuyên bố 'goto là có hại' rất hữu ích trong việc chuyển sang lập trình có cấu trúc, ngay cả khi nó luôn luôn là một khái quát quá mức. Tại thời điểm này, tất cả chúng ta đã nghe thấy nó đủ để cảnh giác khi sử dụng nó (như chúng ta nên làm). Khi đó rõ ràng là công cụ phù hợp cho công việc, chúng ta không cần phải sợ nó.


3

Bạn có thể sử dụng nó để phá vỡ từ một vòng lặp được lồng sâu, nhưng hầu hết thời gian mã của bạn có thể được tái cấu trúc để sạch hơn mà không cần các vòng lặp lồng sâu.

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.