Những loại lỗi nào mà các câu lệnh goto đá dẫn đến? Có bất kỳ ví dụ có ý nghĩa lịch sử?


103

Tôi hiểu rằng tiết kiệm cho việc thoát ra khỏi các vòng lặp được lồng trong các vòng lặp; những gototuyên bố được trốn và chửi rủa như một lỗi phong cách dễ lập trình, để không bao giờ được sử dụng.

XKCD Văn bản thay thế: "Neal Stephenson nghĩ thật dễ thương khi đặt tên cho nhãn của mình là 'dengo'"
Xem truyện tranh gốc tại: http://xkcd.com/292/

Bởi vì tôi đã học được điều này sớm; Tôi thực sự không có bất kỳ cái nhìn sâu sắc hoặc kinh nghiệm nào về loại lỗi gotothực sự dẫn đến. Vì vậy, những gì chúng ta đang nói về ở đây:

  • Sự bất ổn?
  • Mã không thể nhầm lẫn hoặc không thể đọc được?
  • Lỗ hổng bảo mật?
  • Cái gì khác hoàn toàn?

Những loại lỗi nào mà các câu lệnh "goto" thực sự dẫn đến? Có bất kỳ ví dụ có ý nghĩa lịch sử?


33
Bạn đã đọc bài viết ngắn ban đầu của Dijkstra về chủ đề này chưa? (Tức là Yes / No câu hỏi, và bất kỳ câu trả lời khác hơn là ngay lập tức rõ ràng "có" là "không".)
John R. Strohm

2
Tôi đoán một lối thoát báo động là khi một cái gì đó thất bại thảm khốc xảy ra. nơi bạn sẽ không bao giờ trở về. Giống như mất điện, biến mất các thiết bị hoặc tập tin. Một số loại "lỗi goto" ... Nhưng tôi nghĩ rằng hầu hết các lỗi "gần như catastropic" có thể được xử lý bằng các cấu trúc "thử - bắt" hiện nay.
Lenne

13
Không ai đề cập đến GOTO được tính toán nào. Trở lại khi trái đất còn trẻ BASIC có số dòng. Bạn có thể viết như sau: 100 N = A * 100; GOTO N. Hãy thử gỡ lỗi đó :)
hủy bỏ

7
@ JohnR.Strohm Tôi đã đọc bài viết của Dijkstra. Nó được viết vào năm 1968 và cho thấy rằng nhu cầu về gotos có thể giảm trong các ngôn ngữ bao gồm các tính năng nâng cao như câu lệnh và chức năng chuyển đổi. Nó được viết để đáp ứng với các ngôn ngữ trong đó goto là phương pháp chính của điều khiển luồng và có thể được sử dụng để chuyển đến bất kỳ điểm nào trong chương trình. Trong khi các ngôn ngữ được phát triển sau khi bài viết này được viết, ví dụ C, goto chỉ có thể nhảy đến các vị trí trong cùng một khung ngăn xếp và thường chỉ được sử dụng khi các tùy chọn khác không hoạt động tốt. (tiếp tục ...)
Ray

10
(... Tiếp tục) Điểm của anh ấy có giá trị vào thời điểm đó, nhưng không còn phù hợp nữa, bất kể goto có phải là một ý tưởng tốt hay không. Nếu chúng ta muốn tranh luận rằng bằng cách này hay cách khác, các lập luận phải được thực hiện liên quan đến các ngôn ngữ hiện đại. Nhân tiện, bạn đã đọc "Lập trình có cấu trúc với các câu lệnh goto" của Knuth chưa?
Ray

Câu trả lời:


2

Đây là một cách để xem xét nó tôi chưa thấy trong các câu trả lời khác.

Đó là về phạm vi. Một trong những trụ cột chính của thực hành lập trình tốt là giữ cho phạm vi của bạn chặt chẽ. Bạn cần phạm vi chặt chẽ vì bạn thiếu khả năng tinh thần để giám sát và hiểu nhiều hơn chỉ là một vài bước hợp lý. Vì vậy, bạn tạo các khối nhỏ (mỗi khối trở thành "một thứ") và sau đó xây dựng với các khối đó để tạo các khối lớn hơn trở thành một thứ, v.v. Điều này giữ cho mọi thứ có thể quản lý và dễ hiểu.

Một goto làm tăng hiệu quả phạm vi logic của bạn cho toàn bộ chương trình. Điều này chắc chắn sẽ đánh bại bộ não của bạn cho bất kỳ nhưng các chương trình nhỏ nhất chỉ kéo dài một vài dòng.

Vì vậy, bạn sẽ không thể biết nếu bạn đã phạm sai lầm nữa bởi vì có quá nhiều thứ để kiểm tra và kiểm tra cái đầu nhỏ tội nghiệp của bạn. Đây là vấn đề thực sự, lỗi chỉ là một kết quả có khả năng.


Goto không địa phương?
Ded repeatator

thebrain.mcgill.ca/flash/capsules/experience_jaune03.html << Tâm trí con người có thể theo dõi trung bình 7 điều cùng một lúc. Cơ sở lý luận của bạn đóng vai trò trong giới hạn này, vì phạm vi mở rộng sẽ bổ sung thêm nhiều thứ cần được theo dõi. Do đó, các loại lỗi sẽ được giới thiệu có khả năng là một thiếu sót và hay quên. Bạn cũng đưa ra một chiến lược giảm thiểu và thực hành tốt nói chung, "giữ cho phạm vi của bạn chặt chẽ". Như vậy, tôi chấp nhận câu trả lời này. Làm tốt lắm Martin.
Akiva

1
Thật tuyệt phải không?! Trong số hơn 200 phiếu bầu, chiến thắng chỉ với 1!
Martin Maat

68

Bản thân nó không phải gotolà xấu. (Xét cho cùng, mọi lệnh nhảy trong máy tính đều là một goto.) Vấn đề là có một kiểu lập trình của con người trước ngày lập trình có cấu trúc, cái có thể gọi là lập trình "biểu đồ dòng chảy".

Trong lập trình biểu đồ dòng chảy (những người thuộc thế hệ của tôi đã học và được sử dụng cho chương trình mặt trăng Apollo), bạn tạo một sơ đồ với các khối để thực thi câu lệnh và kim cương cho các quyết định và bạn có thể kết nối chúng với các đường đi khắp mọi nơi. (Cái gọi là "mã spaghetti".)

Vấn đề với mã spaghetti là bạn, lập trình viên, có thể "biết" nó đúng, nhưng làm thế nào bạn có thể chứng minh điều đó, với chính mình hoặc bất kỳ ai khác? Trong thực tế, nó thực sự có thể có một hành vi sai trái tiềm năng, và kiến ​​thức của bạn rằng nó luôn luôn đúng có thể sai.

Cùng với lập trìnhcấu trúc với các khối bắt đầu, for, while, if-other, v.v. Chúng có ưu điểm là bạn vẫn có thể làm bất cứ điều gì trong chúng, nhưng nếu bạn cẩn thận, bạn có thể chắc chắn mã của mình là chính xác.

