Có phải mã như thế này là một con tàu đắm tàu ​​Cameron (vi phạm Luật của Demeter)?


23

Lướt qua một số mã tôi đã viết, tôi đã xem qua cấu trúc sau đây khiến tôi suy nghĩ. Thoạt nhìn, nó có vẻ đủ sạch. Có, trong mã thực tế, getLocation()phương thức có một tên cụ thể hơn một chút, mô tả chính xác hơn vị trí mà nó nhận được.

service.setLocation(this.configuration.getLocation().toString());

Trong trường hợp này, servicelà một biến thể hiện của một kiểu đã biết, được khai báo trong phương thức. this.configurationxuất phát từ việc được truyền vào hàm tạo của lớp và là một thể hiện của lớp thực hiện một giao diện cụ thể (bắt buộc một getLocation()phương thức công khai ). Do đó, kiểu trả về của biểu thức this.configuration.getLocation()được biết đến; cụ thể trong trường hợp này, nó là một java.net.URL, trong khi service.setLocation()muốn a String. Do hai loại Chuỗi và URL không tương thích trực tiếp, nên một số loại chuyển đổi được yêu cầu để khớp với chốt vuông trong lỗ tròn.

Tuy nhiên , theo Luật của Demeter như được trích dẫn trong Mã sạch , một phương pháp f trong lớp C nên chỉ gọi các phương thức trên C , các đối tượng được tạo ra bởi hoặc thông qua như là đối số f , và các đối tượng tổ chức trong các biến thể hiện của C . Bất cứ điều gì ngoài điều đó (trận chung kết toString()trong trường hợp cụ thể của tôi ở trên, trừ khi bạn xem xét một đối tượng tạm thời được tạo ra do kết quả của việc gọi phương thức, trong trường hợp đó, toàn bộ Luật dường như được đưa ra) không được phép.

Có một lý do hợp lệ tại sao một cuộc gọi như trên, với các ràng buộc được liệt kê, nên được khuyến khích hoặc thậm chí không được phép? Hay tôi chỉ là quá đáng?

Nếu tôi thực hiện một phương thức URLToString()chỉ đơn giản gọi toString()một URLđối tượng (chẳng hạn như được trả về getLocation()) được truyền cho nó dưới dạng tham số và trả về kết quả, tôi có thể kết thúc getLocation()cuộc gọi trong đó để đạt được kết quả chính xác như nhau; một cách hiệu quả, tôi sẽ chỉ chuyển một bước ra bên ngoài. Điều đó bằng cách nào đó làm cho nó chấp nhận được? ( Dường như với tôi, theo trực giác, nó sẽ không tạo ra bất kỳ sự khác biệt nào, vì tất cả những gì làm là di chuyển mọi thứ xung quanh một chút. Tuy nhiên, đi theo lá thư của Luật Demeter như được trích dẫn, nó sẽ được chấp nhận, vì tôi sau đó sẽ được vận hành trực tiếp trên một tham số cho hàm.)

Nó sẽ làm cho bất kỳ sự khác biệt nếu điều này là về một cái gì đó hơi kỳ lạ hơn so với việc gọi toString()một loại tiêu chuẩn?

Khi trả lời, hãy nhớ rằng việc thay đổi hành vi hoặc API của loại mà servicebiến là không thực tế. Ngoài ra, để tranh luận, hãy nói rằng việc thay đổi kiểu trả về getLocation()cũng không thực tế.

Câu trả lời:


34

Vấn đề ở đây chữ ký của setLocation. Nó được gõ một cách nghiêm ngặt .

Để giải thích: Tại sao nó sẽ mong đợi String? A Stringđại diện cho bất kỳ loại dữ liệu văn bản . Nó có khả năng có thể là bất cứ điều gì ngoại trừ một vị trí hợp lệ.

