Tốt hơn là có 2 phương thức với ý nghĩa rõ ràng, hoặc chỉ 1 phương thức sử dụng kép?


30

Để đơn giản hóa giao diện, tốt hơn là không có getBalance()phương thức? Đi qua 0đến charge(float c);sẽ cho kết quả tương tự:

public class Client {
    private float bal;
    float getBalance() { return bal; }
    float charge(float c) {
        bal -= c;
        return bal;
    }
}

Có thể làm một ghi chú trong javadoc? Hoặc, chỉ để lại cho người dùng lớp để tìm ra cách lấy số dư?


26
Tôi hào phóng cho rằng đây là ví dụ, mã minh họa và rằng bạn không sử dụng toán học dấu phẩy động để lưu trữ các giá trị tiền tệ. Nếu không tôi sẽ phải nhận được tất cả các bài giảng. :-)
corsiKa

1
@corsiKa nah. Đó chỉ là một ví dụ. Nhưng, vâng, tôi đã sử dụng float để đại diện cho tiền trong một lớp học thực sự ... Cảm ơn vì đã nhắc nhở tôi về sự nguy hiểm của các số dấu phẩy động.
david

10
Một số ngôn ngữ phân biệt giữa bộ biến đổi và bộ truy cập để có hiệu quả tốt. Sẽ rất khó chịu khi không thể có được sự cân bằng của một thể hiện chỉ có thể truy cập như một hằng số!
JDługosz

Câu trả lời:


90

Bạn dường như gợi ý rằng độ phức tạp của một giao diện được đo bằng số lượng phần tử mà nó có (phương thức, trong trường hợp này). Nhiều người sẽ lập luận rằng phải nhớ rằng chargephương thức này có thể được sử dụng để trả về số dư của một Clientsự phức tạp hơn nhiều so với việc có thêm phần tử phụ của getBalancephương thức. Làm cho mọi thứ rõ ràng hơn đơn giản hơn nhiều, đặc biệt là đến mức nó không để lại sự mơ hồ, bất kể số lượng phần tử cao hơn trong giao diện.

Bên cạnh đó, việc gọi charge(0)vi phạm nguyên tắc ít gây ngạc nhiên nhất , còn được gọi là số liệu WTF mỗi phút (từ Clean Code, hình ảnh bên dưới), gây khó khăn cho các thành viên mới của nhóm (hoặc những người hiện tại, sau một thời gian rời xa mã) họ hiểu rằng cuộc gọi thực sự được sử dụng để lấy số dư. Hãy nghĩ về cách các độc giả khác sẽ phản ứng:

nhập mô tả hình ảnh ở đây

Ngoài ra, chữ ký của chargephương thức đi ngược lại các hướng dẫn thực hiện một và chỉ một điềutách biệt truy vấn lệnh , bởi vì nó làm cho đối tượng thay đổi trạng thái của nó trong khi cũng trả về một giá trị mới.

Nói chung, tôi tin rằng giao diện đơn giản nhất trong trường hợp này sẽ là:

public class Client {
  private float bal;
  float getBalance() { return bal; }
  void charge(float c) { bal -= c; }
}

25
Điểm về phân tách truy vấn lệnh là cực kỳ quan trọng, IMO. Hãy tưởng tượng bạn là một nhà phát triển mới trong dự án này. Nhìn qua mã, rõ ràng ngay lập tức getBalance()trả về một số giá trị và không sửa đổi bất cứ điều gì. Mặt khác, có charge(0)vẻ như nó có thể sửa đổi một cái gì đó ... có thể nó sẽ gửi một sự kiện PropertyChanged? Và nó trả về cái gì, giá trị trước hay giá trị mới? Bây giờ bạn phải tìm kiếm nó trong các tài liệu và lãng phí trí tuệ, điều có thể tránh được bằng cách sử dụng một phương pháp rõ ràng hơn.
sư Xy

