Làm thế nào tôi có thể thực hiện một cuộc gọi với một boolean rõ ràng hơn? Boolean Bẫy


76

Như đã lưu ý trong các bình luận của @ benjamin-gruenbaum, đây được gọi là bẫy Boolean:

Nói rằng tôi có một chức năng như thế này

UpdateRow(var item, bool externalCall);

và trong bộ điều khiển của tôi, giá trị đó externalCallsẽ luôn luôn ĐÚNG. Cách tốt nhất để gọi chức năng này là gì? Tôi thường viết

UpdateRow(item, true);

Nhưng tôi tự hỏi mình, tôi có nên khai báo boolean không, chỉ để cho biết giá trị 'thật' đó là gì? Bạn có thể biết rằng bằng cách xem khai báo của hàm, nhưng rõ ràng nó nhanh hơn và rõ hơn nếu bạn vừa thấy một cái gì đó như

bool externalCall = true;
UpdateRow(item, externalCall);

PD: Không chắc câu hỏi này có thực sự phù hợp ở đây không, nếu không, tôi có thể lấy thêm thông tin về vấn đề này ở đâu?

PD2: Tôi không gắn thẻ bất kỳ ngôn ngữ nào vì tôi nghĩ đó là một vấn đề rất chung chung. Dù sao, tôi làm việc với c # và câu trả lời được chấp nhận hoạt động cho c #


10
Mô hình này cũng được gọi là bẫy boolean
Benjamin Gruenbaum

11
Nếu bạn đang sử dụng một ngôn ngữ có hỗ trợ cho các loại dữ liệu đại số, tôi rất muốn giới thiệu một quảng cáo mới thay vì boolean. data CallType = ExternalCall | InternalCalltrong haskell chẳng hạn.
Filip Haglund

6
Tôi đoán Enums sẽ điền vào cùng một mục đích; đặt tên cho booleans, và một chút an toàn loại.
Filip Haglund

2
Tôi không đồng ý rằng việc khai báo boolean để chỉ nghĩa là "rõ ràng rõ ràng hơn". Với tùy chọn đầu tiên, rõ ràng là bạn sẽ luôn vượt qua sự thật. Với cái thứ hai, bạn phải kiểm tra (biến được xác định ở đâu? Giá trị của nó có thay đổi không?). Chắc chắn, đó không phải là vấn đề nếu hai dòng kết hợp với nhau ... nhưng ai đó có thể quyết định rằng vị trí hoàn hảo để thêm mã của họ chính xác là giữa hai dòng. Nó xảy ra!
AJPerez

Khai báo một boolean không sạch hơn, rõ ràng hơn là chỉ cần nhảy qua một bước, xem tài liệu của phương pháp đang đề cập. Nếu bạn biết chữ ký, sẽ không rõ ràng để thêm một hằng số mới.
bên trong

Câu trả lời:


154

