Có sai không khi sử dụng tham số boolean để xác định hành vi?


194

Thỉnh thoảng tôi đã thấy một thực tế "cảm thấy" sai, nhưng tôi không thể nói rõ điều gì sai về nó. Hoặc có thể đó chỉ là định kiến ​​của tôi. Đây là:

Một nhà phát triển định nghĩa một phương thức với boolean là một trong các tham số của nó và phương thức đó gọi một phương thức khác, v.v. và cuối cùng, boolean đó được sử dụng, chỉ để xác định có thực hiện một hành động nào đó hay không. Ví dụ, điều này có thể được sử dụng để chỉ cho phép hành động nếu người dùng có một số quyền nhất định hoặc có lẽ nếu chúng tôi (hoặc không) ở chế độ thử nghiệm hoặc chế độ hàng loạt hoặc chế độ trực tiếp hoặc có lẽ chỉ khi hệ thống ở chế độ trạng thái nhất định.

Vâng, luôn luôn có một cách khác để làm điều đó, cho dù bằng cách truy vấn khi đến lúc phải thực hiện hành động (thay vì truyền tham số) hoặc bằng cách có nhiều phiên bản của phương thức, hoặc nhiều triển khai của lớp, v.v. Không có quá nhiều cách để cải thiện điều này, nhưng đúng hơn là nó có thực sự sai hay không (như tôi nghi ngờ), và nếu có, điều gì là sai về nó.


1
Đây là một câu hỏi về nơi quyết định thuộc về. Di chuyển các quyết định ở một vị trí trung tâm thay vì để chúng vương vãi khắp nơi. Điều này sẽ giữ cho độ phức tạp thấp hơn so với việc có một yếu tố hai đường dẫn mã mỗi khi bạn có if.

28
Martin Fowler có một bài viết về điều này: martinfowler.com/bliki/FlagArgument.html
Christoffer Hammarström


@ Christoffer Hammarström Liên kết đẹp. Tôi sẽ đưa nó vào câu trả lời của tôi vì nó giải thích chi tiết cùng một ý tưởng về lời giải thích của tôi.
Alex

1
Tôi không luôn đồng ý với những gì Nick nói, nhưng trong trường hợp này tôi đồng ý 100%: Không sử dụng các tham số boolean .
Marjan Venema

Câu trả lời:


107

Vâng, đây có thể là một mùi mã, sẽ dẫn đến mã không thể nhầm lẫn rất khó hiểu và có khả năng dễ dàng sử dụng lại thấp hơn.

Vì các áp phích khác đã ghi nhận bối cảnh là tất cả mọi thứ (không được xử lý mạnh tay nếu nó là một hoặc nếu thực tế đã được thừa nhận là cố tình phát sinh nợ kỹ thuật để được xác nhận lại sau) nhưng nói rộng ra nếu có thông số được thông qua vào một chức năng chọn hành vi cụ thể sẽ được thực hiện sau đó cần phải tinh chỉnh thêm từng bước; Việc chia nhỏ chức năng này thành các chức năng nhỏ hơn sẽ tạo ra các chức năng gắn kết cao hơn.

Vậy một chức năng gắn kết cao là gì?

Đó là một chức năng chỉ làm một việc và một việc duy nhất.

Vấn đề với một tham số được truyền vào như bạn mô tả, là hàm đang thực hiện nhiều hơn hai điều; nó có thể hoặc không thể kiểm tra quyền truy cập của người dùng tùy thuộc vào trạng thái của tham số Boolean, sau đó tùy thuộc vào cây quyết định đó, nó sẽ thực hiện một phần chức năng.

Sẽ tốt hơn nếu tách các mối quan tâm của Kiểm soát truy cập khỏi các mối quan tâm của Nhiệm vụ, Hành động hoặc Lệnh.

Như bạn đã lưu ý, sự đan xen của những mối quan tâm này dường như tắt.

Vì vậy, khái niệm về Độ kết dính giúp chúng ta xác định rằng hàm trong câu hỏi không có tính gắn kết cao và chúng ta có thể cấu trúc lại mã để tạo ra một tập hợp các hàm gắn kết hơn.

Vì vậy, câu hỏi có thể được trình bày lại; Cho rằng tất cả chúng ta đồng ý thông qua các tham số lựa chọn hành vi là tốt nhất nên tránh làm thế nào để chúng ta cải thiện vấn đề?

Tôi sẽ thoát khỏi tham số hoàn toàn. Có khả năng tắt kiểm soát truy cập ngay cả để thử nghiệm là một rủi ro bảo mật tiềm ẩn. Đối với mục đích thử nghiệm hoặc hoặc kiểm tra khả năng tiếp cận để kiểm tra tất cả các truy cập cho phép và access denied kịch bản.

Tham chiếu: Sự gắn kết (khoa học máy tính)


