Là các biến cờ là một cái ác tuyệt đối? [đóng cửa]


47

Là biến cờ ác? Là loại biến sau đây vô cùng sâu sắc và nó có xấu xa khi sử dụng chúng không?

"Các biến số boolean hoặc số nguyên mà bạn gán một giá trị ở một số vị trí nhất định sau đó xuống bên dưới bạn kiểm tra sau đó trong orther để làm gì đó hay không, ví dụ như sử dụng newItem = truemột số dòng bên dưới if (newItem ) then"


Tôi nhớ đã thực hiện một vài dự án mà tôi hoàn toàn bỏ qua việc sử dụng cờ và kết thúc với kiến ​​trúc / mã tốt hơn; tuy nhiên, đó là một thực tế phổ biến trong các dự án khác mà tôi làm việc và khi mã phát triển và cờ được thêm vào, spaghetti mã IMHO cũng phát triển.

Bạn có nói rằng có bất kỳ trường hợp nào sử dụng cờ là một cách thực hành tốt hoặc thậm chí cần thiết không?, Hoặc bạn có đồng ý rằng sử dụng cờ trong mã là ... cờ đỏ và nên tránh / tái cấu trúc; tôi, tôi chỉ nhận được bằng cách thực hiện các chức năng / phương pháp kiểm tra trạng thái trong thời gian thực thay thế.


9
Có vẻ như MainMa và tôi có một định nghĩa khác về "cờ". Tôi đã suy nghĩ tiền xử lý #ifdefs. Bạn đã hỏi về cái nào?
Karl Bielefeldt

Đây thực sự là một câu hỏi tốt. Tôi đã tự hỏi điều này rất nhiều, và thực sự đã thấy mình nói rằng "ồ, hãy sử dụng một lá cờ" một chút quá nhiều.
Paul Richter

Booleans là cờ. (Số nguyên cũng vậy, ...)
Thomas Eding

7
@KarlBielefeldt Tôi tin rằng OP được đề cập đến boolean hoặc số nguyên biến mà bạn gán một giá trị ở những nơi nhất định sau đó xuống bên dưới, bạn kiểm tra sau đó trong tin tức khác để làm điều gì đó hay không, như thế nào, ví dụ như sử dụng newItem = truesau đó một số dòng dưới đâyif (newItem ) then
Tulains Córdova

1
Cũng xem xét giới thiệu giải thích tái cấu trúc biến trong bối cảnh này. Miễn là phương thức này vẫn còn ngắn và có số lượng đường dẫn thấp, tôi coi đây là một cách sử dụng hợp lệ.
Daniel B

Câu trả lời:


41

Vấn đề tôi đã thấy khi duy trì mã sử dụng cờ là số lượng trạng thái tăng nhanh và hầu như luôn có các trạng thái chưa được xử lý. Một ví dụ từ kinh nghiệm của riêng tôi: Tôi đã làm việc với một số mã có ba cờ này

bool capturing, processing, sending;

Ba người này đã tạo ra tám trạng thái (thực ra, có hai lá cờ khác nữa). Không phải tất cả các kết hợp giá trị có thể được bao phủ bởi mã và người dùng đã thấy lỗi:

if(capturing && sending){ // we must be processing as well
...
}

Hóa ra có những tình huống trong đó giả định trong câu lệnh if ở trên là sai.

Cờ có xu hướng kết hợp theo thời gian và chúng che giấu trạng thái thực tế của một lớp. Đó là lý do tại sao họ nên tránh.


3
+1, "nên tránh". Tôi sẽ thêm vài thứ về 'nhưng cờ là cần thiết trong một số tình huống' (một số người có thể nói 'một điều ác cần thiết')
Trevor Boyd Smith

2
@TrevorBoydSmith Theo kinh nghiệm của tôi thì không, bạn chỉ cần nhiều hơn một chút so với năng lực não trung bình bạn sẽ sử dụng cho một lá cờ
dukeofgaming

Trong bài kiểm tra của bạn, nó phải là một enum đại diện cho trạng thái, không phải là 3 booleans.
dùng949300

Bạn có thể gặp một vấn đề tương tự như những gì tôi đang phải đối mặt ngay bây giờ. Ngoài việc bao gồm tất cả các trạng thái có thể, hai ứng dụng có thể chia sẻ cùng một cờ (ví dụ: tải lên dữ liệu khách hàng). Trong trường hợp này, chỉ có một Trình tải lên sẽ sử dụng cờ, tắt nó và chúc may mắn tìm ra vấn đề trong tương lai.
Alan

38

