Thực hành tốt nhất khi / trở về


60

Tôi muốn biết những gì được coi là cách tốt hơn để trở lại khi tôi có iftuyên bố.

Ví dụ 1:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }
   else
   {
      myString = "Name " + myString;
      // Do something more here...
      return true;
   }
}

Ví dụ 2:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }

   myString = "Name " + myString;
   // Do something more here...
   return true;
}

Như bạn có thể thấy trong cả hai ví dụ, hàm sẽ trả về true/falsenhưng nên đặt elsecâu lệnh như trong ví dụ đầu tiên hay tốt hơn là không đặt nó?


7
Nếu bạn chỉ kiểm tra lỗi trong 'nếu' đầu tiên, tốt nhất bạn không bao gồm 'khác' vì lỗi không nên được coi là một phần của logic thực tế.
Mert Akcakaya

2
Cá nhân tôi sẽ lo lắng hơn về chức năng gây ra tác dụng phụ, nhưng tôi đoán đây chỉ là một ví dụ được lựa chọn kém?
jk.


14
Đối với tôi, phiên bản đầu tiên giống như đuổi tất cả các sinh viên trong phòng không hoàn thành bài tập về nhà, và sau đó khi họ đi vắng, nói với các sinh viên còn lại "Bây giờ nếu bạn đã hoàn thành bài tập về nhà của mình ...". Nó có ý nghĩa, nhưng là không cần thiết. Vì điều kiện không thực sự là điều kiện nữa, tôi có xu hướng bỏ else.
Nicole

1
'Làm gì đó nhiều hơn ở đây' mà bạn muốn làm là gì? Điều đó có thể thay đổi hoàn toàn cách bạn thiết kế chức năng của mình.
Benedict

Câu trả lời:


81

Ví dụ 2 được gọi là khối bảo vệ. Nó phù hợp hơn để trả lại / ném ngoại lệ sớm nếu có sự cố xảy ra (tham số sai hoặc trạng thái không hợp lệ). Trong luồng logic thông thường, tốt hơn là sử dụng Ví dụ 1


+1 - một sự khác biệt tốt, và những gì tôi sẽ trả lời.
Telastyn

3
@ pR0Ps có cùng một câu trả lời dưới đây và cung cấp một ví dụ mã về lý do tại sao cuối cùng nó lại là một đường dẫn sạch hơn để theo dõi. +1 cho câu trả lời hay.

Câu hỏi này đã được hỏi nhiều lần trên SO và mặc dù nó có thể gây tranh cãi nhưng đây là một câu trả lời hay. Nhiều người được đào tạo để chỉ trở về từ cuối có một số vấn đề với khái niệm này.
Bill K

2
+1. Không thích hợp tránh các khối bảo vệ có xu hướng gây ra mã mũi tên.
Brian

Không cả hai ví dụ cho thấy khối bảo vệ?
ernie

44

Phong cách cá nhân của tôi là sử dụng đơn ifcho các khối bảo vệ và if/ elsetrong mã xử lý phương thức thực tế.

Trong trường hợp này, bạn đang sử dụng myString == nullnhư một điều kiện bảo vệ, vì vậy tôi sẽ có xu hướng sử dụng một ifmẫu duy nhất .

Hãy xem xét mã phức tạp hơn một chút:

Ví dụ 1:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }
    else{
        //processing block
        myString = escapedString(myString);

        if (myString == "foo"){
            //some processing here
            return false;
        }
        else{
            myString = "Name " + myString;
            //other stuff
            return true;
        }
    }
}

Ví dụ 2:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }

    //processing block
    myString = escapedString(myString);

    if (myString == "foo"){
        //some processing here
        return false;
    }
    else{
        myString = "Name " + myString;
        //other stuff
        return true;
    }
}

Trong ví dụ 1, cả bộ bảo vệ và phần còn lại của phương thức đều ở dạng if/ else. So sánh với Ví dụ 2, trong đó khối bảo vệ ở ifdạng đơn, trong khi phần còn lại của phương thức sử dụng biểu mẫu if/ else. Cá nhân, tôi thấy ví dụ 2 dễ hiểu hơn, trong khi ví dụ 1 có vẻ lộn xộn và thụt lề quá mức.

