câu lệnh if - đánh giá ngắn mạch so với khả năng đọc


90

Đôi khi, một ifcâu lệnh có thể khá phức tạp hoặc dài, vì vậy để dễ đọc, tốt hơn là trích xuất các lệnh gọi phức tạp trước if.

ví dụ:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

vào cái này

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(ví dụ cung cấp không phải là xấu, nó chỉ mang tính minh họa ... tưởng tượng cuộc gọi khác với nhiều đối số, vv)

Nhưng với trích xuất này, tôi đã mất đánh giá ngắn mạch (SCE).

  1. Tôi có thực sự bị mất SCE mỗi lần? Có một số trường hợp mà trình biên dịch được phép "tối ưu hóa nó" và vẫn cung cấp SCE không?
  2. Có những cách nào để duy trì khả năng đọc được cải thiện của đoạn mã thứ hai mà không làm mất SCE?

20
Thực tế cho thấy rằng hầu hết các câu trả lời về hiệu suất bạn sẽ thấy ở đây hoặc ở những nơi khác trong hầu hết các trường hợp đều sai (4 sai 1 đúng). Lời khuyên của tôi là luôn luôn làm một hồ sơ và tự kiểm tra nó, bạn sẽ tránh được "tối ưu hóa quá sớm" và học được những thứ mới.
Marek R

25
@MarekR là không chỉ là về hiệu suất, đó là về các tác dụng phụ có thể trong OtherCunctionCall ...
relaxxx

3
@David khi giới thiệu các trang web khác, thường hữu ích khi chỉ ra rằng việc đăng chéo bị phản đối
gnat

7
Nếu khả năng đọc là mối quan tâm chính của bạn, không gọi chức năng với tác dụng phụ bên trong một nếu có điều kiện
Morgen

3
Các cử tri thân thiết tiềm năng: đọc lại câu hỏi. Phần (1) không dựa trên quan điểm, trong khi phần (2) có thể dễ dàng ngừng dựa trên ý kiến ​​thông qua một bản chỉnh sửa xóa tham chiếu đến bất kỳ "phương pháp hay nhất" nào được cho là như tôi sắp làm.
duplode

Câu trả lời:


119

Một giải pháp tự nhiên sẽ giống như sau:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

Điều này có lợi ích là dễ hiểu, có thể áp dụng cho mọi trường hợp và có hành vi ngắn mạch.


Đây là giải pháp ban đầu của tôi: Một mẫu tốt trong các cuộc gọi phương thức và các phần thân vòng lặp là như sau:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

Một người nhận được cùng một lợi ích về hiệu suất tốt đẹp của việc đánh giá ngắn mạch, nhưng mã trông dễ đọc hơn.


4
@relaxxx: Tôi hiểu rồi, nhưng "nhiều việc phải làm sau dấu if" cũng là một dấu hiệu cho thấy hàm hoặc phương thức của bạn quá lớn và nên được chia thành các hàm nhỏ hơn. Nó không phải lúc nào cũng là cách tốt nhất nhưng rất thường xuyên là như vậy!
n person325681

2
này vi phạm nguyên tắc danh sách trắng
JoulinRouge

13
@JoulinRouge: Thật thú vị, tôi chưa bao giờ nghe nói về nguyên tắc này. Bản thân tôi thích cách tiếp cận "ngắn mạch" này vì những lợi ích về khả năng đọc: nó làm giảm các vết lõm và loại bỏ khả năng có điều gì đó xảy ra sau khối được thụt vào.
Matthieu M.

2
Nó có dễ đọc hơn không? Đặt tên b2đúng và bạn sẽ nhận được someConditionAndSomeotherConditionIsTrue, không phải siêu ý nghĩa. Ngoài ra, tôi phải giữ một loạt các biến trong ngăn xếp tinh thần của mình trong suốt bài tập này (và tbh cho đến khi tôi ngừng hoạt động trong phạm vi này). Tôi sẽ đi với SJuan76giải pháp số 2 của hoặc chỉ đặt toàn bộ điều trong một hàm.
Nathan Cooper