Trong thực tế, điều này đặt ra một câu hỏi: vị trí là gì? Làm thế nào để tôi biết mà không nhìn vào mã của bạn? Nếu nó là URLhơn tôi sẽ biết nhiều hơn về những gì phương pháp này mong đợi.
Có lẽ nó sẽ có ý nghĩa hơn đối với nó là một lớp tùy chỉnh Location. Ok, ban đầu tôi không biết đó là gì, nhưng tại một số điểm (có thể trước khi viết this.configuration.getLocation()tôi sẽ mất một phút để tìm hiểu xem phương thức này trả về cái gì).
Cấp, trong cả hai trường hợp tôi cần phải tìm một nơi khác để hiểu, những gì được mong đợi. Tuy nhiên, trong trường hợp sau, nếu tôi hiểu, đó là gì Location, tôi có thể sử dụng API của bạn, trong trường hợp trước, nếu tôi hiểu, đó là gì String(có thể dự kiến), tôi vẫn không biết API của bạn mong đợi điều gì.

Trong trường hợp không thể xảy ra, một vị trí là bất kỳ loại dữ liệu văn bản nào, tôi sẽ diễn giải lại điều này với bất kỳ loại dữ liệu nào có biểu diễn văn bản . Với thực tế, đó Objectlà một toStringphương pháp, bạn có thể đi với điều đó, mặc dù điều này đòi hỏi khá nhiều niềm tin từ các khách hàng của mã của bạn.

Ngoài ra, bạn nên xem xét, đây là Java mà bạn đang nói đến, có rất ít tính năng theo thiết kế. Đó là những gì buộc bạn phải thực sự gọi toStringđến cuối cùng.
Nếu bạn lấy C # chẳng hạn, cũng được gõ tĩnh, thì bạn thực sự có thể bỏ qua cuộc gọi đó bằng cách xác định hành vi cho một diễn viên ngầm .
Trong các ngôn ngữ được nhập động, chẳng hạn như Objective-C, bạn cũng không thực sự cần chuyển đổi, vì miễn là giá trị này hoạt động như một chuỗi, mọi người đều hài lòng.

Người ta có thể lập luận rằng cuộc gọi cuối cùng toStringít hơn một cuộc gọi, thực ra chỉ là tiếng ồn do nhu cầu làm chứng của Java tạo ra. Bạn đang gọi một phương thức, rằng bất kỳ đối tượng Java nào cũng có, do đó bạn không thực sự mã hóa bất kỳ kiến ​​thức nào về một "đơn vị ở xa" và do đó không vi phạm Nguyên tắc tối thiểu của Kiến thức. Không có cách nào, bất kể getLocationtrả về cái gì , mà nó không có toStringphương thức.

Nhưng xin vui lòng, không sử dụng chuỗi, trừ khi chúng thực sự là lựa chọn tự nhiên nhất (hoặc trừ khi bạn đang sử dụng ngôn ngữ, thậm chí không có enums ... đã ở đó).


Tôi có xu hướng đồng ý với bạn, nhưng anh ta đã nói rằng anh ta không thể sửa đổi API dịch vụ.
jhocking

1
@jhocking: Anh ấy không nói là không thể. Ông nói nó không thực tế. Tôi không đồng ý. Cố gắng áp dụng các thực tiễn tốt nhất cho mã hoạt động xung quanh các lỗ hổng trong thiết kế API thực sự chỉ có ý nghĩa nếu các cách giải quyết như vậy là lựa chọn khả thi duy nhất. Tuy nhiên, ở đây thiết lập API thẳng là tùy chọn tốt nhất. Loại bỏ những thiếu sót luôn thích hợp hơn để làm việc xung quanh chúng.
back2dos

dù sao thì +1 cũng cho "ít cuộc gọi hơn thực tế chỉ là tiếng ồn được tạo ra bởi nhu cầu làm chứng của Java"
jhocking

1
Tôi sẽ cho +1 này ngay cả khi chỉ để "gõ chuỗi". Trong mã của tôi, tôi cố gắng truyền các loại thể hiện ý nghĩa / ý định càng nhiều càng tốt, nhưng khi làm việc với các thư viện và API của bên thứ ba, đôi khi bạn cảm thấy bế tắc khi sử dụng những gì tác giả của API đó đã quyết định. Và IIRC, một vị trí thực sự có thể là "bất kỳ loại dữ liệu văn bản" nào, nhưng đối với việc triển khai tôi đang thực hiện, một vị trí không có URL là hoàn toàn vô nghĩa.
một CVn