19
Ngoài ra, sử dụng charge(0)như là cách để có được sự cân bằng bởi vì nó xảy ra không có tác dụng bây giờ đưa bạn đến chi tiết thực hiện này; Tôi có thể dễ dàng tưởng tượng một yêu cầu trong tương lai khi việc theo dõi số lượng khoản phí trở nên có liên quan, tại thời điểm đó, cơ sở mã của bạn bị lấp đầy bởi các khoản phí không thực sự trở thành một điểm đau. Bạn nên cung cấp các yếu tố giao diện có nghĩa là những điều khách hàng của bạn cần làm, không phải những yếu tố chỉ đơn thuần xảy ra để làm những việc mà khách hàng của bạn cần làm.
Ben

12
Lời khuyên của CQS không nhất thiết phải phù hợp. Đối với một charge()phương thức, nếu phương thức đó là nguyên tử trong một hệ thống đồng thời, có thể phù hợp để trả về giá trị mới hoặc cũ.
Dietrich Epp

3
Tôi thấy hai trích dẫn của bạn cho CQRS cực kỳ thiếu thuyết phục. Ví dụ, tôi thường thích ý tưởng của Meyer, nhưng nhìn vào kiểu trả về của hàm để biết liệu nó có biến đổi một cái gì đó là tùy ý và không linh hoạt hay không. Nhiều cấu trúc dữ liệu đồng thời hoặc bất biến đòi hỏi đột biến + trả về. Làm thế nào bạn có thể nối vào Chuỗi mà không vi phạm CQRS? Bạn có bất kỳ trích dẫn tốt hơn?
user949300 6/2/2016

6
Hầu như không có mã nào phù hợp với Phân tách truy vấn lệnh. Nó không phải là thành ngữ trong bất kỳ ngôn ngữ hướng đối tượng phổ biến nào và bị vi phạm bởi các API tích hợp của mọi ngôn ngữ tôi từng sử dụng. Để mô tả nó như là một "thực hành tốt nhất" do đó có vẻ kỳ quái; bạn có thể kể tên một dự án phần mềm, bao giờ, thực sự theo mô hình này không?
Đánh dấu Amery

28

IMO, thay thế getBalance()bằng charge(0)ứng dụng của bạn không phải là một sự đơn giản hóa. Vâng, đó là ít dòng hơn, nhưng nó làm xáo trộn ý nghĩa của charge()phương thức, điều này có khả năng gây đau đầu xuống dòng khi bạn hoặc người khác cần xem lại mã này.

Mặc dù họ có thể cho cùng một kết quả, nhưng việc lấy số dư của một tài khoản không giống như một khoản phí bằng 0, vì vậy có lẽ tốt nhất là bạn nên tách rời mối quan tâm của mình. Ví dụ: nếu bạn cần sửa đổi charge()để đăng nhập bất cứ khi nào có giao dịch tài khoản, thì bây giờ bạn có vấn đề và dù sao cũng cần phải tách chức năng.


13

Điều quan trọng cần nhớ là mã của bạn phải tự ghi lại. Khi tôi gọi charge(x), tôi sẽ xbị tính phí. Thông tin về số dư chỉ là thứ yếu. Hơn nữa, tôi có thể không biết cách charge()triển khai khi tôi gọi nó và tôi chắc chắn sẽ không biết nó được triển khai như thế nào vào ngày mai. Ví dụ: xem xét bản cập nhật tiềm năng này trong tương lai để charge():

float charge(float c) {
    lockDownUserAccountUntilChargeClears();
    bal -= c;
    Unlock();
    return bal;
}

Bất ngờ sử dụng charge()để có được sự cân bằng trông không được tốt lắm.


Vâng! Một điểm tốt đó chargelà nặng hơn nhiều.
Ded

2