2
Tôi chưa đọc tất cả các bình luận nhưng sau khi tìm kiếm nhanh, tôi không tìm thấy lợi thế lớn của đoạn mã đầu tiên cụ thể là gỡ lỗi. Việc đặt nội dung trực tiếp vào câu lệnh if thay vì gán nó cho một biến trước đó và sau đó sử dụng biến thay vào đó làm cho việc gỡ lỗi trở nên khó khăn hơn cần thiết. Việc sử dụng các biến cũng cho phép nhóm các giá trị lại với nhau theo ngữ nghĩa để tăng khả năng đọc.
rbaleksandar

31

Tôi có xu hướng chia nhỏ các điều kiện thành nhiều dòng, tức là:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Ngay cả khi giao dịch với nhiều toán tử (&&), bạn chỉ cần tăng dấu nhắc trước với mỗi cặp dấu ngoặc. SCE vẫn hoạt động - không cần sử dụng biến. Viết mã theo cách này khiến nó dễ đọc hơn đối với tôi trong nhiều năm rồi. Ví dụ phức tạp hơn:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {

28

Nếu bạn có một chuỗi điều kiện dài và những gì cần giữ lại một số điều kiện ngắn mạch, thì bạn có thể sử dụng các biến tạm thời để kết hợp nhiều điều kiện. Lấy ví dụ của bạn, có thể làm như

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

Nếu bạn có một trình biên dịch có khả năng C ++ 11, bạn có thể sử dụng các biểu thức lambda để kết hợp các biểu thức thành các hàm, tương tự như ở trên:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }

21

1) Có, bạn không còn SCE nữa. Nếu không, bạn sẽ có cái đó

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

hoạt động theo cách này hay cách khác tùy thuộc nếu có một iftuyên bố sau đó. Cách quá phức tạp.

2) Đây là ý kiến ​​dựa trên ý kiến, nhưng đối với các biểu thức phức tạp hợp lý, bạn có thể làm:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

Nếu nó quá phức tạp, giải pháp rõ ràng là tạo một hàm đánh giá biểu thức và gọi nó.


21

Bạn cũng có thể dùng:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

và SCE sẽ hoạt động.

Nhưng nó không dễ đọc hơn ví dụ:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

3
Tôi không quan tâm đến việc kết hợp boolean với một toán tử bitwise. Điều đó dường như không được tôi đánh máy tốt. Nói chung, tôi sử dụng bất kỳ thứ gì có vẻ dễ đọc nhất trừ khi tôi đang làm việc ở mức rất thấp và số chu kỳ bộ xử lý.
Ant

3
Tôi đã sử dụng cụ thể b = b || otherComplicatedStuff();và @SargeBorsch thực hiện một chỉnh sửa để loại bỏ SCE. Cảm ơn vì đã thông báo cho tôi về sự thay đổi đó @Ant.
KIIV

14

1) Tôi có thực sự bị mất SCE mỗi lần? Trình biên dịch có được phép "tối ưu hóa nó" và vẫn cung cấp SCE không?

Tôi không nghĩ rằng tối ưu hóa như vậy được phép; đặc biệt là OtherComplicatedFunctionCall()có thể có một số tác dụng phụ.

2) Cách thực hành tốt nhất trong tình huống như vậy là gì? Có phải khả năng duy nhất (khi tôi muốn SCE) có trực tiếp tất cả những gì tôi cần bên trong nếu và "chỉ định dạng nó để dễ đọc nhất có thể"?

Tôi muốn cấu trúc lại nó thành một hàm hoặc một biến có tên mô tả; điều này sẽ bảo vệ cả đánh giá ngắn mạch và khả năng đọc:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

Và khi chúng tôi triển khai getSomeResult()dựa trên SomeComplicatedFunctionCall()OtherComplicatedFunctionCall(), chúng tôi có thể phân rã chúng một cách đệ quy nếu chúng vẫn phức tạp.


