Sử dụng phương thức return bên trong một phương thức void có phải là một phương pháp không tốt?


92

Hãy tưởng tượng đoạn mã sau:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

Sử dụng trả về bên trong phương thức void có được không? Nó có bất kỳ hình phạt hiệu suất? Hoặc sẽ tốt hơn nếu viết một mã như thế này:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}
c#  return  void 

1
Còn về: void DoThis () {if (isValid) DoThat (); }
Dscoduc

30
tưởng tượng mã? Tại sao? Nó ở ngay đó! :-D
STW

Đây là một câu hỏi hay, tôi luôn nghĩ rằng việc sử dụng return có phải là phương pháp hay không; để thoát khỏi phương thức hoặc hàm. Đặc biệt là trong phương pháp khai thác dữ liệu LINQ có nhiều kết quả IQueryable <T> và tất cả chúng phụ thuộc vào nhau. Nếu một trong số chúng không có kết quả, hãy cảnh báo và thoát.
Cheung

Câu trả lời:


173

Trả về trong một phương thức void không phải là xấu, là một thực tế phổ biến để đảo ngược các ifcâu lệnh để giảm việc lồng vào nhau .

Và việc ít lồng ghép hơn vào các phương pháp của bạn sẽ cải thiện khả năng đọc và khả năng bảo trì mã.

Trên thực tế, nếu bạn có một phương thức void mà không có bất kỳ câu lệnh trả về nào, trình biên dịch sẽ luôn tạo ra một lệnh ret ở cuối nó.


33

Có một lý do tuyệt vời khác để sử dụng bảo vệ (trái ngược với mã lồng nhau): Nếu một lập trình viên khác thêm mã vào hàm của bạn, họ đang làm việc trong một môi trường an toàn hơn.

Xem xét:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

đấu với:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

Bây giờ, hãy tưởng tượng một lập trình viên khác thêm dòng: obj.DoSomethingElse ();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

Rõ ràng đây là một trường hợp đơn giản, nhưng lập trình viên đã thêm một lỗi vào chương trình trong trường hợp đầu tiên (mã lồng nhau). Trong ví dụ thứ hai (thoát ra sớm với lính canh), khi bạn vượt qua được lính canh, mã của bạn sẽ an toàn khỏi việc vô tình sử dụng tham chiếu rỗng.

Chắc chắn, một lập trình viên giỏi không mắc những lỗi như thế này (thường xuyên). Nhưng phòng bệnh hơn chữa bệnh - chúng ta có thể viết mã theo cách loại bỏ hoàn toàn nguồn lỗi tiềm ẩn này. Việc lồng ghép thêm phức tạp, vì vậy, các phương pháp hay nhất khuyên bạn nên cấu trúc lại mã để giảm việc lồng ghép.


Có, nhưng mặt khác, một số lớp lồng nhau, với các điều kiện của chúng, làm cho mã dễ bị lỗi hơn, logic khó theo dõi hơn và - quan trọng hơn - khó gỡ lỗi hơn. Các hàm phẳng ít ác hơn, IMO.
Skrim

18
Tôi đang tranh luận ủng hộ việc giảm tổ! :-)
Jason Williams

Tôi đồng ý với điều này. Ngoài ra, từ quan điểm cấu trúc lại, sẽ dễ dàng và an toàn hơn để cấu trúc lại phương thức nếu obj trở thành một cấu trúc hoặc một cái gì đó bạn có thể đảm bảo sẽ không bị rỗng.
Phil Cooper

18

Tập dượt ??? Không đời nào. Trên thực tế, luôn tốt hơn nếu xử lý xác thực bằng cách quay lại từ phương thức sớm nhất nếu quá trình xác thực không thành công. Nếu không, nó sẽ dẫn đến một lượng lớn ifs & elses lồng nhau. Kết thúc sớm cải thiện khả năng đọc mã.

