Trường hợp khái niệm về một người trở về chỉ có một người đến từ đâu?


1055

Tôi thường nói chuyện với các lập trình viên nói rằng " Đừng đặt nhiều câu trả về trong cùng một phương thức. " Khi tôi yêu cầu họ cho tôi biết lý do tại sao, tất cả những gì tôi nhận được là " Tiêu chuẩn mã hóa nói như vậy. " Hoặc " Thật khó hiểu " . Khi họ chỉ cho tôi các giải pháp với một tuyên bố trả về duy nhất, mã trông xấu hơn đối với tôi. Ví dụ:

if (condition)
   return 42;
else
   return 97;

" Điều này thật xấu xí, bạn phải sử dụng một biến cục bộ! "

int result;
if (condition)
   result = 42;
else
   result = 97;
return result;

Làm thế nào để mã hóa 50% này làm cho chương trình dễ hiểu hơn? Cá nhân, tôi thấy nó khó hơn, bởi vì không gian trạng thái vừa tăng lên bởi một biến khác có thể dễ dàng bị ngăn chặn.

Tất nhiên, thông thường tôi sẽ chỉ viết:

return (condition) ? 42 : 97;

Nhưng nhiều lập trình viên tránh sử dụng toán tử có điều kiện và thích dạng dài.

Trường hợp khái niệm "chỉ một lần trở lại" này đến từ đâu? Có một lý do lịch sử tại sao hội nghị này xuất hiện?


2
Điều này phần nào được kết nối với tái cấu trúc Guard Guard. stackoverflow.com/a/8493256/679340 Điều khoản bảo vệ sẽ thêm lợi nhuận vào đầu phương thức của bạn. Và nó làm cho mã sạch hơn rất nhiều theo ý kiến ​​của tôi.
Piotr Perak

3
Nó xuất phát từ khái niệm lập trình có cấu trúc. Một số người có thể lập luận rằng chỉ có một lần trả về cho phép bạn dễ dàng sửa đổi mã để làm điều gì đó ngay trước khi quay lại hoặc dễ dàng gỡ lỗi.
martinkunev

3
Tôi nghĩ ví dụ này là một trường hợp đủ đơn giản trong đó tôi sẽ không có ý kiến ​​mạnh mẽ theo cách này hay cách khác. lý tưởng một lối vào duy nhất là nhiều hơn để hướng dẫn chúng ta thoát khỏi những tình huống điên rồ như 15 tuyên bố trả lại và hai chi nhánh khác hoàn toàn không trở lại!
mendota

2
Đó là một trong những bài viết tồi tệ nhất mà tôi từng đọc. Có vẻ như tác giả dành nhiều thời gian để tưởng tượng về độ tinh khiết của OOP của mình hơn là thực sự tìm ra cách để đạt được bất cứ điều gì. Cây biểu hiện và đánh giá có giá trị nhưng không phải khi bạn chỉ có thể viết một hàm bình thường thay thế.
DeadMG

3
Bạn nên loại bỏ điều kiện hoàn toàn. Câu trả lời là 42.
cambunctious

Câu trả lời:


1119

"Lối vào đơn, Lối thoát đơn" được viết khi hầu hết các chương trình được thực hiện bằng ngôn ngữ lắp ráp, FORTRAN hoặc COBOL. Nó đã được giải thích rộng rãi, bởi vì các ngôn ngữ hiện đại không hỗ trợ các thực tiễn Dijkstra đã cảnh báo chống lại.

"Mục nhập đơn" có nghĩa là "không tạo điểm nhập thay thế cho các chức năng". Trong ngôn ngữ lắp ráp, tất nhiên, có thể nhập một chức năng theo bất kỳ hướng dẫn nào. FORTRAN hỗ trợ nhiều mục nhập cho các chức năng với ENTRYcâu lệnh:

      SUBROUTINE S(X, Y)
      R = SQRT(X*X + Y*Y)
C ALTERNATE ENTRY USED WHEN R IS ALREADY KNOWN
      ENTRY S2(R)
      ...
      RETURN
      END

C USAGE
      CALL S(3,4)