1
Đối với việc thay đổi API; Đây là phần mềm máy tính, vì vậy hầu như luôn có thể thay đổi mọi thứ. (Nếu không có gì khác, bạn luôn có thể viết một lớp trừu tượng.) Tuy nhiên, đôi khi làm như vậy đòi hỏi quá nhiều nỗ lực cả ngắn hạn và dài hạn để có thể chứng minh được. Sau đó, có thể hoàn toàn có thể thực hiện những thay đổi như vậy, nhưng vẫn không thực tế.
một CVn

21

Luật của Demeter là một hướng dẫn thiết kế, không phải là luật phải tuân theo tôn giáo.

Nếu bạn cảm thấy rằng các lớp của bạn đủ tách rời thì không có gì sai với dòng this.configuration.getLocation()đặc biệt là, như bạn nói, việc thay đổi các phần khác của API là không thực tế.

Tôi khá chắc chắn rằng khách hàng sẽ hoàn toàn hạnh phúc ngay cả khi bạn thực hiện Luật Demeter thành từng mảnh, miễn là bạn cung cấp những gì anh ta muốn và đúng hạn. Đó không phải là một cái cớ để tạo ra phần mềm xấu, mà là một lời nhắc nhở phải thực dụng khi phát triển phần mềm.


1
this.configurationlà một biến thể hiện của một loại giao diện đã biết, nên gọi một phương thức trên nó được xác định bởi giao diện đó có vẻ tốt ngay cả theo một diễn giải chặt chẽ. Vâng, tôi biết rằng đó là một hướng dẫn, giống như KISS, RẮN, YAGNI, v.v. Có rất ít nếu thực sự có bất kỳ "luật" (theo nghĩa pháp lý) trong phát triển phần mềm nói chung.
một CVn

4
+1 vì thực dụng ;-)
Treb

1
Tôi không nghĩ Luật Dementer thậm chí nên áp dụng trong trường hợp như thế này - cấu hình về cơ bản là một container. Trong mọi API tôi đã từng làm việc với việc tìm kiếm bên trong các thùng chứa là hành vi bình thường và được mong đợi.
Loren Pechtel

2

Điều duy nhất tôi có thể nghĩ về việc không viết mã như thế này là gì nếu this.configuration.getLocation()trả về null? Nó phụ thuộc vào mã xung quanh của bạn và đối tượng mục tiêu sử dụng mã này. Nhưng như Marco nói - luật của Demeter là một quy tắc của ngón tay cái - thật tốt khi tuân theo, nhưng đừng phá vỡ lưng bạn làm điều đó một cách không cần thiết.


Nếu this.configuration.getLocation()trả về null, thì chúng ta (a) rất có thể sẽ không bao giờ có được điều đó, hoặc (b) điều gì đó thảm khốc đã xảy ra trong thời gian tạm thời, trong trường hợp đó tôi muốn mã bị lỗi. Vì vậy, trong khi đây chắc chắn là một điểm hợp lệ nói chung, trong trường hợp cụ thể này, khá an toàn để nói rằng nó không áp dụng. Ngoài ra, xung quanh tất cả những điều này và nhiều hơn nữa là một trình xử lý ngoại lệ được thiết kế đặc biệt để đối phó với những thất bại bất ngờ đó.
CVn

2

Tuân thủ nghiêm ngặt Luật Demeter có nghĩa là bạn nên thực hiện một phương thức trong đối tượng cấu hình như:

function getLocationAsString() {
  return getLocation().toString();
}

nhưng cá nhân tôi sẽ không bận tâm vì đây là một tình huống nhỏ như vậy và dù sao tay bạn cũng bị trói vì API bạn không thể thay đổi. Quy tắc lập trình là về những gì bạn nên làm khi bạn có một lựa chọn, nhưng đôi khi bạn không có lựa chọn nào khác.


1
Điều đó cũng được đề xuất ở đây: c2.com/cgi/wiki?TrainWreck "Tạo một phương thức đại diện cho hành vi mong muốn và cho khách hàng biết phải làm gì. Điều này tuân theo nguyên tắc" nói, đừng hỏi "."
heltonbiker
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.