Các lập trình viên có nên sử dụng các biến boolean để “ghi lại” mã của họ không?


79

Tôi đang đọc Code Complete của McConell và anh ấy thảo luận về việc sử dụng các biến boolean để ghi lại mã của bạn. Ví dụ, thay vì:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
   (elementIndex == lastElementIndex)){
       ...
}

Anh ấy đề nghị:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

Điều này đánh giá tôi là hợp lý, thực hành tốt và rất tự ghi lại. Tuy nhiên, tôi do dự khi bắt đầu sử dụng kỹ thuật này thường xuyên vì tôi hầu như chưa bao giờ gặp nó; và có lẽ nó sẽ gây nhầm lẫn chỉ vì hiếm có. Tuy nhiên, kinh nghiệm của tôi chưa nhiều lắm, vì vậy tôi muốn nghe ý kiến ​​của các lập trình viên về kỹ thuật này và tôi muốn biết liệu có ai sử dụng kỹ thuật này thường xuyên hoặc đã nhìn thấy nó thường xuyên khi đọc mã không. Đây có phải là một quy ước / phong cách / kỹ thuật đáng giá để áp dụng không? Các lập trình viên khác sẽ hiểu và đánh giá cao nó, hay coi nó là lạ?


@Paul R - rất tiếc, cảm ơn. Tôi bắt đầu tự lập tài liệu và nhận ra rằng không có thẻ nào cho điều đó, vì vậy đã cố gắng thay đổi nó thành tự lập tài liệu đã tồn tại. :)
froadie

Không chỉ bình luận về những gì đang xảy ra trong bài kiểm tra sẽ tốt hơn?
JS

3
Khi thích hợp, tôi cũng cố gắng làm điều này, đặc biệt nếu tôi phải kiểm tra (các) điều kiện trong các phần khác nhau của phương pháp. Nó cũng mang tính mô tả nhiều hơn.
geffchang

"Điều này đánh giá tôi là hợp lý, thực hành tốt và rất tự ghi lại." Chỉ cần dựa trên kinh nghiệm của bạn, rất có thể nó sẽ dễ dàng hiểu được bởi các lập trình viên khác, những người lần đầu tiên gặp nó.

2
Tại sao câu hỏi này vẫn còn bỏ ngỏ?
orangepips

Câu trả lời:


55

Tách một biểu thức quá lồng nhau và phức tạp thành các biểu thức con đơn giản hơn được gán cho các biến cục bộ, sau đó ghép lại với nhau, là một kỹ thuật khá phổ biến và phổ biến - hoàn toàn độc lập với việc liệu các biểu thức con và / hoặc biểu thức tổng thể là boolean hay of chỉ về bất kỳ loại nào khác. Với những cái tên được lựa chọn kỹ càng, kiểu phân tách trang nhã kiểu này có thể tăng khả năng đọc và một trình biên dịch tốt sẽ không gặp khó khăn khi tạo mã tương đương với biểu thức ban đầu, phức tạp.

Một số ngôn ngữ không có khái niệm "gán" cho mỗi ngôn ngữ, chẳng hạn như Haskell, thậm chí còn giới thiệu các cấu trúc chuyên biệt để cho phép bạn sử dụng kỹ thuật "đặt tên cho biểu thức con" ( wheremệnh đề trong Haskell) - dường như nói riêng một số phổ biến cho kỹ thuật được đề cập! -)


6
nếu nó đơn giản và dễ dàng hơn để đọc, tôi nói đó là một trường hợp khá rõ ràng về win-win :)
djcouchycouch

Tôi đồng ý. Trong nhiều trường hợp, bạn có thể bỏ qua bằng một bình luận ngắn, nhưng tôi không thấy điều này thực sự có thể gây tổn thương
Tối đa E.

16

Tôi đã sử dụng nó, mặc dù thường gói logic boolean thành một phương thức có thể tái sử dụng (nếu được gọi từ nhiều vị trí).

Nó giúp dễ đọc và khi logic thay đổi, nó chỉ cần thay đổi ở một chỗ.

Những người khác sẽ hiểu nó và sẽ không thấy lạ (trừ những người chỉ viết hàm nghìn dòng, nghĩa là).


Cảm ơn vì câu trả lời! Về các phương thức có thể tái sử dụng, có một lý do hợp lệ khác (không liên quan) để bị loại ra ... Vì vậy, tôi cho rằng câu hỏi của tôi thực sự là liệu bạn có nên tính toán các biểu thức boolean một lần hay không, khi không có lý do nào khác ngoài khả năng đọc được. (Tất nhiên, đó là một lý do đủ lớn của riêng nó :)) Cảm ơn vì đã chỉ ra điều đó.
froadie