Rob, bạn sẽ đưa ra một số lời giải thích về sự gắn kết là gì và nó được áp dụng như thế nào?
Ray

Ray, tôi càng nghĩ về điều này, tôi càng nghĩ rằng bạn nên phản đối mã dựa trên lỗ hổng bảo mật mà boolean chuyển sang điều khiển truy cập đưa vào ứng dụng. Cải thiện chất lượng cho cơ sở mã sẽ là một hiệu ứng phụ tuyệt vời;)
Rob

1
Giải thích rất hay về sự gắn kết và đó là ứng dụng. Điều này thực sự sẽ nhận được nhiều phiếu hơn. Và tôi cũng đồng ý với vấn đề bảo mật ... mặc dù nếu chúng đều là các phương thức riêng tư thì đó là một lỗ hổng tiềm năng nhỏ hơn
Ray

1
Cảm ơn Ray. Âm thanh như nó sẽ đủ dễ dàng để tái yếu tố khi thời gian cho phép. Có thể đáng để bỏ một bình luận TODO để làm nổi bật vấn đề, tạo ra sự cân bằng giữa thẩm quyền kỹ thuật và độ nhạy cảm với áp lực mà đôi khi chúng ta phải chịu để hoàn thành công việc.
Cướp

"không thể
nhầm lẫn

149

Tôi đã ngừng sử dụng mẫu này từ lâu, vì một lý do rất đơn giản; chi phí bảo trì. Nhiều lần tôi thấy rằng tôi có một số hàm nói rằng frobnicate(something, forwards_flag)nó được gọi nhiều lần trong mã của tôi và cần xác định vị trí của tất cả các vị trí trong mã nơi giá trị falseđược truyền là giá trị của forwards_flag. Bạn không thể dễ dàng tìm kiếm những thứ đó, vì vậy điều này trở thành một vấn đề đau đầu về bảo trì. Và nếu bạn cần tạo một lỗi tại mỗi trang web đó, bạn có thể gặp sự cố đáng tiếc nếu bạn bỏ lỡ một trang.

Nhưng vấn đề cụ thể này có thể dễ dàng khắc phục mà không thay đổi căn bản cách tiếp cận:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

Với mã này, người ta chỉ cần tìm kiếm các phiên bản của FrobnicateBackwards. Mặc dù có thể có một số mã gán biến này cho một biến để bạn phải tuân theo một vài luồng điều khiển, nhưng thực tế tôi thấy rằng điều này hiếm khi đủ để sự thay thế này hoạt động tốt.

Tuy nhiên, có một vấn đề khác với việc truyền cờ theo cách này, ít nhất là về nguyên tắc. Đây là một số (chỉ một số) hệ thống có thiết kế này có thể tiết lộ quá nhiều kiến ​​thức về các chi tiết triển khai của các phần được lồng sâu của mã (sử dụng cờ) cho các lớp bên ngoài (cần biết giá trị nào cần vượt qua trong lá cờ này). Để sử dụng thuật ngữ của Larry Constantine, thiết kế này có thể có sự kết hợp quá mạnh giữa người thiết lập và người sử dụng cờ boolean. Franky mặc dù thật khó để nói với bất kỳ mức độ chắc chắn nào về câu hỏi này mà không biết thêm về cơ sở mã.

Để giải quyết các ví dụ cụ thể mà bạn đưa ra, tôi sẽ có một số mức độ quan tâm trong mỗi ví dụ nhưng chủ yếu là vì lý do rủi ro / tính chính xác. Đó là, nếu hệ thống của bạn cần chuyển qua các cờ biểu thị trạng thái của hệ thống, bạn có thể thấy rằng bạn đã có mã cần phải tính đến điều này nhưng không kiểm tra tham số (vì nó không được chuyển đến Chức năng này). Vì vậy, bạn có một lỗi vì ai đó đã bỏ qua để truyền tham số.

Cũng đáng thừa nhận rằng một chỉ báo trạng thái hệ thống cần được truyền cho hầu hết mọi chức năng trên thực tế là một biến toàn cục. Nhiều nhược điểm của một biến toàn cầu sẽ được áp dụng. Tôi nghĩ trong nhiều trường hợp, tốt hơn hết là nên gói gọn kiến ​​thức về trạng thái hệ thống (hoặc thông tin xác thực của người dùng hoặc danh tính của hệ thống) trong một đối tượng chịu trách nhiệm hành động chính xác trên cơ sở dữ liệu đó. Sau đó, bạn chuyển qua một tham chiếu đến đối tượng đó trái ngược với dữ liệu thô. Khái niệm chính ở đây là đóng gói .


9
Những ví dụ cụ thể thực sự tuyệt vời cũng như cái nhìn sâu sắc về bản chất của những gì chúng ta đang giải quyết và nó ảnh hưởng đến chúng ta như thế nào.
Ray