Ngoài ra, hãy kiểm tra các câu trả lời cho một câu hỏi tương tự: Tôi có nên sử dụng câu lệnh return / continue thay vì if-else không?


8

Nó không phải là thực hành xấu (vì tất cả các lý do đã được nêu). Tuy nhiên, bạn càng có nhiều lợi nhuận trong một phương thức, thì càng có nhiều khả năng nó được chia thành các phương thức logic nhỏ hơn.


8

Ví dụ đầu tiên là sử dụng câu lệnh bảo vệ. Từ Wikipedia :

Trong lập trình máy tính, bảo vệ là một biểu thức boolean phải đánh giá là true nếu việc thực thi chương trình tiếp tục trong nhánh được đề cập.

Tôi nghĩ rằng có một nhóm bảo vệ đứng đầu một phương pháp là một cách hoàn toàn dễ hiểu để lập trình. Về cơ bản nó nói "không thực hiện phương thức này nếu bất kỳ phương pháp nào trong số này là đúng".

Vì vậy, nói chung nó sẽ như thế này:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

Tôi nghĩ điều đó dễ đọc hơn sau đó:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}

3

Không có hình phạt về hiệu suất, tuy nhiên đoạn mã thứ hai dễ đọc hơn và do đó dễ bảo trì hơn.


Russell, tôi không đồng ý với ý kiến ​​của bạn nhưng bạn không nên bỏ phiếu cho nó. +1 để thậm chí nó ra. Btw, tôi tin rằng kiểm tra boolean và trả về trên một dòng duy nhất theo sau bởi một dòng trống là một dấu hiệu rõ ràng về những gì đang xảy ra. ví dụ: ví dụ đầu tiên của Rodrigo.
Paul Sasik

Tôi không đồng ý với điều này. Tăng lồng ghép không cải thiện khả năng đọc. Đoạn mã đầu tiên sử dụng câu lệnh "bảo vệ", đây là một mẫu hoàn toàn dễ hiểu.
cdmckay

Tôi cũng không đồng ý. Ngày nay, các điều khoản bảo vệ có thể loại bỏ một chức năng sớm thường được coi là một Điều tốt trong việc giúp người đọc hiểu được cách thực hiện.
Pete Hodgson

2

Trong trường hợp này, ví dụ thứ hai của bạn là mã tốt hơn, nhưng điều đó không liên quan gì đến việc trả về từ một hàm void, nó chỉ đơn giản là vì mã thứ hai trực tiếp hơn. Nhưng trả về từ một hàm void là hoàn toàn tốt.


0

Hoàn toàn ổn và không có 'hình phạt hiệu suất', nhưng đừng bao giờ viết câu lệnh 'nếu' không có dấu ngoặc.

Luôn luôn

if( foo ){
    return;
}

Nó dễ đọc hơn; và bạn sẽ không bao giờ vô tình cho rằng một số phần của mã nằm trong câu lệnh đó khi chúng không nằm trong câu lệnh đó.


2
có thể đọc được là chủ quan. imho, bất cứ thứ gì được thêm vào mã không cần thiết sẽ làm cho nó trở nên ít đọc hơn ... (Tôi phải đọc nhiều hơn, và sau đó tôi tự hỏi tại sao nó ở đó và lãng phí thời gian để cố gắng đảm bảo rằng tôi không thiếu thứ gì đó) ... nhưng đó là của tôi ý kiến ​​chủ quan
Charles Bretana

10
Lý do tốt hơn để luôn bao gồm niềng răng là ít hơn về tính dễ đọc và nhiều hơn về sự an toàn. Nếu không có niềng răng, tất cả sẽ dễ dàng cho một số người sau này sửa một lỗi đòi hỏi các câu lệnh bổ sung như là một phần của if, không chú ý đủ và thêm chúng mà không cần thêm niềng răng. Bằng cách luôn bao gồm cả niềng răng, nguy cơ này được loại bỏ.
Scott Dorman