Tất nhiên, mọi người vẫn có thể viết mã spaghetti ngay cả khi không có goto. Phương pháp phổ biến là viết a while(...) switch( iState ){..., trong đó các trường hợp khác nhau đặt các iStategiá trị khác nhau. Trong thực tế, trong C hoặc C ++, người ta có thể viết một macro để làm điều đó và đặt tên cho nó GOTO, vì vậy nói rằng bạn không sử dụng gotolà một sự khác biệt không có sự khác biệt.

Như một ví dụ về cách chứng minh mã có thể loại trừ không bị hạn chế goto, một thời gian dài trước đây tôi đã vấp phải một cấu trúc điều khiển hữu ích cho các giao diện người dùng thay đổi linh hoạt. Tôi gọi nó là thực hiện khác biệt . Nó hoàn toàn Turing phổ quát, nhưng bằng chứng tính đúng đắn của nó phụ thuộc vào lập trình có cấu trúc tinh khiết - không goto, return, continue, break, hoặc trường hợp ngoại lệ.

nhập mô tả hình ảnh ở đây


49
Tính chính xác không thể được chứng minh ngoại trừ trong các chương trình tầm thường nhất, ngay cả khi lập trình có cấu trúc được sử dụng để viết chúng. Bạn chỉ có thể tăng mức độ tự tin của bạn; lập trình có cấu trúc thực hiện được điều đó.
Robert Harvey

23
@MikeDunlavey: Liên quan: "Coi chừng các lỗi trong đoạn mã trên; tôi chỉ chứng minh nó đúng, không thử nó." staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf
Vịt Mooing

26
@RobertHarvey Không hoàn toàn đúng khi các bằng chứng chính xác chỉ thực tế cho các chương trình tầm thường. Tuy nhiên, nhiều công cụ chuyên dụng hơn lập trình có cấu trúc là cần thiết.
Mike Haskel

18
@MSalters: Đây là một dự án nghiên cứu. Mục tiêu của một dự án nghiên cứu như vậy là cho thấy rằng họ có thể viết một trình biên dịch đã được xác minh nếu họ có tài nguyên. Nếu bạn nhìn vào công việc hiện tại của họ, bạn sẽ thấy rằng họ chỉ đơn giản là không quan tâm đến việc hỗ trợ các phiên bản C sau này, thay vào đó họ quan tâm đến việc mở rộng các thuộc tính chính xác mà họ có thể chứng minh. Và vì điều đó, đơn giản là hoàn toàn không liên quan cho dù họ hỗ trợ C90, C99, C11, Pascal, Fortran, Algol hay Plankalkül.
Jörg W Mittag

8
@martineau: Tôi không hài lòng với những "cụm từ boson" đó, nơi các lập trình viên rơi vào tình trạng đồng ý rằng nó xấu hoặc tốt, bởi vì họ sẽ ngồi vào nếu họ không làm thế. Phải có những lý do cơ bản hơn cho mọi thứ.
Mike Dunlavey

63

Tại sao goto nguy hiểm?

  • gotokhông gây ra sự mất ổn định của chính nó. Mặc dù có khoảng 100.000 gotogiây, nhân Linux vẫn là một mô hình ổn định.
  • gototự nó không gây ra lỗ hổng bảo mật. Tuy nhiên, trong một số ngôn ngữ, việc trộn nó với try/ catchkhối quản lý ngoại lệ có thể dẫn đến các lỗ hổng như được giải thích trong khuyến nghị CERT này . Trình biên dịch C ++ chính thống gắn cờ và ngăn chặn các lỗi như vậy, nhưng thật không may, trình biên dịch cũ hơn hoặc kỳ lạ hơn thì không.
  • gotogây ra mã không thể đọc và không thể nhầm lẫn. Đây cũng được gọi là mã spaghetti , bởi vì, giống như trong một đĩa mì spaghetti, rất khó để theo dòng kiểm soát khi có quá nhiều gotos.

Ngay cả khi bạn quản lý để tránh mã spaghetti và nếu bạn chỉ sử dụng một vài gotos, chúng vẫn tạo điều kiện cho các lỗi như và rò rỉ tài nguyên:

  • Mã sử ​​dụng lập trình cấu trúc, với các khối và vòng lặp lồng nhau rõ ràng, dễ theo dõi; dòng chảy kiểm soát của nó là rất dễ đoán. Do đó, dễ dàng hơn để đảm bảo rằng bất biến được tôn trọng.
  • Với một gototuyên bố, bạn phá vỡ dòng chảy đơn giản đó, và phá vỡ sự mong đợi. Ví dụ, bạn có thể không nhận thấy rằng bạn vẫn còn tài nguyên miễn phí.
  • Nhiều gotonơi khác nhau có thể đưa bạn đến một mục tiêu goto duy nhất. Vì vậy, không rõ ràng để biết chắc chắn trạng thái bạn đang ở khi đến nơi này. Do đó, rủi ro của việc đưa ra các giả định sai / vô căn cứ là khá lớn.

Thông tin bổ sung và báo giá:

C cung cấp các gototuyên bố và nhãn có thể sử dụng vô hạn để phân nhánh. Chính thức gotolà không bao giờ cần thiết, và trong thực tế, hầu như luôn luôn dễ dàng để viết mã mà không cần nó. (...)
Tuy nhiên, chúng tôi sẽ đề xuất một vài tình huống trong đó goto có thể tìm thấy một địa điểm. Việc sử dụng phổ biến nhất là từ bỏ xử lý trong một số cấu trúc lồng nhau sâu, chẳng hạn như thoát ra khỏi hai vòng cùng một lúc. (...)
Mặc dù chúng tôi không giáo điều về vấn đề này, nhưng có vẻ như các tuyên bố goto nên được sử dụng một cách tiết kiệm, nếu có .

  • James Gosling & Henry McGilton đã viết trong sách trắng môi trường ngôn ngữ Java năm 1995 của họ :

    Không có thêm các câu lệnh Goto
    Java không có câu lệnh goto. Các nghiên cứu đã minh họa rằng goto được (mis) sử dụng thường xuyên hơn không chỉ đơn giản là vì vì nó có. Việc loại bỏ goto dẫn đến việc đơn giản hóa ngôn ngữ (...) Các nghiên cứu về khoảng 100.000 dòng mã C đã xác định rằng khoảng 90 phần trăm các câu lệnh goto được sử dụng hoàn toàn để đạt được hiệu quả của việc thoát ra khỏi các vòng lặp lồng nhau. Như đã đề cập ở trên, phá vỡ đa cấp và tiếp tục loại bỏ hầu hết nhu cầu cho các câu lệnh goto.

  • Bjarne Stroustrup định nghĩa goto trong bảng thuật ngữ của mình theo các thuật ngữ mời:

    goto - goto khét tiếng. Chủ yếu hữu ích trong máy tạo mã C ++.

Khi nào goto có thể được sử dụng?

Giống như K & R Tôi không giáo điều về gotos. Tôi thừa nhận rằng có những tình huống mà goto có thể làm dịu cuộc sống của một người.

Thông thường, trong C, goto cho phép thoát vòng lặp đa cấp hoặc xử lý lỗi yêu cầu đạt đến điểm thoát thích hợp giải phóng / giải phóng tất cả các tài nguyên được phân bổ cho đến nay (phân bổ iemultipl trong chuỗi có nghĩa là nhiều nhãn). Bài viết này định lượng việc sử dụng khác nhau của goto trong nhân Linux.

Cá nhân tôi thích tránh nó và trong 10 năm C, tôi đã sử dụng tối đa 10 gotos. Tôi thích sử dụng ifs lồng nhau , mà tôi nghĩ là dễ đọc hơn. Khi điều này sẽ dẫn đến việc lồng quá sâu, tôi sẽ chọn phân tách chức năng của mình thành các phần nhỏ hơn hoặc sử dụng chỉ báo boolean trong tầng. Trình biên dịch tối ưu hóa ngày nay đủ thông minh để tạo ra gần như cùng một mã so với cùng mã goto.

Việc sử dụng goto phụ thuộc nhiều vào ngôn ngữ:

  • Trong C ++, việc sử dụng RAII đúng cách sẽ khiến trình biên dịch tự động phá hủy các đối tượng nằm ngoài phạm vi, do đó tài nguyên / khóa sẽ được làm sạch bằng mọi cách và không cần thêm goto nữa.

  • Trong Java không cần cho goto (xem trích dẫn tác giả của Java trên và điều này xuất sắc trả lời Stack Overflow ): thu gom rác làm sạch đống lộn xộn, break, continue, và try/ catchxử lý ngoại lệ bao gồm tất cả các trường hợp gotocó thể là hữu ích, nhưng trong một an toàn hơn và tốt hơn cách thức. Sự phổ biến của Java chứng minh rằng tuyên bố goto có thể tránh được bằng ngôn ngữ hiện đại.

Phóng to lỗ hổng SSL goto nổi tiếng

Tuyên bố miễn trừ trách nhiệm quan trọng: theo quan điểm thảo luận gay gắt trong các bình luận, tôi muốn làm rõ rằng tôi không giả vờ rằng tuyên bố goto là nguyên nhân duy nhất của lỗi này. Tôi không giả vờ rằng nếu không có goto thì sẽ không có lỗi. Tôi chỉ muốn chỉ ra rằng một goto có thể liên quan đến một lỗi nghiêm trọng.

Tôi không biết có bao nhiêu lỗi nghiêm trọng liên quan đến gotolịch sử lập trình: chi tiết thường không được truyền đạt. Tuy nhiên, có một lỗi Apple SSL nổi tiếng làm suy yếu tính bảo mật của iOS. Tuyên bố dẫn đến lỗi này là một gototuyên bố sai .

Một số ý kiến ​​cho rằng nguyên nhân sâu xa của lỗi không phải là bản thân goto, mà là bản sao / dán sai, thụt sai, thiếu dấu ngoặc nhọn quanh khối điều kiện hoặc có lẽ là thói quen làm việc của nhà phát triển. Tôi không thể xác nhận bất kỳ trong số họ: tất cả những lập luận này là những giả thuyết và giải thích có thể xảy ra. Không ai thực sự biết. ( trong khi đó, giả thuyết về sự hợp nhất đã sai khi ai đó đề xuất trong các bình luận dường như là một ứng cử viên rất tốt trong quan điểm về một số mâu thuẫn thụt đầu dòng khác trong cùng chức năng ).

Thực tế khách quan duy nhất là một nhân đôi gotodẫn đến thoát khỏi chức năng sớm. Nhìn vào mã, chỉ một tuyên bố duy nhất có thể gây ra hiệu ứng tương tự sẽ là một sự trở lại.

Lỗi là trong chức năng SSLEncodeSignedServerKeyExchange()trong tập tin này :

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
        goto fail;
    if ((err =...) !=0)
        goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;  // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
    if (...)
        goto fail;

    ... // Do some cryptographic operations here

fail:
    ... // Free resources to process error

Thật vậy, các dấu ngoặc nhọn xung quanh khối có điều kiện có thể đã ngăn được lỗi:
nó sẽ dẫn đến một lỗi cú pháp khi biên dịch (và do đó là một hiệu chỉnh) hoặc một goto vô hại dự phòng. Nhân tiện, GCC 6 sẽ có thể phát hiện ra các lỗi này nhờ cảnh báo tùy chọn để phát hiện vết lõm không nhất quán.

Nhưng ở nơi đầu tiên, tất cả các gotos này có thể tránh được với mã có cấu trúc chặt chẽ hơn. Vì vậy, goto ít nhất là gián tiếp là một nguyên nhân của lỗi này. Có ít nhất hai cách khác nhau có thể tránh được:

Phương pháp 1: nếu khoản hoặc lồng nhau ifs

Thay vì kiểm tra rất nhiều điều kiện cho lỗi liên tục và mỗi lần gửi đến failnhãn trong trường hợp có vấn đề, người ta có thể đã chọn thực hiện các hoạt động mã hóa theo cách ifthức chỉ thực hiện nếu không có điều kiện trước sai:

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
        (err = ...) == 0 ) &&
        (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
        ...
        (err = ...) == 0 ) )
    {
         ... // Do some cryptographic operations here
    }
    ... // Free resources