Sử dụng charge(0);để lấy số dư là một ý tưởng tồi: một ngày nào đó, ai đó có thể thêm một số mã vào đó để ghi lại các khoản phí được thực hiện mà không nhận ra việc sử dụng chức năng khác, và sau đó mỗi khi ai đó nhận được số dư, nó sẽ được ghi lại dưới dạng phí. (Có nhiều cách xoay quanh vấn đề này, chẳng hạn như một tuyên bố có điều kiện có nội dung như sau:

if (c > 0) {
    // make and log charge
}
return bal;

nhưng những điều này phụ thuộc vào lập trình viên biết để thực hiện chúng, điều mà anh ta sẽ không làm nếu không rõ ràng ngay lập tức rằng chúng là cần thiết.

Nói tóm lại: đừng dựa vào người dùng hoặc người kế thừa lập trình viên của bạn nhận ra rằng đó charge(0);là cách chính xác để có được số dư, bởi vì trừ khi có tài liệu mà họ được đảm bảo không bỏ lỡ thì thật ra đó là cách đáng sợ nhất để có được sự cân bằng khả thi.


0

Tôi biết có rất nhiều câu trả lời, nhưng một lý do khác chống lại charge(0)là một lỗi đánh máy đơn giản charge(9)sẽ khiến số dư của khách hàng của bạn giảm đi mỗi khi bạn muốn lấy số dư của họ. Nếu bạn có thử nghiệm đơn vị tốt, bạn có thể giảm thiểu rủi ro đó, nhưng nếu bạn không siêng năng trong mọi cuộc gọi đến chargebạn có thể gặp phải rủi ro này.


-2

Tôi muốn đề cập đến một trường hợp cụ thể trong đó sẽ có ý nghĩa khi có các phương thức ít hơn, đa mục đích hơn: nếu có nhiều đa hình, nghĩa là, nhiều triển khai của giao diện này ; đặc biệt là nếu các triển khai đó nằm trong mã được phát triển riêng mà không thể cập nhật đồng bộ hóa (giao diện được xác định bởi thư viện).

Trong trường hợp đó, đơn giản hóa công việc viết mỗi lần thực hiện có giá trị hơn nhiều so với sự rõ ràng của việc sử dụng nó, bởi vì trước đây tránh được các lỗi vi phạm hợp đồng (hai phương pháp không phù hợp với nhau), trong khi phương pháp sau chỉ làm tổn thương tính dễ đọc, có thể được phục hồi bởi một hàm trợ giúp hoặc phương thức siêu lớp xác định getBalancetheo các điều khoản của charge.

(Đây là một mẫu thiết kế, mà tôi không nhớ tên cụ thể để: xác định giao diện thân thiện với người gọi phức tạp theo giao diện thân thiện với người triển khai tối thiểu. nhưng điều này dường như không phải là một thuật ngữ phổ biến.)

Nếu đây không phải là trường hợp (có một vài triển khai hoặc chính xác một) thì tách biệt các phương thức cho rõ ràng và để cho phép bổ sung đơn giản các hành vi liên quan đến các khoản phí khác không charge(), có ý nghĩa.


1
Thật vậy, tôi đã có trường hợp giao diện tối thiểu hơn được chọn để giúp kiểm tra phạm vi hoàn chỉnh dễ dàng hơn. Nhưng điều này không nhất thiết phải tiếp xúc với người dùng của lớp. Ví dụ , chèn , nốixóa tất cả có thể là các hàm bao đơn giản của một thay thế chung có tất cả các đường dẫn điều khiển được nghiên cứu và thử nghiệm tốt.
JDługosz

Tôi đã thấy một API như vậy. Bộ cấp phát Lua 5.1+ sử dụng nó. Bạn cung cấp một chức năng và dựa trên các tham số mà nó truyền qua, bạn quyết định xem nó có đang phân bổ bộ nhớ mới, giải phóng bộ nhớ hoặc phân bổ lại bộ nhớ từ bộ nhớ cũ hay không. Thật đau đớn khi viết các chức năng như vậy. Tôi muốn thay vì Lua vừa trao cho bạn hai chức năng, một cho phân bổ và một cho phân bổ.
Nicol Bolas

@NicolBolas: Và không có gì cho việc tái phân bổ? Dù sao, người ta có thêm "lợi ích" gần như giống như realloc, đôi khi, trên một số hệ thống.
Ded

@Ded repeatator: phân bổ so với phân bổ lại là ... OK. Thật không hay khi đặt chúng vào cùng một chức năng, nhưng nó cũng không tệ như đặt giao dịch trong cùng một chức năng. Đó cũng là một sự phân biệt dễ dàng để thực hiện.
Nicol Bolas

Tôi muốn nói rằng việc phân bổ hợp lệ với phân bổ (lại) là một ví dụ đặc biệt tồi tệ của loại giao diện này. (Giao dịch có một hợp đồng rất khác: nó không dẫn đến một con trỏ hợp lệ.)
Kevin Reid
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.