Lưu ý rằng đây là một ví dụ giả định và bạn có thể sử dụng các else ifcâu lệnh để dọn sạch nó, nhưng tôi đang hướng đến việc hiển thị sự khác biệt giữa các khối bảo vệ và mã xử lý chức năng thực tế.

Một trình biên dịch tốt sẽ tạo ra cùng một đầu ra cho cả hai. Lý do duy nhất để sử dụng cái này hay cái kia là sở thích cá nhân hoặc để phù hợp với phong cách của mã hiện có.


18
Ví dụ 2 sẽ dễ đọc hơn nếu bạn thoát khỏi cái thứ hai.
briddums

18

Cá nhân, tôi thích phương pháp thứ hai. Tôi cảm thấy như thể nó ngắn hơn, ít thụt hơn và dễ đọc hơn.


+1 cho ít thụt lề. Cử chỉ này gây khó chịu nếu bạn có nhiều kiểm tra
d.raev

15

Thực hành cá nhân của tôi là như sau:

  • Tôi không thích các chức năng với một số điểm thoát, tôi thấy khó duy trì và làm theo, sửa đổi mã đôi khi phá vỡ logic bên trong vì nó vốn đã hơi cẩu thả. Khi nó là một phép tính phức tạp, tôi tạo một giá trị trả về khi bắt đầu và trả về ở cuối. Điều này buộc tôi phải cẩn thận theo dõi từng đường dẫn if-other, switch, v.v., đặt giá trị chính xác tại các vị trí thích hợp. Tôi cũng dành một chút thời gian để quyết định nên đặt giá trị trả về mặc định hay không để nó chưa được khởi tạo khi bắt đầu. Phương pháp này cũng giúp khi logic hoặc loại giá trị trả về hoặc ý nghĩa thay đổi.

Ví dụ:

public bool myFunction()
{
   // First parameter loading
   String myString = getString();

   // Location of "quick exits", see the second example
   // ...

   // declaration of external resources that MUST be released whatever happens
   // ...

   // the return variable (should think about giving it a default value or not) 
   // if you have no default value, declare it final! You will get compiler 
   // error when you try to set it multiple times or leave uninitialized!
   bool didSomething = false;

   try {
     if (myString != null)
     {
       myString = "Name " + myString;
       // Do something more here...

       didSomething = true;
     } else {
       // get other parameters and data
       if ( other conditions apply ) {
         // do something else
         didSomething = true;
       }
     }

     // Edit: previously forgot the most important advantage of this version
     // *** HOUSEKEEPING!!! ***

   } finally {

     // this is the common place to release all resources, reset all state variables

     // Yes, if you use try-finally, you will get here from any internal returns too.
     // As I said, it is only my taste that I like to have one, straightforward path 
     // leading here, and this works even if you don't use the try-finally version.

   }

   return didSomething;
}
  • Một ngoại lệ duy nhất: "thoát nhanh" khi bắt đầu (hoặc trong những trường hợp hiếm hoi, bên trong quy trình). Nếu logic tính toán thực không thể xử lý một sự kết hợp nhất định của các tham số đầu vào và trạng thái bên trong hoặc có một giải pháp dễ dàng mà không cần chạy thuật toán, thì sẽ không có tất cả các mã được gói gọn trong (đôi khi sâu) nếu các khối. Đây là một "trạng thái đặc biệt", không phải là một phần của logic cốt lõi, vì vậy tôi phải thoát ra khỏi tính toán ngay khi tôi phát hiện ra. Trong trường hợp này không có nhánh nào khác, trong điều kiện bình thường, việc thực thi đơn giản diễn ra. (Tất nhiên, "trạng thái đặc biệt" được thể hiện tốt hơn bằng cách ném một ngoại lệ, nhưng đôi khi nó là một sự quá mức.)