Cách tiếp cận 2: sử dụng bộ tích lũy lỗi

Cách tiếp cận này dựa trên thực tế là hầu hết tất cả các câu lệnh ở đây đều gọi một số hàm để đặt errmã lỗi và chỉ thực thi phần còn lại của mã nếu errlà 0 (nghĩa là hàm được thực thi không có lỗi). Một sự thay thế an toàn và dễ đọc là:

bool ok = true;
ok =  ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok =  ok && (err = NextFunction(...)) == 0;
...
ok =  ok && (err = ...) == 0;
... // Free resources

Ở đây, không có một goto nào: không có rủi ro để nhảy nhanh đến điểm thoát thất bại. Và trực quan sẽ dễ dàng phát hiện ra một dòng sai hoặc bị lãng quên ok &&.

Cấu trúc này nhỏ gọn hơn. Nó dựa trên thực tế là trong C, phần thứ hai của logic và ( &&) chỉ được đánh giá nếu phần thứ nhất là đúng. Trong thực tế, trình biên dịch được tạo bởi trình biên dịch tối ưu hóa gần như tương đương với mã gốc với gotos: Trình tối ưu hóa phát hiện rất tốt chuỗi điều kiện và tạo mã, ở giá trị trả về không null đầu tiên nhảy đến cuối ( bằng chứng trực tuyến ).

Bạn thậm chí có thể dự tính một kiểm tra tính nhất quán ở cuối chức năng có thể trong giai đoạn thử nghiệm xác định sự không khớp giữa cờ ok và mã lỗi.

assert( (ok==false && err!=0) || (ok==true && err==0) );

Những lỗi ==0vô ý được thay thế bằng !=0lỗi kết nối logic hoặc lỗi sẽ dễ dàng bị phát hiện trong giai đoạn gỡ lỗi.