33
+1. Tôi sử dụng enums cho điều này càng nhiều càng tốt. Tôi đã thấy các chức năng trong đó các booltham số bổ sung đã được thêm vào sau đó và các cuộc gọi bắt đầu trông như thế DoSomething( myObject, false, false, true, false ). Không thể hiểu được các đối số boolean thêm có nghĩa là gì, trong khi với các giá trị enum được đặt tên có ý nghĩa, thật dễ dàng.
Graeme Perrow

17
Ôi trời, cuối cùng là một ví dụ điển hình về cách FrobnicateBackwards. Đã tìm kiếm điều này mãi mãi.
Alex Pritchard

38

Điều này không hẳn là sai nhưng nó có thể đại diện cho mùi mã .

Kịch bản cơ bản nên tránh liên quan đến các tham số boolean là:

public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}

Sau đó, khi gọi, bạn thường gọi foo(false)foo(true)tùy thuộc vào hành vi chính xác mà bạn muốn.

Đây thực sự là một vấn đề bởi vì đó là một trường hợp gắn kết xấu. Bạn đang tạo ra sự phụ thuộc giữa các phương thức không thực sự cần thiết.

Những gì bạn nên làm trong trường hợp này là để lại doThisdoThatnhư các phương pháp riêng biệt và công khai sau đó làm:

doThis();
doThat();

hoặc là

doThis();

Bằng cách đó, bạn để lại quyết định chính xác cho người gọi (chính xác như thể bạn đang truyền tham số boolean) mà không tạo khớp nối.

Tất nhiên không phải tất cả các tham số boolean đều được sử dụng theo cách xấu như vậy nhưng chắc chắn đó là mùi mã và bạn có quyền nghi ngờ nếu bạn thấy điều đó rất nhiều trong mã nguồn.

Đây chỉ là một ví dụ về cách giải quyết vấn đề này dựa trên các ví dụ tôi đã viết. Có những trường hợp khác mà một cách tiếp cận khác sẽ là cần thiết.

Có một bài viết hay từ Martin Fowler giải thích chi tiết hơn về cùng một ý tưởng.

PS: nếu phương thức foothay vì gọi hai phương thức đơn giản có triển khai phức tạp hơn thì tất cả những gì bạn phải làm là áp dụng một phương thức trích xuất tái cấu trúc nhỏ để mã kết quả trông giống như cách thực hiện foomà tôi đã viết.


1
Cảm ơn bạn đã gọi ra thuật ngữ "mùi mã" - tôi biết nó có mùi khó chịu, nhưng không thể hiểu được mùi này là gì. Ví dụ của bạn khá nổi bật với những gì tôi đang xem.
Ray

24
Có rất nhiều tình huống mà if (flag) doThat()bên trong foo()là hợp pháp. Đẩy quyết định về việc gọi doThat()tới mọi người gọi buộc phải lặp lại nếu bạn sau đó tìm ra một số phương thức, flaghành vi cũng cần phải gọi doTheOther(). Tôi thà đặt sự phụ thuộc giữa các phương thức trong cùng một lớp hơn là phải truy quét tất cả những người gọi sau này.
Blrfl

1
@Blrfl: Vâng, tôi nghĩ rằng các phép tái cấu trúc đơn giản hơn sẽ tạo ra một phương thức doOnedoBoth(tương ứng cho trường hợp sai và đúng) hoặc sử dụng một loại enum riêng biệt, như được đề xuất bởi James Youngman
hugomg

@missingno: Bạn vẫn muốn có cùng một vấn đề đẩy mã dư thừa ra để người gọi để làm cho doOne()hoặc doBoth()quyết định. Các chương trình con / hàm / phương thức có đối số để hành vi của chúng có thể được thay đổi. Sử dụng một enum cho một điều kiện Boolean thực sự nghe có vẻ giống như lặp lại chính mình nếu tên của đối số đã giải thích những gì nó làm.
Blrfl

3
Nếu việc gọi hai phương thức lần lượt theo phương thức kia hoặc một phương thức có thể được coi là dư thừa thì đó cũng là cách bạn dự phòng cách viết một khối thử bắt hoặc có thể là nếu khác. Có nghĩa là bạn sẽ viết một hàm để trừu tượng tất cả chúng? Không! Hãy cẩn thận, tạo một phương thức mà tất cả những gì nó làm là gọi hai phương thức khác không nhất thiết phải thể hiện một sự trừu tượng hóa tốt.
Alex

29

