Một hàm chỉ có một câu lệnh return?


780

Có lý do chính đáng tại sao đó là một cách thực hành tốt hơn khi chỉ có một câu lệnh return trong một hàm không?

Hoặc có thể trả về từ một hàm ngay khi nó đúng về mặt logic để làm như vậy, có nghĩa là có thể có nhiều câu lệnh return trong hàm?


25
Tôi không đồng ý rằng câu hỏi là bất khả tri ngôn ngữ. Với một số ngôn ngữ, có nhiều lợi nhuận là tự nhiên và thuận tiện hơn so với các ngôn ngữ khác. Tôi có nhiều khả năng phàn nàn về lợi nhuận sớm trong hàm C hơn là trong C ++ sử dụng RAII.
Adrian McCarthy

3
Điều này có liên quan chặt chẽ và có câu trả lời tuyệt vời: lập trình
viên.stackexchange.com/questions/118703/iêu

bất khả tri ngôn ngữ? Giải thích cho ai đó sử dụng ngôn ngữ chức năng rằng anh ta phải sử dụng một trả về cho mỗi chức năng: p
Boiethios

Câu trả lời:


741

Tôi thường có một vài câu khi bắt đầu một phương thức để trả về các tình huống "dễ dàng". Ví dụ: cái này:

public void DoStuff(Foo foo)
{
    if (foo != null)
    {
        ...
    }
}

... có thể dễ đọc hơn (IMHO) như thế này:

public void DoStuff(Foo foo)
{
    if (foo == null) return;

    ...
}

Vì vậy, có, tôi nghĩ sẽ tốt khi có nhiều "điểm thoát" từ một hàm / phương thức.


83
Đã đồng ý. Mặc dù có nhiều điểm thoát có thể vượt khỏi tầm kiểm soát, tôi chắc chắn nghĩ rằng nó tốt hơn là đưa toàn bộ chức năng của bạn vào một khối IF. Sử dụng trả lại thường xuyên vì nó có ý nghĩa để giữ cho mã của bạn có thể đọc được.
Joshua Carmody

172
Điều này được gọi là "tuyên bố bảo vệ" là Tái cấu trúc của Fowler.
Lars Westergren

12
Khi các hàm được giữ tương đối ngắn, không khó để theo cấu trúc của hàm với điểm trả về gần giữa.
KJAWolf

21
Khối lượng lớn các câu lệnh if-other, mỗi câu trả lại? Đó là không có gì. Một điều như vậy thường dễ dàng tái cấu trúc. (ít nhất là biến thể thoát đơn phổ biến hơn với biến kết quả là, vì vậy tôi nghi ngờ biến thể thoát đa sẽ khó khăn hơn.) Nếu bạn muốn đau đầu thực sự, hãy xem kết hợp các vòng lặp if và if booleans), trong đó các biến kết quả được đặt, dẫn đến điểm thoát cuối cùng ở cuối phương thức. Đó là ý tưởng thoát duy nhất phát điên, và vâng tôi đang nói từ kinh nghiệm thực tế đã phải đối phó với nó.
Marcus Andrén

7
'Hãy tưởng tượng: bạn cần thực hiện phương thức "TăngStuffCallCorer" ở cuối chức năng' DoStuff '. Bạn sẽ làm gì trong trường hợp này? :) '-DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
Jim Balter

355

Không ai đã đề cập hoặc trích dẫn Hoàn thành mã vì vậy tôi sẽ làm điều đó.

Trả lại 17.1

Giảm thiểu số lượng lợi nhuận trong mỗi thói quen . Thật khó để hiểu một thói quen nếu đọc nó ở phía dưới, bạn không biết về khả năng nó trở lại ở đâu đó phía trên.

Sử dụng trở lại khi nó tăng cường khả năng đọc . Trong một số thói quen nhất định, một khi bạn biết câu trả lời, bạn muốn đưa nó trở lại thói quen gọi ngay lập tức. Nếu thường trình được định nghĩa theo cách mà nó không yêu cầu dọn dẹp, không quay lại ngay có nghĩa là bạn phải viết thêm mã.


64
+1 cho sắc thái "tối thiểu hóa" nhưng không cấm nhiều lợi nhuận.
Raedwald

13
"khó hiểu" là rất chủ quan, đặc biệt là khi tác giả không cung cấp bất kỳ bằng chứng thực nghiệm nào để hỗ trợ cho yêu cầu chung ... có một biến duy nhất phải được đặt ở nhiều vị trí có điều kiện trong mã cho tuyên bố hoàn trả cuối cùng để "không biết về khả năng biến được gán ở đâu đó bên trên int anh ta hoạt động!
Heston T. Holtmann

26
Cá nhân, tôi thích trở về sớm nếu có thể. Tại sao? Chà, khi bạn thấy từ khóa trả về cho một trường hợp cụ thể, bạn sẽ biết ngay "Tôi đã kết thúc" - bạn không cần phải đọc để tìm hiểu xem, nếu có bất cứ điều gì, xảy ra sau đó.
Mark Simpson

12
@ HestonT.Holtmann: Điều khiến Code Complete trở nên độc đáo trong các cuốn sách lập trình là lời khuyên được hỗ trợ bởi bằng chứng thực nghiệm.
Adrian McCarthy

9
Đây có lẽ nên là câu trả lời được chấp nhận, vì nó đề cập rằng có nhiều điểm trả về không phải lúc nào cũng tốt, nhưng đôi khi cần thiết.
Rafid

229

Tôi sẽ nói rằng sẽ rất khó khôn ngoan khi quyết định tùy tiện đối với nhiều điểm thoát vì tôi đã thấy kỹ thuật này hữu ích trong thực tế nhiều lần , trên thực tế tôi thường tái cấu trúc mã hiện tại thành nhiều điểm thoát cho rõ ràng. Chúng ta có thể so sánh hai cách tiếp cận như vậy: -

string fooBar(string s, int? i) {
  string ret = "";
  if(!string.IsNullOrEmpty(s) && i != null) {
    var res = someFunction(s, i);

    bool passed = true;
    foreach(var r in res) {
      if(!r.Passed) {
        passed = false;
        break;
      }
    }

    if(passed) {
      // Rest of code...
    }
  }

  return ret;
}

So sánh mã này với mã nơi nhiều điểm thoát được cho phép: -