Như đã nói: Tôi không giả vờ rằng các cấu trúc thay thế sẽ tránh được bất kỳ lỗi nào. Tôi chỉ muốn nói rằng họ có thể đã làm cho lỗi khó xảy ra hơn.


34
Không, lỗi goto thất bại của Apple là do một lập trình viên thông minh quyết định không bao gồm các dấu ngoặc nhọn sau một câu lệnh if. :-) Vì vậy, khi lập trình viên bảo trì tiếp theo (hoặc cùng một sự khác biệt) xuất hiện và thêm một dòng mã khác chỉ nên thực thi khi câu lệnh if được đánh giá là đúng, câu lệnh sẽ chạy mỗi lần. Bam, lỗi "goto fail", đây là một lỗi bảo mật lớn . Nhưng về cơ bản nó không liên quan gì đến thực tế là một tuyên bố goto đã được sử dụng.
Craig

47
Lỗi "goto fail" của Apple được gây ra trực tiếp bởi một dòng mã bị trùng lặp. Hiệu ứng đặc biệt của lỗi đó là do dòng đó gotonằm trong một iftuyên bố không có dấu ngoặc . Nhưng nếu bạn đề xuất rằng việc tránh gotosẽ làm cho mã của bạn miễn nhiễm với tác động của các dòng trùng lặp ngẫu nhiên, thì tôi có tin xấu cho bạn.
Immibis

12
Bạn đang sử dụng hành vi toán tử boolean lối tắt để thực thi một câu lệnh thông qua hiệu ứng phụ và cho rằng điều đó rõ ràng hơn một ifcâu lệnh? Thời gian vui vẻ. :)
Craig

38
Nếu thay vì "goto fail", đã có một tuyên bố "fail = true" thì điều tương tự chính xác sẽ xảy ra.
gnasher729

15
Lỗi đó là do một nhà phát triển cẩu thả không chú ý đến những gì anh ta hoặc cô ta đang làm và không đọc các thay đổi mã của riêng họ. Không hơn không kém. Được rồi, có thể ném vào một số kiểm tra quá mức trong đó quá.
Các cuộc đua nhẹ nhàng trong quỹ đạo

34

Bài báo Dijkstra nổi tiếng được viết vào thời điểm một số ngôn ngữ lập trình thực sự có khả năng tạo ra các chương trình con có nhiều điểm vào và thoát. Nói cách khác, bạn thực sự có thể nhảy vào giữa một hàm và nhảy ra ở bất kỳ vị trí nào trong hàm, mà không thực sự gọi hàm đó hoặc quay trở lại từ nó theo cách thông thường. Điều đó vẫn đúng với ngôn ngữ lắp ráp. Không ai từng tranh luận rằng cách tiếp cận như vậy là vượt trội so với phương pháp viết phần mềm có cấu trúc mà chúng ta hiện đang sử dụng.

Trong hầu hết các ngôn ngữ lập trình hiện đại, các hàm được xác định rất cụ thể với một mục nhập và một điểm thoát. Điểm vào là nơi bạn chỉ định các tham số cho hàm và gọi nó, và điểm thoát là nơi bạn trả về giá trị kết quả và tiếp tục thực hiện tại lệnh theo lệnh gọi hàm ban đầu.

Trong chức năng đó, bạn phải có thể làm bất cứ điều gì bạn muốn, trong phạm vi lý do. Nếu đặt một hoặc hai goto trong chức năng làm cho nó rõ ràng hơn hoặc cải thiện tốc độ của bạn, tại sao không? Toàn bộ quan điểm của một chức năng là sắp xếp lại một chút chức năng được xác định rõ ràng, để bạn không phải suy nghĩ về cách thức hoạt động bên trong nữa. Khi nó được viết, bạn chỉ cần sử dụng nó.

Và có, bạn có thể có nhiều câu lệnh return trong một hàm; vẫn luôn luôn có một vị trí trong một chức năng phù hợp mà bạn trả về (về cơ bản là mặt sau của chức năng). Điều đó hoàn toàn không giống với việc nhảy ra khỏi một chức năng với một chiếc goto trước khi nó có cơ hội trở lại đúng cách.

nhập mô tả hình ảnh ở đây

Vì vậy, nó không thực sự về việc sử dụng goto. Đó là về việc tránh lạm dụng của họ. Mọi người đều đồng ý rằng bạn có thể tạo ra một chương trình phức tạp khủng khiếp bằng cách sử dụng gotos, nhưng bạn cũng có thể làm điều tương tự bằng cách lạm dụng các chức năng (việc lạm dụng gotos cũng dễ dàng hơn nhiều).

Đối với những gì nó có giá trị, kể từ khi tôi tốt nghiệp từ các chương trình BASIC kiểu số dòng đến lập trình có cấu trúc bằng ngôn ngữ Pascal và xoăn, tôi chưa bao giờ phải sử dụng goto. Lần duy nhất tôi muốn sử dụng một lần là thực hiện một lối thoát sớm từ một vòng lặp lồng nhau (trong các ngôn ngữ không hỗ trợ thoát sớm nhiều cấp từ các vòng lặp), nhưng tôi thường có thể tìm một cách khác sạch hơn.


2
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

11

Những loại lỗi nào mà các câu lệnh goto đá dẫn đến? Có bất kỳ ví dụ có ý nghĩa lịch sử?

Tôi đã từng sử dụng các gotocâu lệnh khi viết các chương trình BASIC khi còn nhỏ như một cách đơn giản để có được và trong khi các vòng lặp (Commodore 64 BASIC không có vòng lặp while và tôi còn quá non nớt để học cú pháp và cách sử dụng các vòng lặp thích hợp ). Mã của tôi thường không quan trọng, nhưng bất kỳ lỗi vòng lặp nào cũng có thể được quy cho việc sử dụng goto của tôi ngay lập tức.

Bây giờ tôi sử dụng chủ yếu Python, một ngôn ngữ lập trình cấp cao đã xác định nó không cần goto.

Khi Edsger Dijkstra tuyên bố "Goto coi là có hại" vào năm 1968 ông đã không đưa ra một số ít các ví dụ nơi lỗi liên quan có thể được đổ lỗi cho các goto, đúng hơn, ông tuyên bố rằng gotokhông cần thiết cho các ngôn ngữ cấp độ cao hơn và nó cần phải tránh thiên về những gì chúng tôi xem xét lưu lượng kiểm soát bình thường ngày hôm nay: các vòng lặp và điều kiện. Những từ ngữ của anh ấy:

Việc sử dụng không kiểm soát đi đến có một hậu quả ngay lập tức 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ý.
[...]
Việc đi đến tuyên bố khi nó đứng là quá nguyên thủy; đó là quá nhiều lời mời để làm cho một chương trình lộn xộn.

Có lẽ anh ta có hàng núi ví dụ về các lỗi từ mỗi lần anh ta gỡ lỗi mã gototrong đó. Nhưng bài báo của ông là một tuyên bố vị trí tổng quát được hỗ trợ bởi bằng chứng gotokhông cần thiết cho các ngôn ngữ cấp cao hơn. Lỗi tổng quát là bạn có thể không có khả năng phân tích tĩnh mã theo câu hỏi.