C ALTERNATE USAGE
      CALL S2(5)

"Độc Thoát" có nghĩa là một chức năng chỉ nên trở lại để một nơi: báo cáo kết quả ngay sau cuộc gọi. Điều đó không có nghĩa là một chức năng chỉ nên trở về từ một nơi. Khi Lập trình có cấu trúc được viết, thông thường một hàm sẽ chỉ ra lỗi bằng cách quay lại vị trí thay thế. FORTRAN đã hỗ trợ điều này thông qua "lợi nhuận thay thế":

C SUBROUTINE WITH ALTERNATE RETURN.  THE '*' IS A PLACE HOLDER FOR THE ERROR RETURN
      SUBROUTINE QSOLVE(A, B, C, X1, X2, *)
      DISCR = B*B - 4*A*C
C NO SOLUTIONS, RETURN TO ERROR HANDLING LOCATION
      IF DISCR .LT. 0 RETURN 1
      SD = SQRT(DISCR)
      DENOM = 2*A
      X1 = (-B + SD) / DENOM
      X2 = (-B - SD) / DENOM
      RETURN
      END

C USE OF ALTERNATE RETURN
      CALL QSOLVE(1, 0, 1, X1, X2, *99)
C SOLUTION FOUND
      ...
C QSOLVE RETURNS HERE IF NO SOLUTIONS
99    PRINT 'NO SOLUTIONS'

Cả hai kỹ thuật này rất dễ bị lỗi. Sử dụng các mục thay thế thường để lại một số biến chưa được khởi tạo. Việc sử dụng các lợi nhuận thay thế có tất cả các vấn đề của câu lệnh GOTO, với sự phức tạp thêm rằng điều kiện nhánh không liền kề với nhánh, nhưng ở đâu đó trong chương trình con.


38
Và đừng quên mã spaghetti . Không rõ các chương trình con thoát ra bằng GOTO thay vì trả về, để lại các tham số gọi hàm và địa chỉ trả về trên ngăn xếp. Lối thoát đơn được quảng bá như một cách để ít nhất là kênh tất cả các đường dẫn mã đến câu lệnh RETURN.
TMN

2
@TMN: trong những ngày đầu, hầu hết các máy không có ngăn xếp phần cứng. Đệ quy thường không được hỗ trợ. Đối số chương trình con và địa chỉ trả về được lưu trữ ở các vị trí cố định liền kề với mã chương trình con. Trả lại chỉ là một goto gián tiếp.
kevin cline

5
@kevin: Vâng, nhưng theo bạn điều này thậm chí không còn ý nghĩa gì nữa mà nó được phát minh ra. (BTW, tôi thực sự hợp lý chắc chắn rằng Fred hỏi là những ưu đãi cho các dòng giải thích "Thoát Độc" đến từ đâu.) Ngoài ra, C đã có consttừ trước khi nhiều người sử dụng ở đây được sinh ra, vì vậy không cần cho hằng vốn nữa ngay cả trong C. Nhưng Java vẫn bảo tồn tất cả những thói quen C cũ tồi tệ đó .
sbi

3
Vì vậy, các trường hợp ngoại lệ vi phạm cách giải thích này của Thoát đơn? (Hoặc anh em họ nguyên thủy hơn của họ , setjmp/longjmp?)
Mason Wheeler

2
Mặc dù các op đã hỏi về cách giải thích hiện tại về sự trở lại duy nhất, câu trả lời này là câu trả lời có nguồn gốc lịch sử nhất. Không có điểm nào trong việc sử dụng một lợi nhuận duy nhất làm quy tắc , trừ khi bạn muốn ngôn ngữ của mình phù hợp với sự tuyệt vời của VB (không phải .NET). Chỉ cần nhớ sử dụng logic boolean không ngắn mạch là tốt.
acelent

912

Khái niệm về Lối vào đơn, Lối thoát đơn (SESE) xuất phát từ các ngôn ngữ có quản lý tài nguyên rõ ràng , như C và lắp ráp. Trong C, mã như thế này sẽ rò rỉ tài nguyên:

void f()
{
  resource res = acquire_resource();  // think malloc()
  if( f1(res) )
    return; // leaks res
  f2(res);
  release_resource(res);  // think free()
}

Trong các ngôn ngữ như vậy, về cơ bản bạn có ba tùy chọn:

  • Nhân rộng mã dọn dẹp.
    Ừ Sự dư thừa luôn là xấu.

  • Sử dụng một gotođể nhảy đến mã dọn dẹp.
    Điều này đòi hỏi mã dọn dẹp là điều cuối cùng trong hàm. (Và đây là lý do tại sao một số tranh luận gotocó vị trí của nó. Và nó thực sự có - trong C.)

  • Giới thiệu một biến cục bộ và điều khiển luồng điều khiển thông qua đó.
    Điểm bất lợi là dòng điều khiển thao tác thông qua cú pháp (suy nghĩ break, return, if, while) là dễ dàng hơn để làm theo hơn dòng điều khiển thao tác thông qua các trạng thái của các biến (vì những biến này không có trạng thái khi bạn nhìn vào các thuật toán).

Khi lắp ráp, nó thậm chí còn lạ hơn, bởi vì bạn có thể chuyển đến bất kỳ địa chỉ nào trong hàm khi bạn gọi hàm đó, điều đó có nghĩa là bạn có số lượng điểm nhập gần như không giới hạn cho bất kỳ chức năng nào. (Đôi khi điều này hữu ích. Các thunks như vậy là một kỹ thuật phổ biến để các trình biên dịch thực hiện thisđiều chỉnh con trỏ cần thiết để gọi các virtualhàm trong các kịch bản đa kế thừa trong C ++.)

Khi bạn phải quản lý tài nguyên theo cách thủ công, việc khai thác các tùy chọn nhập hoặc thoát một chức năng ở bất kỳ đâu dẫn đến mã phức tạp hơn và do đó xảy ra lỗi. Do đó, một trường phái tư tưởng đã xuất hiện tuyên truyền SESE, để có được mã sạch hơn và ít lỗi hơn.


Tuy nhiên, khi một ngôn ngữ có các trường hợp ngoại lệ, (hầu như) bất kỳ chức năng nào cũng có thể bị thoát sớm tại (gần như) bất kỳ điểm nào, vì vậy dù sao bạn cũng cần phải cung cấp các điều khoản cho việc hoàn trả sớm. (Tôi nghĩ rằng finallyđược sử dụng chủ yếu cho rằng trong Java và using(khi thực hiện IDisposable, finallykhác) trong C #, C ++ thay vì sử dụng RAII .) Một khi bạn đã làm điều này, bạn không thể thất bại trong việc làm sạch sau khi mình do một đầu returntuyên bố, vì vậy những gì có lẽ là lập luận mạnh mẽ nhất ủng hộ SESE đã biến mất.

Điều đó để lại khả năng đọc. Tất nhiên, hàm 200 LoC với nửa tá returncâu lệnh được rắc ngẫu nhiên trên nó không phải là kiểu lập trình tốt và không tạo ra mã có thể đọc được. Nhưng một chức năng như vậy sẽ không dễ hiểu nếu không có những lợi nhuận sớm đó.

Trong các ngôn ngữ mà tài nguyên không hoặc không nên được quản lý thủ công, có rất ít hoặc không có giá trị trong việc tuân thủ quy ước SESE cũ. OTOH, như tôi đã lập luận ở trên, SESE thường làm cho mã phức tạp hơn . Đó là một con khủng long (trừ C) không phù hợp với hầu hết các ngôn ngữ ngày nay. Thay vì giúp sự dễ hiểu của mã, nó cản trở nó.


Tại sao các lập trình viên Java dính vào điều này? Tôi không biết, nhưng từ POV (bên ngoài) của tôi, Java đã lấy rất nhiều quy ước từ C (nơi chúng có ý nghĩa) và áp dụng chúng vào thế giới OO của nó (nơi chúng vô dụng hoặc hoàn toàn xấu), nơi mà bây giờ nó dính vào họ, không có vấn đề gì (Giống như quy ước để xác định tất cả các biến của bạn ở đầu phạm vi.)

Các lập trình viên dính vào tất cả các loại ký hiệu lạ vì lý do phi lý. (Các câu lệnh cấu trúc lồng nhau sâu sắc - "đầu mũi tên" -, trong các ngôn ngữ như Pascal, từng được coi là mã đẹp.) Áp dụng lý luận logic thuần túy cho điều này dường như không thuyết phục được phần lớn trong số chúng đi chệch khỏi cách thiết lập của chúng. Cách tốt nhất để thay đổi thói quen như vậy có lẽ là dạy chúng sớm làm những gì tốt nhất, không phải những gì thông thường. Bạn, là một giáo viên lập trình, có nó trong tay của bạn.:)