+1 để giảm các thay đổi mã cần thiết khi logic thay đổi và làm trò cười cho các lập trình viên hàm nghìn dòng.
Jeffrey L Whitledge,

9

Tôi cố gắng làm điều đó bất cứ khi nào có thể. Chắc chắn, bạn đang sử dụng một "dòng bổ sung" của mã, nhưng đồng thời, bạn đang mô tả lý do tại sao bạn thực hiện so sánh hai giá trị.

Trong ví dụ của bạn, tôi nhìn vào mã và tự hỏi bản thân "được rồi tại sao người nhìn thấy giá trị nhỏ hơn 0?" Trong phần thứ hai, bạn đang nói rõ với tôi rằng một số quy trình đã kết thúc khi điều này xảy ra. Không đoán trong phần thứ hai ý định của bạn là gì.

Điều quan trọng đối với tôi là khi tôi thấy một phương thức như: DoSomeMethod(true); Tại sao nó tự động được đặt thành true? Nó dễ đọc hơn như

bool deleteOnCompletion = true;

DoSomeMethod(deleteOnCompletion);

7
Tôi không thích các tham số boolean vì lý do này. Bạn sẽ nhận được các cuộc gọi như "createOrder (true, false, true, true, false)" và điều đó có nghĩa là gì? Tôi thích sử dụng enum's hơn, vì vậy bạn có thể nói những thứ như "createOrder (Source.MAIL_ORDER, BackOrder.NO, CustomOrder.CUSTOM, PayType.CREDIT)".
Jay,

Nhưng nếu bạn làm theo ví dụ của Kevin, nó tương đương với của bạn. Điều đó có gì khác biệt nếu biến có thể nhận 2 hoặc nhiều hơn 2 giá trị?
Mark Ruzon

2
Với Jay's, bạn có thể có lợi thế là rõ ràng hơn trong một số trường hợp nhất định. Ví dụ: khi sử dụng PayType. Nếu đó là boolean, tham số có thể sẽ được đặt tên là isPayTypeCredit. Bạn không biết biến thể là gì. Với enum, bạn có thể thấy rõ PayType là những tùy chọn nào: Tín dụng, Séc, Tiền mặt và chọn đúng.
kemiller2002 19/03

++ cũng một điều tra không cho phép chuyển nhượng null để kiểm soát giá trị theo đúng nghĩa đen hoàn toàn, và các tính năng tự động tài liệu của enum là gravy trên đầu trang
Hardryv

Objective-C và Smalltalk thực sự giải quyết vấn đề này, trong Objective-C:[Object createOrderWithSource:YES backOrder:NO custom:YES type:kCreditCard];
Grant Paul

5

Mẫu được cung cấp:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

Cũng có thể được viết lại để sử dụng các phương pháp, giúp cải thiện khả năng đọc và bảo toàn logic boolean (như Konrad đã chỉ ra):

if (IsFinished(elementIndex) || IsRepeatedEntry(elementIndex, lastElementIndex)){
   ...
}

...

private bool IsFinished(int elementIndex) {
    return ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
}

private bool IsRepeatedEntry(int elementIndex, int lastElementIndex) {
    return (elementIndex == lastElementIndex);
}

Tất nhiên, nó đi kèm với một mức giá, đó là hai phương pháp phụ. Nếu bạn làm điều này nhiều, nó có thể làm cho mã của bạn dễ đọc hơn, nhưng các lớp của bạn kém minh bạch hơn. Nhưng một lần nữa, bạn cũng có thể chuyển các phương thức bổ sung vào các lớp trợ giúp.


Nếu bạn gặp phải nhiều nhiễu mã với C #, bạn cũng có thể tận dụng các lớp từng phần và chuyển nhiễu sang từng phần và nếu mọi người quan tâm đến những gì IsFinishing đang kiểm tra thì rất dễ dàng chuyển sang.
Chris Marisic

3

Cách duy nhất tôi có thể thấy điều này đang xảy ra sai là nếu đoạn boolean không có một cái tên có ý nghĩa và một cái tên được chọn theo cách nào đó.

//No clue what the parts might mean.
if(price>0 && (customer.IsAlive || IsDay(Thursday)))

=>

first_condition = price>0
second_condition =customer.IsAlive || IsDay(Thursday)

//I'm still not enlightened.
if(first_condition && second_condition)

Tôi chỉ ra điều này vì việc đưa ra các quy tắc như "nhận xét tất cả mã của bạn", "sử dụng boolean được đặt tên cho tất cả các tiêu chí if có nhiều hơn 3 phần" là điều phổ biến chỉ để nhận các nhận xét trống về mặt ngữ nghĩa của loại sau