Mã khó phân tích tĩnh hơn với các gotocâu lệnh, đặc biệt nếu bạn quay lại dòng điều khiển của mình (điều mà tôi đã từng làm) hoặc với một số phần mã không liên quan. Mã có thể và được viết theo cách này. Nó được thực hiện để tối ưu hóa cao cho các tài nguyên điện toán rất khan hiếm và do đó kiến ​​trúc của các máy móc.

Một ví dụ về ngày tận thế

Có một chương trình blackjack mà một người bảo trì nhận thấy được tối ưu hóa khá thanh lịch nhưng cũng không thể để anh ta "sửa chữa" do bản chất của mã. Nó được lập trình bằng mã máy phụ thuộc rất nhiều vào gotos - vì vậy tôi nghĩ câu chuyện này khá phù hợp. Đây là ví dụ kinh điển tốt nhất mà tôi có thể nghĩ ra.

Một ví dụ phản tác dụng

Tuy nhiên, nguồn C của CPython (triển khai Python tham chiếu và phổ biến nhất) sử dụng các gotocâu lệnh để có hiệu quả tuyệt vời. Chúng được sử dụng để bỏ qua luồng điều khiển không liên quan bên trong các chức năng để đi đến cuối các chức năng, làm cho các chức năng hiệu quả hơn mà không mất khả năng đọc. Điều này tôn trọng một trong những lý tưởng của lập trình có cấu trúc - để có một điểm thoát duy nhất cho các hàm.

Đối với bản ghi, tôi thấy ví dụ phản biện này khá dễ dàng để phân tích tĩnh.


5
"Câu chuyện về Mel" mà tôi đã gặp là trên một kiến ​​trúc kỳ lạ, trong đó (1) mọi hướng dẫn ngôn ngữ máy đều có một goto sau khi hoạt động và (2) nó rất kém hiệu quả khi đặt một chuỗi các lệnh trong các vị trí bộ nhớ liên tiếp. Và chương trình được viết bằng cách tối ưu hóa bằng tay, không phải là ngôn ngữ được biên dịch.

@MilesRout Tôi nghĩ bạn có thể đúng ở đây, nhưng trang wikipedia về lập trình có cấu trúc nói: "Độ lệch phổ biến nhất, được tìm thấy trong nhiều ngôn ngữ, là việc sử dụng câu lệnh return để thoát sớm khỏi chương trình con. Điều này dẫn đến nhiều điểm thoát , thay vì điểm thoát duy nhất theo yêu cầu của lập trình có cấu trúc. " - bạn đang nói điều này không đúng? Bạn có một nguồn để trích dẫn?
Aaron Hall

@AaronHall Đó không phải là nghĩa gốc.
Miles Rout

11

Khi lều của người da đỏ là nghệ thuật kiến trúc hiện nay, các nhà xây dựng của họ chắc chắn có thể cung cấp cho bạn những lời khuyên thiết thực liên quan đến xây dựng wigwams, làm thế nào để cho khói thoát, và vân vân. May mắn thay, các nhà xây dựng ngày nay có thể quên hầu hết lời khuyên đó.

Khi stagecoach là nghệ thuật vận chuyển hiện tại, các tài xế của họ chắc chắn có thể cho bạn lời khuyên thực tế liên quan đến ngựa của stagecoaches, cách phòng thủ chống lại người đi đường, v.v. May mắn thay, người lái xe ngày nay cũng có thể quên hầu hết lời khuyên đó.

Khi thẻ đục lỗ là nghệ thuật lập trình hiện tại, các học viên của họ cũng có thể cho bạn lời khuyên thực tế về cách tổ chức thẻ, cách đánh số, v.v. Tôi không chắc chắn rằng lời khuyên đó rất phù hợp ngày hôm nay.

Bạn đã đủ tuổi thậm chí để biết cụm từ "báo cáo số" chưa? Bạn không cần phải biết điều đó, nhưng nếu bạn không biết thì bạn không quen thuộc với bối cảnh lịch sử trong đó cảnh báo chống lại gotochủ yếu có liên quan.

Cảnh báo chống lại gotochỉ là không liên quan ngày hôm nay. Với đào tạo cơ bản trong vòng / cho các vòng lặp và các lệnh gọi hàm, bạn thậm chí sẽ không nghĩ sẽ phát hành gotothường xuyên. Khi bạn nghĩ về nó, bạn có thể có một lý do, vì vậy hãy tiếp tục.

Nhưng gototuyên bố không thể bị lạm dụng?

Trả lời: chắc chắn, nó có thể bị lạm dụng, nhưng lạm dụng của nó là một vấn đề khá nhỏ trong công nghệ phần mềm so với các lỗi phổ biến, xa hơn như sử dụng một biến trong đó một hằng số sẽ phục vụ, hoặc như lập trình cắt và dán (nếu không được gọi là bỏ bê để tái cấu trúc). Tôi nghi ngờ rằng bạn đang gặp nhiều nguy hiểm. Trừ khi bạn đang sử dụng longjmphoặc chuyển giao quyền kiểm soát sang mã ở xa, nếu bạn nghĩ sử dụng một gotohoặc bạn chỉ muốn dùng thử cho vui, hãy tiếp tục. Bạn sẽ ổn thôi.

Bạn có thể nhận thấy thiếu những câu chuyện kinh dị gần đây trong đó gotođóng vai phản diện. Hầu hết hoặc tất cả những câu chuyện này dường như là 30 hoặc 40 tuổi. Bạn đứng trên nền tảng vững chắc nếu bạn coi những câu chuyện đó hầu hết là lỗi thời.


1
Tôi thấy rằng tôi đã thu hút ít nhất một downvote. Vâng, đó là dự kiến. Nhiều downvote có thể đến. Tôi sẽ không đưa ra câu trả lời thông thường, mặc dù, khi hàng thập kỷ kinh nghiệm lập trình đã dạy tôi rằng, trong trường hợp này, câu trả lời thông thường xảy ra là sai. Đây là quan điểm của tôi, ở mức nào. Tôi đã đưa ra lý do của tôi. Người đọc có thể quyết định.
THB

1
Bài viết của bạn tốt quá xấu Tôi không thể downvote bình luận.
Pieter B

7

Để thêm một điều vào các câu trả lời xuất sắc khác, với các gotocâu lệnh có thể khó có thể nói chính xác làm thế nào bạn đến bất kỳ vị trí nào trong chương trình. Bạn có thể biết rằng một ngoại lệ đã xảy ra tại một số dòng cụ thể nhưng nếu có gotomã trong đó thì không có cách nào để biết câu lệnh nào được thực thi dẫn đến trạng thái gây ra ngoại lệ này mà không tìm kiếm toàn bộ chương trình. Không có ngăn xếp cuộc gọi và không có dòng chảy trực quan. Có thể có một câu lệnh cách xa 1000 dòng khiến bạn rơi vào trạng thái xấu thực thi một gotodòng dẫn đến một ngoại lệ.