Trước hết: lập trình không phải là một môn khoa học vì nó là một nghệ thuật. Vì vậy, rất hiếm khi một cách "sai" và một cách "đúng" để lập trình. Hầu hết các tiêu chuẩn mã hóa chỉ là "sở thích" mà một số lập trình viên cho là hữu ích; nhưng cuối cùng họ khá độc đoán. Vì vậy, tôi sẽ không bao giờ gắn nhãn một lựa chọn tham số là "sai" trong chính nó - và chắc chắn không phải là một cái gì đó chung chung và hữu ích như một tham số boolean. Việc sử dụng một boolean(hoặc một int, cho vấn đề đó) để đóng gói trạng thái là hoàn toàn chính đáng trong nhiều trường hợp.

Các quyết định mã hóa, bởi & lớn, nên chủ yếu dựa trên hiệu suất và khả năng bảo trì. Nếu hiệu suất không bị đe dọa (và tôi không thể tưởng tượng được nó có thể như thế nào trong các ví dụ của bạn), thì sự cân nhắc tiếp theo của bạn sẽ là: điều này sẽ dễ dàng như thế nào đối với tôi (hoặc một người sửa chữa trong tương lai) để duy trì? Có trực quan và dễ hiểu không? Có bị cô lập không? Ví dụ về các cuộc gọi hàm bị xiềng xích của bạn trong thực tế có vẻ dễ vỡ hơn về mặt này: nếu bạn quyết định thay đổi bIsUpthành bIsDown, thì có bao nhiêu vị trí khác trong mã cũng sẽ cần phải thay đổi? Ngoài ra, danh sách paramater của bạn là khinh khí cầu? Nếu chức năng của bạn có 17 tham số, thì khả năng đọc là một vấn đề và bạn cần xem xét lại xem bạn có đánh giá cao lợi ích của kiến ​​trúc hướng đối tượng hay không.


4
Tôi đánh giá cao sự báo trước trong đoạn đầu tiên. Tôi đã cố tình khiêu khích bằng cách nói "sai", và chắc chắn thừa nhận rằng chúng ta đang đối phó với "nguyên tắc thiết kế tốt nhất" và các nguyên tắc thiết kế, và những thứ này thường mang tính tình huống và người ta phải cân nhắc nhiều yếu tố
Ray

13
Câu trả lời của bạn làm tôi nhớ đến một câu trích dẫn mà tôi không thể nhớ được nguồn gốc của "- nếu chức năng của bạn có 17 tham số, có lẽ bạn đang thiếu một tham số".
Joris Timmermans

Tôi rất đồng ý với câu hỏi này, và áp dụng cho câu hỏi để nói rằng vâng, đó thường là một ý tưởng tồi để vượt qua trong cờ boolean, nhưng nó không bao giờ đơn giản như xấu / tốt ...
JohnB

15

Tôi nghĩ rằng bài viết về mã sạch của Robert C Martins nói rằng bạn nên loại bỏ các đối số boolean khi có thể vì chúng hiển thị một phương thức thực hiện nhiều hơn một điều. Một phương pháp nên làm một điều và một điều duy nhất tôi nghĩ là một trong những phương châm của anh ấy.


@dreza bạn đang đề cập đến Luật Quăn .
MattDavey

Tất nhiên với kinh nghiệm bạn nên biết khi nào nên bỏ qua những lập luận như vậy.
gnasher729

8

Tôi nghĩ rằng điều quan trọng nhất ở đây là phải thực tế.

Khi boolean xác định toàn bộ hành vi, chỉ cần thực hiện một phương thức thứ hai.

Khi boolean chỉ xác định một chút hành vi ở giữa, bạn có thể muốn giữ nó trong một để cắt giảm sao chép mã. Nếu có thể, bạn thậm chí có thể chia phương thức thành ba: Hai phương thức gọi cho tùy chọn boolean và một phương thức thực hiện phần lớn công việc.

Ví dụ:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

Tất nhiên, trong thực tế, bạn sẽ luôn có một điểm ở giữa những thái cực này. Thông thường tôi chỉ đi với những gì cảm thấy đúng, nhưng tôi thích sai ở phía ít sao chép mã hơn.


Tôi chỉ sử dụng các đối số boolean để kiểm soát hành vi trong các phương thức riêng tư (như được hiển thị ở đây). Nhưng vấn đề: Nếu một số dufus quyết định tăng khả năng hiển thị FooInternaltrong tương lai, thì sao?
ADTC

Thật ra, tôi sẽ đi một con đường khác. Mã bên trong FooI Internalal nên được chia thành 4 phần: 2 hàm để xử lý các trường hợp đúng / sai Boolean, một cho công việc xảy ra trước và một cho công việc xảy ra sau đó. Sau đó, bạn Foo1trở thành : { doWork(); HandleTrueCase(); doMoreWork() }. Một cách lý tưởng, các hàm doWorkdoMoreWorkmỗi hàm được chia thành (một hoặc nhiều) các đoạn có ý nghĩa của các hành động riêng biệt (nghĩa là các hàm riêng biệt), không chỉ là hai hàm vì mục đích chia tách.
jpaugh

7