i++; //increment i by adding 1 to i's previous value

2
Bạn đã nhóm sai các điều kiện trong ví dụ của mình, phải không? '&&' liên kết chặt chẽ hơn '||' trong hầu hết các ngôn ngữ sử dụng chúng (các tập lệnh shell là một ngoại lệ).
Jonathan Leffler

Parens thêm vào. Vì vậy, đây sẽ là một cuộc tấn công khác chống lại việc tách thành các biến đã đặt tên, nó tạo ra cơ hội để thay đổi nhóm theo kiểu không rõ ràng.
MatthewMartin

2

Bằng cách làm điều này

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

bạn loại bỏ logic khỏi bộ não của bạn và đưa nó vào mã. Bây giờ chương trình biết bạn muốn nói gì.
Bất cứ khi nào bạn đặt tên một cái gì đó, bạn cho nó đại diện vật lý . Nó có tồn tại.
Bạn có thể thao tác và sử dụng lại nó.

Bạn thậm chí có thể xác định toàn bộ khối dưới dạng một vị từ:

bool ElementBlahBlah? (elementIndex, lastElementIndex);

và làm nhiều thứ hơn (sau này) trong chức năng đó.


Và quan trọng hơn, nhà phát triển tiếp theo xem mã cũng sẽ biết ý của bạn! Đây là một thực hành tuyệt vời, và tôi làm điều đó mọi lúc.
Chris Thornton

1
Ngoài ra, nhà phát triển tiếp theo có thể thường là chính bạn, nhìn lại mã sau một vài tháng (hoặc thậm chí vài tuần) và vui mừng vì nó đã được tự ghi nhận.
Louis

2

Nếu biểu thức phức tạp thì tôi hoặc di chuyển nó sang một hàm khác trả về một boolví dụ, isAnEveningInThePubAGoodIdea(dayOfWeek, sizeOfWorkLoad, amountOfSpareCash)hoặc xem xét lại mã để biểu thức phức tạp như vậy không cần thiết.


2

Hãy nhớ rằng theo cách này bạn tính toán nhiều hơn mức cần thiết. Do các điều kiện từ mã, bạn luôn tính toán cả hai (không đoản mạch).

Vậy nên:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
   (elementIndex == lastElementIndex)){
   ...
}

Sau khi biến đổi trở thành:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) |
   (elementIndex == lastElementIndex)){
   ...
}

Không phải là vấn đề trong hầu hết các trường hợp, nhưng trong một số trường hợp, nó có thể có nghĩa là hiệu suất kém hơn hoặc các vấn đề khác, ví dụ: khi trong biểu thức thứ 2, bạn cho rằng biểu thức thứ nhất không thành công.


Đúng - Tôi chỉ nhận thấy điều này bây giờ khi tôi quay lại một số mã của mình để cấu trúc lại một vài câu lệnh if bằng cách sử dụng phương pháp này. Có một câu lệnh if lợi dụng việc đánh giá ngắn mạch bằng && (phần thứ hai ném NPE nếu phần đầu tiên là sai) và mã của tôi đã bị lỗi khi tôi cấu trúc lại điều này (vì nó luôn đánh giá cả hai và lưu trữ chúng trong boolean biến). Điểm tốt, cảm ơn! Nhưng tôi đã tự hỏi khi tôi thử điều này - có cách nào để lưu trữ logic trong một biến và nó có trì hoãn việc đánh giá cho đến khi có câu lệnh if thực không?
froadie

2
Tôi thực sự nghi ngờ rằng trình biên dịch sẽ giải quyết điều đó. Nếu lệnh gọi thứ hai không hiệu quả, thường là do lệnh gọi của một số hàm và AFAIK không có trình biên dịch nào đang cố gắng xác định xem một hàm được gọi có không có tác dụng phụ hay không.
JS

Bạn có thể lồng các IF và không thực hiện các tính toán sau trừ khi thử nghiệm đầu tiên không đủ để quyết định có nên tiếp tục hay không.
Jay,

@froadie Một số ngôn ngữ như Kotlin (và sắp tới là Dart) cho phép các biến "lười biếng" sẽ chỉ được tính toán khi được sử dụng. Ngoài ra, đặt logic vào một hàm thay vì một biến sẽ có tác dụng tương tự ở đây. Chỉ là, bạn biết đấy, trong trường hợp bạn vẫn muốn biết 10 năm sau.
hacker1024,

2

