it-swarm-vi.com

Tôi nên trở về từ một hàm sớm hay sử dụng câu lệnh if?

Tôi thường viết loại hàm này ở cả hai định dạng và tôi tự hỏi liệu một định dạng này có được ưu tiên hơn một định dạng không, và tại sao.

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

hoặc là

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

Tôi thường viết mã với cái đầu tiên vì đó là cách mà bộ não của tôi hoạt động trong khi mã hóa, mặc dù tôi nghĩ rằng tôi thích cái thứ hai hơn vì nó xử lý mọi lỗi xử lý ngay lập tức và tôi thấy nó dễ đọc hơn

302
Rachel

Tôi thích phong cách thứ hai. Trước tiên, hãy loại bỏ các trường hợp không hợp lệ, đơn giản là thoát hoặc đưa ra các ngoại lệ khi thích hợp, đặt một dòng trống vào đó, sau đó thêm phần thân "thực" của phương thức. Tôi thấy nó dễ đọc hơn.

400
Mason Wheeler

Chắc chắn là cái sau. Cái trước trông không tệ lắm, nhưng khi bạn nhận được mã phức tạp hơn, tôi không thể tưởng tượng được ai sẽ nghĩ điều này:

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

dễ đọc hơn

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

Tôi hoàn toàn thừa nhận tôi không bao giờ hiểu lợi thế của các điểm thoát đơn.

169
Jason Viers

Nó phụ thuộc - Nói chung tôi sẽ không cố gắng di chuyển một loạt mã để thoát ra khỏi chức năng sớm - trình biên dịch nói chung sẽ đảm nhiệm việc đó cho tôi. Mặc dù vậy, nếu có một số thông số cơ bản ở đầu mà tôi cần và không thể tiếp tục nếu không, tôi sẽ thoát ra sớm. Tương tự như vậy, nếu một điều kiện tạo ra một khối if khổng lồ trong chức năng, tôi cũng sẽ phá vỡ nó sớm do kết quả của điều đó.

Mặc dù vậy, nếu một hàm yêu cầu một số dữ liệu khi nó được gọi, tôi thường sẽ đưa ra một ngoại lệ (xem ví dụ) thay vì chỉ trả về.

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}
32
rjzii

Tôi thích sự trở lại sớm.

Nếu bạn có một điểm vào và một điểm thoát thì bạn luôn phải theo dõi toàn bộ mã trong đầu cho đến điểm thoát (bạn không bao giờ biết nếu một đoạn mã khác dưới đây có làm gì khác với kết quả không, vì vậy bạn phải theo dõi nó cho đến khi tồn tại). Bạn làm điều đó không có vấn đề mà chi nhánh xác định kết quả cuối cùng. Điều này là khó theo dõi.

Với một mục nhập và nhiều mục tồn tại, bạn quay lại khi bạn có kết quả của mình và đừng bận tâm theo dõi nó để thấy rằng không ai làm gì khác với nó (vì sẽ không có gì khác kể từ khi bạn quay lại). Nó giống như việc cơ thể phương pháp chia thành nhiều bước hơn, mỗi bước có khả năng trả về kết quả hoặc để bước tiếp theo thử vận ​​may.

24
user7197

Trong lập trình C, nơi bạn phải dọn dẹp thủ công, có rất nhiều điều để nói về lợi nhuận một điểm. Ngay cả khi không có nhu cầu ngay bây giờ để dọn dẹp thứ gì đó, ai đó có thể chỉnh sửa chức năng của bạn, phân bổ thứ gì đó và cần dọn dẹp trước khi trả lại. Nếu điều đó xảy ra, đó sẽ là một công việc ác mộng khi xem qua tất cả các tuyên bố trở lại.

Trong lập trình C++, bạn có các hàm hủy và thậm chí bây giờ các trình bảo vệ thoát phạm vi. Tất cả những điều này cần phải có ở đây để đảm bảo mã an toàn ngoại lệ ngay từ đầu, vì vậy mã được bảo vệ tốt trước lối ra sớm và do đó làm như vậy không có nhược điểm logic và hoàn toàn là vấn đề về kiểu dáng.

Tôi không đủ hiểu biết về Java, liệu mã khối "cuối cùng" có được gọi hay không và liệu trình hoàn thiện có thể xử lý tình huống cần thiết để đảm bảo điều gì đó xảy ra hay không.

C # Tôi chắc chắn không thể trả lời.

Ngôn ngữ D cung cấp cho bạn các bộ bảo vệ thoát phạm vi tích hợp thích hợp và do đó được chuẩn bị tốt để thoát sớm và do đó không nên trình bày một vấn đề nào khác ngoài phong cách.

Các hàm tất nhiên không nên quá dài ở vị trí đầu tiên và nếu bạn có một câu lệnh chuyển đổi lớn, mã của bạn có thể cũng bị ảnh hưởng xấu.

13
CashCow

Tôi sử dụng cả hai.

Nếu DoSomething là 3-5 dòng mã thì mã chỉ trông đẹp khi sử dụng phương thức định dạng đầu tiên.

Nhưng nếu nó có nhiều dòng hơn thế, thì tôi thích định dạng thứ hai. Tôi không thích khi dấu ngoặc mở và đóng không nằm trên cùng một màn hình.