Tôi thích cách tiếp cận tùy chỉnh hành vi thông qua các phương thức xây dựng trả về các thể hiện bất biến. Đây là cách GuavaSplitter sử dụng nó:

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");

Lợi ích của việc này là:

  • Khả năng đọc cao
  • Xóa cấu hình và phương thức hành động
  • Thúc đẩy sự gắn kết bằng cách buộc bạn phải suy nghĩ về đối tượng là gì, những gì nên và không nên làm. Trong trường hợp này đó là một Splitter. Bạn sẽ không bao giờ đặt someVaguelyStringRelatedOperation(List<Entity> myEntities)một lớp được gọi Splitter, nhưng bạn nghĩ về việc đặt nó như một phương thức tĩnh trong một StringUtilslớp.
  • Các phiên bản được cấu hình sẵn do đó dễ dàng phụ thuộc vào tiêm. Khách hàng không cần phải lo lắng về việc nên vượt qua truehay falseđến một phương pháp để có được hành vi chính xác.

1
Tôi là một phần cho giải pháp của bạn với tư cách là người yêu và nhà truyền giáo ổi lớn ... nhưng tôi không thể cho bạn +1, vì bạn bỏ qua phần tôi thực sự đang tìm kiếm, đó là điều sai (hoặc có mùi) về cách khác Tôi nghĩ rằng nó thực sự tiềm ẩn trong một số giải thích của bạn, vì vậy có lẽ nếu bạn có thể làm cho nó rõ ràng, nó sẽ trả lời câu hỏi tốt hơn.
Ray

Tôi thích ý tưởng tách biệt các phương thức cấu hình và acton!
Sher10ck

Liên kết đến thư viện Guava bị hỏng
Josh Noe

4

Chắc chắn là một mùi mã . Nếu nó không vi phạm Nguyên tắc Trách nhiệm Đơn lẻ thì có lẽ nó vi phạm Tell, Đừng hỏi . Xem xét:

Nếu hóa ra không vi phạm một trong hai nguyên tắc đó, bạn vẫn nên sử dụng enum. Cờ Boolean là số boolean tương đương với số ma thuật . foo(false)làm cho nhiều ý nghĩa như bar(42). Enums có thể hữu ích cho Mẫu chiến lược và có tính linh hoạt cho phép bạn thêm một chiến lược khác. (Chỉ cần nhớ đặt tên cho chúng một cách thích hợp .)

Ví dụ cụ thể của bạn đặc biệt làm phiền tôi. Tại sao lá cờ này được thông qua rất nhiều phương pháp? Điều này có vẻ như bạn cần chia tham số của bạn thành các lớp con .


4

TL; DR: không sử dụng đối số boolean.

Xem bên dưới tại sao chúng xấu, và làm thế nào để thay thế chúng (trong khuôn mặt đậm).


Đối số Boolean rất khó đọc, và do đó khó duy trì. Vấn đề chính là mục đích nói chung là rõ ràng khi bạn đọc chữ ký phương thức trong đó đối số được đặt tên. Tuy nhiên, việc đặt tên cho một tham số thường không bắt buộc trong hầu hết các ngôn ngữ. Vì vậy, bạn sẽ có các kiểu chống giống như RSACryptoServiceProvider#encrypt(Byte[], Boolean)nơi tham số boolean xác định loại mã hóa nào sẽ được sử dụng trong hàm.

Vì vậy, bạn sẽ nhận được một cuộc gọi như:

rsaProvider.encrypt(data, true);

trong đó người đọc phải tra cứu chữ ký của phương thức chỉ để xác định trueý nghĩa thực sự của địa ngục . Truyền một số nguyên tất nhiên cũng tệ như vậy:

rsaProvider.encrypt(data, 1);

sẽ nói với bạn nhiều như vậy - hay đúng hơn: chỉ là ít thôi. Ngay cả khi bạn xác định các hằng số được sử dụng cho số nguyên thì người dùng của hàm có thể chỉ cần bỏ qua chúng và tiếp tục sử dụng các giá trị bằng chữ.

Cách tốt nhất để giải quyết điều này là sử dụng bảng liệt kê . Nếu bạn phải vượt qua một enum RSAPaddingvới hai giá trị: OAEPhoặc PKCS1_V1_5ngay lập tức bạn sẽ có thể đọc mã:

rsaProvider.encrypt(data, RSAPadding.OAEP);

Booleans chỉ có thể có hai giá trị. Điều này có nghĩa là nếu bạn có tùy chọn thứ ba, thì bạn phải cấu trúc lại chữ ký của mình. Nói chung, điều này không thể được thực hiện dễ dàng nếu khả năng tương thích ngược là một vấn đề, vì vậy bạn phải mở rộng bất kỳ lớp công khai nào bằng một phương thức công khai khác. Đây là những gì Microsoft cuối cùng đã làm khi họ giới thiệu RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding)nơi họ đã sử dụng một bảng liệt kê (hoặc ít nhất là một lớp bắt chước một bảng liệt kê) thay vì boolean.