52
Đúng. Trong Java, mã dọn dẹp thuộc về finallycác mệnh đề nơi nó được thực thi bất kể returnngoại lệ hay ngoại lệ sớm .
dan04

15
@ dan04 trong Java 7 bạn thậm chí không cần finallyhầu hết thời gian.
R. Martinho Fernandes

93
@Steven: Tất nhiên bạn có thể chứng minh điều đó! Trong thực tế, bạn có thể hiển thị mã phức tạp và phức tạp với bất kỳ tính năng nào cũng có thể được hiển thị để làm cho mã đơn giản và dễ hiểu hơn. Mọi thứ đều có thể bị lạm dụng. Vấn đề là viết mã sao cho dễ hiểu hơn và khi điều đó liên quan đến việc ném SESE ra khỏi cửa sổ, thì cũng vậy, và bỏ đi những thói quen cũ áp dụng cho các ngôn ngữ khác nhau. Nhưng tôi sẽ không ngần ngại kiểm soát việc thực hiện bằng các biến nếu tôi nghĩ rằng nó làm cho mã dễ đọc hơn. Chỉ là tôi không thể nhớ đã thấy mã như vậy trong gần hai thập kỷ.
sbi

21
@Karl: Thật vậy, đó là một thiếu sót nghiêm trọng của các ngôn ngữ GC như Java mà chúng giúp bạn không phải dọn sạch một tài nguyên, nhưng thất bại với tất cả các ngôn ngữ khác. (C ++ giải quyết vấn đề này cho tất cả các tài nguyên bằng RAII .) Nhưng tôi thậm chí không nói về bộ nhớ (tôi chỉ đưa ra malloc()free()nhận xét làm ví dụ), tôi đã nói về tài nguyên nói chung. Tôi cũng không ngụ ý rằng GC sẽ giải quyết những vấn đề này. (Tôi đã đề cập đến C ++, không có GC ngoài hộp.) Theo những gì tôi hiểu, trong Java finallyđược sử dụng để giải quyết vấn đề này.
sbi

10
@sbi: Quan trọng hơn đối với một chức năng (thủ tục, phương thức, v.v.) hơn là không quá một trang dài để chức năng có hợp đồng được xác định rõ ràng; nếu nó không làm gì đó rõ ràng vì nó bị băm nhỏ để đáp ứng một ràng buộc về độ dài tùy ý, thì đó là Xấu. Lập trình là để chơi khác nhau, đôi khi xung đột lực lượng với nhau.
Donal Fellows

81

Một mặt, các câu lệnh hoàn trả đơn lẻ giúp việc ghi nhật ký dễ dàng hơn, cũng như các hình thức gỡ lỗi dựa trên việc ghi nhật ký. Tôi nhớ rất nhiều lần tôi phải giảm hàm thành một lần trả lại chỉ để in ra giá trị trả về tại một điểm duy nhất.

  int function() {
     if (bidi) { print("return 1"); return 1; }
     for (int i = 0; i < n; i++) {
       if (vidi) { print("return 2"); return 2;}
     }
     print("return 3");
     return 3;
  }

Mặt khác, bạn có thể cấu trúc lại điều này vào function()các cuộc gọi đó _function()và ghi lại kết quả.


31
Tôi cũng sẽ thêm rằng nó làm cho việc gỡ lỗi dễ dàng hơn bởi vì bạn chỉ cần đặt một điểm dừng để bắt tất cả các lối thoát * từ hàm. Tôi tin rằng một số IDE cho phép bạn đặt một điểm dừng trên niềng răng chặt chẽ của chức năng để làm điều tương tự. (* trừ khi bạn gọi thoát)
Skizz

