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


39

Theo Có sai không khi sử dụng tham số boolean để xác định hành vi? , Tôi biết tầm quan trọng của việc tránh sử dụng các tham số boolean để xác định hành vi, ví dụ:

phiên bản gốc

public void setState(boolean flag){
    if(flag){
        a();
    }else{
        b();
    }
    c();
}

phiên bản mới:

public void setStateTrue(){
    a();
    c();
}

public void setStateFalse(){
    b();
    c();
}

Nhưng làm thế nào về trường hợp tham số boolean được sử dụng để xác định giá trị thay vì hành vi? ví dụ:

public void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

Tôi đang cố gắng loại bỏ cờ isHintOn và tạo 2 chức năng riêng biệt:

public void setHintOn(){
    this.layer1.visible=true;
    this.layer2.visible=false;
    this.layer3.visible=true;
}

public void setHintOff(){
    this.layer1.visible=false;
    this.layer2.visible=true;
    this.layer3.visible=false;
}

nhưng phiên bản sửa đổi có vẻ ít bảo trì hơn vì:

  1. Nó có nhiều mã hơn phiên bản gốc

  2. rõ ràng không thể thấy rằng khả năng hiển thị của layer2 ngược với tùy chọn gợi ý

  3. Khi một lớp mới (ví dụ: layer4) được thêm vào, tôi cần thêm

    this.layer4.visible=false;
    

    this.layer4.visible=true;  
    

    thành setHintOn () và setHint Offer ()

Vì vậy, câu hỏi của tôi là, nếu tham số boolean chỉ được sử dụng để xác định các giá trị, nhưng không phải là hành vi (ví dụ: no if-other trên tham số đó), liệu có còn nên loại bỏ tham số boolean đó không?


26
Không bao giờ sai nếu mã kết quả dễ đọc và dễ bảo trì hơn ;-) Tôi khuyên bạn nên sử dụng phương thức đơn thay vì hai phương thức riêng biệt.
helb

32
Bạn trình bày một lập luận thuyết phục rằng một triển khai duy nhất của một phương thức đặt các booleans này sẽ giúp duy trì lớp dễ dàng hơn và hiểu được cách thực hiện của nó. Rất tốt; đó là những cân nhắc chính đáng. Nhưng công cộng giao diện của lớp không cần phải được biến dạng để phù hợp với họ. Nếu phương pháp riêng biệt sẽ làm cho giao diện nào dễ dàng hơn để hiểu và làm việc với, định nghĩa của bạn setHint(boolean isHintOn)như một cá nhân phương pháp, và thêm công cộng setHintOnsetHintOffphương pháp tương ứng gọi setHint(true)setHint(false).
Đánh dấu Amery

9
Tôi rất không hài lòng với những tên phương thức đó: chúng không thực sự mang lại lợi ích nào hơn setHint(true|false). Khoai tây khoai tây. Ít nhất là sử dụng một cái gì đó như setHintunsetHint.
Konrad Rudolph


4
@kevincline Nếu điều kiện là một tên, bạn viết isở đầu. isValidvv Vậy tại sao thay đổi điều đó cho hai từ? Bên cạnh đó, "tự nhiên hơn" là trong mắt của kẻ si tình. Nếu bạn muốn phát âm nó như một câu tiếng Anh, thì đối với tôi sẽ là tự nhiên hơn nếu "gợi ý được bật" với một "the" được nhét vào.
Mr Lister

Câu trả lời:


95

Thiết kế API nên tập trung vào những gì có thể sử dụng nhất cho một khách hàng của API, từ phía gọi .

Ví dụ: nếu API mới này yêu cầu người gọi viết mã thường xuyên như thế này

if(flag)
    foo.setStateTrue();
else
    foo.setStateFalse();

thì rõ ràng là việc tránh tham số còn tệ hơn việc có API cho phép người gọi viết

 foo.setState(flag);

Phiên bản cũ chỉ tạo ra một vấn đề mà sau đó phải được giải quyết ở phía gọi (và có thể hơn một lần). Điều đó không làm tăng khả năng đọc và bảo trì.

Các bên thực hiện , tuy nhiên, không nên ra lệnh như thế nào vẻ API công cộng thích. Nếu một hàm như setHintvới một tham số cần ít mã hơn khi triển khai, nhưng API theo thuật ngữ setHintOn/ có setHintOffvẻ dễ sử dụng hơn cho máy khách, thì người ta có thể thực hiện theo cách này:

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Vì vậy, mặc dù API công khai không có tham số boolean, không có logic trùng lặp ở đây, do đó chỉ có một nơi để thay đổi khi có yêu cầu mới (như trong ví dụ về câu hỏi).

Điều này cũng hoạt động theo cách khác: nếu setStatephương thức từ trên cần chuyển đổi giữa hai đoạn mã khác nhau, thì đoạn mã đó có thể được tái cấu trúc thành hai phương thức riêng tư khác nhau. Vì vậy, IMHO không có ý nghĩa gì khi tìm kiếm một tiêu chí để quyết định giữa "một tham số / một phương thức" và "không tham số / hai phương thức" bằng cách nhìn vào bên trong. Tuy nhiên, hãy nhìn vào cách bạn muốn thấy API trong vai trò của người tiêu dùng.