Ví dụ:

public bool myFunction()
{
   String myString = getString();

   if (null == myString)
   {
     // there is nothing to do if myString is null
     return false;
   } 

   myString = "Name " + myString;
   // Do something more here...

   // not using return value variable now, because the operation is straightforward.
   // if the operation is complex, use the variable as the previous example.

   return true;
}

Quy tắc "một lối thoát" cũng có ích khi tính toán yêu cầu các tài nguyên bên ngoài mà bạn phải giải phóng hoặc nói rằng bạn phải đặt lại trước khi rời khỏi hàm; đôi khi chúng được thêm vào sau này trong quá trình phát triển. Với nhiều lối thoát bên trong thuật toán, việc mở rộng tất cả các nhánh đúng cách sẽ khó hơn nhiều. (Và nếu ngoại lệ có thể xảy ra, việc phát hành / thiết lập lại cũng nên được đặt trong một khối cuối cùng, để tránh tác dụng phụ trong các trường hợp ngoại lệ hiếm gặp ...).

Trường hợp của bạn dường như rơi vào danh mục "thoát nhanh trước khi làm việc thực sự" và tôi sẽ viết nó giống như phiên bản Ví dụ 2 của bạn.


9
+1 cho "Tôi không thích các chức năng có nhiều điểm thoát"
Corv1nus

@LorandKedves - đã thêm một ví dụ - hy vọng bạn không phiền.
Matthew Flynn

@MatthewFlynn tốt, nếu bạn không bận tâm rằng nó trái ngược với những gì tôi đề nghị ở cuối câu trả lời của tôi ;-) Tôi tiếp tục ví dụ, tôi hy vọng cuối cùng nó sẽ tốt cho cả hai chúng tôi :-)
Lorand Kedves

@MatthewFlynn (xin lỗi, tôi chỉ là một thằng khốn, và cảm ơn bạn đã giúp tôi làm rõ ý kiến ​​của mình. Tôi hy vọng bạn thích phiên bản hiện tại.)
Lorand Kedves

13
Trong số các hàm có nhiều điểm thoát và các hàm có biến kết quả có thể thay đổi, thì trước đây đối với tôi có ít hơn hai tệ nạn.
Jon Purdy

9

Tôi thích sử dụng các khối bảo vệ bất cứ nơi nào tôi có thể vì hai lý do:

  1. Họ cho phép một lối thoát nhanh chóng đưa ra một số điều kiện cụ thể.
  2. Việc loại bỏ sự cần thiết cho phức tạp và không cần thiết nếu các câu lệnh sau trong mã.

Nói chung, tôi thích xem các phương thức trong đó chức năng cốt lõi của phương thức là rõ ràng và tối thiểu. Khối bảo vệ giúp trực quan thực hiện điều này.


5

Tôi thích cách tiếp cận "Fall Through":

public bool MyFunction()
{
   string myString = GetString();

   if (myString != null)
   {
     myString = "Name " + myString;
     return true;
    }
    return false;
}

Hành động có một điều kiện cụ thể, bất cứ điều gì khác chỉ là sự trở lại "rơi qua" mặc định.


1

Nếu tôi có một điều kiện duy nhất nếu tôi không dành quá nhiều thời gian để suy nghĩ về phong cách. Nhưng nếu tôi có nhiều điều kiện bảo vệ, tôi sẽ thích style2

Picuture này. Giả sử rằng các bài kiểm tra rất phức tạp và bạn thực sự không muốn buộc chúng vào một điều kiện if-ORed duy nhất để tránh sự phức tạp:

//Style1
if (this1 != Right)
{ 
    return;
}
else if(this2 != right2)
{
    return;
}
else if(this3 != right2)
{
    return;
}
else
{
    //everything is right
    //do something
    return;
}

đấu với

//Style 2
if (this1 != Right)
{ 
   return;
}
if(this2 != right2)
{
    return;
}
if(this3 != right2)
{
    return;
}


//everything is right
//do something
return;