2
i như thế này bởi vì bạn có thể đạt được một số khả năng đọc bằng cách cho các chức năng bao bọc một tên mô tả (mặc dù có lẽ không getSomeResult), quá nhiều câu trả lời khác không thực sự thêm bất cứ điều gì có giá trị
aw04

9

1) Tôi có thực sự bị mất SCE mỗi lần? Trình biên dịch có được phép "tối ưu hóa nó" và vẫn cung cấp SCE không?

Không, bạn không, nhưng nó được áp dụng theo cách khác:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Ở đây, trình biên dịch thậm chí sẽ không chạy OtherComplicatedFunctionCall()nếu SomeComplicatedFunctionCall()trả về true.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Ở đây, cả hai hàm sẽ chạy vì chúng phải được lưu trữ vào b1b2. Ff b1 == truesau đó b2sẽ không được đánh giá (SCE). Nhưng OtherComplicatedFunctionCall()đã được chạy rồi.

Nếu không b2được sử dụng ở nơi nào khác, trình biên dịch thể đủ thông minh để nội dòng lệnh gọi hàm bên trong hàm if if không có tác dụng phụ có thể quan sát được.

2) Cách thực hành tốt nhất trong tình huống như vậy là gì? Có phải khả năng duy nhất (khi tôi muốn SCE) có trực tiếp tất cả những gì tôi cần bên trong nếu và "chỉ định dạng nó để dễ đọc nhất có thể"?

Mà phụ thuộc. Bạn có cần OtherComplicatedFunctionCall() phải chạy do tác dụng phụ hay hiệu suất của hàm là tối thiểu thì bạn nên sử dụng cách tiếp cận thứ hai để dễ đọc. Nếu không, hãy theo đuổi SCE qua cách tiếp cận đầu tiên.


8

Một khả năng khác gây ra ngắn mạch và có các điều kiện ở một nơi:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

Bạn có thể đặt vòng lặp vào một hàm và để hàm chấp nhận một danh sách các điều kiện và xuất ra một giá trị boolean.


1
@Erbureth Không, họ không phải vậy. Các phần tử của mảng là con trỏ hàm, chúng không được thực thi cho đến khi các hàm được gọi trong vòng lặp.
Barmar

Cảm ơn Barmar, nhưng tôi đã chỉnh sửa, Erbureth đã đúng, trước khi chỉnh sửa (tôi nghĩ rằng chỉnh sửa của tôi sẽ đề xuất trực quan hơn).
levilime

4

Rất lạ: bạn đang nói về khả năng đọc khi không ai đề cập đến việc sử dụng nhận xét trong mã:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

Trên hết, tôi luôn ghi trước các hàm của mình bằng một số nhận xét, về bản thân hàm, về đầu vào và đầu ra của nó, và đôi khi tôi đặt một ví dụ, như bạn có thể thấy ở đây:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Rõ ràng là định dạng để sử dụng cho nhận xét của bạn có thể phụ thuộc vào môi trường phát triển của bạn (Visual studio, JavaDoc trong Eclipse, ...)

Đối với SCE có liên quan, tôi cho rằng ý của bạn là như sau:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}

-7

Khả năng đọc là cần thiết nếu bạn làm việc trong một công ty và mã của bạn sẽ bị người khác đọc. Nếu bạn viết một chương trình cho chính mình, bạn có muốn hy sinh hiệu suất vì mã dễ hiểu hay không.


23
Hãy nhớ rằng "bạn trong thời gian sáu tháng" chắc chắn là "người khác", và "bạn ngày mai" đôi khi có thể là. Tôi sẽ không bao giờ hy sinh khả năng đọc cho hiệu suất cho đến khi tôi có một số bằng chứng chắc chắn rằng có vấn đề về hiệu suất.
Martin Bonner hỗ trợ Monica
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.