2
Silky, vui lòng nhấn enter trước của bạn {. Điều này xếp hàng của bạn {với của bạn }trong cùng một cột, điều này hỗ trợ đáng kể khả năng đọc (dễ dàng hơn nhiều để tìm các dấu ngoặc nhọn đóng / mở tương ứng).
Imagist

1
@Imagist Tôi sẽ để điều đó theo sở thích cá nhân; và nó được thực hiện theo cách tôi thích :)
Noon Silk

1
Nếu mọi dấu ngoặc nhọn được so khớp với một dấu ngoặc nhọn được đặt ở cùng mức thụt lề , thì việc phân biệt trực quan ifcâu lệnh nào yêu cầu dấu ngoặc nhọn sẽ dễ dàng và do đó việc ifkiểm soát các câu lệnh một câu lệnh sẽ an toàn. Việc đẩy dấu ngoặc nhọn trở lại dòng với dấu sẽ iftiết kiệm một dòng không gian dọc trên mỗi câu lệnh đa if, nhưng sẽ yêu cầu sử dụng một dòng dấu ngoặc nhọn không cần thiết.
supercat

0

Tôi sẽ không đồng ý với tất cả các bạn những người trẻ tuổi đánh cá về điều này.

Sử dụng return ở giữa một phương thức, void hoặc cách khác, là một thực tiễn rất tồi, vì những lý do đã được trình bày khá rõ ràng, gần bốn mươi năm trước, bởi quá cố Edsger W. Dijkstra, bắt đầu trong "Tuyên bố GOTO được coi là có hại ", và tiếp tục trong" Lập trình có cấu trúc ", của Dahl, Dijkstra và Hoare.

Quy tắc cơ bản là mọi cấu trúc điều khiển và mọi mô-đun phải có chính xác một lối vào và một lối ra. Một kết quả trả về rõ ràng ở giữa mô-đun phá vỡ quy tắc đó và khiến việc suy luận về trạng thái của chương trình trở nên khó khăn hơn nhiều, do đó, điều này làm cho việc nói chương trình có đúng hay không sẽ khó hơn nhiều (đó là một thuộc tính mạnh hơn nhiều so với "liệu nó có hoạt động hay không").

"Tuyên bố GOTO bị coi là có hại" và "Lập trình có cấu trúc" đã khởi động cuộc cách mạng "Lập trình có cấu trúc" trong những năm 1970. Hai phần đó là lý do chúng ta có các cấu trúc điều khiển if-then-else, while-do và các cấu trúc điều khiển rõ ràng khác ngày nay và tại sao các câu lệnh GOTO bằng ngôn ngữ cấp cao lại nằm trong danh sách Các loài nguy cấp. (Ý kiến ​​cá nhân của tôi là chúng cần phải nằm trong danh sách Các loài tuyệt chủng.)

Điều đáng chú ý là Message Flow Modulator, phần mềm quân sự đầu tiên mà EVER đã vượt qua thử nghiệm chấp nhận trong lần thử đầu tiên, không có sai lệch, từ bỏ, hoặc nguyên văn "vâng, nhưng", được viết bằng một ngôn ngữ thậm chí không có một tuyên bố GOTO.

Cũng cần nhắc lại rằng Nicklaus Wirth đã thay đổi ngữ nghĩa của câu lệnh RETURN trong Oberon-07, phiên bản mới nhất của ngôn ngữ lập trình Oberon, khiến nó trở thành một đoạn sau của phần khai báo của một thủ tục đã nhập (tức là hàm), thay vì một câu lệnh thực thi trong phần thân của hàm. Sự giải thích của anh ấy về sự thay đổi nói rằng anh ấy đã làm điều đó chính xác vì biểu mẫu trước đó vi phạm nguyên tắc một lối ra của Lập trình có cấu trúc.


2
@John: chúng tôi đã vượt qua lệnh của Dykstra về nhiều lần trả lại chỉ khoảng thời gian chúng tôi vượt qua Pascal (dù sao thì hầu hết chúng tôi).
John Saunders