Đây là một ví dụ khi cờ hữu ích.

Tôi có một đoạn mã tạo mật khẩu (sử dụng trình tạo số giả ngẫu nhiên bảo mật bằng mật mã). Người gọi phương thức chọn có hay không mật khẩu nên chứa chữ in hoa, chữ nhỏ, chữ số, ký hiệu cơ bản, ký hiệu mở rộng, ký hiệu Hy Lạp, ký hiệu Cyrillic và unicode.

Với cờ, gọi phương thức này thật dễ dàng:

var password = this.PasswordGenerator.Generate(
    CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);

và thậm chí có thể được đơn giản hóa để:

var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);

Không có cờ, chữ ký phương thức sẽ là gì?

public byte[] Generate(
    bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
    bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);

gọi như thế này:

// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
    true, true, true, false, false, false, false, false);

Như đã lưu ý trong các ý kiến, một cách tiếp cận khác là sử dụng một bộ sưu tập:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LowercaseLetters,
        CharacterSet.UppercaseLetters,
    });

Điều này dễ đọc hơn nhiều so với tập hợp truefalse, nhưng vẫn có hai nhược điểm:

Hạn chế chính là để cho phép các giá trị kết hợp, giống như CharacterSet.LettersAndDigitsbạn sẽ viết một cái gì đó giống như vậy trong Generate()phương thức:

if (set.Contains(CharacterSet.LowercaseLetters) ||
    set.Contains(CharacterSet.Letters) ||
    set.Contains(CharacterSet.LettersAndDigits) ||
    set.Contains(CharacterSet.Default) ||
    set.Contains(CharacterSet.All))
{
    // The password should contain lowercase letters.
}

có thể viết lại như thế này:

var lowercaseGroups = new []
{
    CharacterSet.LowercaseLetters,
    CharacterSet.Letters,
    CharacterSet.LettersAndDigits,
    CharacterSet.Default,
    CharacterSet.All,
};

if (lowercaseGroups.Any(s => set.Contains(s)))
{
    // The password should contain lowercase letters.
}

So sánh điều này với những gì bạn có bằng cách sử dụng cờ:

if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
    // The password should contain lowercase letters.
}

Hạn chế thứ hai, rất nhỏ là không rõ phương thức này sẽ hoạt động như thế nào nếu được gọi như thế này:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });

10
Tôi tin rằng OP đang đề cập đến các biến số boolean hoặc số nguyên mà bạn gán một giá trị ở một số vị trí nhất định sau đó xuống bên dưới bạn kiểm tra sau đó để làm gì đó hay không, ví dụ như sử dụng newItem = truemột số dòng bên dướiif (newItem ) then
Tulains Córdova

1
@MainMa Rõ ràng có lần thứ 3: Phiên bản có 8 đối số boolean là những gì tôi nghĩ khi đọc "cờ" ...
Izkata

4
Xin lỗi, nhưng IMHO đây là trường hợp hoàn hảo cho chuỗi phương thức ( en.wikipedia.org/wiki/Method_chained ), Ngoài ra, bạn có thể sử dụng một mảng tham số (phải là một mảng kết hợp hoặc bản đồ), trong đó bất kỳ mục nào trong mảng tham số đó bạn bỏ qua sử dụng hành vi giá trị mặc định cho tham số đó. Cuối cùng, cuộc gọi thông qua chuỗi phương thức hoặc mảng tham số có thể ngắn gọn và biểu cảm như cờ bit, ngoài ra, không phải ngôn ngữ nào cũng có toán tử bit (tôi thực sự thích cờ nhị phân, nhưng sẽ sử dụng các phương thức tôi vừa đề cập thay thế).
dukeofgaming

3
Nó không phải là rất OOP, phải không? Tôi sẽ tạo một giao diện ala: String myNewPassword = makePassword (RandomComposeSupplier (new RandomLowerCaseSupplier (), RandomUpperCaseSupplier (), RandomNumberSupplier mới)); với String makePassword (Nhà cung cấp <Ký tự> charSupplier); và Nhà cung cấp <Nhân vật> RandomComposeSupplier (Nhà cung cấp <Nhân vật> ... nhà cung cấp); Bây giờ bạn có thể sử dụng lại các nhà cung cấp của mình cho các tác vụ khác, soạn chúng theo bất kỳ cách nào bạn thích và đơn giản hóa phương thức GenerPassword của bạn để nó sử dụng trạng thái tối thiểu.
Dibbeke

4
@Dibbeke Nói về một vương quốc của danh từ ...
Phil

15