3
Vì một lý do tương tự, nó cũng giúp mở rộng (thêm) chức năng dễ dàng hơn, vì chức năng mới của bạn không phải chèn trước mỗi lần trả về. Giả sử bạn cần cập nhật nhật ký với kết quả của lệnh gọi, chẳng hạn.
JeffSahol

63
Thành thật mà nói, nếu tôi đang duy trì mã đó, tôi muốn có một định nghĩa hợp lý _function(), với returncác vị trí thích hợp và một trình bao bọc có tên function()xử lý việc ghi nhật ký bên ngoài, hơn là có một function()logic với méo mó để làm cho tất cả trả về khớp với một lối thoát duy nhất -point chỉ để tôi có thể chèn một tuyên bố bổ sung trước thời điểm đó.
ruakh

11
Trong một số trình gỡ lỗi (MSVS), bạn có thể đặt điểm dừng trên cú đúp cuối cùng
Abyx

6
in! = gỡ lỗi. Đó không phải là tranh luận gì cả.
Piotr Perak

53

"Lối vào đơn, Lối thoát đơn" bắt nguồn từ cuộc cách mạng lập trình có cấu trúc đầu những năm 1970, được khởi động bởi bức thư của Edsger W. Dijkstra gửi cho Biên tập viên " Tuyên bố GOTO được coi là có hại ". Các khái niệm đằng sau lập trình có cấu trúc đã được trình bày chi tiết trong cuốn sách kinh điển "Lập trình có cấu trúc" của Ole Johan-Dahl, Edsger W. Dijkstra và Charles Anthony Richard Hoare.

"Tuyên bố GOTO được coi là có hại" được yêu cầu đọc, ngay cả ngày nay. "Lập trình có cấu trúc" có niên đại, nhưng vẫn rất, rất bổ ích và nên đứng đầu trong danh sách "Phải đọc" của bất kỳ nhà phát triển nào, vượt xa mọi thứ từ ví dụ Steve McConnell. (Phần của Dahl đưa ra những điều cơ bản của các lớp trong Simula 67, là nền tảng kỹ thuật cho các lớp trong C ++ và tất cả các chương trình hướng đối tượng.)


6
Bài báo được viết trong vài ngày trước C khi GOTO được sử dụng nhiều. Họ không phải là kẻ thù, nhưng câu trả lời này hoàn toàn chính xác. Một câu lệnh return không nằm ở cuối hàm có hiệu quả là một goto.
dùng606723

31
Bài báo cũng đã được viết trong những ngày khi gotothật sự có thể đi bất cứ nơi nào , giống như ngay vào một lúc nào đó ngẫu nhiên trong chức năng khác, bỏ qua bất kỳ khái niệm về thủ tục, chức năng, một chồng gọi, vv Không có giấy phép ngôn ngữ lành mạnh mà những ngày này với một thẳng goto. C ' setjmp/ longjmplà trường hợp bán đặc biệt duy nhất tôi biết và thậm chí điều đó đòi hỏi sự hợp tác từ cả hai đầu. (Semi-mỉa mai rằng tôi đã sử dụng từ "ngoại lệ" ở đó, mặc dù, xem xét rằng các ngoại lệ đó gần như giống nhau ...) Về cơ bản, bài viết không khuyến khích một thực hành đã chết từ lâu.
cHao

5
Từ đoạn cuối của "Tuyên bố Goto được coi là có hại": "trong [2] Guiseppe Jacopini dường như đã chứng minh sự thừa thãi (logic) của tuyên bố đi tới. Bài tập dịch một sơ đồ dòng chảy tùy ý ít nhiều thành một bước nhảy- ít hơn một, tuy nhiên, không được khuyến khích . Sau đó, sơ đồ dòng kết quả có thể được dự kiến ​​sẽ minh bạch hơn so với bản gốc. "
hugomg