Ở đây có hai ưu điểm chính

  1. Bạn đang tách mã trong một hàm duy nhất thành hai khối logcal trực quan: khối xác nhận trên (điều kiện bảo vệ) và khối mã chạy được thấp hơn.

  2. Nếu bạn phải thêm / xóa một điều kiện, bạn sẽ giảm cơ hội làm rối tung toàn bộ thang if-otherif-other.

Một lợi thế nhỏ khác là bạn có một bộ niềng răng ít hơn để chăm sóc.


0

Đây phải là một vấn đề mà âm thanh tốt hơn.

If condition
  do something
else
  do somethingelse

thể hiện bản thân tốt hơn sau đó

if condition
  do something
do somethingelse

đối với các phương thức nhỏ, nó sẽ không có nhiều khác biệt, nhưng đối với các phương thức ghép lớn hơn, nó có thể làm cho nó khó hiểu hơn vì nó không được tách biệt đúng cách


3
Nếu một phương thức quá dài mà hình thức thứ hai khó hiểu, thì nó quá dài.
kevin cline

7
Hai trường hợp của bạn chỉ tương đương nếu do somethingbao gồm một sự trở lại. Nếu không, mã thứ hai sẽ thực thi do somethingelsebất kể ifvị từ không phải là cách khối đầu tiên hoạt động.
Ad Nam

Chúng cũng là những gì OP đã giải thích trong các ví dụ của mình. Chỉ cần làm theo dòng câu hỏi
Jose Valente

0

Tôi thấy ví dụ 1 gây khó chịu, bởi vì câu lệnh trả lại bị thiếu ở cuối hàm trả về giá trị ngay lập tức kích hoạt "Chờ, có gì đó không đúng" -flag. Vì vậy, trong những trường hợp như thế này, tôi sẽ đi với ví dụ 2.

Tuy nhiên, thường có liên quan nhiều hơn, tùy thuộc vào mục đích của chức năng, ví dụ như xử lý lỗi, ghi nhật ký, v.v. của một biến cờ bổ sung. Nó làm cho bảo trì và mở rộng sau này dễ dàng hơn trong hầu hết các trường hợp.


0

Khi ifcâu lệnh của bạn luôn trả về thì không có lý do gì để sử dụng một mã khác cho mã còn lại trong hàm. Làm như vậy thêm dòng bổ sung và thụt thêm. Thêm mã không cần thiết làm cho nó khó đọc hơn và như mọi người đều biết Đọc mã là Khó .


0

Trong ví dụ của bạn, elserõ ràng là không cần thiết, NHƯNG ...

Khi lướt nhanh qua các dòng mã, mắt của một người thường nhìn vào các dấu ngoặc và các vết lõm để có ý tưởng về dòng mã; trước khi họ thực sự giải quyết trên chính mã. Do đó, việc viết elsevà đặt dấu ngoặc và thụt xung quanh khối mã thứ hai thực sự giúp người đọc nhanh chóng nhận thấy đây là tình huống "A hoặc B", trong đó khối này hoặc khối kia sẽ chạy. Đó là, người đọc nhìn thấy niềng răng và thụt đầu TRƯỚC KHI họ thấy returnsau lần đầu tiên if.

Theo tôi, đây là một trong những trường hợp hiếm hoi khi thêm một cái gì đó dư thừa vào mã thực sự làm cho nó dễ đọc hơn, không ít hơn.


0

Tôi thích ví dụ 2 vì rõ ràng hàm này trả về một cái gì đó . Nhưng thậm chí còn hơn thế, tôi muốn trở về từ một nơi, như thế này:

public bool MyFunction()
{
    bool result = false;

    string myString = GetString();

    if (myString != nil) {
        myString = "Name " + myString;

        result = true;
    }

    return result;
}

Với phong cách này tôi có thể:

  1. Xem ngay rằng tôi đang trả lại một cái gì đó.

  2. Nắm bắt kết quả của mọi lời gọi của hàm chỉ với một điểm dừng.