Thậm chí có thể dễ dàng hơn để sử dụng một đối tượng hoặc giao diện đầy đủ làm tham số, trong trường hợp chính tham số đó cần được tham số hóa. Trong ví dụ trên, phần đệm OAEP có thể được tham số hóa với giá trị băm để sử dụng nội bộ. Lưu ý rằng hiện có 6 thuật toán băm SHA-2 và 4 thuật toán băm SHA-3, vì vậy số lượng giá trị liệt kê có thể phát nổ nếu bạn chỉ sử dụng một phép liệt kê duy nhất thay vì tham số (đây có thể là điều tiếp theo Microsoft sẽ tìm ra ).


Các tham số Boolean cũng có thể chỉ ra rằng phương thức hoặc lớp không được thiết kế tốt. Như với ví dụ ở trên: bất kỳ thư viện mã hóa nào khác ngoài .NET không sử dụng cờ đệm trong chữ ký phương thức.


Hầu như tất cả các bậc thầy về phần mềm mà tôi thích đều cảnh báo chống lại các lập luận boolean. Chẳng hạn, Joshua Bloch cảnh báo chống lại họ trong cuốn sách được đánh giá cao "Java hiệu quả". Nói chung, chúng không nên được sử dụng. Bạn có thể lập luận rằng chúng có thể được sử dụng nếu trường hợp có một tham số dễ hiểu. Nhưng ngay cả sau đó: Bit.set(boolean)có lẽ được thực hiện tốt hơn bằng cách sử dụng hai phương pháp : Bit.set()Bit.unset().


Nếu bạn không thể trực tiếp cấu trúc lại mã, bạn có thể xác định các hằng số để ít nhất làm cho chúng dễ đọc hơn:

const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);

dễ đọc hơn nhiều so với:

cipher.init(key, true);

ngay cả khi bạn muốn có:

cipher.initForEncryption(key);
cipher.initForDecryption(key);

thay thế.


3

Tôi ngạc nhiên không ai có đề cập tên-thông số .

Vấn đề tôi thấy với cờ boolean là chúng làm tổn thương khả năng đọc. Ví dụ, những gì truetrong

myObject.UpdateTimestamps(true);

làm gì Tôi không có ý kiến. Nhưng những gì về:

myObject.UpdateTimestamps(saveChanges: true);

Bây giờ thì rõ ràng tham số chúng ta truyền có nghĩa là gì: chúng ta đang nói với hàm để lưu các thay đổi của nó. Trong trường hợp này, nếu lớp không công khai, tôi nghĩ tham số boolean là tốt.


Tất nhiên, bạn không thể ép buộc người dùng trong lớp của bạn sử dụng các tham số có tên. Vì lý do này, một enumhoặc hai phương thức riêng biệt thường được ưa thích hơn, tùy thuộc vào mức độ phổ biến của trường hợp mặc định của bạn. Đây chính xác là những gì .Net làm:

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 

1
Câu hỏi đặt ra không phải là về những gì là thích hợp hơn để một lá cờ hành vi xác định, đó là dù một lá cờ như một mùi, và nếu như vậy, tại sao
Ray

2
@Ray: Tôi không thấy sự khác biệt giữa hai câu hỏi đó. Trong ngôn ngữ mà bạn có thể thực thi việc sử dụng các tham số được đặt tên hoặc khi bạn có thể chắc chắn các tham số được đặt tên sẽ luôn được sử dụng (ví dụ: các phương thức riêng tư) , các tham số boolean vẫn ổn. Nếu các tham số được đặt tên không thể được thi hành bởi ngôn ngữ (C #) và lớp là một phần của API công khai hoặc nếu ngôn ngữ không hỗ trợ các tham số có tên (C ++), thì mã đó myFunction(true)có thể được viết, đó là mã- mùi.
BlueRaja - Danny Pflughoeft

Cách tiếp cận tham số được đặt tên thậm chí còn sai hơn. Không có tên, người ta sẽ buộc phải đọc tài liệu API. Với một cái tên, bạn nghĩ rằng bạn không cần: nhưng thông số có thể là tên sai. Ví dụ, nó có thể đã được sử dụng để lưu (tất cả) thay đổi, nhưng sau đó, việc cấy ghép đã thay đổi một chút để chỉ lưu các thay đổi lớn (đối với một số giá trị lớn).
Ingo

@Ingo Tôi không đồng ý. Đó là một vấn đề lập trình chung; bạn có thể dễ dàng xác định SaveBigtên khác . Bất kỳ mã nào có thể được vặn lên, loại vít này không đặc biệt cho các tham số được đặt tên.
Maarten Bodewes

1
@Ingo: Nếu bạn bị bao vây bởi những kẻ ngốc, thì bạn hãy đi tìm việc ở nơi khác. Đó là những gì bạn có đánh giá mã cho.
gnasher729

1

Tôi không thể nói rõ điều gì sai về nó.

Nếu nó trông giống mùi mã, cảm giác giống mùi mã và - cũng - có mùi giống mùi mã, có lẽ đó là mùi mã.

Những gì bạn muốn làm là:

1) Tránh các phương pháp có tác dụng phụ.