1
Visual Basic 6 và VBA có xử lý lỗi đau đớn, nhưng nếu bạn sử dụng On Error Goto errh, bạn có thể sử dụng Resumeđể thử lại, Resume Nextbỏ qua dòng vi phạm và Erlđể biết đó là dòng nào (nếu bạn sử dụng số dòng).
Cees Timmerman

@CeesTimmerman Tôi đã làm rất ít với Visual Basic, tôi không biết điều đó. Tôi sẽ thêm một nhận xét về nó.
qfwfq

2
@CeesTimmerman: Ngữ nghĩa VB6 của "sơ yếu lý lịch" thật kinh khủng nếu xảy ra lỗi trong chương trình con không có khối lỗi riêng. Resumesẽ thực hiện lại chức năng đó từ đầu và Resume Nextsẽ bỏ qua mọi thứ trong chức năng chưa có.
supercat

2
@supercat Như tôi đã nói, thật đau đớn. Nếu bạn cần biết chính xác dòng, bạn nên đánh số tất cả hoặc tạm thời vô hiệu hóa xử lý lỗi bằng On Error Goto 0và Gỡ lỗi thủ công. Resumecó nghĩa là được sử dụng sau khi kiểm tra Err.Numbervà thực hiện các điều chỉnh cần thiết.
Cees Timmerman

1
Tôi nên lưu ý rằng trong một ngôn ngữ hiện đại, gotochỉ hoạt động với các dòng được gắn nhãn, dường như đã được thay thế bởi các vòng lặp được gắn nhãn .
Cees Timmerman

7

goto khó cho con người lý luận hơn các hình thức kiểm soát dòng chảy khác.

Lập trình mã đúng là khó. Viết chương trình đúng là khó, xác định chương trình đúng là khó, chứng minh chương trình đúng là khó.

Lấy mã để mơ hồ làm những gì bạn muốn thật dễ dàng so với mọi thứ khác về lập trình.

goto giải quyết một số chương trình với việc lấy mã để làm những gì bạn muốn. Nó không giúp làm cho việc kiểm tra tính chính xác dễ dàng hơn, trong khi các giải pháp thay thế thường làm.

Có những phong cách lập trình trong đó goto là giải pháp thích hợp. Thậm chí có những phong cách lập trình trong đó song sinh độc ác của nó, đến từ, là giải pháp thích hợp. Trong cả hai trường hợp này, phải hết sức cẩn thận để đảm bảo bạn đang sử dụng nó theo mô hình đã hiểu và rất nhiều kiểm tra thủ công rằng bạn không làm điều gì khó khăn để đảm bảo tính chính xác với nó.

Ví dụ, có một tính năng của một số ngôn ngữ được gọi là coroutines. Coroutines là chủ đề không có chủ đề; trạng thái thực thi mà không có một chủ đề để chạy nó trên. Bạn có thể yêu cầu họ thực thi, và họ có thể tự chạy một phần và sau đó tự đình chỉ, trao lại quyền kiểm soát dòng chảy.

Các coroutine "nông" trong các ngôn ngữ không có hỗ trợ coroutine (như C ++ pre-C ++ 20 và C) có thể sử dụng hỗn hợp gotos và quản lý trạng thái thủ công. Các coroutines "sâu" có thể được thực hiện bằng cách sử dụng setjmpvà các longjmpchức năng của C.

Có những trường hợp coroutines rất hữu ích đến nỗi viết chúng bằng tay và cẩn thận là đáng giá.

Trong trường hợp của C ++, chúng được tìm thấy đủ hữu ích để chúng mở rộng ngôn ngữ để hỗ trợ chúng. Việc gotoquản lý trạng thái thủ công và thủ công đang được ẩn đằng sau lớp trừu tượng chi phí bằng 0, cho phép các lập trình viên viết chúng mà không gặp khó khăn trong việc chứng minh sự lộn xộn của goto, void**s, xây dựng thủ công / phá hủy trạng thái, v.v.

Các goto được ẩn đằng sau một mức độ trừu tượng cao hơn, như whilehoặc forhoặc ifhoặc switch. Những tóm tắt cấp cao hơn dễ dàng hơn để chứng minh chính xác và kiểm tra.

Nếu ngôn ngữ bị thiếu một số trong số chúng (như một số ngôn ngữ hiện đại bị thiếu coroutines), hoặc bị khập khiễng cùng với một mẫu không phù hợp với vấn đề hoặc sử dụng goto, sẽ trở thành lựa chọn thay thế của bạn.

Bắt một chiếc máy tính mơ hồ làm những gì bạn muốn nó làm trong những trường hợp thông thường thật dễ dàng . Viết mã đáng tin cậy là khó khăn . Goto giúp cái thứ nhất xa hơn cái thứ hai; do đó "goto coi có hại", vì đó là dấu hiệu của mã "làm việc" hời hợt với các lỗi sâu và khó theo dõi. Với nỗ lực đầy đủ, bạn vẫn có thể tạo mã bằng "goto" một cách đáng tin cậy, do đó, việc giữ quy tắc là tuyệt đối là sai; nhưng như một quy luật của nó, nó là một trong những tốt.


1
longjmpchỉ hợp pháp trong C để hủy bỏ ngăn xếp trở lại setjmptrong hàm cha. (Giống như thoát ra khỏi nhiều cấp độ lồng nhau, nhưng đối với các lệnh gọi hàm thay vì các vòng lặp). longjjmpđến một bối cảnh từ một setjmpchức năng được thực hiện trong một chức năng đã trở lại là không hợp pháp (và tôi nghi ngờ sẽ không hoạt động trong thực tế trong hầu hết các trường hợp). Tôi không quen thuộc với các thường trình, nhưng từ mô tả của bạn về một thói quen chung tương tự như một luồng không gian người dùng, tôi nghĩ rằng nó sẽ cần ngăn xếp riêng cũng như bối cảnh đăng ký đã lưu và longjmpchỉ cung cấp cho bạn đăng ký -save, không phải là một ngăn xếp riêng biệt.
Peter Cordes

1
Ồ, tôi nên vừa mới googled: fanf.livejournal.com/105413.html mô tả việc tạo không gian ngăn xếp cho coroutine chạy. (Như tôi nghi ngờ, là cần thiết khi chỉ sử dụng setjmp / longjmp).
Peter Cordes

2
@PeterCordes: Việc triển khai không bắt buộc phải cung cấp bất kỳ phương tiện nào theo đó mã có thể tạo một cặp jmp_buff phù hợp để sử dụng làm coroutine. Một số triển khai thực hiện chỉ định một phương tiện thông qua đó mã có thể tạo một phương thức mặc dù Tiêu chuẩn không yêu cầu chúng cung cấp khả năng như vậy. Tôi sẽ không mô tả mã dựa trên các kỹ thuật như "tiêu chuẩn C", vì trong nhiều trường hợp, jmp_buff sẽ là một cấu trúc mờ không được đảm bảo để hành xử nhất quán giữa các trình biên dịch khác nhau.
supercat

6