Một khối chức năng khổng lồ là mùi chứ không phải cờ. Nếu bạn đặt cờ trên dòng 5, thì chỉ kiểm tra cờ trên dòng 354, thì điều đó thật tệ. Nếu bạn đặt cờ trên dòng 8 và kiểm tra cờ trên dòng 10, điều đó tốt. Ngoài ra, một hoặc hai cờ cho mỗi khối mã là tốt, 300 cờ trong một chức năng là xấu.


10

Thông thường các cờ có thể được thay thế hoàn toàn bằng một số hương vị của mẫu chiến lược, với một triển khai chiến lược cho mọi giá trị có thể của cờ. Điều này làm cho việc thêm hành vi mới dễ dàng hơn rất nhiều.

Trong các tình huống quan trọng về hiệu suất, chi phí của sự gián tiếp có thể xuất hiện và biến cấu trúc thành các cờ rõ ràng cần thiết. Điều đó được nói rằng tôi gặp khó khăn để nhớ một trường hợp duy nhất mà tôi thực sự phải làm điều đó.


6

Không, cờ không phải là xấu hay xấu mà phải được tái cấu trúc bằng mọi giá.

Hãy xem xét lệnh gọi Pattern.compile (String regex, int flags) của Java . Đây là một bitmask truyền thống và nó hoạt động. Nhìn vào các hằng số trong java và bất cứ nơi nào bạn thấy một bó 2 n bạn biết có những lá cờ đang ở đó.

Trong một thế giới được tái cấu trúc lý tưởng, thay vào đó, người ta sẽ sử dụng một Enumset trong đó các hằng số thay vào đó là các giá trị trong một enum và như tài liệu ghi:

Hiệu suất không gian và thời gian của lớp này phải đủ tốt để cho phép sử dụng nó như một sự thay thế an toàn, chất lượng cao cho các "cờ bit" dựa trên int truyền thống.

Trong một thế giới hoàn hảo, cuộc gọi Pattern.compile đó trở thành Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags).

Tất cả những gì đã nói, nó vẫn còn cờ. Nó dễ làm việc Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)hơn nhiều so với nó sẽ có Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())hoặc một số phong cách khác cố gắng làm những gì cờ thực sự và tốt cho.

Cờ thường được nhìn thấy khi làm việc với những thứ cấp hệ thống. Khi giao tiếp với một cái gì đó ở cấp hệ điều hành, người ta có thể có một cờ ở đâu đó - có thể là giá trị trả về của một quá trình, hoặc quyền của tệp hoặc cờ để mở ổ cắm. Cố gắng cấu trúc lại các trường hợp này trong một số cuộc săn phù thủy chống lại mùi mã nhận thức sẽ có khả năng kết thúc với mã tồi tệ hơn so với việc người ta sử dụng chấp nhận và hiểu cờ.

Vấn đề xảy ra khi mọi người lạm dụng các lá cờ để ném chúng lại với nhau và tạo ra một tập hợp các loại cờ không liên quan hoặc cố gắng sử dụng chúng ở nơi chúng không có cờ.


5

Tôi giả sử chúng ta đang nói về cờ trong chữ ký phương thức.

Sử dụng một lá cờ là đủ xấu.

Nó sẽ không có ý nghĩa gì với đồng nghiệp của bạn khi họ thấy nó. Họ sẽ phải xem mã nguồn của phương thức để thiết lập những gì nó làm. Bạn có thể sẽ ở cùng một vị trí trong vài tháng, khi bạn quên mất phương pháp của bạn là gì.

Truyền cờ cho phương thức, thông thường có nghĩa là phương thức của bạn chịu trách nhiệm cho nhiều thứ. Bên trong phương thức có thể bạn đang thực hiện một kiểm tra đơn giản trên các dòng:

if (flag)
   DoFlagSet();
else
   DoFlagNotSet();

Đó là một sự tách biệt đáng lo ngại và bạn thường có thể tìm ra cách giải quyết.

Tôi thường có hai phương pháp riêng biệt:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

Điều này sẽ có ý nghĩa hơn với các tên phương thức có thể áp dụng cho vấn đề mà bạn đang giải quyết.

Vượt qua nhiều cờ là xấu gấp đôi. Nếu bạn thực sự cần phải vượt qua nhiều cờ, hơn là xem xét đóng gói chúng trong một lớp. Ngay cả khi đó, bạn vẫn sẽ phải đối mặt với cùng một vấn đề, vì phương pháp của bạn có thể làm nhiều việc.


3

Cờ và hầu hết các biến temp là một mùi mạnh. Nhiều khả năng chúng có thể được tái cấu trúc và thay thế bằng các phương thức truy vấn.