Nếu nghi ngờ, hãy thử sử dụng "phát triển theo hướng kiểm tra" (TDD), điều đó sẽ buộc bạn phải suy nghĩ về API công khai và cách sử dụng nó.


DocBrown, bạn có nói đường phân chia thích hợp là liệu cài đặt của mỗi trạng thái có tác dụng phụ phức tạp và có thể không đảo ngược không? Ví dụ: nếu bạn chỉ đơn giản là bật cờ thực hiện những gì nó nói trên hộp thiếc và không có máy trạng thái cơ bản, bạn sẽ tham số hóa các trạng thái khác nhau. Trong khi đó, ví dụ, bạn sẽ không tham số hóa một phương thức như thế nào SetLoanFacility(bool enabled), vì đã cung cấp một khoản vay, có thể không dễ dàng mang nó đi nữa và hai tùy chọn có thể liên quan đến logic hoàn toàn khác nhau - và bạn muốn chuyển sang Tạo riêng / Xóa phương thức.
Steve

15
@Steve: bạn vẫn đang cố gắng thiết kế API từ các yêu cầu bạn thấy ở phía triển khai. Nói thẳng ra: điều đó hoàn toàn không liên quan. Sử dụng bất kỳ biến thể nào của API công khai sẽ dễ sử dụng hơn từ phía gọi. Trong nội bộ, bạn luôn có thể để hai phương thức công khai gọi một phương thức riêng tư với một tham số. Hoặc ngược lại, bạn có thể để một phương thức có tham số chuyển đổi giữa hai phương thức riêng với logic khác nhau.
Doc Brown

@Steve: xem chỉnh sửa của tôi.
Doc Brown

Tôi lấy tất cả các điểm của bạn - Tôi thực sự đang suy nghĩ về nó từ phía người gọi (do đó tôi tham khảo "những gì nó nói trên tin") và cố gắng xây dựng quy tắc phù hợp mà người gọi thường sẽ sử dụng mỗi cách tiếp cận. Dường như đối với tôi, quy tắc là liệu người gọi có mong đợi các cuộc gọi lặp đi lặp lại là tạm thời hay không, và các chuyển đổi trạng thái không bị giới hạn và không có tác dụng phụ phức tạp. Việc bật và tắt công tắc đèn phòng sẽ được tham số hóa, bật và tắt một trạm phát điện trong khu vực sẽ là đa phương pháp.
Steve

1
@Steve Vì vậy, nếu người dùng cần xác nhận một cài đặt cụ thể thì đó Toggle()không phải là chức năng chính xác để cung cấp. Đó là toàn bộ vấn đề; nếu người gọi chỉ quan tâm đến "thay đổi nó" chứ không phải "những gì nó kết thúc là" thì đó Toggle()là tùy chọn tránh kiểm tra bổ sung và ra quyết định. Tôi sẽ không gọi đó là trường hợp CỘNG SẢN, tôi cũng không khuyên bạn nên cung cấp nó mà không có lý do chính đáng, nhưng nếu người dùng cần chuyển đổi thì tôi sẽ cung cấp cho họ chuyển đổi.
Kamil Drakari

40

Martin Fowler trích dẫn Kent Beck trong việc đề xuất các setOn() setOff()phương pháp riêng biệt , nhưng cũng nói rằng điều này không nên được coi là bất khả xâm phạm:

Nếu bạn lấy dữ liệu [sic] từ nguồn boolean, chẳng hạn như điều khiển UI hoặc nguồn dữ liệu, tôi muốn có setSwitch(aValue)hơn

if (aValue)
  setOn();
else
  setOff();

Đây là một ví dụ về việc nên viết API để giúp người gọi dễ dàng hơn, vì vậy nếu chúng ta biết người gọi đến từ đâu, chúng ta nên thiết kế API với thông tin đó. Điều này cũng lập luận rằng đôi khi chúng tôi có thể cung cấp cả hai phong cách nếu chúng tôi nhận được người gọi theo cả hai cách.

Một khuyến nghị khác là sử dụng một giá trị liệt kê hoặc loại cờ để cung cấp truefalsecác tên cụ thể theo ngữ cảnh tốt hơn. Trong ví dụ của bạn, showHinthideHintcó thể tốt hơn.


16
Theo kinh nghiệm của tôi, setSwitch (giá trị) hầu như luôn dẫn đến mã tổng thể ít hơn so với setOn / set Offer chính xác vì mã if / other được trích dẫn trong câu trả lời của bạn. Tôi có xu hướng nguyền rủa các nhà phát triển cung cấp cho tôi API của setOn / setPack thay vì setSwitch (value).
17 của ngày 26