2) Xử lý các trạng thái cần thiết với một máy trạng thái trung tâm, chính thức (như thế này ).


1

Tôi đồng ý với tất cả các mối quan tâm về việc sử dụng Tham số Boolean để không xác định hiệu suất để; cải thiện, Khả năng đọc, Độ tin cậy, Giảm độ phức tạp, Giảm rủi ro từ Đóng gói & Gắn kết kém và Tổng chi phí sở hữu thấp hơn với Khả năng duy trì.

Tôi bắt đầu thiết kế phần cứng vào giữa những năm 70 mà bây giờ chúng tôi gọi là SCADA (kiểm soát giám sát và thu thập dữ liệu) và đây là những phần cứng được điều chỉnh tốt với mã máy trong EPROM chạy điều khiển từ xa macro và thu thập dữ liệu tốc độ cao.

Logic được gọi là máy Mealey & Moore mà chúng ta gọi là Máy trạng thái hữu hạn . Những điều này cũng phải được thực hiện theo các quy tắc như trên, trừ khi nó là một cỗ máy thời gian thực với thời gian thực hiện hữu hạn và sau đó các phím tắt phải được thực hiện để phục vụ mục đích.

Dữ liệu là đồng bộ nhưng các lệnh không đồng bộ và logic lệnh theo logic Boolean không bộ nhớ nhưng với các lệnh tuần tự dựa trên bộ nhớ của trạng thái tiếp theo trước, hiện tại và mong muốn. Để điều đó hoạt động với ngôn ngữ máy hiệu quả nhất (chỉ 64kB), cần phải hết sức cẩn thận để xác định mọi quy trình theo kiểu heuristic IBM HIPO. Điều đó đôi khi có nghĩa là chuyển các biến Boolean và thực hiện các nhánh được lập chỉ mục.

Nhưng bây giờ với rất nhiều bộ nhớ và dễ sử dụng OOK, Encapsulation là một thành phần thiết yếu ngày nay nhưng là một hình phạt khi mã được tính bằng byte cho thời gian thực và mã máy SCADA ..


0

Nó không hẳn là sai, nhưng trong ví dụ cụ thể của bạn về hành động tùy thuộc vào một số thuộc tính của "người dùng", tôi sẽ chuyển qua một tham chiếu đến người dùng thay vì cờ.

Điều này làm rõ và giúp theo một số cách.

Bất cứ ai đọc câu lệnh gọi sẽ nhận ra rằng kết quả sẽ thay đổi tùy thuộc vào người dùng.

Trong chức năng cuối cùng được gọi, bạn có thể dễ dàng thực hiện các quy tắc kinh doanh phức tạp hơn vì bạn có thể truy cập bất kỳ thuộc tính người dùng nào.

Nếu một hàm / phương thức trong "chuỗi" thực hiện một số thứ khác nhau tùy thuộc vào thuộc tính người dùng, thì rất có thể một phụ thuộc tương tự vào thuộc tính người dùng sẽ được đưa vào một số phương thức khác trong "chuỗi".


0

Hầu hết thời gian, tôi sẽ xem xét mã hóa xấu này. Tôi có thể nghĩ về hai trường hợp, tuy nhiên, đây có thể là một thực hành tốt. Vì có rất nhiều câu trả lời đã nói tại sao nó xấu, tôi đưa ra hai lần khi nó có thể tốt:

Đầu tiên là nếu mỗi cuộc gọi trong chuỗi có ý nghĩa theo đúng nghĩa của nó. Sẽ có ý nghĩa nếu mã gọi có thể được thay đổi từ true thành false hoặc false thành true hoặc nếu phương thức được gọi có thể được thay đổi để sử dụng tham số boolean trực tiếp thay vì chuyển nó vào. Khả năng mười cuộc gọi như vậy liên tiếp là nhỏ, nhưng nó có thể xảy ra, và nếu nó được thực hiện thì đó sẽ là thực hành lập trình tốt.