9
azheglov

Trả lại sớm cho chiến thắng. Chúng có vẻ xấu, nhưng xấu hơn nhiều so với các hàm bao if lớn, đặc biệt là nếu có nhiều điều kiện để kiểm tra.

9
STW

Một lý do kinh điển cho lối ra một lần duy nhất là vì nếu không thì ngữ nghĩa chính thức trở nên xấu đến mức không thể tin được (lý do tương tự GOTO được coi là có hại).

Nói cách khác, lý do dễ dàng hơn là khi nào phần mềm của bạn sẽ thoát khỏi thói quen nếu bạn chỉ có 1 lần trả lại. Đó cũng là một lập luận chống lại các ngoại lệ.

Điển hình là tôi giảm thiểu cách tiếp cận sớm.

8
Paul Nathan

Cá nhân, tôi thích kiểm tra điều kiện đạt/không ngay từ đầu. Điều đó cho phép tôi nhóm hầu hết các lỗi phổ biến nhất ở đầu hàm với phần còn lại của logic để theo dõi.

7
nathan

Nó phụ thuộc.

Quay trở lại sớm nếu có một số điều kiện cụ thể rõ ràng để kiểm tra ngay lập tức sẽ khiến việc chạy phần còn lại của chức năng trở nên vô nghĩa. *

Đặt Retval + trả về một lần nếu hàm phức tạp hơn và có thể có nhiều điểm thoát khác (vấn đề dễ đọc).

* Điều này thường có thể chỉ ra vấn đề thiết kế. Nếu bạn thấy rằng rất nhiều phương thức của bạn cần kiểm tra một số trạng thái bên ngoài/paramater hoặc như vậy trước khi chạy phần còn lại của mã, đó có thể là điều mà người gọi nên xử lý .

6
Bobby Tables

Sử dụng Nế

Trong cuốn sách của Don Knuth về GOTO, tôi đã đọc anh ta đưa ra một lý do cho việc luôn luôn có điều kiện có khả năng nhất đến trước trong một tuyên bố if. Theo giả định rằng đây vẫn là một ý tưởng hợp lý (và không phải là một trong những cân nhắc thuần túy cho tốc độ của thời đại). Tôi nói rằng trả về sớm không phải là thực hành lập trình tốt, đặc biệt là xem xét thực tế rằng chúng thường xuyên hơn không được sử dụng để xử lý lỗi, trừ khi mã của bạn có nhiều khả năng thất bại hơn là không thất bại :-)

Nếu bạn làm theo lời khuyên ở trên, bạn cần đặt trả về đó ở dưới cùng của hàm, và sau đó bạn thậm chí có thể không gọi nó là trả về ở đó, chỉ cần đặt mã lỗi và trả lại hai dòng. Qua đó đạt được mục nhập 1 lối thoát lý tưởng.

Cụ thể Delphi ...

Tôi nghĩ rằng đây là một thực hành lập trình tốt cho các lập trình viên Delphi, mặc dù tôi không có bất kỳ bằng chứng nào. Trước D2009, chúng tôi không có cách nguyên tử để trả về giá trị, chúng tôi có exit;result := foo; hoặc chúng ta chỉ có thể ném ngoại lệ.

Nếu bạn phải thay thế

if (true) {
 return foo;
} 

for

if true then 
begin
  result := foo; 
  exit; 
end;

bạn có thể cảm thấy mệt mỏi khi thấy điều đó ở đầu mỗi chức năng của bạn và thích

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

và chỉ cần tránh exit hoàn toàn.

3
Peter Turner

Tôi đồng ý với tuyên bố sau:

Cá nhân tôi là một fan hâm mộ của các mệnh đề bảo vệ (ví dụ thứ hai) vì nó làm giảm sự thụt dòng của hàm. Một số người không thích chúng vì nó dẫn đến nhiều điểm trả về từ chức năng, nhưng tôi nghĩ nó rõ ràng hơn với họ.

Lấy từ câu hỏi này trong stackoverflow .

2
Toto

Tôi muốn viết:

if(someCondition)
{
    SomeFunction();
}
1
Josh K

Giống như bạn, tôi thường viết cái đầu tiên, nhưng thích cái cuối cùng. Nếu tôi có nhiều kiểm tra lồng nhau, tôi thường tái cấu trúc cho phương thức thứ hai.

Tôi không thích cách xử lý lỗi được chuyển khỏi kiểm tra.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Tôi thích điều này:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something
1
jolt

Tôi sử dụng lợi nhuận sớm gần như độc quyền những ngày này, đến mức cực đoan. Tôi viết cái này

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

như

self = [super init];
if (!self)
    return;

// your code here

return self;

nhưng nó thực sự không quan trọng. Nếu bạn có nhiều hơn một hoặc hai cấp độ lồng trong các chức năng của mình, chúng cần được tăng cường.

1
Dan Rosenstark

Các điều kiện trên cùng được gọi là "điều kiện tiên quyết". Bằng cách đặt if(!precond) return;, bạn sẽ liệt kê trực quan tất cả các điều kiện tiên quyết.

Sử dụng khối "if-other" lớn có thể làm tăng chi phí thụt lề (tôi quên trích dẫn về thụt lề 3 cấp).

0
Ming-Tang