Hãy xem mã này, từ http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html thực sự là một phần của mô phỏng Dilemma của Nhà tù lớn. Nếu bạn đã thấy mã FORTRAN hoặc BASIC cũ, bạn sẽ nhận ra nó không phải là bất thường.

C  Not nice rules in second round of tour (cut and pasted 7/15/93)
   FUNCTION K75R(J,M,K,L,R,JA)
C  BY P D HARRINGTON
C  TYPED BY JM 3/20/79
   DIMENSION HIST(4,2),ROW(4),COL(2),ID(2)
   K75R=JA       ! Added 7/32/93 to report own old value
   IF (M .EQ. 2) GOTO 25
   IF (M .GT. 1) GOTO 10
   DO 5 IA = 1,4
     DO 5 IB = 1,2
5  HIST(IA,IB) = 0

   IBURN = 0
   ID(1) = 0
   ID(2) = 0
   IDEF = 0
   ITWIN = 0
   ISTRNG = 0
   ICOOP = 0
   ITRY = 0
   IRDCHK = 0
   IRAND = 0
   IPARTY = 1
   IND = 0
   MY = 0
   INDEF = 5
   IOPP = 0
   PROB = .2
   K75R = 0
   RETURN

10 IF (IRAND .EQ. 1) GOTO 70
   IOPP = IOPP + J
   HIST(IND,J+1) = HIST(IND,J+1) + 1
   IF (M .EQ. 15 .OR. MOD(M,15) .NE. 0 .OR. IRAND .EQ. 2) GOTO 25
   IF (HIST(1,1) / (M - 2) .GE. .8) GOTO 25
   IF (IOPP * 4 .LT. M - 2 .OR. IOPP * 4 .GT. 3 * M - 6) GOTO 25
   DO 12 IA = 1,4
12 ROW(IA) = HIST(IA,1) + HIST(IA,2)

   DO 14 IB = 1,2
     SUM = .0
     DO 13 IA = 1,4
13   SUM = SUM + HIST(IA,IB)
14 COL(IB) = SUM

   SUM = .0
   DO 16 IA = 1,4
     DO 16 IB = 1,2
       EX = ROW(IA) * COL(IB) / (M - 2)
       IF (EX .LE. 1.) GOTO 16
       SUM = SUM + ((HIST(IA,IB) - EX) ** 2) / EX
16 CONTINUE

   IF (SUM .GT. 3) GOTO 25
   IRAND = 1
   K75R = 1
   RETURN

25 IF (ITRY .EQ. 1 .AND. J .EQ. 1) IBURN = 1
   IF (M .LE. 37 .AND. J .EQ. 0) ITWIN = ITWIN + 1
   IF (M .EQ. 38 .AND. J .EQ. 1) ITWIN = ITWIN + 1
   IF (M .GE. 39 .AND. ITWIN .EQ. 37 .AND. J .EQ. 1) ITWIN = 0
   IF (ITWIN .EQ. 37) GOTO 80
   IDEF = IDEF * J + J
   IF (IDEF .GE. 20) GOTO 90
   IPARTY = 3 - IPARTY
   ID(IPARTY) = ID(IPARTY) * J + J
   IF (ID(IPARTY) .GE. INDEF) GOTO 78
   IF (ICOOP .GE. 1) GOTO 80
   IF (M .LT. 37 .OR. IBURN .EQ. 1) GOTO 34
   IF (M .EQ. 37) GOTO 32
   IF (R .GT. PROB) GOTO 34
32 ITRY = 2
   ICOOP = 2
   PROB = PROB + .05
   GOTO 92

34 IF (J .EQ. 0) GOTO 80
   GOTO 90

70 IRDCHK = IRDCHK + J * 4 - 3
   IF (IRDCHK .GE. 11) GOTO 75
   K75R = 1
   RETURN

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

78 ID(IPARTY) = 0
   ISTRNG = ISTRNG + 1
   IF (ISTRNG .EQ. 8) INDEF = 3
80 K75R = 0
   ITRY = ITRY - 1
   ICOOP = ICOOP - 1
   GOTO 95

90 ID(IPARTY) = ID(IPARTY) + 1
92 K75R = 1
95 IND = 2 * MY + J + 1
   MY = K75R
   RETURN
   END

Có rất nhiều vấn đề ở đây vượt xa tuyên bố của GOTO ở đây; Tôi thành thật nghĩ rằng tuyên bố GOTO là một chút của một vật tế thần. Nhưng luồng điều khiển hoàn toàn không rõ ràng ở đây và mã được trộn lẫn với nhau theo những cách làm cho nó rất không rõ ràng những gì đang xảy ra. Ngay cả khi không thêm nhận xét hoặc sử dụng tên biến tốt hơn, việc thay đổi cấu trúc này thành cấu trúc khối mà không có GOTO sẽ giúp việc đọc và theo dõi dễ dàng hơn nhiều.


4
rất đúng :-) Có thể đáng để đề cập: di sản FORTAN và BASIC không có cấu trúc khối, do đó, nếu mệnh đề THEN không thể giữ trên một dòng thì GOTO là lựa chọn duy nhất.
Christophe

Nhãn văn bản thay vì số, hoặc ít nhất là nhận xét về các dòng được đánh số, sẽ giúp ích rất nhiều.
Cees Timmerman

Quay lại những ngày FORTRAN-IV của tôi: Tôi đã hạn chế việc sử dụng GOTO để triển khai các khối IF / ELSE IF / ELSE, các vòng lặp WHILE và BREAK và NEXT trong các vòng lặp. Ai đó đã chỉ cho tôi những tệ nạn của mã spaghetti và lý do tại sao bạn nên cấu trúc để tránh nó ngay cả khi bạn phải thực hiện cấu trúc khối của mình với GOTO. Sau đó tôi bắt đầu sử dụng bộ xử lý trước (RATFOR, IIRC). Goto về bản chất không phải là xấu xa, nó chỉ là một cấu trúc cấp thấp quá mạnh, ánh xạ tới một lệnh của trình biên dịch chương trình. Nếu bạn phải sử dụng nó vì ngôn ngữ của bạn bị thiếu, hãy sử dụng nó để xây dựng hoặc gia tăng các cấu trúc khối thích hợp.
nigel222

@CeesTimmerman: Đó là mã FORTRAN IV dành cho khu vườn. Nhãn văn bản không được hỗ trợ trong FORTRAN IV. Tôi không biết liệu các phiên bản gần đây hơn của ngôn ngữ có hỗ trợ chúng hay không. Nhận xét trên cùng dòng với mã không được hỗ trợ trong FORTRAN IV tiêu chuẩn, mặc dù có thể có các tiện ích mở rộng dành riêng cho nhà cung cấp để hỗ trợ chúng. Tôi không biết tiêu chuẩn FORTRAN "hiện tại" nói gì: Tôi đã bỏ rơi FORTRAN từ lâu và tôi không bỏ lỡ nó. (Mã FORTRAN gần đây nhất mà tôi thấy có sự tương đồng mạnh mẽ với PASCAL đầu những năm 1970.)
John R. Strohm