1
Nhìn vào nó từ một ống kính tương tự: Nếu bạn cần mã hóa giá trị sẽ là bao nhiêu, điều đó thật dễ dàng với một trong hai. Tuy nhiên, nếu bạn cần đặt nó thành, giả sử, đầu vào của người dùng, nếu bạn có thể chuyển trực tiếp giá trị vào, điều đó sẽ tiết kiệm một bước.
Nic Hartley

@ 17of26 như một phản ví dụ cụ thể (để chứng minh rằng "nó phụ thuộc" hơn là một là một lợi thế cho người khác), trong AppKit của Apple có một -[NSView setNeedsDisplay:]phương pháp mà bạn vượt qua YESnếu một cái nhìn nên vẽ lại và NOnếu nó không nên. Bạn gần như không bao giờ cần phải nói không, vì vậy UIKit chỉ -[UIView setNeedsDisplay]không có tham số. Nó không có -setDoesNotNeedDisplayphương thức tương ứng .
Graham Lee

2
@GrahamLee, tôi nghĩ lập luận của Fowler khá tinh tế và thực sự dựa vào phán đoán. Tôi sẽ không sử dụng bool isPremiumcờ trong ví dụ của anh ấy, nhưng tôi sẽ sử dụng enum ( BookingType bookingType) để tham số hóa cùng một phương thức, trừ khi logic cho mỗi lần đặt phòng khá khác nhau. "Logic rối" mà Fowler đề cập thường được mong muốn nếu người ta muốn có thể thấy sự khác biệt giữa hai chế độ. Và nếu chúng hoàn toàn khác nhau, tôi sẽ phơi bày phương thức được tham số hóa ra bên ngoài và thực hiện các phương thức riêng biệt bên trong.
Steve

3

Tôi nghĩ rằng bạn đang trộn hai thứ trong bài viết của mình, API và cách triển khai. Trong cả hai trường hợp tôi không nghĩ có một quy tắc mạnh mẽ mà bạn có thể sử dụng mọi lúc, nhưng bạn nên xem xét hai điều này một cách độc lập (càng nhiều càng tốt).

Hãy bắt đầu với API, cả hai:

public void setHint(boolean isHintOn)

và:

public void setHintOn()
public void setHintOff()

là những lựa chọn thay thế hợp lệ tùy thuộc vào những gì đối tượng của bạn được cung cấp và cách khách hàng của bạn sẽ sử dụng API. Như Doc đã chỉ ra, nếu người dùng của bạn đã có biến Boolean (từ điều khiển UI, hành động người dùng, bên ngoài, API, v.v.) thì tùy chọn đầu tiên có ý nghĩa hơn, nếu không, bạn chỉ buộc thêm một câu lệnh if vào mã của máy khách . Tuy nhiên, nếu ví dụ bạn đang thay đổi gợi ý thành đúng khi bắt đầu một quy trình và thành sai ở cuối thì tùy chọn đầu tiên cung cấp cho bạn một cái gì đó như thế này:

setHint(true)
// Do your process
…
setHint(false)

trong khi tùy chọn thứ hai cung cấp cho bạn điều này:

setHintOn()
// Do your process
…
setHintOff()

IMO nào dễ đọc hơn nhiều, vì vậy tôi sẽ chọn tùy chọn thứ hai trong trường hợp này. Rõ ràng, không có gì ngăn bạn cung cấp cả hai tùy chọn (hoặc nhiều hơn, bạn có thể sử dụng một enum như Graham đã nói nếu điều đó có ý nghĩa hơn chẳng hạn).

Vấn đề là bạn nên chọn API của mình dựa trên những gì đối tượng phải làm và cách khách hàng sẽ sử dụng nó, không dựa trên cách bạn sẽ triển khai nó.

Sau đó, bạn phải chọn cách bạn triển khai API công khai của mình. Giả sử chúng tôi đã chọn các phương thức setHintOnsetHintOfflàm API công khai của chúng tôi và chúng chia sẻ logic chung này như trong ví dụ của bạn. Bạn có thể dễ dàng trừu tượng logic này thông qua một phương thức riêng tư (mã được sao chép từ Doc):

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Ngược lại, giả sử chúng tôi đã chọn setHint(boolean isHintOn)API của mình nhưng hãy đảo ngược ví dụ của bạn, vì bất kỳ lý do gì, việc đặt gợi ý Bật hoàn toàn khác với đặt thành Tắt. Trong trường hợp này, chúng tôi có thể thực hiện nó như sau:

public void setHint(boolean isHintOn){
    if(isHintOn){
        // Set it On
    } else {
        // Set it Off
    }    
}

Hoặc thậm chí:

public void setHint(boolean isHintOn){    
    if(isHintOn){
        setHintOn()
    } else {
        setHintOff()
   }    
}

private void setHintOn(){
   // Set it On
}

private void setHintOff(){
   // Set it Off 
}