Luôn luôn không có một giải pháp hoàn hảo, nhưng bạn có nhiều lựa chọn thay thế:

  • Sử dụng các đối số được đặt tên , nếu có sẵn trong ngôn ngữ của bạn. Điều này hoạt động rất tốt và không có nhược điểm cụ thể. Trong một số ngôn ngữ, bất kỳ đối số nào cũng có thể được chuyển qua dưới dạng đối số được đặt tên, ví dụ updateRow(item, externalCall: true)( C # ) hoặc update_row(item, external_call=True)(Python).

    Đề xuất của bạn sử dụng một biến riêng biệt là một cách để mô phỏng các đối số được đặt tên, nhưng không có lợi ích an toàn liên quan (không có gì đảm bảo rằng bạn đã sử dụng tên biến chính xác cho đối số đó).

  • Sử dụng các chức năng riêng biệt cho giao diện công cộng của bạn, với tên tốt hơn. Đây là một cách khác để mô phỏng các tham số được đặt tên, bằng cách đặt các giá trị paremeter trong tên.

    Điều này rất dễ đọc, nhưng dẫn đến rất nhiều bản tóm tắt cho bạn, người đang viết các chức năng này. Nó cũng không thể đối phó tốt với vụ nổ tổ hợp khi có nhiều đối số boolean. Một nhược điểm đáng kể là khách hàng không thể thiết lập giá trị này một cách linh hoạt mà phải sử dụng if / other để gọi hàm chính xác.

  • Sử dụng một enum . Vấn đề với booleans là chúng được gọi là "đúng" và "sai". Vì vậy, thay vì giới thiệu một loại có tên tốt hơn (ví dụ enum CallType { INTERNAL, EXTERNAL }). Là một lợi ích bổ sung, điều này làm tăng sự an toàn của loại chương trình của bạn (nếu ngôn ngữ của bạn thực hiện enum như các loại khác biệt). Hạn chế của enums là họ thêm một loại vào API hiển thị công khai của bạn. Đối với các chức năng hoàn toàn bên trong, điều này không quan trọng và enum không có nhược điểm đáng kể.

    Trong các ngôn ngữ không có enums, chuỗi ngắn đôi khi được sử dụng thay thế. Điều này hoạt động, và thậm chí có thể tốt hơn so với booleans thô, nhưng rất dễ bị lỗi chính tả. Sau đó, hàm sẽ ngay lập tức xác nhận rằng đối số khớp với một tập hợp các giá trị có thể.

Không có giải pháp nào trong số này có tác động cấm hiệu quả. Các tham số và enum được đặt tên có thể được giải quyết hoàn toàn tại thời gian biên dịch (đối với ngôn ngữ được biên dịch). Sử dụng các chuỗi có thể liên quan đến so sánh chuỗi, nhưng chi phí đó không đáng kể đối với các chuỗi ký tự nhỏ và hầu hết các loại ứng dụng.


7
Tôi chỉ học được rằng "đối số được đặt tên" tồn tại. Tôi nghĩ rằng đây là gợi ý tốt nhất. Nếu bạn có thể tạo ra một chút về tùy chọn đó (để người khác không cần quá nhiều về nó) tôi sẽ chấp nhận câu trả lời này. Ngoài ra bất kỳ gợi ý về hiệu suất nếu bạn có thể ... Cảm ơn
Mario Garcia

6
Một điều có thể giúp giảm bớt các vấn đề với các chuỗi ngắn là sử dụng các hằng số với các giá trị chuỗi. @MarioGarcia Không có nhiều thứ để giải thích. Ngoài những gì được đề cập ở đây, hầu hết các chi tiết sẽ phụ thuộc vào ngôn ngữ cụ thể. Có điều gì đặc biệt bạn muốn thấy được đề cập ở đây?
jpmc26

8
Trong các ngôn ngữ mà chúng ta đang nói về một phương thức enum có thể được đặt trong một lớp tôi thường sử dụng một enum. Nó hoàn toàn tự ghi lại tài liệu (trong một dòng mã khai báo loại enum, vì vậy nó cũng rất nhẹ), hoàn toàn ngăn chặn phương thức được gọi bằng boolean trần trụi và tác động lên API công khai là không đáng kể (vì các định danh được xếp vào lớp).
davidbak

4
Một thiếu sót khác của việc sử dụng các chuỗi trong vai trò này là bạn không thể kiểm tra tính hợp lệ của chúng tại thời điểm biên dịch, không giống như enums.
Ruslan

32
enum là người chiến thắng Chỉ vì một tham số chỉ có thể có một trong hai trạng thái có thể, điều đó không có nghĩa là nó sẽ tự động là một boolean.
Pete

39

Giải pháp chính xác là làm những gì bạn đề xuất, nhưng gói nó thành một mặt tiền nhỏ:

void updateRowExternally() {
  bool externalCall = true;
  UpdateRow(item, externalCall);
}

Khả năng đọc vượt qua tối ưu hóa vi mô. Bạn có thể đủ khả năng gọi thêm chức năng, chắc chắn tốt hơn bạn có thể đủ khả năng cho nhà phát triển nỗ lực tìm kiếm ngữ nghĩa của cờ Boolean dù chỉ một lần.


8
Sự đóng gói này có thực sự đáng không khi tôi chỉ gọi chức năng đó một lần trong bộ điều khiển của mình?
Mario Garcia

14
Đúng. Vâng, đúng vậy. Lý do, như tôi đã viết ở trên, là chi phí (một cuộc gọi chức năng) nhỏ hơn lợi ích (bạn tiết kiệm một lượng thời gian không cần thiết của nhà phát triển vì không ai phải tìm kiếm những gì họ truelàm.) Nó sẽ đáng giá ngay cả khi nó tốn nhiều thời gian CPU vì nó tiết kiệm thời gian của nhà phát triển, vì thời gian CPU rẻ hơn nhiều.
Kilian Foth

8
Ngoài ra, bất kỳ trình tối ưu hóa có thẩm quyền nào cũng có thể thực hiện điều đó cho bạn để cuối cùng bạn không bị mất bất cứ thứ gì.
fireraco

33
@KilianFoth Ngoại trừ đó là một giả định sai. Một nhà duy trì không cần phải biết những gì các tham số thứ hai không, và tại sao nó là sự thật, và hầu như luôn luôn điều này liên quan đến từng chi tiết về những gì bạn đang cố gắng làm. Chức năng ẩn trong các chức năng nhỏ chết chóc hàng ngàn lần sẽ làm giảm khả năng bảo trì, không làm tăng chức năng này. Tôi đã thấy nó xảy ra chính mình, buồn để nói. Phân vùng quá mức vào các chức năng thực sự có thể tồi tệ hơn một chức năng Thần khổng lồ.
Graham

6
Tôi nghĩ rằng UpdateRow(item, true /*external call*/);sẽ sạch hơn, trong các ngôn ngữ cho phép cú pháp nhận xét đó. Làm đầy mã của bạn với một chức năng bổ sung chỉ để tránh viết bình luận dường như không xứng đáng với trường hợp đơn giản. Có lẽ nếu có nhiều đối số khác cho chức năng này và / hoặc một số mã lộn xộn + mã xung quanh, nó sẽ bắt đầu hấp dẫn hơn. Nhưng tôi nghĩ rằng nếu bạn gỡ lỗi thì cuối cùng bạn sẽ kiểm tra chức năng trình bao bọc để xem nó hoạt động như thế nào và phải nhớ những chức năng nào là trình bao bọc mỏng xung quanh API thư viện và thực sự có logic.
Peter Cordes

25

Nói rằng tôi có một chức năng như UpdateRow (var item, bool bên ngoài);

Tại sao bạn có một chức năng như thế này?

Trong trường hợp nào bạn sẽ gọi nó với đối số bên ngoài được đặt thành các giá trị khác nhau?

Nếu một là từ, giả sử, một ứng dụng khách bên ngoài và ứng dụng khác nằm trong cùng một chương trình (nghĩa là các mô-đun mã khác nhau), thì tôi cho rằng bạn nên có hai phương thức khác nhau , một phương thức cho mỗi trường hợp, thậm chí có thể được xác định trên riêng biệt Giao diện.

Tuy nhiên, nếu bạn đang thực hiện cuộc gọi dựa trên một số mốc được lấy từ một nguồn không phải là chương trình (giả sử, tệp cấu hình hoặc cơ sở dữ liệu đã đọc), thì phương thức chuyển Boolean sẽ có ý nghĩa hơn.


6
Tôi đồng ý. Và chỉ cần nhấn mạnh quan điểm được đưa ra cho những người đọc khác, một phân tích có thể được thực hiện cho tất cả những người gọi, những người sẽ vượt qua cả đúng, sai hoặc một biến. Nếu không tìm thấy phương pháp nào sau đây, tôi sẽ tranh luận về hai phương thức riêng biệt (w / o boolean) so với một phương thức boolean - đó là một đối số YAGNI mà boolean đưa ra độ phức tạp không sử dụng / không cần thiết. Ngay cả khi có một số người gọi đang chuyển các biến, tôi vẫn có thể cung cấp các phiên bản không có tham số (boolean) để sử dụng cho người gọi chuyển một hằng số - đơn giản hơn, điều này tốt.
Erik Eidt

18

Mặc dù tôi đồng ý rằng lý tưởng là sử dụng một tính năng ngôn ngữ để thực thi cả khả năng đọc và an toàn giá trị, bạn cũng có thể chọn cách tiếp cận thực tế : nhận xét trong thời gian gọi. Như:

UpdateRow(item, true /* row is an external call */);

hoặc là:

UpdateRow(item, true); // true means call is external

hoặc (đúng, theo đề xuất của Frax):

UpdateRow(item, /* externalCall */true);

1
Không có lý do để downvote. Đây là một đề nghị hoàn toàn hợp lệ.
Suncat2000

Đánh giá cao <3 @ Suncat2000!
giám mục

11
Một biến thể (có thể thích hợp hơn) chỉ là đặt tên đối số đơn giản ở đó, như thế UpdateRow(item, /* externalCall */ true ). Nhận xét toàn câu khó phân tích hơn nhiều, thực ra chủ yếu là nhiễu (đặc biệt là biến thể thứ hai, cũng rất lỏng lẻo kết hợp với lập luận).
Frax

1
Đây là câu trả lời thích hợp nhất. Đây chính xác là những gì bình luận được dành cho.
Sentinel

9
Không phải là một fan hâm mộ lớn của các ý kiến ​​như thế này. Nhận xét có xu hướng trở nên cũ kỹ nếu không được xử lý khi tái cấu trúc hoặc thực hiện các thay đổi khác. Chưa kể ngay khi bạn có nhiều hơn một bool param, điều này sẽ gây nhầm lẫn nhanh chóng.
John V.

11
  1. Bạn có thể 'đặt tên' bools của bạn. Dưới đây là một ví dụ cho ngôn ngữ OO (nơi nó có thể được thể hiện trong lớp cung cấp UpdateRow()), tuy nhiên bản thân khái niệm này có thể được áp dụng trong bất kỳ ngôn ngữ nào:

    class Table
    {
    public:
        static const bool WithExternalCall = true;
        static const bool WithoutExternalCall = false;
    

    và trong trang web cuộc gọi:

    UpdateRow(item, Table::WithExternalCall);
    
  2. Tôi tin rằng Mục số 1 tốt hơn nhưng nó không bắt buộc người dùng sử dụng các biến mới khi sử dụng hàm. Nếu loại an toàn là quan trọng đối với bạn, bạn có thể tạo một enumloại và UpdateRow()chấp nhận điều này thay vì bool:

    UpdateRow(var item, ExternalCallAvailability ec);

  3. Bạn có thể sửa đổi tên của hàm bằng cách nào đó nó phản ánh ý nghĩa của booltham số tốt hơn. Không chắc lắm nhưng có lẽ:

    UpdateRowWithExternalCall(var item, bool externalCall)


8
Đừng như # 3 như bây giờ nếu bạn gọi hàm này externalCall=false, tên của nó hoàn toàn không có ý nghĩa. Phần còn lại của câu trả lời của bạn là tốt.
Các cuộc đua nhẹ nhàng trong quỹ đạo

Đã đồng ý. Tôi không chắc lắm nhưng nó có thể là một lựa chọn khả thi cho một số chức năng khác. Tôi
simurg

@LightnessRacesinOrbit Thật vậy. Đi an toàn hơn UpdateRowWithUsuallyExternalButPossiblyInternalCall.
Eric Duminil

@EricDuminil: Phát hiện trên 😂
Các cuộc đua nhẹ nhàng trong quỹ đạo

10

Một tùy chọn khác tôi chưa đọc ở đây là: Sử dụng IDE hiện đại.

Ví dụ: IntelliJ IDEA in tên biến của các biến trong phương thức bạn đang gọi nếu bạn chuyển một chữ như true, nullhoặc username + “@company.com. Điều này được thực hiện trong một phông chữ nhỏ để nó không chiếm quá nhiều không gian trên màn hình của bạn và trông rất khác so với mã thực tế.

Tôi vẫn không nói rằng đó là một ý tưởng tốt để ném vào booleans ở mọi nơi. Đối số nói rằng bạn đọc mã thường xuyên hơn bạn viết thường rất mạnh, nhưng trong trường hợp cụ thể này, nó phụ thuộc rất nhiều vào công nghệ mà bạn (và đồng nghiệp của bạn!) Sử dụng để hỗ trợ công việc của bạn. Với một IDE, vấn đề ít hơn nhiều so với vim chẳng hạn.

Ví dụ sửa đổi một phần của thiết lập thử nghiệm của tôi: nhập mô tả hình ảnh ở đây


5
Điều này sẽ chỉ đúng nếu mọi người trong nhóm của bạn chỉ sử dụng IDE để đọc mã của bạn. Mặc dù mức độ phổ biến của các trình soạn thảo không phải IDE, bạn cũng có các công cụ đánh giá mã, công cụ kiểm soát nguồn, câu hỏi Stack Overflow, v.v. và điều cực kỳ quan trọng là mã vẫn có thể đọc được ngay cả trong các bối cảnh đó.
Daniel Pryden

@DanielPryden chắc chắn, do đó bình luận của tôi 'và các đồng nghiệp của bạn. Việc có một IDE tiêu chuẩn trong các công ty là điều phổ biến. Đối với các công cụ khác, tôi cho rằng những loại công cụ đó hoàn toàn có thể hỗ trợ điều này hoặc sẽ trong tương lai, hoặc những lý lẽ đó đơn giản là không áp dụng (như đối với nhóm lập trình cặp 2 người)
Sebastiaan van den Broek

2
@Sebastiaan: Tôi không nghĩ mình đồng ý. Ngay cả khi là một nhà phát triển solo, tôi thường xuyên đọc mã của riêng mình trong Git diffs. Git sẽ không bao giờ có thể thực hiện cùng một kiểu trực quan nhận biết ngữ cảnh mà IDE có thể làm, cũng không nên làm điều đó. Tôi hoàn toàn sử dụng một IDE tốt để giúp bạn viết mã, nhưng bạn không bao giờ nên sử dụng IDE như một cái cớ để viết mã mà bạn sẽ không viết nếu không có nó.
Daniel Pryden

1
@DanielPryden Tôi không ủng hộ việc viết mã cực kỳ cẩu thả. Đó không phải là một tình huống đen trắng. Có thể có những trường hợp trong quá khứ cho một tình huống cụ thể, bạn sẽ được 40% ủng hộ việc viết một hàm với boolean và 60% không và cuối cùng bạn không làm điều đó. Có thể với sự hỗ trợ IDE tốt bây giờ, số dư viết là 55% như vậy và 45% thì không. Và vâng, đôi khi bạn vẫn cần đọc nó bên ngoài IDE để nó không hoàn hảo. Nhưng đó vẫn là một sự đánh đổi hợp lệ đối với một giải pháp thay thế, như thêm một phương pháp hoặc liệt kê khác để làm rõ mã khác.
Sebastiaan van den Broek

6

2 ngày và không có ai đề cập đến đa hình?

target.UpdateRow(item);

Khi tôi là khách hàng muốn cập nhật một hàng với một mục tôi không muốn nghĩ về tên của cơ sở dữ liệu, URL của dịch vụ vi mô, giao thức được sử dụng để liên lạc hoặc có hay không một cuộc gọi bên ngoài sẽ cần phải được sử dụng để làm cho nó xảy ra. Ngừng đẩy những chi tiết này vào tôi. Chỉ cần lấy mục của tôi và đi cập nhật một hàng ở đâu đó.

Làm điều đó và nó trở thành một phần của vấn đề xây dựng. Điều đó có thể được giải quyết bằng nhiều cách. Đây là một:

Target xyzTargetFactory(TargetBuilder targetBuilder) {
    return targetBuilder
        .connectionString("some connection string")
        .table("table_name")
        .external()
        .build()
    ; 
}

Nếu bạn đang nhìn vào đó và suy nghĩ "Nhưng ngôn ngữ của tôi có tên là đối số, tôi không cần điều này". Tốt, tuyệt, đi sử dụng chúng. Chỉ cần tránh những điều vô nghĩa này ra khỏi ứng dụng khách đang gọi mà thậm chí không biết nó đang nói gì.

Cần lưu ý rằng đây là nhiều hơn một vấn đề ngữ nghĩa. Đó cũng là một vấn đề thiết kế. Vượt qua trong một boolean và bạn buộc phải sử dụng phân nhánh để đối phó với nó. Không hướng đối tượng lắm. Có hai hoặc nhiều booleans để vượt qua? Muốn ngôn ngữ của bạn có nhiều công văn? Nhìn vào những gì bộ sưu tập có thể làm khi bạn lồng chúng. Cấu trúc dữ liệu phù hợp có thể làm cho cuộc sống của bạn dễ dàng hơn rất nhiều.


2

Nếu bạn có thể sửa đổi updateRowhàm, có lẽ bạn có thể cấu trúc lại thành hai hàm. Cho rằng hàm lấy tham số boolean, tôi nghi ngờ nó trông như thế này:

function updateRow(var item, bool externalCall) {
  Database.update(item);

  if (externalCall) {
    Service.call();
  }
}

Đó có thể là một chút mùi mã. Hàm có thể có hành vi khác nhau đáng kể tùy thuộc vào externalCallbiến được đặt thành gì, trong trường hợp đó, nó có hai trách nhiệm khác nhau. Tái cấu trúc nó thành hai chức năng chỉ có một trách nhiệm có thể cải thiện khả năng đọc:

function updateRowAndService(var item) {
  updateRow(item);
  Service.call();
}

function updateRow(var item) {
  Database.update(item);
}

Bây giờ, bất cứ nơi nào bạn gọi các chức năng này, bạn có thể biết trong nháy mắt liệu dịch vụ bên ngoài có được gọi hay không.

Tất nhiên, đây không phải là trường hợp. Đó là tình huống và một vấn đề của hương vị. Tái cấu trúc một hàm lấy tham số boolean thành hai hàm thường đáng để xem xét, nhưng không phải lúc nào cũng là lựa chọn tốt nhất.


0

Nếu UpdateRow nằm trong một cơ sở mã được kiểm soát, tôi sẽ xem xét mô hình chiến lược:

public delegate void Request(string query);    
public void UpdateRow(Item item, Request request);

Trong đó Yêu cầu đại diện cho một số loại DAO (một cuộc gọi lại trong trường hợp tầm thường).

Trường hợp thật:

UpdateRow(item, query =>  queryDatabase(query) ); // perform remote call

Trường hợp sai:

UpdateRow(item, query => readLocalCache(query) ); // skip remote call

Thông thường, việc thực hiện điểm cuối sẽ được cấu hình ở mức độ trừu tượng cao hơn và tôi sẽ chỉ sử dụng nó ở đây. Là một hiệu ứng phụ miễn phí hữu ích, điều này cho tôi một tùy chọn để thực hiện một cách khác để truy cập dữ liệu từ xa mà không có bất kỳ thay đổi nào đối với mã:

UpdateRow(item, query => {
  var data = readLocalCache(query);
  if (data == null) {
    data = queryDatabase(query);
  }
  return data;
} );

Nói chung, việc đảo ngược điều khiển như vậy đảm bảo khớp nối thấp hơn giữa lưu trữ dữ liệu và mô hình của bạ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.