10
Điều này có liên quan gì đến câu hỏi? Vâng, công việc của Dijkstra cuối cùng đã dẫn đến các ngôn ngữ SESE, và vậy thì sao? Công việc của Babbage cũng vậy. Và có lẽ bạn nên đọc lại bài báo nếu bạn nghĩ nó nói bất cứ điều gì về việc có nhiều điểm thoát trong một hàm. Bởi vì nó không.
jalf

10
@ John, dường như bạn đang cố gắng trả lời câu hỏi mà không thực sự trả lời nó. Đó là một danh sách đọc tốt, nhưng bạn không trích dẫn hay diễn giải bất cứ điều gì để biện minh cho tuyên bố của bạn rằng bài tiểu luận và cuốn sách này có bất cứ điều gì để nói về mối quan tâm của người hỏi. Thật vậy, bên ngoài các ý kiến ​​bạn đã nói không có gì đáng kể về câu hỏi nào. Hãy xem xét mở rộng câu trả lời này.
Shog9

35

Luôn luôn dễ dàng để liên kết Fowler.

Một trong những ví dụ chính chống lại SESE là các điều khoản bảo vệ:

Thay thế điều kiện lồng nhau bằng các khoản bảo vệ

Sử dụng các điều khoản bảo vệ cho tất cả các trường hợp đặc biệt

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
return result;
};  

                                                                                                         http://www.refactoring.com/catalog/arrow.gif

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
};  

Để biết thêm thông tin, xem trang 250 của Tái cấu trúc ...


11
Một ví dụ tồi tệ khác: nó có thể dễ dàng được sửa với các if-if khác.
Jack

1
Ví dụ của bạn không công bằng, làm thế nào về điều này: double getPayAmount () {double ret = normalPayAmount (); if (_isDead) ret = deadAmount (); if (_isSAXated) ret = separAmount (); if (_isRetired) ret = retiredAmount (); trở về ret; };
Charbel

6
@Charbel Đó không phải là điều tương tự. Nếu _isSeparated_isRetiredcả hai đều có thể đúng (và tại sao điều đó không thể xảy ra?) Bạn trả lại số tiền sai.
hvd

2
@Konchog " các điều kiện lồng nhau sẽ cung cấp thời gian thực hiện tốt hơn các mệnh đề bảo vệ " Điều này chủ yếu cần một trích dẫn. Tôi nghi ngờ rằng nó hoàn toàn đúng. Trong trường hợp này, ví dụ, trả về sớm như thế nào khác với ngắn mạch logic về mặt mã được tạo? Ngay cả khi nó quan trọng, tôi không thể tưởng tượng được một trường hợp mà sự khác biệt sẽ nhiều hơn một mảnh vô hạn. Vì vậy, bạn đang áp dụng tối ưu hóa sớm bằng cách làm cho mã ít đọc hơn, chỉ để đáp ứng một số điểm lý thuyết chưa được chứng minh về những gì bạn nghĩ dẫn đến mã nhanh hơn một chút. Chúng tôi không làm điều đó ở đây
underscore_d

1
@underscore_d, bạn nói đúng. nó phụ thuộc rất nhiều vào trình biên dịch, nhưng nó có thể chiếm nhiều không gian hơn .. Hãy nhìn vào hai cụm giả và thật dễ hiểu tại sao các mệnh đề bảo vệ đến từ các ngôn ngữ cấp cao. Thử nghiệm "A" (1); nhánh_fail kết thúc; kiểm tra (2); nhánh_fail kết thúc; kiểm tra (3); nhánh_fail kết thúc; {CODE} kết thúc: trả lại; Thử nghiệm "B" (1); nhánh_good next1; trở về; next1: kiểm tra (2); nhánh_good next2; trở về; next2: kiểm tra (3); nhánh_good next3; trở về; next3: {CODE} trả lại;
Konchog

11

Tôi đã viết một bài blog về chủ đề này một thời gian trở lại.