Vấn đề là, trong cả hai trường hợp, trước tiên, chúng tôi đã chọn API công khai và sau đó điều chỉnh triển khai của chúng tôi để phù hợp với API đã chọn (và các ràng buộc chúng tôi có), chứ không phải theo cách khác.

Nhân tiện, tôi nghĩ điều tương tự cũng áp dụng cho bài đăng bạn liên kết về việc sử dụng tham số boolean để xác định hành vi, tức là bạn nên quyết định dựa trên trường hợp sử dụng cụ thể của mình thay vì một số quy tắc cứng (mặc dù trong trường hợp cụ thể đó thường là điều đúng là phá vỡ nó trong nhiều chức năng).


3
Như một lưu ý phụ, nếu đó là một cái gì đó giống như khối giả (một câu ở đầu, một câu ở cuối), có lẽ bạn nên sử dụng beginendhoặc từ đồng nghĩa, chỉ để làm rõ ràng đó là những gì họ làm và ngụ ý rằng bắt đầu phải có một kết thúc và ngược lại.
Nic Hartley

Tôi đồng ý với Nic và sẽ thêm: Nếu bạn muốn đảm bảo bắt đầu và kết thúc luôn được ghép nối, bạn cũng nên cung cấp các thành ngữ dành riêng cho ngôn ngữ cho điều đó: RAII / phạm vi bảo vệ trong C ++, usingcác khối trong C #, trình withquản lý bối cảnh câu lệnh trong Python, chuyển qua cơ thể như một lambda hoặc đối tượng có thể gọi được (ví dụ cú pháp khối Ruby), v.v.
Daniel Pryden

Tôi đồng ý với cả hai điểm, tôi chỉ cố gắng đưa ra một ví dụ đơn giản để minh họa cho trường hợp khác (mặc dù đó là một ví dụ tồi tệ như cả hai bạn đã chỉ ra :)).
jesm00

2

Điều đầu tiên trước tiên: mã không tự động ít bảo trì hơn, chỉ vì nó dài hơn một chút. Rõ ràng là những gì quan trọng.

Bây giờ, nếu bạn thực sự chỉ đang xử lý dữ liệu, thì cái bạn có là một setter cho thuộc tính boolean. Trong trường hợp đó, bạn có thể chỉ muốn lưu trữ giá trị đó trực tiếp và lấy được mức độ hiển thị của lớp, tức là

bool isBackgroundVisible() {
    return isHintVisible;
}    

bool isContentVisible() {
    return !isHintVisible;
}

(Tôi đã lấy tự do để đặt tên thực tế cho các lớp - nếu bạn không có mã này trong mã gốc của mình, tôi sẽ bắt đầu với điều đó)

Điều này vẫn để lại cho bạn câu hỏi liệu có một setHintVisibility(bool)phương pháp. Cá nhân, tôi khuyên bạn nên thay thế nó bằng một phương thức showHint()hideHint()- cả hai sẽ thực sự đơn giản và bạn sẽ không phải thay đổi chúng khi bạn thêm các lớp. Tuy nhiên, đó không phải là một sự cắt giảm đúng / sai.

Bây giờ nếu gọi hàm thực sự sẽ thay đổi mức độ hiển thị của các lớp đó, bạn thực sự có hành vi. Trong trường hợp đó, tôi chắc chắn sẽ đề nghị các phương pháp riêng biệt.


Vì vậy, tl; dr là: Bạn khuyên bạn nên chia thành hai chức năng vì bạn sẽ không phải thay đổi chúng khi bạn thêm các lớp? Nếu một layer4 được thêm vào, chúng ta có thể cần phải nói "this.layer4.visibility = isHintOn", vì vậy tôi không chắc là mình đồng ý. Nếu bất cứ điều gì, đó là một điểm trừ đối với nó, vì bây giờ khi một lớp được thêm vào, chúng ta phải chỉnh sửa hai chức năng, không chỉ một.
Erdrik Ironrose

Không tôi (yếu) giới thiệu nó cho rõ ràng ( showHintvs setHintVisibility). Tôi đã đề cập đến lớp mới chỉ vì OP đã lo lắng về nó. Ngoài ra, chúng ta chỉ cần thêm một phương thức mới : isLayer4Visible. showHinthideHintchỉ đặt isHintVisiblethuộc tính thành true / false và điều đó không thay đổi.
doubleYou

1
@doubleYou, bạn nói mã dài hơn không tự động ít bảo trì hơn. Tôi muốn nói rằng độ dài mã là một trong những biến chính trong khả năng duy trì và độ rõ ràng, chỉ vượt qua sự phức tạp về cấu trúc. Bất kỳ mã nào trở nên dài hơn và phức tạp hơn về mặt cấu trúc đều phải xứng đáng với nó, nếu không, các vấn đề đơn giản sẽ nhận được một mã xử lý phức tạp hơn so với mã và mã cơ sở thu được các dòng không cần thiết (vấn đề "quá nhiều mức độ không xác định").
Steve