Các trường hợp yêu cầu trả về nhiều lần thường là dấu hiệu cho thấy một phương thức đang cố gắng thực hiện quá nhiều và nên được giảm bớt. Tôi sẽ không đi xa như John với điều này và câu lệnh trả về như một phần của xác thực tham số có thể là một ngoại lệ hợp lý, nhưng tôi hiểu được ý tưởng đến từ đâu.
kyoryu

@nairdaen: Vẫn còn tranh cãi về các trường hợp ngoại lệ trong quý đó. Hướng dẫn của tôi là: Nếu hệ thống đang được phát triển PHẢI khắc phục sự cố gây ra tình trạng đặc biệt ban đầu và tôi không ngại làm phiền người sẽ phải viết mã đó, tôi sẽ ném một ngoại lệ. Sau đó, tôi bị la trong một cuộc họp, vì anh ta không thèm tìm ngoại lệ, và ứng dụng gặp sự cố trong quá trình thử nghiệm, và tôi giải thích TẠI SAO anh ta phải khắc phục sự cố và mọi thứ ổn định trở lại.
John R. Strohm,

Có một sự khác biệt lớn giữa tuyên bố bảo vệ và gotos. Cái ác của gotos là chúng có thể nhảy ở bất cứ đâu, vì vậy có thể rất khó hiểu để làm sáng tỏ và ghi nhớ. Các câu lệnh Guard thì hoàn toàn ngược lại - chúng cung cấp một mục nhập được kiểm soát cho một phương thức, sau đó bạn biết rằng bạn đang làm việc trong một môi trường "an toàn", giảm số thứ bạn phải xem xét khi viết phần còn lại của mã (ví dụ: "Tôi biết con trỏ này sẽ không bao giờ là null, vì vậy tôi không cần phải xử lý trường hợp đó trong suốt mã").
Jason Williams

@Jason: Câu hỏi ban đầu không đặc biệt về các câu lệnh bảo vệ, mà là về các câu lệnh trả về ngẫu nhiên ở giữa một phương thức. Ví dụ được đưa ra ví dụ yêu cầu trở thành một người bảo vệ. Vấn đề quan trọng là, tại trang web trả về, bạn muốn có thể suy luận về những gì phương pháp đã làm hoặc không làm, và trả về ngẫu nhiên khiến điều đó khó hơn, vì CHÍNH XÁC cùng những lý do mà các GOTO ngẫu nhiên làm cho nó khó hơn. Xem: Dijkstra, "Tuyên bố của GOTO được coi là có hại". Về mặt cú pháp, cdmckay đã đưa ra, trong một câu trả lời khác, cú pháp ưa thích của anh ta cho lính canh; Tôi không đồng ý với ý kiến ​​của anh ấy về hình thức nào dễ đọc hơn.
John R. Strohm,

0

Trong khi sử dụng bảo vệ, hãy đảm bảo bạn tuân theo một số nguyên tắc nhất định để không làm người đọc nhầm lẫn.

  • chức năng làm một việc
  • bảo vệ chỉ được giới thiệu như là logic đầu tiên trong hàm
  • phần unnested chứa ý định cốt lõi của hàm

Thí dụ

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;

-3

Ném ngoại lệ thay vì trả về không có gì khi đối tượng là null, v.v.

Phương thức của bạn mong đợi đối tượng không phải là null và không phải như vậy, vì vậy bạn nên ném ngoại lệ và để người gọi xử lý điều đó.

Nhưng việc trở về sớm không phải là xấu.


1
Câu trả lời không trả lời câu hỏi. Câu hỏi là một phương thức void nên không có gì được trả về. Ngoài ra, các phương thức không có tham số. Tôi nhận được điểm không trả về null nếu kiểu trả về là một đối tượng nhưng điều đó không áp dụng cho câu hỏi này.
Luke Hammer
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.