Trường hợp thứ hai là một chút kéo dài mà chúng ta đang đối phó với một boolean. Nhưng nếu chương trình có nhiều luồng hoặc sự kiện, truyền tham số là cách đơn giản nhất để theo dõi dữ liệu cụ thể của luồng / sự kiện. Ví dụ, một chương trình có thể nhận đầu vào từ hai hoặc nhiều ổ cắm. Mã chạy cho một ổ cắm có thể cần tạo ra các thông báo cảnh báo và một mã chạy cho một ổ cắm khác có thể không. Sau đó (sắp xếp) có ý nghĩa đối với một tập hợp boolean ở mức rất cao để được chuyển qua nhiều lệnh gọi phương thức đến những nơi có thể tạo ra các thông báo cảnh báo. Dữ liệu không thể được lưu (ngoại trừ khó khăn lớn) trong bất kỳ loại toàn cầu nào vì nhiều luồng hoặc các sự kiện xen kẽ sẽ cần mỗi giá trị riêng.

Để chắc chắn, trong trường hợp sau, tôi có thể tạo một lớp / cấu trúc có nội dung duy nhất là boolean và thay vào đó chuyển qua đó . Tôi gần như chắc chắn cần các trường khác trước khi quá lâu - ví dụ như nơi gửi thông điệp cảnh báo.


Một WARN enum, SILENT sẽ có ý nghĩa hơn, ngay cả khi sử dụng một lớp / cấu trúc như bạn đã viết (tức là một bối cảnh ). Hoặc thực sự chỉ cần cấu hình đăng nhập bên ngoài - không cần phải vượt qua bất cứ điều gì.
Maarten Bodewes

-1

Bối cảnh rất quan trọng. Các phương pháp như vậy là khá phổ biến trong iOS. Như chỉ là một ví dụ thường được sử dụng, UINavestionControll cung cấp phương thức -pushViewController:animated:animatedtham số là BOOL. Phương thức này thực hiện về cơ bản cùng một chức năng, nhưng nó hoạt hình quá trình chuyển đổi từ bộ điều khiển khung nhìn này sang bộ điều khiển chế độ xem khác nếu bạn chuyển qua CÓ, và không nếu bạn vượt qua NO. Điều này có vẻ hoàn toàn hợp lý; thật là ngớ ngẩn khi phải cung cấp hai phương pháp thay cho phương pháp này chỉ để bạn có thể xác định có sử dụng hoạt hình hay không.

Có thể dễ dàng hơn để chứng minh loại điều này trong Objective-C, trong đó cú pháp đặt tên phương thức cung cấp nhiều ngữ cảnh cho mỗi tham số hơn là bạn có trong các ngôn ngữ như C và Java. Tuy nhiên, tôi nghĩ rằng một phương thức lấy một tham số duy nhất có thể dễ dàng lấy boolean và vẫn có ý nghĩa:

file.saveWithEncryption(false);    // is there any doubt about the meaning of 'false' here?

21
Thật ra tôi không có ý falsenghĩa gì trong file.saveWithEncryptionví dụ. Có nghĩa là nó sẽ tiết kiệm mà không cần mã hóa? Nếu vậy, tại sao trên thế giới phương thức này lại có "mã hóa" ngay trong tên? Tôi có thể hiểu có một phương thức như thế nào save(boolean withEncryption), nhưng khi tôi nhìn thấy file.save(false), nó không rõ ràng trong nháy mắt rằng tham số chỉ ra rằng nó sẽ có hoặc không có mã hóa. Tôi nghĩ rằng, trên thực tế, điều này làm cho điểm đầu tiên của James Youngman, về việc sử dụng một enum.
Ray

6
Một giả thuyết khác là điều đó falsecó nghĩa là, đừng ghi đè lên bất kỳ tệp nào có cùng tên. Tôi biết ví dụ, nhưng để chắc chắn bạn cần kiểm tra tài liệu (hoặc mã) cho hàm.
James Youngman

5
Một phương pháp có tên saveWithEncryptionđôi khi không lưu với mã hóa là một lỗi. Nó nên có khả năng file.encrypt().save(), hoặc giống như Javas new EncryptingOutputStream(new FileOutputStream(...)).write(file).
Christoffer Hammarström

2
Trên thực tế, mã làm một cái gì đó khác với những gì nó nói không hoạt động, và do đó là một lỗi. Nó không bay để tạo ra một phương thức có tên saveWithEncryption(boolean)đôi khi lưu mà không cần mã hóa, giống như nó không bay để tạo ra một phương saveWithoutEncryption(boolean)thức đôi khi tiết kiệm bằng mã hóa.
Christoffer Hammarström

2
Phương pháp này rất tệ, vì rõ ràng có "nghi ngờ về ý nghĩa của" sai "ở đây". Dù sao, tôi sẽ không bao giờ viết một phương pháp như thế ở nơi đầu tiên. Lưu và mã hóa là các hành động riêng biệt và một phương pháp nên thực hiện một việc và làm tốt. Xem ý kiến ​​trước đây của tôi để biết ví dụ tốt hơn làm thế nào để làm điều đó.
Christoffer Hammarström
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.