@Steve Tôi hoàn toàn đồng ý rằng bạn có thể thiết kế quá nhiều thứ, tức là tạo mã lâu hơn mà không làm cho nó rõ ràng hơn - otoh, bạn luôn có thể tạo mã ngắn hơn với chi phí rõ ràng, vì vậy không có mối quan hệ 1: 1 nào ở đây.
doubleYou

@Steve Think “golf mã” - lấy mã như vậy và viết lại nó trong nhiều dòng không thường làm cho nó rõ ràng hơn. „Code golf, là một cực đoan, nhưng vẫn có nhiều lập trình viên ngoài kia nghĩ rằng nhồi nhét mọi thứ vào một biểu hiện thông minh là một cách tao nhã và thậm chí có thể nhanh hơn vì trình biên dịch không tối ưu đủ tốt.
BlackJack

1

Các tham số Boolean là tốt trong ví dụ thứ hai. Như bạn đã tìm ra, các tham số boolean không phải là vấn đề. Đó là chuyển đổi hành vi dựa trên một cờ, đó là vấn đề.

Ví dụ đầu tiên là có vấn đề, bởi vì việc đặt tên chỉ ra một phương thức setter, nhưng việc triển khai dường như là một cái gì đó khác nhau. Vì vậy, bạn có antipotype chuyển đổi hành vi một phương pháp được đặt tên sai. Nhưng nếu phương thức thực sự là một setter thông thường (không có chuyển đổi hành vi), thì không có vấn đề gì với setState(boolean). Có hai phương pháp, setStateTrue()setStateFalse()chỉ cần làm phức tạp mọi thứ không có lợi.


1

Một cách khác để giải quyết vấn đề này là giới thiệu một đối tượng để thể hiện từng gợi ý và để đối tượng chịu trách nhiệm xác định các giá trị boolean liên quan đến gợi ý đó. Bằng cách này, bạn có thể thêm các hoán vị mới thay vì chỉ có hai trạng thái boolean.

Ví dụ: trong Java, bạn có thể làm:

public enum HintState {
    SHOW_HINT(true, false, true),
    HIDE_HINT(false, true, false);

    private HintState(boolean layer1Visible, boolean layer2Visible, boolean layer3Visible) {
         // constructor body and accessors omitted for clarity
    }
}

Và sau đó mã người gọi của bạn sẽ trông như thế này:

setHint(HintState.SHOW_HINT);

Và mã thực hiện của bạn sẽ trông như thế này:

public void setHint(HintState hint) {
    this.layer1Visible = hint.isLayer1Visible();
    this.layer2Visible = hint.isLayer2Visible();
    this.layer3Visible = hint.isLayer3Visible();
}

Điều này giữ cho mã thực hiện và mã người gọi ngắn gọn, để đổi lấy việc xác định một kiểu dữ liệu mới, ánh xạ rõ ràng được đánh máy mạnh, các ý định được đặt tên cho các bộ trạng thái tương ứng. Tôi nghĩ rằng đó là tốt hơn tất cả xung quanh.


0

Vì vậy, câu hỏi của tôi là, nếu tham số boolean chỉ được sử dụng để xác định các giá trị, nhưng không phải là hành vi (ví dụ: no if-other trên tham số đó), liệu có còn nên loại bỏ tham số boolean đó không?

Khi tôi có nghi ngờ về những điều như vậy. Tôi muốn hình dung ra dấu vết ngăn xếp sẽ như thế nào.

Trong nhiều năm, tôi đã làm việc với một dự án PHP sử dụng cùng chức năng như settergetter . Nếu bạn chuyển null, nó sẽ trả về giá trị, nếu không thì đặt nó. Thật là kinh khủng khi làm việc với .

Dưới đây là một ví dụ về dấu vết ngăn xếp:

function visible() : line 440
function parent() : line 398
function mode() : line 384
function run() : line 5

Bạn không có ý tưởng về trạng thái nội bộ và nó làm cho việc gỡ lỗi khó khăn hơn. Có một loạt các tác dụng phụ tiêu cực khác, nhưng thử để xem có giá trị trong một tiết tên hàm và rõ ràng khi chức năng thực hiện một single action.

Bây giờ hình ảnh làm việc với theo dõi ngăn xếp cho một hàm có hành vi A hoặc B dựa trên giá trị boolean.

function bar() : line 92
function setVisible() : line 120
function foo() : line 492
function setVisible() : line 120
function run() : line 5

Điều đó thật khó hiểu nếu bạn hỏi tôi. Các setVisibledòng giống nhau mang lại hai đường dẫn khác nhau.

Vì vậy, trở lại câu hỏi của bạn. Hãy thử hình dung dấu vết ngăn xếp sẽ như thế nào, cách nó truyền đạt cho một người những gì đang xảy ra và tự hỏi liệu bạn có đang giúp một người trong tương lai gỡ lỗi mã không.

Dưới đây là một số lời khuyên:

  • một tên hàm rõ ràng ngụ ý mà không cần biết các giá trị đối số.
  • một chức năng thực hiện một hành động
  • tên ngụ ý đột biến hoặc hành vi bất biến
  • giải quyết các thách thức gỡ lỗi liên quan đến khả năng sửa lỗi của ngôn ngữ và công cụ.