Sửa đổi:

Cờ và biến tạm thời khi thể hiện trạng thái, nên được cấu trúc lại thành các phương thức truy vấn. Các giá trị trạng thái (booleans, ints và các nguyên hàm khác) gần như sẽ được ẩn đi như một phần của chi tiết triển khai.

Các cờ được sử dụng để điều khiển, định tuyến và luồng chương trình chung cũng có thể chỉ ra cơ hội tái cấu trúc các phần của cấu trúc điều khiển thành các chiến lược hoặc nhà máy riêng biệt hoặc bất cứ điều gì có thể phù hợp theo tình huống, tiếp tục sử dụng các phương thức truy vấn.


2

Khi chúng ta nói về các cờ, chúng ta nên biết rằng chúng sẽ được sửa đổi theo thời gian thực hiện chương trình và chúng sẽ ảnh hưởng đến hành vi của chương trình dựa trên trạng thái của chúng. Miễn là chúng ta có quyền kiểm soát gọn gàng hai thứ này, chúng sẽ hoạt động rất tốt.

Cờ có thể làm việc tuyệt vời nếu

  • Bạn đã xác định chúng trong một phạm vi thích hợp. Theo ý tôi, phạm vi không nên chứa bất kỳ mã nào không cần / không nên sửa đổi chúng. Hoặc ít nhất là mã được bảo mật (ví dụ: nó có thể không được gọi trực tiếp từ bên ngoài)
  • Nếu cần phải xử lý cờ từ bên ngoài và nếu có nhiều cờ chúng ta có thể mã trình xử lý cờ như một cách duy nhất để sửa đổi cờ một cách an toàn. Trình xử lý cờ này có thể tự đóng gói các cờ và phương thức để sửa đổi chúng. Sau đó, nó có thể được tạo thành singleton và sau đó có thể được chia sẻ giữa các lớp cần truy cập vào cờ.
  • Và cuối cùng cho khả năng bảo trì, nếu có quá nhiều cờ:
    • Không cần phải nói họ nên tuân theo cách đặt tên hợp lý
    • Cần được ghi lại với các giá trị hợp lệ là gì (có thể bằng cách liệt kê)
    • Nên được ghi lại bằng MÃ SỐ MÀ S MOD SỬA ĐỔI từng người trong số họ, và với ĐIỀU KIỆN NÀO sẽ dẫn đến việc gán một giá trị cụ thể cho cờ.
    • MÃ SỐ NÀO S CONS TIÊU THỤ cho họ và CÁI GÌ sẽ dẫn đến một giá trị cụ thể

Nếu có nhiều cờ thì công việc thiết kế tốt nên đi trước các cờ sau đó bắt đầu đóng vai trò quan trọng trong hành vi của chương trình. Bạn có thể đi cho sơ đồ trạng thái cho mô hình. Các sơ đồ như vậy cũng hoạt động như tài liệu và hướng dẫn trực quan trong khi xử lý chúng.

Miễn là những thứ này được đặt đúng chỗ tôi nghĩ nó sẽ không dẫn đến sự lộn xộn.


1

Tôi giả sử từ câu hỏi rằng QA có nghĩa là các biến cờ (toàn cầu) và không phải là các bit của tham số hàm.

Có những tình huống mà bạn không có nhiều khả năng khác. Ví dụ, không có hệ điều hành, bạn phải đánh giá các ngắt. Nếu một sự gián đoạn xảy ra rất thường xuyên và bạn không có thời gian để đánh giá dài trong ISR, thì không chỉ được phép mà đôi khi, cách tốt nhất là chỉ đặt một số cờ toàn cầu trong ISR (bạn nên dành ít thời gian nhất có thể trong ISR) và để đánh giá các cờ đó trong vòng lặp chính của bạn.


0

Tôi không nghĩ bất cứ điều gì là một điều xấu xa tuyệt đối trong lập trình.

Có một tình huống khác mà cờ có thể theo thứ tự, chưa được đề cập ở đây ...

Hãy xem xét việc sử dụng các bao đóng trong đoạn mã Javascript này:

exports.isPostDraft = function ( post, draftTag ) {
  var isDraft = false;
  if (post.tags)
    post.tags.forEach(function(tag){ 
      if (tag === draftTag) isDraft = true;
    });
  return isDraft;
}

Hàm bên trong, được chuyển đến "Array.forEach", không thể đơn giản là "trả về true".

Do đó, bạn cần giữ trạng thái bên ngoài bằng một lá cờ.

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.