Điều này tương tự như cách tôi sẽ tiếp cận vấn đề. Sự khác biệt duy nhất là tôi sẽ sử dụng biến kết quả để nắm bắt đánh giá myString và sử dụng nó làm giá trị trả về.
Chuck Conway

@ChuckConway Đồng ý, nhưng tôi đã cố gắng bám sát nguyên mẫu của OP.
Caleb

0

Phong cách cá nhân của tôi có xu hướng đi

function doQuery(string) {
    if (!query(string)) {
        query("ABORT");
        return false;
    } // else
    if(!anotherquery(string) {
        query("ABORT");
        return false;
    } // else
    return true;
}

Sử dụng các elsecâu lệnh nhận xét để chỉ ra luồng chương trình và giữ cho nó có thể đọc được, nhưng tránh các vết lõm lớn có thể dễ dàng tiếp cận trên màn hình nếu có nhiều bước liên quan.


1
Cá nhân, tôi hy vọng rằng tôi không bao giờ phải làm việc với mã của bạn.
một CVn

Nếu bạn có nhiều kiểm tra, phương pháp này có thể hơi lâu. Việc trả về từng mệnh đề if tương tự như sử dụng câu lệnh goto để bỏ qua các phần của mã.
Chuck Conway

Nếu đó là sự lựa chọn giữa điều này và mã với các elsemệnh đề được bao gồm, tôi sẽ chọn cái sau vì trình biên dịch cũng sẽ bỏ qua chúng một cách hiệu quả. Mặc dù nó sẽ phụ thuộc vào else if không tăng thụt đầu dòng hơn so với bản gốc một lần - nếu nó đã làm tôi đồng ý với bạn. Nhưng lựa chọn của tôi sẽ là bỏ elsehoàn toàn nếu phong cách đó được cho phép.
Đánh dấu

0

tôi sẽ viết

public bool SetUserId(int id)
{
   // Get user name from id
   string userName = GetNameById(id);

   if (userName != null)
   {
       // update local ID and userName
       _id = id;
       _userNameLabelText = "Name: " + userName;
   }

   return userName != null;
}

Tôi thích phong cách này bởi vì rõ ràng là tôi sẽ trở lại userName != null.


1
Câu hỏi là tại sao bạn thích phong cách đó?
Caleb

... Thành thật mà nói, tôi không chắc tại sao bạn thậm chí muốn chuỗi trong trường hợp này, không đề cập đến lý do tại sao bạn thêm một bài kiểm tra boolean bổ sung vào cuối mà bạn thực sự không cần vì bất kỳ lý do gì.
Shadur

@Shadur Tôi biết về thử nghiệm boolean thêm và myStringkết quả không sử dụng . Tôi chỉ sao chép chức năng từ OP. Tôi sẽ thay đổi nó thành một cái gì đó trông giống cuộc sống thực hơn. Kiểm tra Boolean là tầm thường và không nên là vấn đề.
tia

1
@Shadur Chúng tôi không thực hiện tối ưu hóa ở đây, phải không? Quan điểm của tôi là làm cho mã của tôi dễ đọc và duy trì, và cá nhân tôi sẽ trả nó với chi phí cho một lần kiểm tra null tham chiếu.
tia

1
@tia Tôi thích tinh thần của mã của bạn. Thay vì đánh giá tên người dùng hai lần, tôi sẽ ghi lại đánh giá đầu tiên trong một biến và trả lại kết quả đó.
Chuck Conway

-1

Quy tắc ngón tay cái của tôi là hoặc thực hiện if-return mà không có người khác (chủ yếu là do lỗi) hoặc thực hiện chuỗi if-if-if đầy đủ trên tất cả các khả năng.

Bằng cách này, tôi tránh cố tỏ ra quá thích thú với việc phân nhánh của mình, vì bất cứ khi nào tôi kết thúc việc tôi mã hóa logic ứng dụng vào ifs của mình, khiến chúng khó bảo trì hơn (Ví dụ: nếu enum OK hoặc ERR và tôi viết tất cả các if của mình lợi dụng thực tế là! OK <-> ERR sẽ trở thành một vấn đề khó khăn khi thêm tùy chọn thứ ba vào enum vào lần tới.)

Ví dụ, trong trường hợp của bạn, tôi sẽ sử dụng một đơn giản nếu, vì "return if null" chắc chắn là một mẫu chung và không có khả năng bạn sẽ phải lo lắng về khả năng thứ ba (ngoài null / không null) trong tương lai.

Tuy nhiên, nếu thử nghiệm là thứ gì đó thực hiện kiểm tra dữ liệu sâu hơn về dữ liệu, tôi sẽ chuyển sang một if-if khác thay thế.


-1

Tôi nghĩ rằng có rất nhiều cạm bẫy đối với Ví dụ 2, rằng việc đi xuống có thể dẫn đến mã không lường trước được. Lần đầu tiên tập trung ở đây dựa trên logic xung quanh biến 'myString'. Do đó, để rõ ràng, tất cả các thử nghiệm các điều kiện nên xảy ra trong một kế toán khối mã cho logic đã biếtmặc định / chưa biết .

Điều gì xảy ra nếu sau này mã được đưa vào vô tình cho ví dụ 2 đã thay đổi đáng kể thông số:

   if (myString == null)
   {
      return false;
   }

   //add some v1 update code here...
   myString = "And the winner is: ";
   //add some v2 update code here...
   //buried in v2 updates the following line was added
   myString = null;
   //add some v3 update code here...
   //Well technically this should not be hit because myString = null
   //but we already passed that logic
   myString = "Name " + myString;
   // Do something more here...
   return true;

Tôi nghĩ rằng với elsekhối ngay sau khi kiểm tra null sẽ khiến các lập trình viên thêm các cải tiến cho các phiên bản trong tương lai thêm tất cả logic lại với nhau bởi vì bây giờ chúng ta có một chuỗi logic không dự định cho quy tắc ban đầu (trả về nếu giá trị là vô giá trị).

Tôi tin tưởng vào điều này rất nhiều cho một số hướng dẫn của C # về Codeplex (liên kết đến đó tại đây: http://csharpguferences.codeplex.com/ ) nêu rõ như sau:

"Thêm một nhận xét mô tả nếu khối mặc định (khác) được cho là trống. Ngoài ra, nếu khối đó không được yêu cầu, hãy ném UnlimitedOperationException để phát hiện các thay đổi trong tương lai có thể rơi vào các trường hợp hiện có. Điều này đảm bảo mã tốt hơn, bởi vì tất cả các con đường mà mã có thể đi đã được nghĩ đến. "

Tôi nghĩ rằng đó là cách thực hành lập trình tốt khi sử dụng các khối logic như thế này để luôn có một khối mặc định được thêm vào (if-other, case: default) để giải thích rõ ràng tất cả các đường dẫn mã và không để mã mở cho các hậu quả logic ngoài ý muốn.


Làm thế nào để không có một người khác trong ví dụ hai không xem xét tất cả các trường hợp? Kết quả dự định IS tiếp tục thực hiện. Thêm một mệnh đề khác trong trường hợp này chỉ làm tăng độ phức tạp của phương thức.
Chuck Conway

Tôi không đồng ý dựa trên khả năng không có tất cả logic liên quan trong câu hỏi trong một khối ngắn gọn và xác định. Trọng tâm là thao tác myStringgiá trị & mã thành công ifkhối là logic kết quả nếu chuỗi! = Null. Do đó, vì trọng tâm là logic bổ sung thao túng giá trị cụ thể đó, tôi nghĩ rằng nó nên được gói gọn trong một elsekhối. Mặt khác, điều này mở ra tiềm năng khi tôi chỉ ra cách vô tình tách logic & đưa ra những hậu quả không lường trước được. Có thể không xảy ra mọi lúc, nhưng đây thực sự là một câu hỏi về phong cách ý kiến thực hành tốt nhất .
atconway
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.