Đôi khi mã trông quá mức dưới kính hiển vi, nhưng khi bạn quay lại bức tranh lớn hơn, một cách tiếp cận tối giản sẽ khiến nó biến mất. Nếu bạn cần nó nổi bật để bạn có thể duy trì nó. Thêm nhiều chức năng nhỏ có thể cảm thấy quá dài dòng, nhưng nó cải thiện khả năng bảo trì khi được sử dụng rộng rãi trong bối cảnh lớn hơn.


1
Điều khiến tôi bối rối về setVisibledấu vết ngăn xếp là việc gọi điện setVisible(true)xuất hiện dẫn đến một cuộc gọi đến setVisible(false)(hoặc ngược lại, tùy thuộc vào cách bạn có dấu vết đó xảy ra).
David K

0

Trong hầu hết mọi trường hợp khi bạn truyền một booleantham số cho một phương thức dưới dạng cờ để thay đổi hành vi của một cái gì đó, bạn nên xem xét một cách rõ ràng và an toàn hơn để làm điều này.

Nếu bạn không làm gì hơn việc sử dụng Enumđại diện cho nhà nước bạn đã cải thiện sự hiểu biết của mã của bạn.

Ví dụ này sử dụng Nodelớp từ JavaFX:

public enum Visiblity
{
    SHOW, HIDE

    public boolean toggleVisibility(@Nonnull final Node node) {
        node.setVisible(!node.isVisible());
    }
}

luôn luôn tốt hơn, cũng giống như tìm thấy trên nhiều JavaFXđối tượng:

public void setVisiblity(final boolean flag);

nhưng tôi nghĩ .setVisible().setHidden()là giải pháp tốt nhất cho tình huống cờbooleanvì nó rõ ràng nhất và ít dài dòng nhất.

trong trường hợp một cái gì đó có nhiều lựa chọn, điều này thậm chí còn quan trọng hơn để làm theo cách này. EnumSettồn tại chỉ vì lý do này.

Ed Mann có một bài đăng blog thực sự tốt về chủ đề này. Tôi đã định diễn giải đúng những gì anh ta nói, vì vậy để không trùng lặp nỗ lực, tôi sẽ chỉ đăng một liên kết đến bài đăng trên blog của anh ta như một phụ lục cho câu trả lời này.


0

Khi quyết định giữa một cách tiếp cận đối với một số giao diện vượt qua tham số (boolean) so với các phương thức bị quá tải với tham số đã nói, hãy tìm đến các máy khách đang sử dụng.

Nếu tất cả các cách sử dụng sẽ vượt qua các giá trị không đổi (ví dụ: đúng, sai) thì điều đó lập luận cho sự quá tải.

Nếu tất cả các cách sử dụng sẽ vượt qua một giá trị biến thì điều đó lập luận cho phương thức với cách tiếp cận tham số.

Nếu cả hai thái cực đó đều không áp dụng, điều đó có nghĩa là có sự kết hợp giữa các cách sử dụng máy khách, vì vậy bạn phải chọn hỗ trợ cả hai hình thức, hoặc tạo một loại khách hàng để thích ứng với kiểu khác (đối với họ là kiểu không tự nhiên hơn).


Điều gì xảy ra nếu bạn đang thiết kế một hệ thống tích hợp và cuối cùng bạn nhà sản xuất mã và "khách hàng tiêu thụ" của mã? Làm thế nào một người đứng trong đôi giày của một khách hàng tiêu dùng hình thành sở thích của họ cho một cách tiếp cận khác?
Steve

@Steve, Là khách hàng tiêu dùng, bạn biết liệu bạn có vượt qua hằng số hay biến hay không. Nếu truyền hằng, thích quá tải với tham số.
Erik Eidt

Nhưng tôi quan tâm đến việc nói rõ tại sao nên như vậy. Tại sao không sử dụng enum cho một số lượng giới hạn của hằng số, vì đó là một cú pháp nhẹ trong hầu hết các ngôn ngữ được thiết kế chính xác cho mục đích này?
Steve

@Steve, nếu chúng ta biết tại thời điểm thiết kế / thời gian biên dịch rằng khách hàng sẽ sử dụng giá trị không đổi (đúng / sai) trong mọi trường hợp, thì điều đó cho thấy thực sự có hai phương thức cụ thể khác nhau thay vì một phương thức tổng quát (có tham số). Tôi sẽ tranh luận về việc giới thiệu tính tổng quát của một phương thức được tham số hóa khi nó không được sử dụng - đó là một đối số YAGNI.
Erik Eidt

0

Có hai cân nhắc để thiết kế:

  • API: giao diện nào bạn trình bày cho người dùng,
  • Thực hiện: sự rõ ràng, khả năng bảo trì, vv ...

Họ không nên bị giam cầm.