1
@CeesTimmerman: Tiện ích mở rộng dành riêng cho nhà cung cấp. Các mã nói chung là thực sự ngại ngùng về ý kiến. Ngoài ra, có một quy ước đã trở nên phổ biến ở một số nơi, chỉ đặt số dòng trên các câu lệnh TIẾP TỤC và FORMAT, để dễ dàng thêm các dòng sau này. Mã này không tuân theo quy ước đó.
John R. Strohm

0

Một trong những nguyên tắc của lập trình duy trì là đóng gói . Vấn đề là bạn giao diện với một mô-đun / thường trình / chương trình con / thành phần / đối tượng bằng cách sử dụng một giao diện được xác định và chỉ giao diện đó, và kết quả sẽ có thể dự đoán được (giả sử rằng đơn vị đã được kiểm tra hiệu quả).

Trong một đơn vị mã, nguyên tắc tương tự được áp dụng. Nếu bạn luôn áp dụng các nguyên tắc lập trình có cấu trúc hoặc lập trình hướng đối tượng, bạn sẽ không:

  1. tạo đường dẫn bất ngờ thông qua mã
  2. đến các phần của mã với các giá trị biến không xác định hoặc không thể chấp nhận
  3. thoát một đường dẫn của mã với các giá trị biến không xác định hoặc không thể chấp nhận
  4. không hoàn thành một đơn vị giao dịch
  5. để lại các phần mã hoặc dữ liệu trong bộ nhớ đã chết một cách hiệu quả, nhưng không đủ điều kiện để dọn rác và phân bổ lại rác, vì bạn đã không báo hiệu việc phát hành của chúng bằng cách sử dụng một cấu trúc rõ ràng để giải phóng chúng khi đường dẫn điều khiển mã thoát khỏi đơn vị

Một số triệu chứng phổ biến của các lỗi xử lý này bao gồm rò rỉ bộ nhớ, tích trữ bộ nhớ, tràn con trỏ, sự cố, bản ghi dữ liệu không đầy đủ, ngoại lệ thêm / chg / xóa, lỗi trang bộ nhớ, v.v.

Các biểu hiện có thể quan sát được của người dùng về các sự cố này bao gồm khóa giao diện người dùng, hiệu suất giảm dần, hồ sơ dữ liệu không đầy đủ, không thể thực hiện hoặc hoàn thành giao dịch, dữ liệu bị hỏng, mất mạng, mất điện, lỗi hệ thống nhúng (do mất kiểm soát tên lửa đến việc mất khả năng theo dõi và kiểm soát trong các hệ thống kiểm soát không lưu), lỗi hẹn giờ, v.v. Danh mục rất rộng.


3
Bạn trả lời mà không đề cập đến gotodù chỉ một lần. :)
Robert Harvey

0

goto là nguy hiểm vì nó thường được sử dụng khi không cần thiết. Sử dụng bất cứ thứ gì không cần thiết đều nguy hiểm, nhưng đặc biệt là goto . Nếu bạn google, bạn sẽ tìm thấy rất nhiều lỗi xung quanh do goto gây ra, đây không phải là lý do để không sử dụng nó (lỗi luôn xảy ra khi bạn sử dụng các tính năng ngôn ngữ vì đó là nội tại trong lập trình), nhưng một số trong số chúng có liên quan rõ ràng để sử dụng goto .


Lý do nên sử dụng / không sử dụng goto :

  • Nếu bạn cần một vòng lặp, bạn phải sử dụng whilehoặc for.

  • Nếu bạn cần phải nhảy có điều kiện, sử dụng if/then/else

  • Nếu bạn cần thủ tục, hãy gọi một hàm / phương thức.

  • Nếu bạn cần bỏ một chức năng, chỉ cần return.

Tôi có thể đếm trên đầu ngón tay những nơi tôi đã thấy gotosử dụng và sử dụng đúng cách.

  • CPython
  • libKTX
  • có lẽ vài

Trong libKTX có một hàm có đoạn mã sau

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

Bây giờ ở nơi gotonày là hữu ích vì ngôn ngữ là C:

  • chúng tôi đang ở trong một chức năng
  • chúng ta không thể viết một chức năng dọn dẹp (vì chúng ta nhập một phạm vi khác và làm cho trạng thái chức năng của người gọi có thể truy cập trở nên nặng nề hơn)

Ca sử dụng này rất hữu ích vì trong C chúng ta không có các lớp, vì vậy cách đơn giản nhất để dọn dẹp là sử dụng a goto.

Nếu chúng ta có cùng mã trong C ++ thì không cần goto nữa:

class MyClass{
    public:

    void Function(){
        if(something)
            return Cleanup(); // invalid syntax in C#, but valid in C++
        if(bla)
            return Cleanup(); // invalid syntax in C#, but valid in C++
    }

    // access same members, no need to pass state (compiler do it for us).
    void Cleanup(){

    }



}

Những loại lỗi nó có thể dẫn đến? Bất cứ điều gì. Vòng lặp vô hạn, thứ tự thực hiện sai, vít ngăn xếp ..

Một trường hợp được ghi lại là một lỗ hổng trong SSL cho phép Man trong các cuộc tấn công ở giữa do sử dụng sai goto: đây là bài viết

Đó là một lỗi đánh máy, nhưng đã không được chú ý trong một thời gian, nếu mã được cấu trúc theo một cách khác, đã kiểm tra đúng lỗi như vậy có thể không thể xảy ra.


2
Có rất nhiều ý kiến ​​cho một câu trả lời giải thích rất rõ ràng rằng việc sử dụng "goto" hoàn toàn không liên quan đến lỗi SSL - lỗi này là do sự trùng lặp bất cẩn của một dòng mã, do đó, nó đã được thực hiện một cách có điều kiện và một lần vô điều kiện.
gnasher729

Vâng, tôi đang chờ một ví dụ lịch sử tốt hơn về lỗi goto trước khi tôi chấp nhận. Như đã nói; Nó thực sự không có vấn đề gì với dòng mã đó; Việc thiếu các dấu ngoặc nhọn sẽ thực hiện nó và gây ra lỗi bất kể. Tôi tò mò mặc dù; "Trục vít" là gì?
Akiva

1
Một ví dụ từ mã tôi đã làm việc trước đây trong PL / 1 CICS. Chương trình đưa ra một màn hình bao gồm các bản đồ dòng đơn. Khi màn hình được trả về, nó tìm thấy mảng bản đồ mà nó đã gửi. Đã sử dụng các phần tử của nó để tìm chỉ mục trong một mảng và sau đó sử dụng chỉ mục đó cho một chỉ mục của các biến mảng để thực hiện GOTO để nó có thể xử lý bản đồ riêng lẻ đó. Nếu mảng bản đồ được gửi không chính xác (hoặc đã bị hỏng) thì nó có thể cố gắng thực hiện GOTO sang nhãn sai hoặc sự cố nghiêm trọng hơn vì nó đã truy cập một phần tử sau khi kết thúc mảng biến số nhãn. Viết lại để sử dụng cú pháp loại trường hợp.
Khởi động
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.