Điểm mấu chốt là quy tắc này xuất phát từ thời đại ngôn ngữ không có bộ sưu tập rác hoặc xử lý ngoại lệ. Không có nghiên cứu chính thức nào cho thấy quy tắc này dẫn đến mã tốt hơn trong các ngôn ngữ hiện đại. Hãy bỏ qua nó bất cứ khi nào điều này sẽ dẫn đến mã ngắn hơn hoặc dễ đọc hơn. Những kẻ Java khăng khăng về điều này là mù quáng và không nghi ngờ gì theo một quy tắc lỗi thời, vô nghĩa.

Câu hỏi này cũng đã được hỏi trên Stackoverflow


Hey, tôi không thể đạt được liên kết đó nữa. Bạn có tình cờ có một phiên bản được lưu trữ ở đâu đó vẫn có thể truy cập không?
Nic Hartley

Xin chào, QPT, điểm tốt. Tôi đã mang bài viết trở lại và cập nhật URL ở trên. Nó nên liên kết ngay bây giờ!
Anthony

Có nhiều thứ hơn thế. Dễ dàng hơn nhiều để quản lý thời gian thực hiện chính xác bằng cách sử dụng SESE. Các điều kiện lồng nhau thường có thể được cấu trúc lại bằng một công tắc. Nó không chỉ là về việc có hay không có giá trị trả về.
Konchog

Nếu bạn sẽ tuyên bố rằng không có nghiên cứu chính thức nào hỗ trợ nó, nó sẽ khiến bạn phải liên kết với một nghiên cứu chống lại nó.
Mehrdad

Mehrdad, Nếu có một nghiên cứu chính thức hỗ trợ nó, hãy cho thấy nó. Đó là tất cả. Nhấn mạnh vào bằng chứng chống lại đang thay đổi gánh nặng của bằng chứng.
Anthony

7

Một sự trở lại làm cho tái cấu trúc dễ dàng hơn. Cố gắng thực hiện "phương pháp trích xuất" vào phần bên trong của vòng lặp for có chứa trả lại, ngắt hoặc tiếp tục. Điều này sẽ thất bại khi bạn đã phá vỡ dòng kiểm soát của bạn.

Vấn đề là: Tôi đoán rằng không ai đang giả vờ viết mã hoàn hảo. Vì vậy, mã được tái cấu trúc để được "cải thiện" và mở rộng. Vì vậy, mục tiêu của tôi sẽ là giữ cho mã của tôi tái cấu trúc thân thiện nhất có thể.

Thường thì tôi phải đối mặt với vấn đề là tôi phải điều chỉnh lại các chức năng hoàn toàn nếu chúng có chứa các bộ ngắt dòng điều khiển và nếu tôi chỉ muốn thêm ít chức năng. Điều này rất dễ xảy ra lỗi khi bạn thay đổi toàn bộ luồng điều khiển thay vì đưa các đường dẫn mới đến các tổ bị cô lập. Nếu bạn chỉ có một lần trả lại duy nhất ở cuối hoặc nếu bạn sử dụng bộ bảo vệ để thoát khỏi vòng lặp, tất nhiên bạn có nhiều mã lồng nhau hơn và nhiều mã hơn. Nhưng bạn có được trình biên dịch và IDE hỗ trợ khả năng tái cấu trúc.


Áp dụng tương tự cho các biến. Đó là những thay thế cho việc sử dụng các cấu trúc dòng điều khiển như trả về sớm.
Ded repeatator

Các biến chủ yếu sẽ không cản trở bạn chia mã của bạn thành các phần theo cách mà luồng điều khiển hiện có được bảo tồn. Hãy thử "phương pháp giải nén". Các IDE chỉ có thể thực hiện các phép tái cấu trúc dòng điều khiển vì chúng không thể lấy được ngữ nghĩa từ những gì bạn đã viết.
oopexpert

5

Hãy xem xét thực tế rằng nhiều báo cáo hoàn trả tương đương với việc có GOTO cho một tuyên bố hoàn trả duy nhất. Đây là trường hợp tương tự với các tuyên bố phá vỡ. Do đó, một số người, như tôi, coi họ là GOTO cho tất cả ý định và mục đích.

Tuy nhiên, tôi không coi các loại GOTO này có hại và sẽ không ngần ngại sử dụng GOTO thực tế trong mã của mình nếu tôi tìm thấy lý do chính đáng cho nó.