Nó là hoàn toàn tốt để:

  • có nhiều phương thức trong API ủy nhiệm cho một triển khai,
  • có một phương thức duy nhất trong công văn API tới nhiều triển khai tùy thuộc vào điều kiện.

Như vậy, bất kỳ đối số nào cố gắng cân bằng chi phí / lợi ích của thiết kế API bằng chi phí / lợi ích của thiết kế triển khai là không rõ ràng và cần được xem xét cẩn thận.


Về phía API

Là một lập trình viên, tôi thường sẽ ưu tiên các API lập trình được. Mã rõ ràng hơn nhiều khi tôi có thể chuyển tiếp một giá trị so với khi tôi cần một câu lệnh if/ switchtrên giá trị để quyết định hàm nào sẽ gọi.

Cái sau có thể là cần thiết nếu mỗi hàm mong đợi các đối số khác nhau.

Trong trường hợp của bạn, do đó, một phương pháp duy nhất setState(type value)có vẻ tốt hơn.

Tuy nhiên , không có gì tồi tệ hơn không tên là true, false, 2, vv ... những giá trị ảo thuật không có ý nghĩa riêng của họ. Tránh ám ảnh nguyên thủy , và nắm lấy gõ mạnh.

Do đó, từ một API POV, tôi muốn : setState(State state).


Về phía thực hiện

Tôi khuyên bạn nên làm bất cứ điều gì dễ dàng hơn.

Nếu phương pháp này đơn giản, tốt nhất nên giữ cùng nhau. Nếu luồng điều khiển bị chập, tốt nhất nên tách nó theo nhiều phương thức, mỗi phương thức xử lý một trường hợp con hoặc một bước của đường ống.


Cuối cùng, xem xét nhóm .

Trong ví dụ của bạn (có thêm khoảng trắng để dễ đọc):

this.layer1.visible = isHintOn;
this.layer2.visible = ! isHintOn;
this.layer3.visible = isHintOn;

Tại sao layer2xô xu hướng? Đây có phải là một tính năng hay là một lỗi?

Có thể có 2 danh sách [layer1, layer3][layer2], với một tên rõ ràng cho biết lý do chúng được nhóm lại với nhau, và sau đó lặp lại qua các danh sách đó.

Ví dụ:

for (auto layer : this.mainLayers) { // layer2
    layer.visible = ! isHintOn;
}
for (auto layer : this.hintLayers) { // layer1 and layer3
    layer.visible = isHintOn;
}

Mã nói cho chính nó, rõ ràng tại sao có hai nhóm và hành vi của họ khác nhau.


0

Riêng setOn() + setOff()với set(flag)câu hỏi vs , tôi sẽ xem xét cẩn thận xem một loại boolean là tốt nhất ở đây. Bạn có chắc chắn sẽ không bao giờ có lựa chọn thứ ba?

Nó có thể đáng để xem xét một enum thay vì boolean. Cùng với việc cho phép mở rộng, điều này cũng khiến cho việc boolean sai cách trở nên khó khăn hơn, ví dụ:

setHint(false)

đấu với

setHint(Visibility::HIDE)

Với enum, việc mở rộng sẽ dễ dàng hơn nhiều khi ai đó quyết định họ muốn có tùy chọn 'nếu cần':

enum class Visibility {
  SHOW,
  HIDE,
  IF_NEEDED // New
}

đấu với

setHint(false)
setHint(true)
setHintAutomaticMode(true) // New

0

Theo ..., tôi biết tầm quan trọng của việc tránh sử dụng các tham số boolean để xác định hành vi

Tôi sẽ đề nghị đánh giá lại kiến ​​thức này.

Trước hết, tôi không thấy kết luận mà bạn đang đề xuất trong câu hỏi SE mà bạn liên kết. Họ chủ yếu nói về việc chuyển tiếp một tham số qua một số bước của các cuộc gọi phương thức, trong đó nó được đánh giá rất xa trong chuỗi.

Trong ví dụ của bạn, bạn đang đánh giá tham số ngay trong phương thức của mình. Về mặt đó, nó hoàn toàn không khác biệt với bất kỳ loại tham số nào khác.

Nói chung, hoàn toàn không có gì sai khi sử dụng các tham số boolean; và rõ ràng bất kỳ tham số nào sẽ xác định một hành vi, hoặc tại sao bạn sẽ có nó ở nơi đầu tiên?


0

Xác định câu hỏi

Câu hỏi tiêu đề của bạn là "Có sai với [...] không?" - nhưng bạn có ý gì với "sai"?.

Theo trình biên dịch C # hoặc Java, điều đó không sai. Tôi chắc chắn rằng bạn biết điều đó và đó không phải là những gì bạn đang hỏi. Tôi sợ khác hơn, chúng tôi chỉ có các nlập trình viên n+1ý kiến ​​khác nhau. Câu trả lời này trình bày những gì cuốn sách Clean Code nói về điều này.

Câu trả lời

Clean Code tạo ra một trường hợp mạnh mẽ chống lại các đối số chức năng nói chung:

Luận cứ thì khó. Họ có rất nhiều sức mạnh khái niệm. [...] độc giả của chúng tôi sẽ phải giải thích nó mỗi lần họ nhìn thấy nó.

Một "độc giả" ở đây có thể là người tiêu dùng API. Nó cũng có thể là lập trình viên tiếp theo, người chưa biết mã này làm gì - có thể là bạn trong một tháng. Họ sẽ trải qua 2 chức năng riêng biệt hoặc thông qua 1 chức năng hai lần , một lần giữ truevà một lần falsetrong tâm trí.
Tóm lại, sử dụng càng ít đối số càng tốt .

Trường hợp cụ thể của một đối số cờ sau đó được giải quyết trực tiếp:

Đối số cờ là xấu xí. Truyền boolean vào một chức năng là một thực tế thực sự khủng khiếp. Nó ngay lập tức làm phức tạp chữ ký của phương thức, lớn tiếng tuyên bố rằng chức năng này làm nhiều hơn một điều. Nó làm một điều nếu cờ là đúng và một điều khác nếu cờ là sai!

Để trả lời câu hỏi của bạn trực tiếp:
Theo Clean Code , nên loại bỏ tham số đó.


Thông tin bổ sung:

Ví dụ của bạn khá đơn giản, nhưng thậm chí ở đó bạn có thể thấy sự đơn giản lan truyền đến mã của bạn: Các hàm không tham số chỉ thực hiện các phép gán đơn giản, trong khi chức năng khác phải thực hiện số học boolean để đạt được cùng một mục tiêu. Đó là số học boolean tầm thường trong ví dụ đơn giản này, nhưng có thể khá phức tạp trong một tình huống thực tế.


Tôi đã thấy rất nhiều đối số ở đây rằng bạn nên làm cho nó phụ thuộc vào người dùng API, bởi vì phải làm điều này ở nhiều nơi sẽ là ngu ngốc:

if (isAfterSunset) light.TurnOn();
else light.TurnOff();

Tôi làm đồng ý rằng một cái gì đó không phải là tối ưu đang xảy ra ở đây. Có thể quá rõ ràng để xem, nhưng câu đầu tiên của bạn đang đề cập đến "tầm quan trọng của việc tránh [sic] sử dụng các tham số boolean để xác định hành vi" và đó là cơ sở cho toàn bộ câu hỏi. Tôi không thấy một lý do nào khiến điều đó trở nên tồi tệ để làm dễ dàng hơn cho người dùng API.


Tôi không biết nếu bạn làm thử nghiệm - trong trường hợp đó, cũng xem xét điều này:

Đối số thậm chí còn khó hơn từ quan điểm thử nghiệm. Hãy tưởng tượng sự khó khăn của việc viết tất cả các trường hợp thử nghiệm để đảm bảo rằng tất cả các kết hợp khác nhau của các đối số hoạt động đúng. Nếu không có đối số, đây là chuyện nhỏ.


Bạn thực sự đã chôn cái lede ở đây: "... câu đầu tiên của bạn đang đề cập đến" tầm quan trọng của việc tránh [sic] sử dụng các tham số boolean để xác định hành vi "và đó là cơ sở cho toàn bộ câu hỏi. Tôi không thấy một lý do để khiến điều đó trở nên tồi tệ để làm dễ dàng hơn cho người dùng API. " Đây là một điểm thú vị, nhưng bạn thay vào đó làm suy yếu lập luận của bạn trong đoạn cuối cùng của bạn.
tự đại diện

Chôn lede? Cốt lõi của câu trả lời này là "sử dụng càng ít đối số càng tốt", điều này được giải thích trong nửa đầu của câu trả lời này. Mọi thứ sau đó chỉ là thông tin bổ sung: bác bỏ một đối số mâu thuẫn (bởi một người dùng khác, không phải OP) và một cái gì đó không áp dụng cho mọi người.
R. Schmitz

Đoạn cuối chỉ là cố gắng làm rõ rằng câu hỏi tiêu đề không được xác định rõ đủ để được trả lời. OP hỏi nó có "sai" không, nhưng không nói theo ai hay cái gì. Theo Trình biên dịch? Hình như mã hợp lệ, nên không sai. Theo cuốn sách Clean Code? Nó sử dụng một đối số cờ, vì vậy, nó là "sai". Tuy nhiên, thay vào đó, tôi viết "đề nghị", vì thực dụng> giáo điều. Bạn có nghĩ rằng tôi cần phải làm cho điều đó rõ ràng hơn?
R. Schmitz

Vì vậy, chờ đợi, bảo vệ câu trả lời của bạn là câu hỏi tiêu đề quá không rõ ràng để được trả lời? : D Được rồi ... Tôi thực sự nghĩ rằng điểm tôi trích dẫn là một sự thú vị mới mẻ.
tự đại diện

1
Rõ ràng ngay bây giờ; làm tốt lắm!
tự đại diệ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.