string fooBar(string s, int? i) {
  var ret = "";
  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Tôi nghĩ rằng sau này là rõ ràng hơn đáng kể. Theo như tôi có thể nói những lời chỉ trích về nhiều điểm thoát là một quan điểm khá cổ xưa ngày nay.


12
Sự rõ ràng nằm trong mắt của kẻ si tình - Tôi nhìn vào một chức năng và tìm kiếm một sự khởi đầu giữa và kết thúc. Khi chức năng nhỏ, nó vẫn ổn - nhưng khi bạn đang cố gắng tìm ra lý do tại sao một cái gì đó bị hỏng và "Phần còn lại của mã" hóa ra lại không tầm thường, bạn có thể mất nhiều thời gian để tìm kiếm lý do tại sao lại là
Murph

7
Trước hết, đây là ví dụ giả định. Thứ hai, Trường hợp: chuỗi ret; "đi trong phiên bản thứ hai? Thứ ba, Ret không chứa thông tin hữu ích. Thứ tư, tại sao có quá nhiều logic trong một hàm / phương thức? Thứ năm, tại sao không tách DidValuesPass (loại res) sau đó tách rời RestOfCode () các chức năng phụ?
Rick Minerich 20/03/2016

25
@Rick 1. Không có kinh nghiệm của tôi, đây thực sự là một mô hình tôi đã gặp nhiều lần, 2. Nó được gán trong 'phần còn lại của mã', có thể điều đó không rõ ràng. 3. Ừm? Đó là một ví dụ? 4. Chà tôi đoán nó được đặt ra ở khía cạnh này, nhưng có thể biện minh cho điều này rất nhiều, 5. có thể làm ...
ljs

5
@ Hãy nhấn vào vấn đề là việc trở về sớm thường dễ dàng hơn là bọc mã trong một câu lệnh if lớn. Nó xuất hiện rất nhiều trong kinh nghiệm của tôi, ngay cả với tái cấu trúc thích hợp.
ljs

5
Điểm không có nhiều câu lệnh return là làm cho việc gỡ lỗi trở nên dễ dàng hơn, một giây là nó dễ đọc. Một điểm dừng ở cuối cho phép bạn xem giá trị thoát, sans ngoại lệ. Về khả năng đọc, 2 chức năng hiển thị không hoạt động giống nhau. Trả về mặc định là một chuỗi rỗng nếu! R.assass nhưng "dễ đọc hơn" sẽ thay đổi chuỗi này thành trả về null. Tác giả đã đọc sai rằng trước đó đã có một mặc định chỉ sau một vài dòng. Ngay cả trong các ví dụ tầm thường, rất dễ có các trả về mặc định không rõ ràng, một cái gì đó một lần trả lại ở cuối giúp thực thi.
Mitch

191

Tôi hiện đang làm việc trên một cơ sở mã, trong đó hai trong số những người làm việc trên nó đăng ký một cách mù quáng theo lý thuyết "một điểm thoát" và tôi có thể nói với bạn rằng từ kinh nghiệm, đó là một thực tế khủng khiếp. Nó làm cho mã cực kỳ khó bảo trì và tôi sẽ chỉ cho bạn lý do tại sao.

Với lý thuyết "một điểm thoát duy nhất", chắc chắn bạn sẽ kết thúc với mã trông như thế này:

function()
{
    HRESULT error = S_OK;

    if(SUCCEEDED(Operation1()))
    {
        if(SUCCEEDED(Operation2()))
        {
            if(SUCCEEDED(Operation3()))
            {
                if(SUCCEEDED(Operation4()))
                {
                }
                else
                {
                    error = OPERATION4FAILED;
                }
            }
            else
            {
                error = OPERATION3FAILED;
            }
        }
        else
        {
            error = OPERATION2FAILED;
        }
    }
    else
    {
        error = OPERATION1FAILED;
    }

    return error;
}

Điều này không chỉ làm cho mã rất khó theo dõi, mà bây giờ nói sau này bạn cần quay lại và thêm một thao tác trong khoảng từ 1 đến 2. Bạn phải thụt lề về toàn bộ chức năng kỳ dị và may mắn đảm bảo tất cả điều kiện if / other và niềng răng của bạn được kết hợp đúng.

Phương pháp này làm cho việc bảo trì mã vô cùng khó khăn và dễ bị lỗi.


5
@Murph: Bạn không có cách nào để biết rằng không có gì khác xảy ra sau mỗi điều kiện mà không đọc qua từng điều kiện. Thông thường tôi sẽ nói những loại chủ đề này là chủ quan, nhưng điều này rõ ràng là sai. Với một sự trở lại trên mỗi lỗi, bạn đã hoàn thành, bạn biết chính xác những gì đã xảy ra.
GEOCHET

6
@Murph: Tôi đã thấy loại mã này được sử dụng, lạm dụng và lạm dụng. Ví dụ này khá đơn giản, vì không có sự thật nếu / khác bên trong. Tất cả những loại mã này cần được phá vỡ là một "cái khác" bị lãng quên. AFAIK, mã này là thực sự cần ngoại lệ xấu.
paercebal

15
Bạn có thể cấu trúc lại nó thành cái này, giữ nguyên "độ tinh khiết" của nó: if (! SUCCEEDED (Operation1 ())) {} other error = OPERATION1FAILED; if (error! = S_OK) {if (SUCCEEDED (Operation2 ())) {} other error = OPERATION2FAILED; } if (error! = S_OK) {if (SUCCEEDED (Operation3 ())) {} other error = OPERATION3FAILED; } //Vân vân.
Joe Pineda

6
Mã này không chỉ có một điểm thoát: mỗi câu lệnh "error =" nằm trên đường dẫn thoát. Nó không chỉ là về việc thoát khỏi các chức năng, mà còn là về việc thoát khỏi bất kỳ khối hoặc chuỗi nào.
Jay Bazuzi

6
Tôi không đồng ý rằng kết quả duy nhất "không thể tránh khỏi" dẫn đến việc làm tổ sâu. Ví dụ của bạn có thể được viết dưới dạng một hàm tuyến tính đơn giản với một trả về duy nhất (không có gotos). Và nếu bạn không hoặc không thể chỉ dựa vào RAII để quản lý tài nguyên, thì việc trả lại sớm sẽ gây ra rò rỉ hoặc trùng lặp mã. Quan trọng nhất, lợi nhuận sớm làm cho nó không thực tế để khẳng định hậu điều kiện.
Adrian McCarthy

72

Lập trình có cấu trúc nói rằng bạn chỉ nên có một câu lệnh return cho mỗi hàm. Điều này là để hạn chế sự phức tạp. Nhiều người như Martin Fowler cho rằng việc viết các hàm với nhiều câu lệnh return trở nên đơn giản hơn. Ông trình bày lập luận này trong cuốn sách tái cấu trúc cổ điển mà ông đã viết. Điều này hoạt động tốt nếu bạn làm theo lời khuyên khác của anh ấy và viết các chức năng nhỏ. Tôi đồng ý với quan điểm này và chỉ những người theo chủ nghĩa lập trình có cấu trúc chặt chẽ mới tuân thủ các câu trả về duy nhất cho mỗi hàm.


44
Lập trình có cấu trúc không nói lên điều gì. Một số (nhưng không phải tất cả) những người tự mô tả họ là những người ủng hộ lập trình có cấu trúc nói rằng.
wnoise

15
"Điều này hoạt động tốt nếu bạn làm theo lời khuyên khác của anh ấy và viết các chức năng nhỏ." Đây là điểm quan trọng nhất. Các hàm nhỏ hiếm khi cần nhiều điểm trả về.

6
@wnoise +1 cho nhận xét, rất đúng. Tất cả "lập trình structude" nói rằng không sử dụng GOTO.
paxos1977

1
@ceretullis: trừ khi nó cần thiết. Tất nhiên nó không cần thiết, nhưng có thể hữu ích trong C. Hạt nhân linux sử dụng nó và vì những lý do chính đáng. GOTO đã xem xét Có hại khi nói về việc sử dụng GOTOđể di chuyển luồng điều khiển ngay cả khi chức năng tồn tại. Nó không bao giờ nói "không bao giờ sử dụng GOTO".
Esteban Küber

1
"Tất cả là về việc bỏ qua" cấu trúc "của mã." - Không, hoàn toàn ngược lại. "Thật ý nghĩa khi nói rằng họ nên tránh" - không, không.
Jim Balter

62

Như Kent Beck lưu ý khi thảo luận về các mệnh đề bảo vệ trong Các mẫu thực hiện tạo một thói quen có một điểm vào và thoát duy nhất ...

. với các phương pháp nhỏ và chủ yếu là dữ liệu cục bộ, nó không cần thiết phải bảo thủ. "

Tôi tìm thấy một hàm được viết bằng các mệnh đề bảo vệ dễ theo dõi hơn nhiều so với một if then elsecâu lệnh lồng nhau dài .


Tất nhiên, "một loạt các câu lệnh if-then-other lồng nhau" không phải là sự thay thế duy nhất cho các mệnh đề bảo vệ.
Adrian McCarthy

@AdrianMcCarthy bạn có cách nào tốt hơn không? Nó sẽ hữu ích hơn châm biếm.
shinzou

@kuhaku: Tôi không chắc là tôi gọi đó là mỉa mai. Câu trả lời gợi ý rằng đó là một hoặc / tình huống: hoặc là các mệnh đề bảo vệ hoặc các bó dài của if-then-other. Nhiều ngôn ngữ lập trình (hầu hết?) Cung cấp nhiều cách để xác định logic như vậy bên cạnh các mệnh đề bảo vệ.
Adrian McCarthy

61

Trong một chức năng không có tác dụng phụ, không có lý do chính đáng nào để có nhiều hơn một lần trả lại và bạn nên viết chúng theo kiểu chức năng. Trong một phương thức có tác dụng phụ, mọi thứ sẽ tuần tự hơn (được lập chỉ mục theo thời gian), vì vậy bạn viết theo kiểu bắt buộc, sử dụng câu lệnh return làm lệnh để dừng thực thi.

Nói cách khác, khi có thể, hãy ủng hộ phong cách này

return a > 0 ?
  positively(a):
  negatively(a);

trên này

if (a > 0)
  return positively(a);
else
  return negatively(a);

Nếu bạn thấy mình viết một vài lớp điều kiện lồng nhau, có lẽ có một cách bạn có thể cấu trúc lại, bằng cách sử dụng danh sách vị ngữ chẳng hạn. Nếu bạn thấy rằng if và elses của bạn cách xa nhau về mặt cú pháp, bạn có thể muốn chia nó thành các chức năng nhỏ hơn. Một khối điều kiện kéo dài hơn một màn hình văn bản rất khó đọc.

Không có quy tắc cứng và nhanh nào áp dụng cho mọi ngôn ngữ. Một cái gì đó giống như có một tuyên bố trả lại duy nhất sẽ không làm cho mã của bạn tốt. Nhưng mã tốt sẽ có xu hướng cho phép bạn viết các chức năng của mình theo cách đó.


6
+1 "Nếu bạn thấy rằng if và elses của bạn cách xa nhau về mặt cú pháp, bạn có thể muốn chia nó thành các chức năng nhỏ hơn."
Andres Jaan Tack

4
+1, nếu đây là một vấn đề, điều đó thường có nghĩa là bạn đang làm quá nhiều trong một chức năng. Nó thực sự làm tôi thất vọng vì đây không phải là câu trả lời được bình chọn cao nhất
Matt Briggs

1
Tuyên bố bảo vệ cũng không có bất kỳ tác dụng phụ nào, nhưng hầu hết mọi người sẽ coi chúng là hữu ích. Vì vậy, có thể có lý do để dừng thực hiện sớm ngay cả khi không có tác dụng phụ. Câu trả lời này không giải quyết đầy đủ vấn đề theo ý kiến ​​của tôi.
Maarten Bodewes


43

Tôi đã thấy nó trong các tiêu chuẩn mã hóa cho C ++, bị treo từ C, vì nếu bạn không có RAII hoặc quản lý bộ nhớ tự động khác thì bạn phải dọn sạch cho mỗi lần trả lại, có nghĩa là cắt và dán của việc dọn dẹp hoặc một goto (về mặt logic giống như 'cuối cùng' trong các ngôn ngữ được quản lý), cả hai đều được coi là hình thức xấu. Nếu thực tiễn của bạn là sử dụng các con trỏ và bộ sưu tập thông minh trong C ++ hoặc một hệ thống bộ nhớ tự động khác, thì đó không phải là lý do mạnh mẽ cho nó, và nó trở thành tất cả về khả năng đọc và nhiều hơn một cuộc gọi phán xét.


Nói tốt, mặc dù tôi tin rằng tốt hơn là sao chép các xóa khi cố gắng viết mã được tối ưu hóa cao (chẳng hạn như phần mềm tạo lưới 3d phức tạp!)
Grant Peters

1
Điều gì khiến bạn tin vào điều đó? Nếu bạn có một trình biên dịch với tối ưu hóa kém, trong đó có một số chi phí trong hội thảo auto_ptr, bạn có thể sử dụng các con trỏ đơn giản song song. Mặc dù sẽ rất kỳ quặc khi viết mã 'được tối ưu hóa' với trình biên dịch không tối ưu hóa ngay từ đầu.
Pete Kirkham

Điều này tạo ra một ngoại lệ thú vị cho quy tắc: Nếu ngôn ngữ lập trình của bạn không chứa thứ gì đó được gọi tự động ở cuối phương thức (chẳng hạn như try... finallytrong Java) và bạn cần bảo trì tài nguyên bạn có thể làm với một trở lại vào cuối của một phương thức. Trước khi bạn làm điều này, bạn nên nghiêm túc xem xét việc tái cấu trúc mã để thoát khỏi tình huống.
Maarten Bodewes

@PeteKirkham tại sao goto để dọn dẹp lại xấu? có goto có thể được sử dụng xấu, nhưng cách sử dụng cụ thể này không phải là xấu.
q126y

1
@ q126y trong C ++, không giống như RAII, nó không thành công khi ném ngoại lệ. Trong C, đó là một thực hành hoàn toàn hợp lệ. Xem stackoverflow.com/questions/379172/use-goto-or-not
Pete Kirkham

40

Tôi nghiêng về ý tưởng rằng các câu lệnh return ở giữa hàm là xấu. Bạn có thể sử dụng trả về để xây dựng một vài mệnh đề bảo vệ ở đầu hàm và dĩ nhiên nói cho trình biên dịch biết cái gì sẽ trả về ở cuối hàm mà không gặp vấn đề gì, nhưng trả về ở giữa hàm có thể dễ bị bỏ lỡ và có thể làm cho chức năng khó diễn giải hơn.


38

Có lý do chính đáng tại sao đó là một cách thực hành tốt hơn khi chỉ có một câu lệnh return trong một hàm không?

Vâng , có:

  • Điểm thoát duy nhất cung cấp một nơi tuyệt vời để khẳng định các điều kiện hậu của bạn.
  • Có thể đặt một điểm dừng gỡ lỗi trên một lần trả về ở cuối hàm thường hữu ích.
  • Trả về ít hơn có nghĩa là ít phức tạp hơn. Mã tuyến tính thường đơn giản hơn để hiểu.
  • Nếu cố gắng đơn giản hóa một hàm thành một lần trả lại gây ra sự phức tạp, thì đó là khuyến khích tái cấu trúc thành các hàm nhỏ hơn, tổng quát hơn, dễ hiểu hơn.
  • Nếu bạn đang sử dụng ngôn ngữ không có công cụ phá hủy hoặc nếu bạn không sử dụng RAII, thì một lần trả lại sẽ giảm số lượng địa điểm bạn phải dọn dẹp.
  • Một số ngôn ngữ yêu cầu một điểm thoát duy nhất (ví dụ: Pascal và Eiffel).

Câu hỏi thường được đặt ra như một sự phân đôi sai giữa nhiều câu trả lời hoặc được lồng sâu nếu các câu lệnh. Hầu như luôn luôn có một giải pháp thứ ba rất tuyến tính (không làm tổ sâu) chỉ với một điểm thoát duy nhất.

Cập nhật : Rõ ràng hướng dẫn MISRA cũng thúc đẩy lối thoát đơn .

Để rõ ràng, tôi không nói rằng luôn luôn sai khi có nhiều lợi nhuận. Nhưng đưa ra các giải pháp tương đương khác, có rất nhiều lý do tốt để thích một giải pháp duy nhất.


2
một lý do chính đáng khác, có lẽ là tốt nhất trong những ngày này, để có một tuyên bố hoàn trả duy nhất là đăng nhập. nếu bạn muốn thêm ghi nhật ký vào một phương thức, bạn có thể đặt một câu lệnh nhật ký duy nhất truyền tải những gì phương thức trả về.
vào

Làm thế nào phổ biến là tuyên bố FORTRAN ENTRY? Xem docs.oracle.com/cd/E19957-01/805-4939/6j4m0vn99/index.html . Và nếu bạn thích phiêu lưu, bạn có thể đăng nhập các phương pháp với AOP và tư vấn sau
Eric Jablow

1
+1 2 điểm đầu tiên là đủ để thuyết phục tôi. Điều đó với đoạn cuối thứ hai. Tôi sẽ không đồng ý với yếu tố đăng nhập vì lý do tương tự vì tôi không khuyến khích các điều kiện lồng nhau sâu sắc vì chúng khuyến khích phá vỡ quy tắc trách nhiệm duy nhất, đó là lý do chính của đa hình được đưa vào OOP.
Francis Rodgers

Tôi chỉ muốn thêm rằng với C # và Hợp đồng mã, vấn đề hậu điều kiện là không phát hành, vì bạn vẫn có thể sử dụng Contract.Ensuresvới nhiều điểm trả về.
julealgon

1
@ q126y: Nếu bạn đang sử dụng gotođể lấy mã dọn dẹp chung, thì có lẽ bạn đã đơn giản hóa chức năng để có một mã returnở cuối mã dọn dẹp. Vì vậy, bạn có thể nói rằng bạn đã giải quyết vấn đề với goto, nhưng tôi muốn nói rằng bạn đã giải quyết nó bằng cách đơn giản hóa thành một return.
Adrian McCarthy

33

Có một điểm thoát duy nhất cung cấp một lợi thế trong việc gỡ lỗi, bởi vì nó cho phép bạn đặt một điểm dừng duy nhất ở cuối hàm để xem giá trị nào thực sự sẽ được trả về.


6
BRAVO! Bạn là người duy nhất đề cập đến lý do khách quan này . Đó là lý do tôi thích các điểm thoát đơn hơn so với nhiều điểm thoát. Nếu trình gỡ lỗi của tôi có thể đặt điểm dừng trên bất kỳ điểm thoát nào, tôi có thể thích nhiều điểm thoát hơn. Ý kiến ​​hiện tại của tôi là những người mã hóa nhiều điểm thoát làm điều đó vì lợi ích của họ với chi phí của những người khác phải sử dụng trình gỡ lỗi trên mã của họ (và vâng tôi đang nói về tất cả những người đóng góp mã nguồn mở viết mã với nhiều điểm thoát.)
MikeSchinkel

3
ĐÚNG. Tôi đang thêm mã đăng nhập vào một hệ thống không liên tục hoạt động sai trong sản xuất (nơi tôi không thể bước qua). Nếu người viết mã trước đó đã sử dụng lối thoát đơn, thì nó sẽ dễ dàng hơn.
Michael Blackburn

3
Đúng, trong gỡ lỗi nó là hữu ích. Nhưng trong thực tế, trong hầu hết các trường hợp, tôi có thể đặt điểm dừng trong chức năng gọi, ngay sau cuộc gọi - có hiệu quả tương tự. (Và vị trí đó được tìm thấy trên ngăn xếp cuộc gọi, tất nhiên.) YMMV.
foo

Điều này là trừ khi trình gỡ lỗi của bạn cung cấp chức năng trả lại hoặc trả lại từng bước (và mỗi trình gỡ lỗi thực hiện theo như tôi biết), cho thấy giá trị được trả về ngay sau khi được trả về. Thay đổi giá trị sau đó có thể là một chút khó khăn nếu nó không được gán cho một biến.
Maarten Bodewes 17/03/2016

7
Tôi đã không thấy trình gỡ lỗi trong một thời gian dài, điều đó sẽ không cho phép bạn đặt điểm dừng trên "đóng" của phương thức (kết thúc, dấu ngoặc nhọn, bất kể ngôn ngữ của bạn) và nhấn điểm dừng đó bất kể ở đâu, hoặc bao nhiêu , trả lại stHRets là trong phương pháp. Ngoài ra, ngay cả khi chức năng của bạn chỉ có một lần trả về, điều đó không có nghĩa là bạn không thể thoát khỏi chức năng với một ngoại lệ (rõ ràng hoặc được kế thừa). Vì vậy, tôi nghĩ rằng đây không thực sự là một điểm hợp lệ.
Scott Gartner

19

Nói chung, tôi cố gắng chỉ có một điểm thoát duy nhất từ ​​một chức năng. Tuy nhiên, đôi khi, làm như vậy thực sự kết thúc việc tạo ra một cơ thể chức năng phức tạp hơn mức cần thiết, trong trường hợp đó tốt hơn là có nhiều điểm thoát. Nó thực sự phải là một "cuộc gọi phán xét" dựa trên mức độ phức tạp, nhưng mục tiêu phải là càng ít điểm thoát càng tốt mà không làm mất đi sự phức tạp và dễ hiểu.


"Nói chung tôi cố gắng chỉ có một điểm thoát duy nhất từ ​​một chức năng" - tại sao? "mục tiêu nên càng ít điểm thoát càng tốt" - tại sao? Và tại sao 19 người bỏ phiếu không trả lời này?
Jim Balter

@JimBalter Cuối cùng, nó sôi sục theo sở thích cá nhân. Nhiều điểm thoát thường dẫn đến một phương thức phức tạp hơn (mặc dù không phải lúc nào cũng vậy) và khiến người khác khó hiểu hơn.
Scott Dorman

"nó sôi sục theo sở thích cá nhân." - Nói cách khác, bạn không thể đưa ra lý do. "Nhiều điểm thoát thường dẫn đến một phương thức phức tạp hơn (mặc dù không phải lúc nào cũng vậy)" - Thực tế, chúng không. Với hai hàm tương đương logic, một hàm có mệnh đề bảo vệ và một hàm thoát đơn, hàm thứ hai sẽ có độ phức tạp chu kỳ cao hơn, trong đó nhiều nghiên cứu cho thấy kết quả mã dễ bị lỗi và khó hiểu hơn. Bạn sẽ được hưởng lợi từ việc đọc các phản ứng khác ở đây.
Jim Balter


14

Sở thích của tôi sẽ là cho một lối thoát duy nhất trừ khi nó thực sự làm phức tạp mọi thứ. Tôi đã thấy rằng trong một số trường hợp, nhiều điểm tồn tại có thể che giấu các vấn đề thiết kế quan trọng khác:

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

Khi thấy mã này, tôi sẽ hỏi ngay:

  • Là 'foo' bao giờ null?
  • Nếu vậy, có bao nhiêu khách hàng của 'DoStuff' đã từng gọi hàm với null 'foo'?

Tùy thuộc vào câu trả lời cho những câu hỏi này mà có thể là

  1. kiểm tra là vô nghĩa vì nó không bao giờ là đúng (nghĩa là nó phải là một khẳng định)
  2. kiểm tra là rất hiếm khi đúng và vì vậy có thể tốt hơn để thay đổi các chức năng người gọi cụ thể vì họ có thể nên thực hiện một số hành động khác.

Trong cả hai trường hợp trên, mã có thể được làm lại với một xác nhận để đảm bảo rằng 'foo' không bao giờ là null và những người gọi có liên quan đã thay đổi.

Có hai lý do khác (tôi nghĩ cụ thể đối với mã C ++) khi nhiều tồn tại thực sự có thể có ảnh hưởng tiêu cực . Chúng là kích thước mã và tối ưu hóa trình biên dịch.

Một đối tượng không phải POD C ++ trong phạm vi ở lối ra của hàm sẽ có hàm hủy của nó được gọi. Trong trường hợp có một vài câu lệnh return, có thể có trường hợp có các đối tượng khác nhau trong phạm vi và do đó, danh sách các hàm hủy sẽ gọi sẽ khác nhau. Do đó trình biên dịch cần tạo mã cho mỗi câu lệnh return:

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

Nếu kích thước mã là một vấn đề - thì đây có thể là điều đáng để tránh.

Vấn đề khác liên quan đến "Tối ưu hóa giá trị trả về được đặt tên" (còn gọi là Sao chép Elision, ISO C ++ '03 12.8 / 15). C ++ cho phép thực hiện bỏ qua việc gọi hàm tạo sao chép nếu có thể:

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

Chỉ cần lấy mã như hiện tại, đối tượng 'a1' được xây dựng trong 'foo' và sau đó cấu trúc sao chép của nó sẽ được gọi để xây dựng 'a2'. Tuy nhiên, sao chép bản sao cho phép trình biên dịch xây dựng 'a1' ở cùng một vị trí trên ngăn xếp là 'a2'. Do đó, không cần phải "sao chép" đối tượng khi hàm trả về.

Nhiều điểm thoát làm phức tạp công việc của trình biên dịch khi cố gắng phát hiện điều này và ít nhất là đối với phiên bản tương đối gần đây của VC ++, việc tối ưu hóa không diễn ra khi cơ thể hàm có nhiều trả về. Xem Tối ưu hóa giá trị trả về được đặt tên trong Visual C ++ 2005 để biết thêm chi tiết.


1
Nếu bạn lấy tất cả trừ dtor cuối cùng ra khỏi ví dụ C ++ của bạn, mã để hủy B và sau đó là C và B, sẽ vẫn phải được tạo khi phạm vi của câu lệnh if kết thúc, vì vậy bạn thực sự không thu được gì khi không có nhiều trả về .
Nhật thực

4
+1 Và chờ đợi ở cuối danh sách, chúng tôi có lý do THỰC SỰ thực hành mã hóa này tồn tại - NRVO. Tuy nhiên, đây là một tối ưu hóa vi mô; và, giống như tất cả các thực hành tối ưu hóa vi mô, có lẽ đã được bắt đầu bởi một "chuyên gia" 50 tuổi, người đã từng lập trình trên PDP-8 300 kHz, và không hiểu tầm quan trọng của mã sạch và có cấu trúc. Nói chung, hãy nghe lời khuyên của Chris S và sử dụng nhiều câu trả lời bất cứ khi nào nó có ý nghĩa.
BlueRaja - Daniel Pflughoeft

Mặc dù tôi không đồng ý với sở thích của bạn (trong suy nghĩ của tôi, đề xuất Khẳng định của bạn cũng là điểm quay lại, như throw new ArgumentNullException()trong C # trong trường hợp này), tôi thực sự thích những cân nhắc khác của bạn, tất cả đều hợp lệ với tôi và có thể rất quan trọng trong một số bối cảnh thích hợp.
julealgon

Đây là nghẹt thở đầy rơm. Câu hỏi tại sao foođang được thử nghiệm không liên quan gì đến chủ đề này, đó là liệu có nên làm if (foo == NULL) return; dowork; hay khôngif (foo != NULL) { dowork; }
Jim Balter

11

Có một điểm thoát duy nhất làm giảm Độ phức tạp theo chu kỳ và do đó, theo lý thuyết , sẽ giảm khả năng bạn sẽ đưa các lỗi vào mã của mình khi bạn thay đổi nó. Tuy nhiên, thực tế có xu hướng đề xuất rằng một cách tiếp cận thực tế hơn là cần thiết. Do đó, tôi có xu hướng nhắm đến một điểm thoát duy nhất, nhưng cho phép mã của tôi có một vài điểm nếu điều đó dễ đọc hơn.


Rất sâu sắc. Mặc dù, tôi cảm thấy rằng cho đến khi một lập trình viên biết khi nào nên sử dụng nhiều điểm thoát, họ nên bị ràng buộc với một điểm.
Rick Minerich

5
Không hẳn vậy. Độ phức tạp chu kỳ của "if (...) return; ... return;" giống như "if (...) {...} return;". Cả hai đều có hai con đường xuyên qua chúng.
Steve Emmerson

11

Tôi buộc bản thân chỉ sử dụng một returntuyên bố, vì nó sẽ tạo ra mùi mã. Hãy để tôi giải thích:

function isCorrect($param1, $param2, $param3) {
    $toret = false;
    if ($param1 != $param2) {
        if ($param1 == ($param3 * 2)) {
            if ($param2 == ($param3 / 3)) {
                $toret = true;
            } else {
                $error = 'Error 3';
            }
        } else {
            $error = 'Error 2';
        }
    } else {
        $error = 'Error 1';
    }
    return $toret;
}

(Các điều kiện là đơn giản ...)

Càng nhiều điều kiện, chức năng càng lớn thì càng khó đọc. Vì vậy, nếu bạn cảm nhận được mùi mã, bạn sẽ nhận ra nó và muốn cấu trúc lại mã. Hai giải pháp có thể là:

  • Nhiều lợi nhuận
  • Tái cấu trúc thành các chức năng riêng biệt

Nhiều lợi nhuận

function isCorrect($param1, $param2, $param3) {
    if ($param1 == $param2)       { $error = 'Error 1'; return false; }
    if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
    if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
    return true;
}

Chức năng riêng biệt

function isEqual($param1, $param2) {
    return $param1 == $param2;
}

function isDouble($param1, $param2) {
    return $param1 == ($param2 * 2);
}

function isThird($param1, $param2) {
    return $param1 == ($param2 / 3);
}

function isCorrect($param1, $param2, $param3) {
    return !isEqual($param1, $param2)
        && isDouble($param1, $param3)
        && isThird($param2, $param3);
}

Cấp, nó dài hơn và hơi lộn xộn, nhưng trong quá trình tái cấu trúc chức năng theo cách này, chúng tôi đã

  • đã tạo ra một số chức năng có thể sử dụng lại,
  • làm cho chức năng của con người dễ đọc hơn, và
  • trọng tâm của các chức năng là tại sao các giá trị là chính xác.

5
-1: Ví dụ xấu. Bạn đã bỏ qua việc xử lý thông báo lỗi. Nếu điều đó là không cần thiết, isC chính xác có thể được biểu thị giống như return xx && yy && zz; trong đó xx, yy và z là các biểu thức isEqual, isDouble và isThird.
kauppi

10

Tôi muốn nói rằng bạn nên có nhiều như yêu cầu, hoặc bất kỳ điều gì làm cho mã sạch hơn (như các mệnh đề bảo vệ ).

Cá nhân tôi chưa bao giờ nghe / thấy bất kỳ "thực hành tốt nhất" nào nói rằng bạn chỉ nên có một tuyên bố trở lại.

Đối với hầu hết các phần, tôi có xu hướng thoát khỏi một chức năng càng sớm càng tốt dựa trên một đường dẫn logic (mệnh đề bảo vệ là một ví dụ tuyệt vời về điều này).


10

Tôi tin rằng nhiều lợi nhuận thường là tốt (trong mã mà tôi viết bằng C #). Kiểu trả về một lần là một sự tiếp quản từ C. Nhưng có lẽ bạn không mã hóa bằng C.

Không có luật chỉ yêu cầu một điểm thoát cho một phương thức trong tất cả các ngôn ngữ lập trình . Một số người nhấn mạnh vào tính ưu việt của phong cách này, và đôi khi họ nâng nó thành "quy tắc" hoặc "luật" nhưng niềm tin này không được hỗ trợ bởi bất kỳ bằng chứng hay nghiên cứu nào.

Nhiều kiểu trả về có thể là một thói quen xấu trong mã C, trong đó các tài nguyên phải được phân bổ rõ ràng, nhưng các ngôn ngữ như Java, C #, Python hoặc JavaScript có các cấu trúc như bộ sưu tập và try..finallykhối rác tự động (và usingcác khối trong C # ) và đối số này không được áp dụng - trong các ngôn ngữ này, rất hiếm khi cần xử lý tài nguyên thủ công tập trung.

Có những trường hợp một lợi nhuận duy nhất dễ đọc hơn và những trường hợp không trả lại. Xem nếu nó làm giảm số lượng dòng mã, làm cho logic rõ ràng hơn hoặc giảm số lượng dấu ngoặc và thụt lề hoặc các biến tạm thời.

Do đó, sử dụng càng nhiều lợi nhuận càng phù hợp với sự nhạy cảm nghệ thuật của bạn, bởi vì đây là vấn đề về bố cục và khả năng đọc, không phải là vấn đề kỹ thuật.

Tôi đã nói về điều này ở độ dài lớn hơn trên blog của tôi .


10

Có những điều tốt để nói về việc có một điểm thoát duy nhất, giống như có những điều không tốt để nói về lập trình "mũi tên" không thể tránh khỏi mà kết quả.

Nếu sử dụng nhiều điểm thoát trong quá trình xác thực đầu vào hoặc phân bổ tài nguyên, tôi cố gắng đặt tất cả các 'lỗi thoát' ở trên cùng của hàm.

Cả bài viết Lập trình Spartan của "SSDSLPedia" và bài viết điểm thoát chức năng duy nhất của "Wiki của Kho lưu trữ mẫu Portland" đều có một số tranh luận sâu sắc xung quanh vấn đề này. Ngoài ra, tất nhiên, có bài này để xem xét.

Ví dụ, nếu bạn thực sự muốn một điểm thoát (trong bất kỳ ngôn ngữ không có ngoại lệ nào) để phát hành tài nguyên ở một nơi duy nhất, tôi thấy ứng dụng cẩn thận của goto là tốt; xem ví dụ ví dụ khá giả định này (được nén để lưu màn hình bất động sản):

int f(int y) {
    int value = -1;
    void *data = NULL;

    if (y < 0)
        goto clean;

    if ((data = malloc(123)) == NULL)
        goto clean;

    /* More code */

    value = 1;
clean:
   free(data);
   return value;
}

Cá nhân tôi, nói chung, không thích lập trình mũi tên hơn tôi không thích nhiều điểm thoát, mặc dù cả hai đều hữu ích khi được áp dụng đúng. Tất nhiên, tốt nhất là cấu trúc chương trình của bạn để không yêu cầu. Chia nhỏ chức năng của bạn thành nhiều phần thường giúp :)

Mặc dù khi làm như vậy, tôi thấy rằng cuối cùng tôi cũng có nhiều điểm thoát như trong ví dụ này, trong đó một số hàm lớn hơn đã được chia thành một số hàm nhỏ hơn:

int g(int y) {
  value = 0;

  if ((value = g0(y, value)) == -1)
    return -1;

  if ((value = g1(y, value)) == -1)
    return -1;

  return g2(y, value);
}

Tùy thuộc vào dự án hoặc hướng dẫn mã hóa, hầu hết mã của nồi hơi có thể được thay thế bằng macro. Là một lưu ý phụ, phá vỡ nó theo cách này làm cho các chức năng g0, g1, g2 rất dễ dàng để kiểm tra riêng lẻ.

Rõ ràng, trong một ngôn ngữ OO và ngôn ngữ kích hoạt ngoại lệ, tôi sẽ không sử dụng các câu lệnh if như thế (hoặc hoàn toàn, nếu tôi có thể thoát khỏi nó với một chút nỗ lực), và mã sẽ đơn giản hơn nhiều. Và không mũi tên. Và hầu hết lợi nhuận không phải là cuối cùng có thể là ngoại lệ.

Nói ngắn gọn;

  • Ít lợi nhuận tốt hơn nhiều lợi nhuận
  • Nhiều hơn một lần trở lại là tốt hơn so với mũi tên lớn, và các điều khoản bảo vệ nói chung là ok.
  • Các ngoại lệ có thể / nên thay thế hầu hết các 'mệnh đề bảo vệ' khi có thể.

Ví dụ gặp sự cố cho y <0, vì nó cố gắng giải phóng con trỏ NULL ;-)
Erich Kitzmueller

2
opengroup.org/onlinepub/009695399/fifts/free.html "Nếu ptr là một con trỏ null, sẽ không có hành động nào xảy ra."
Henrik Gustafsson

1
Không, nó sẽ không sụp đổ, bởi vì chuyển NULL sang miễn phí là một định nghĩa không xác định. Đó là một quan niệm sai lầm phổ biến khó chịu mà bạn phải kiểm tra NULL trước.
hlovdal

Mẫu "mũi tên" không phải là một sự thay thế không thể tránh khỏi. Đây là một sự phân đôi giả.
Adrian McCarthy

9

Bạn biết câu ngạn ngữ - vẻ đẹp là trong mắt của kẻ si tình .

Một số người chửi bới NetBeans và một số bởi IntelliJ IDEA , một số bởi Python và một số bởi PHP .

Trong một số cửa hàng, bạn có thể mất việc nếu bạn khăng khăng làm việc này:

public void hello()
{
   if (....)
   {
      ....
   }
}

Câu hỏi là tất cả về tầm nhìn và khả năng bảo trì.

Tôi nghiện sử dụng đại số boolean để giảm và đơn giản hóa logic và sử dụng các máy trạng thái. Tuy nhiên, có những đồng nghiệp trong quá khứ tin rằng việc tôi sử dụng "các kỹ thuật toán học" trong mã hóa là không phù hợp, bởi vì nó sẽ không thể nhìn thấy và duy trì được. Và đó sẽ là một thực hành tồi. Xin lỗi mọi người, các kỹ thuật tôi sử dụng rất dễ thấy và có thể duy trì được - bởi vì khi tôi trở lại mã sáu tháng sau, tôi sẽ hiểu mã rõ ràng hơn là nhìn thấy một mớ mì spaghetti tục ngữ.

Này anh bạn (giống như một khách hàng cũ từng nói) làm những gì bạn muốn miễn là bạn biết cách sửa nó khi tôi cần bạn sửa nó.

Tôi nhớ 20 năm trước, một đồng nghiệp của tôi đã bị sa thải vì sử dụng chiến lược phát triển nhanh ngày nay . Ông đã có một kế hoạch gia tăng tỉ mỉ. Nhưng người quản lý của anh ta đã hét vào mặt anh ta "Bạn không thể tăng dần các tính năng cho người dùng! Bạn phải gắn bó với thác nước ." Phản ứng của ông với người quản lý là sự phát triển gia tăng sẽ chính xác hơn với nhu cầu của khách hàng. Ông tin vào việc phát triển cho nhu cầu của khách hàng, nhưng người quản lý tin vào mã hóa theo "yêu cầu của khách hàng".

Chúng tôi thường xuyên phạm tội vì phá vỡ chuẩn hóa dữ liệu, ranh giới MVPMVC . Chúng tôi nội tuyến thay vì xây dựng một chức năng. Chúng tôi đi đường tắt.

Cá nhân, tôi tin rằng PHP là thực tiễn tồi, nhưng tôi biết gì. Tất cả các lý luận lý thuyết tập trung vào việc cố gắng hoàn thành một bộ quy tắc

chất lượng = độ chính xác, khả năng duy trì và lợi nhuận.

Tất cả các quy tắc khác mờ dần vào nền. Và tất nhiên quy tắc này không bao giờ mất dần:

Lười biếng là đức tính của một lập trình viên giỏi.


1
"Này bạn thân (như một khách hàng cũ thường nói) làm những gì bạn muốn miễn là bạn biết cách sửa nó khi tôi cần bạn sửa nó." Vấn đề: thường không phải bạn là người 'sửa chữa' nó.
Dan Barron

+1 về câu trả lời này vì tôi đồng ý với nơi bạn sẽ đến nhưng không nhất thiết là bạn đến đó bằng cách nào. Tôi sẽ tranh luận rằng có mức độ hiểu biết. tức là sự hiểu biết mà nhân viên A có sau 5 năm kinh nghiệm lập trình và 5 năm với một công ty rất khác so với nhân viên B, một sinh viên mới tốt nghiệp đại học chỉ mới bắt đầu với một công ty. Quan điểm của tôi là nếu nhân viên A là người duy nhất có thể hiểu được mã, thì nó KHÔNG thể duy trì được và vì vậy tất cả chúng ta nên cố gắng viết mã mà nhân viên B có thể hiểu. Đây là nơi nghệ thuật thực sự trong phần mềm.
Francis Rodgers

9

Tôi nghiêng về phía sử dụng các mệnh đề bảo vệ để quay lại sớm và thoát ra khi kết thúc một phương thức. Quy tắc nhập và thoát đơn có ý nghĩa lịch sử và đặc biệt hữu ích khi xử lý mã kế thừa chạy tới 10 trang A4 cho một phương thức C ++ duy nhất có nhiều trả về (và nhiều lỗi). Gần đây, thực hành tốt được chấp nhận là giữ cho các phương thức nhỏ làm cho nhiều lối thoát ít cản trở sự hiểu biết. Trong ví dụ Kronoz sau được sao chép từ phía trên, câu hỏi là điều gì xảy ra trong // Phần còn lại của mã ... ?:

void string fooBar(string s, int? i) {

  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Tôi nhận ra ví dụ này hơi khó hiểu nhưng tôi sẽ cố gắng cấu trúc lại vòng lặp foreach thành một câu lệnh LINQ mà sau đó có thể được coi là một mệnh đề bảo vệ. Một lần nữa, trong một ví dụ contrived mục đích của mã này là không rõ ràng và someFunction () có thể có một số tác dụng phụ khác hoặc kết quả có thể được sử dụng trong // Phần còn lại của mã ... .

if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;

Đưa ra chức năng tái cấu trúc sau:

void string fooBar(string s, int? i) {

  if (string.IsNullOrEmpty(s) || i == null) return null;
  if (someFunction(s, i).Any(r => !r.Passed)) return null;

  // Rest of code...

  return ret;
}

C ++ không có ngoại lệ? Vậy thì tại sao bạn lại quay trở lại nullthay vì ném một ngoại lệ cho thấy rằng đối số không được chấp nhận?
Maarten Bodewes

1
Như tôi đã chỉ ra, mã ví dụ được sao chép từ một câu trả lời trước đó ( stackoverflow.com/a/36729/132599 ). Ví dụ ban đầu trả về null và tái cấu trúc để đưa ra các ngoại lệ đối số không phải là vấn đề quan trọng đến mức tôi đang cố gắng thực hiện hoặc cho câu hỏi ban đầu. Như một vấn đề thực tiễn tốt, sau đó, tôi thường (trong C #) sẽ ném một ArgumentNullException vào một mệnh đề bảo vệ thay vì trả về giá trị null.
David Clarke

7

Một lý do tốt mà tôi có thể nghĩ đến là để bảo trì mã: bạn có một điểm thoát duy nhất. Nếu bạn muốn thay đổi định dạng của kết quả, ..., việc thực hiện sẽ đơn giản hơn nhiều. Ngoài ra, để gỡ lỗi, bạn chỉ cần đặt một điểm dừng ở đó :)

Phải nói rằng, tôi đã từng phải làm việc trong một thư viện nơi các tiêu chuẩn mã hóa áp đặt 'một tuyên bố trả về cho mỗi chức năng' và tôi thấy điều đó khá khó khăn. Tôi viết rất nhiều mã tính toán số và thường có 'trường hợp đặc biệt', vì vậy mã cuối cùng khá khó theo dõi ...


Điều đó không thực sự tạo ra sự khác biệt. Nếu bạn thay đổi loại biến cục bộ được trả về, thì bạn sẽ phải sửa tất cả các phép gán cho biến cục bộ đó. Và có lẽ tốt hơn là định nghĩa một phương thức với một chữ ký khác, vì bạn cũng sẽ phải sửa tất cả các lệnh gọi phương thức.
Maarten Bodewes

@ MaartenBodewes-Owlstead - Nó có thể tạo ra sự khác biệt. Để chỉ đặt tên cho hai ví dụ mà bạn không phải sửa tất cả các phép gán cho biến cục bộ hoặc thay đổi các lệnh gọi phương thức, hàm có thể trả về một ngày dưới dạng một chuỗi (biến cục bộ sẽ là một ngày thực tế, được định dạng dưới dạng một chuỗi tại thời điểm cuối cùng), hoặc nó có thể trả về một số thập phân và bạn muốn thay đổi số vị trí thập phân.
nnnnnn

@nnnnnn OK, nếu bạn muốn xử lý bài đăng trên đầu ra ... Nhưng tôi sẽ chỉ tạo một phương thức mới thực hiện xử lý bài và để lại phương thức cũ. Điều đó chỉ khó hơn một chút để cấu trúc lại, nhưng bạn sẽ phải kiểm tra xem các cuộc gọi khác có tương thích với định dạng mới hay không. Nhưng đó là một lý do hợp lệ không hơn không kém.
Maarten Bodewes

7

Nhiều điểm thoát là tốt cho các chức năng đủ nhỏ - nghĩa là, một chức năng có thể được xem trên toàn bộ một chiều dài màn hình. Nếu một hàm dài tương tự bao gồm nhiều điểm thoát, thì đó là dấu hiệu cho thấy hàm này có thể được cắt nhỏ hơn nữa.

Điều đó nói rằng tôi tránh các chức năng đa lối ra trừ khi thực sự cần thiết . Tôi đã cảm thấy đau đớn của các lỗi là do một số trở lại đi lạc trong một số dòng tối nghĩa trong các chức năng phức tạp hơn.


6

Tôi đã làm việc với các tiêu chuẩn mã hóa khủng khiếp đã tạo ra một lối thoát duy nhất cho bạn và kết quả là hầu như luôn luôn không có cấu trúc spaghetti nếu chức năng là bất cứ điều gì ngoài tầm thường - bạn kết thúc với rất nhiều lần nghỉ và tiếp tục cản trở.


Không đề cập đến việc phải nói với tâm trí của bạn để bỏ qua iftuyên bố trước mỗi cuộc gọi phương thức có trả lại thành công hay không :(
Maarten Bodewes

6

Điểm thoát đơn - tất cả những thứ khác bằng nhau - làm cho mã dễ đọc hơn đáng kể. Nhưng có một nhược điểm: xây dựng phổ biến

resulttype res;
if if if...
return res;

là giả, "res =" không tốt hơn "trả lại". Nó có câu lệnh return duy nhất, nhưng nhiều điểm trong đó hàm thực sự kết thúc.

Nếu bạn có hàm có nhiều trả về (hoặc "res =" s), thì thường nên chia nó thành nhiều hàm nhỏ hơn với một điểm thoát duy nhất.


6

Chính sách thông thường của tôi là chỉ có một câu lệnh return ở cuối hàm trừ khi độ phức tạp của mã giảm đi rất nhiều bằng cách thêm nhiều hơn. Trên thực tế, tôi khá hâm mộ Eiffel, thực thi quy tắc trả về duy nhất bằng cách không có tuyên bố trả về (chỉ có một biến 'kết quả' được tạo tự động để đưa kết quả của bạn vào).

Chắc chắn có những trường hợp mã có thể được làm rõ ràng hơn với nhiều lợi nhuận hơn phiên bản rõ ràng mà không có chúng. Người ta có thể lập luận rằng cần phải làm lại nhiều hơn nếu bạn có một hàm quá phức tạp để có thể hiểu được nếu không có nhiều câu trả về, nhưng đôi khi thật tốt khi thực dụng về những điều đó.


5

Nếu bạn kết thúc với nhiều hơn một vài lần trả về, có thể có gì đó không đúng với mã của bạn. Mặt khác, tôi đồng ý rằng đôi khi thật tuyệt khi có thể quay lại từ nhiều nơi trong chương trình con, đặc biệt là khi nó làm cho mã sạch hơn.

Perl 6: Ví dụ xấu

sub Int_to_String( Int i ){
  given( i ){
    when 0 { return "zero" }
    when 1 { return "one" }
    when 2 { return "two" }
    when 3 { return "three" }
    when 4 { return "four" }
    ...
    default { return undef }
  }
}

sẽ được viết tốt hơn như thế này

Perl 6: Ví dụ hay

@Int_to_String = qw{
  zero
  one
  two
  three
  four
  ...
}
sub Int_to_String( Int i ){
  return undef if i < 0;
  return undef unless i < @Int_to_String.length;
  return @Int_to_String[i]
}

Lưu ý đây chỉ là một ví dụ nhanh


Ok Tại sao điều này đã được bỏ phiếu? Nó không giống như nó không phải là một ý kiến.
Brad Gilbert

5

Tôi bỏ phiếu cho một lần trở lại vào cuối như một hướng dẫn. Điều này giúp xử lý dọn dẹp mã phổ biến ... Ví dụ: hãy xem mã sau đây ...

void ProcessMyFile (char *szFileName)
{
   FILE *fp = NULL;
   char *pbyBuffer = NULL:

   do {

      fp = fopen (szFileName, "r");

      if (NULL == fp) {

         break;
      }

      pbyBuffer = malloc (__SOME__SIZE___);

      if (NULL == pbyBuffer) {

         break;
      }

      /*** Do some processing with file ***/

   } while (0);

   if (pbyBuffer) {

      free (pbyBuffer);
   }

   if (fp) {

      fclose (fp);
   }
}

Bạn bỏ phiếu cho một lần trả lại - bằng mã C. Nhưng điều gì sẽ xảy ra nếu bạn đang viết mã bằng một ngôn ngữ có bộ sưu tập Rác và thử..tất cả các khối?
Anthony

4

Đây có lẽ là một viễn cảnh khác thường, nhưng tôi nghĩ rằng bất cứ ai tin rằng nhiều tuyên bố hoàn trả sẽ được ưa chuộng thì không bao giờ phải sử dụng trình gỡ lỗi trên bộ vi xử lý chỉ hỗ trợ 4 điểm dừng phần cứng. ;-)

Mặc dù các vấn đề về "mã mũi tên" là hoàn toàn chính xác, một vấn đề dường như biến mất khi sử dụng nhiều câu lệnh trả về là trong trường hợp bạn đang sử dụng trình gỡ lỗi. Bạn không có vị trí bắt tất cả thuận tiện để đặt điểm dừng để đảm bảo rằng bạn sẽ thấy lối ra và do đó điều kiện quay trở lại.


5
Đó chỉ là một loại tối ưu hóa sớm khác nhau. Bạn không bao giờ nên tối ưu hóa cho trường hợp đặc biệt. Nếu bạn thấy mình gỡ lỗi một phần mã cụ thể rất nhiều, thì có nhiều điều sai với nó hơn là chỉ có bao nhiêu điểm thoát.
Nêm

Phụ thuộc vào trình gỡ lỗi của bạn quá.
Maarten Bodewes

4

Bạn càng có nhiều câu lệnh return trong hàm, độ phức tạp càng cao trong một phương thức đó. Nếu bạn thấy mình tự hỏi nếu bạn có quá nhiều câu lệnh return, bạn có thể tự hỏi mình nếu bạn có quá nhiều dòng mã trong hàm đó.

Nhưng, không, không có gì sai với một / nhiều báo cáo trả lại. Trong một số ngôn ngữ, đó là một cách thực hành tốt hơn (C ++) so với các ngôn ngữ khác (C).

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.