Quy tắc chung của tôi là GOTO chỉ dành cho kiểm soát luồng. Chúng không bao giờ được sử dụng cho bất kỳ vòng lặp nào và bạn không bao giờ nên GOTO 'lên trên' hoặc 'ngược'. (đó là cách nghỉ / trả lại công việc)

Như những người khác đã đề cập, sau đây là phải đọc Tuyên bố GOTO được coi là có hại
Tuy nhiên, hãy nhớ rằng điều này đã được viết vào năm 1970 khi GOTO bị lạm dụng quá mức. Không phải mọi GOTO đều có hại và tôi sẽ không khuyến khích việc sử dụng chúng miễn là bạn không sử dụng chúng thay vì các cấu trúc thông thường, nhưng trong trường hợp kỳ lạ là sử dụng các cấu trúc bình thường sẽ rất bất tiện.

Tôi thấy rằng việc sử dụng chúng trong các trường hợp lỗi mà bạn cần phải thoát khỏi một khu vực vì một lỗi không bao giờ xảy ra trong các trường hợp bình thường đôi khi hữu ích. Nhưng bạn cũng nên xem xét đưa mã này vào một chức năng riêng biệt để bạn có thể quay lại sớm thay vì sử dụng GOTO ... nhưng đôi khi điều đó cũng bất tiện.


6
Tất cả các cấu trúc có cấu trúc thay thế gotos được thực hiện theo thuật ngữ goto. Ví dụ: các vòng lặp, "nếu" và "trường hợp". Điều này không làm cho họ xấu - thực tế thì ngược lại. Ngoài ra, đó là "ý định và mục đích".
Anthony

Touche, nhưng điều này không khác biệt quan điểm của tôi ... Nó chỉ làm cho lời giải thích của tôi hơi sai. ồ
dùng606723

GOTO phải luôn ổn, miễn là (1) mục tiêu nằm trong cùng một phương thức hoặc chức năng và (2) hướng được chuyển tiếp trong mã (bỏ qua một số mã) và (3) mục tiêu không nằm trong một cấu trúc lồng nhau khác (ví dụ: GOTO từ giữa trường hợp if đến trường hợp khác). Nếu bạn tuân theo các quy tắc này, tất cả các hành vi lạm dụng GOTO đều có mùi mã thực sự mạnh mẽ cả về mặt trực quan và logic.
Mikko Rantalainen

3

Phức tạp cyclomatic

Tôi đã thấy SonarCube sử dụng nhiều câu lệnh return để xác định độ phức tạp chu kỳ. Vì vậy, càng nhiều báo cáo trả về, độ phức tạp chu kỳ càng cao

Thay đổi loại trả về

Nhiều trả về có nghĩa là chúng ta cần thay đổi tại nhiều vị trí trong hàm khi chúng ta quyết định thay đổi loại trả về của mình.

Nhiều lối ra

Khó gỡ lỗi hơn vì logic cần phải được nghiên cứu cẩn thận kết hợp với các câu lệnh có điều kiện để hiểu nguyên nhân gây ra giá trị trả về.

Giải pháp tái cấu trúc

Giải pháp cho nhiều câu lệnh return là thay thế chúng bằng đa hình có một lần trả về sau khi giải quyết đối tượng thực hiện được yêu cầu.


3
Chuyển từ nhiều trả về để đặt giá trị trả về ở nhiều nơi không loại bỏ độ phức tạp theo chu kỳ, nó chỉ thống nhất vị trí thoát. Tất cả các vấn đề mà độ phức tạp chu kỳ có thể chỉ ra trong bối cảnh cụ thể vẫn còn. "Khó gỡ lỗi hơn vì logic cần phải được nghiên cứu cẩn thận cùng với các câu lệnh có điều kiện để hiểu nguyên nhân gây ra giá trị trả về" Một lần nữa, logic không thay đổi bằng cách thống nhất trả về. Nếu bạn phải nghiên cứu kỹ mã để hiểu cách thức hoạt động của nó, nó cần phải được cấu trúc lại, dừng hoàn toàn.
WillD
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.