Tôi nghĩ tốt hơn nên tạo các hàm / phương thức thay vì các biến tạm thời. Bằng cách này, khả năng đọc cũng được tăng lên vì các phương thức ngắn hơn. Cuốn sách Refactoring của Martin Fowler có lời khuyên hữu ích để cải thiện chất lượng mã. Các cấu trúc lại liên quan đến ví dụ cụ thể của bạn được gọi là "Replace Temp with Query" và "Extract Method".


2
Có phải bạn đang nói rằng bằng cách làm lộn xộn không gian lớp với rất nhiều chức năng một lần, bạn đang tăng khả năng đọc? Vui lòng giải thích.
Zano

Nó luôn luôn là một sự đánh đổi. Khả năng đọc của chức năng gốc sẽ được cải thiện. Nếu chức năng ban đầu ngắn, nó có thể không có giá trị.
mkj

Ngoài ra, "lộn xộn không gian lớp" là điều gì đó tôi nghĩ phụ thuộc vào ngôn ngữ được sử dụng và cách bạn phân vùng mã của mình.
mkj

2

Cá nhân tôi nghĩ rằng đây là một thực hành tuyệt vời. Tác động của nó đối với việc thực thi mã là rất ít, nhưng sự rõ ràng mà nó có thể cung cấp, nếu được sử dụng đúng cách, là vô giá khi nói đến việc duy trì mã sau này.


1

nếu phương thức yêu cầu thông báo thành công: (ví dụ trong c #) Tôi muốn sử dụng

bool success = false;

Để bắt đầu. mã này không ổn cho đến khi tôi thay đổi nó thành:

success = true;

sau đó ở cuối:

return success;

0

Tôi nghĩ, nó phụ thuộc vào phong cách bạn / nhóm của bạn thích. Tái cấu trúc "Giới thiệu biến" có thể hữu ích, nhưng đôi khi không :)

Và tôi không đồng ý với Kevin trong bài viết trước của anh ấy. Ví dụ của anh ấy, tôi cho rằng có thể sử dụng được trong trường hợp khi biến được giới thiệu có thể được thay đổi, nhưng việc giới thiệu nó chỉ cho một boolean tĩnh là vô ích, bởi vì chúng ta có tên tham số trong khai báo phương thức, vậy tại sao lại sao chép nó trong mã?

ví dụ:

void DoSomeMethod(boolean needDelete) { ... }

// useful
boolean deleteOnCompletion = true;
if ( someCondition ) {
    deleteOnCompletion = false;
}
DoSomeMethod(deleteOnCompletion);

// useless
boolean shouldNotDelete = false;
DoSomeMethod(shouldNotDelete);

0

Theo kinh nghiệm của mình, tôi thường quay lại một số kịch bản cũ và tự hỏi 'hồi đó mình đang nghĩ cái quái gì vậy?'. Ví dụ:

Math.p = function Math_p(a) {
    var r = 1, b = [], m = Math;
    a = m.js.copy(arguments);
    while (a.length) {
        b = b.concat(a.shift());
    }
    while (b.length) {
        r *= b.shift();
    }
    return r;
};

không trực quan như:

/**
 * An extension to the Math object that accepts Arrays or Numbers
 * as an argument and returns the product of all numbers.
 * @param(Array) a A Number or an Array of numbers.
 * @return(Number) Returns the product of all numbers.
 */
Math.product = function Math_product(a) {
    var product = 1, numbers = [];
    a = argumentsToArray(arguments);
    while (a.length) {
        numbers = numbers.concat(a.shift());
    }
    while (numbers.length) {
        product *= numbers.shift();
    }
    return product;
};

-1. Thành thật mà nói, điều này có một chút liên quan đến câu hỏi ban đầu. Câu hỏi là về một thứ khác, rất cụ thể, không phải về cách viết mã tốt nói chung.
P Shved

0

Tôi hiếm khi tạo các biến riêng biệt. Điều tôi làm khi các bài kiểm tra trở nên phức tạp là lồng các IF và thêm nhận xét. Giống

boolean processElement=false;
if (elementIndex < 0) // Do we have a valid element?
{
  processElement=true;
}
else if (elementIndex==lastElementIndex) // Is it the one we want?
{
  processElement=true;
}
if (processElement)
...

Lỗ hổng được thừa nhận của kỹ thuật này là lập trình viên tiếp theo có thể thay đổi logic nhưng không bận tâm đến việc cập nhật các nhận xét. Tôi đoán đó là một vấn đề chung, nhưng tôi đã rất nhiều lần nhìn thấy một nhận xét có nội dung "xác thực id khách hàng" và dòng tiếp theo là kiểm tra số bộ phận hoặc một số như vậy và tôi còn lại để tự hỏi khách hàng ở đâu id đến.

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.