Nhà điều hành null có điều kiện mới của C # 6.0 có trái với Luật Demeter không?


29

Các Luật của Demeter nói những điều sau đây:

  • Mỗi đơn vị chỉ nên có kiến ​​thức hạn chế về các đơn vị khác: chỉ các đơn vị "chặt chẽ" liên quan đến đơn vị hiện tại.
  • Mỗi đơn vị chỉ nên nói chuyện với bạn bè của mình; đừng nói chuyện với người lạ.
  • Chỉ nói chuyện với bạn bè ngay lập tức của bạn.

C # 6.0 đã giới thiệu một toán tử mới gọi là toán tử có điều kiện null . IMHO, nó làm cho mã hóa dễ dàng hơn và cải thiện khả năng đọc. Nhưng nó cũng làm cho việc viết mã được ghép dễ dàng hơn, vì nó dễ dàng điều hướng qua các trường lớp hơn, đã kiểm tra tính vô hiệu (đại loại như var x = A?.B?.C?.D?.E?.F?).

Có đúng không khi tuyên bố rằng nhà điều hành mới này đi ngược lại Luật Demeter?


2
Tại sao bạn tin rằng điều đó A?.B?.C?.D?.E?.F?sẽ vi phạm nó - LoD không phải là về bao nhiêu dấu chấm và nếu phương thức gọi có thông tin như vậy về cấu trúc không vi phạm với các điểm của nó, thì một cuộc gọi như vậy sẽ hoàn toàn chấp nhận được. Mã đó có thể vi phạm LoD không đủ để nói rằng tất cả các sử dụng của nó đều vi phạm LoD.

14
Đọc " Luật của Demeter không phải là một bài tập đếm số chấm "? Nó thảo luận về ví dụ chính xác này.
outis

@outis: đọc xuất sắc. Tôi không nói rằng mọi mã dưới dạng X.Y.Z.W.Ulà vi phạm "luật". Nhưng, theo kinh nghiệm của tôi khi xử lý mã, 90% thời gian chỉ là mã được ghép xấu xí.
Arthur Rizzo

2
@ArthurRizzo nhưng đó không phải là vấn đề với toán tử có điều kiện null chống lại LoD. Đó là mã có lỗi. Toán tử chỉ là một công cụ để đơn giản hóa con người đọc nó. Không .?có nhiều vi phạm LoD hơn +hoặc -không.

1
RC Martin phân biệt giữa các lớp dữ liệu thuần túy và các lớp hành vi. Nếu Thuộc tính được truy cập hiển thị dữ liệu nội bộ của lớp hành vi, đoạn mã chắc chắn vi phạm LoD, nhưng điều này không liên quan gì đến toán tử có điều kiện null. Dù sao, các thuộc tính không bị ràng buộc để lộ dữ liệu nội bộ, có thể là một mùi, nhưng không vi phạm LoD. Theo RC Martin, lược đồ có thể hoàn toàn hợp lệ với các lớp dữ liệu thuần túy.
Paul Kertscher

Câu trả lời:


44

Có đúng không khi tuyên bố rằng nhà điều hành mới này đi ngược lại Luật Demeter?

Không *


* Toán tử điều kiện null là một công cụ trong ngôn ngữ và khung .NET. Bất kỳ công cụ nào cũng có khả năng bị lạm dụng và sử dụng theo những cách có thể gây hại cho khả năng duy trì của một ứng dụng nhất định.

Nhưng thực tế là một công cụ có thể bị lạm dụng không nhất thiết có nghĩa là nó bị lạm dụng, cũng không phải là công cụ vi phạm bất kỳ nguyên tắc cụ thể (s) có thể sẽ được tổ chức.

Luật Demeter và những người khác là hướng dẫn về cách bạn nên viết mã của mình. Nó nhắm đến con người, không phải công cụ. Vì vậy, thực tế là ngôn ngữ C # 6.0 có một công cụ mới bên trong nó không nhất thiết ảnh hưởng đến cách bạn nên viết và cấu trúc mã của mình.

Với bất kỳ công cụ mới nào, bạn cần đánh giá nó là ... nếu anh chàng người kết thúc việc duy trì mã của bạn sẽ trở thành một kẻ tâm thần bạo lực ... . Lưu ý một lần nữa, đây là hướng dẫn cho người viết mã chứ không phải về các công cụ đang được sử dụng.


foo = new FiveDMatrix(); foo.get(0).get(0).get(0).get(0).set(0,1);sẽ ổn (và không tệ hơn foo[0][0][0][0][0] = 1) ... và rất nhiều tình huống khác khi điều đó không vi phạm LoD.

@MichaelT Khi bạn bắt đầu đi vào ma trận của chiều đó, có vẻ như việc xử lý các chỉ số như một vectơ / tuple / mảng sẽ trở nên dễ dàng hơn và khiến các phần bên trong của lớp ma trận lo lắng về cách dữ liệu được lưu trữ thực sự. (Mà, bây giờ tôi nghĩ về nó, là một ứng dụng của Luật Demeter, ít nhất là liên quan đến đóng gói.)
JAB 7/10/2015

(Và, tất nhiên, kiểu thực hành đó giúp thực hiện cắt lát đa chiều dễ dàng hơn và có một số công cụ ma trận thực sự mạnh mẽ.)
JAB

1
@JAB Tôi chỉ cố gắng đưa ra một ví dụ. Một cái tốt hơn có thể sẽ là Dom file = prase("some.xml"); file.get(tag1).getChild().get(tag2).getChild() ...- đó là vấn đề xử lý cấu trúc của một số mã ngu ngốc. Nó không phải là một người xa lạ ... nó chỉ là ngu ngốc. Việc .?trở nên rất hữu ích trong các cấu trúc như vậy.

10

Sắp xếp

Nếu bạn chỉ thực hiện một truy cập (a?.Foo ) thì nó tương đương với:

a == null ? null : a.Foo

mà hầu hết mọi người sẽ đồng ý không phải là vi phạm Luật Demeter. Tại thời điểm đó, nó chỉ là đường cú pháp để cải thiện khả năng đọc.

Bất cứ điều gì hơn thế, và nó có thể sẽ vi phạm Luật Demeter, và tính năng này có xu hướng thúc đẩy loại sử dụng đó. Tôi thậm chí sẽ nói rằng chỉ riêng cách sử dụng "tốt" ở trên là không đủ để đảm bảo loại thay đổi này đối với ngôn ngữ, vì vậy tôi hy vọng rằng nó được tạo ra để hỗ trợ việc sử dụng ít rõ ràng hơn.

Điều đó nói rằng, điều đáng ghi nhớ là Luật Demeter không phải là luật pháp, mà là một hướng dẫn. Rất nhiều mã vi phạm nó và hoạt động tốt. Đôi khi sự đơn giản của thiết kế hoặc mã có giá trị cao hơn rủi ro do vi phạm Luật Demeter.


bất cứ điều gì nhiều hơn thế không nhất thiết phải phá vỡ LoD, ví dụ như mẫu xây dựng
jk.

@Telastyn: Cú pháp ngôn ngữ mới mà chúng ta đang nói đến không hỗ trợ các cuộc gọi phương thức: a?.Func1(x)?.Func2(y) Toán tử hợp nhất null là một cái gì đó khác.
Ben Voigt

@BenVoigt - ah, tôi đã đi ra khỏi bài viết, trong đó chỉ ra rằng nó chỉ hoạt động với các trường, thuộc tính và bộ chỉ mục. Tôi không có sẵn MSVS2015 để thử nghiệm. Bạn đúng rồi.
Telastyn

1
a ?. Bạn không hoàn toàn tương đương với a == null? null: a.Foo. Cái trước đánh giá chỉ một lần, cái sau đánh giá nó hai lần. Điều đó có thể quan trọng nếu a là một iterator.
Loren Pechtel

9

Không. Hãy xem xét cả người vận hành, và việc sử dụng rất nhiều bạn có cho nó.

Tự nó .?Aphụ thuộc vào cùng một lượng kiến ​​thức của lớp, giá trị bên trái là và loại được trả về bởi phương thức .A != null, viz. Nó cần biết về Atài sản tồn tại và trả về một giá trị có thể so sánh với null.

Chúng tôi chỉ có thể lập luận rằng điều này vi phạm luật của Demeter nếu tài sản đánh máy làm. Chúng tôi thậm chí không bị buộc phải có Amột loại cụ thể (giá trị của nó có thể là một loại dẫn xuất). Các khớp nối ở đây là tối thiểu.

Bây giờ hãy xem xét var x = A?.B?.C?.D?.E?.F.

Điều đó có nghĩa là Aphải thuộc loại có thể là null hoặc có thể có một thuộc Btính, phải là loại có thể là null hoặc có một Ctài sản, và cứ như vậy cho đến khi loại Etài sản đó là một thứ gì đó có thể là null hoặc có thể có một Ftài sản.

Nói cách khác, chúng ta cần thực hiện điều này với một ngôn ngữ gõ tĩnh hoặc đã áp dụng một ràng buộc đối với các loại có thể được trả về nếu gõ bị lỏng. C # trong hầu hết các trường hợp sử dụng kiểu gõ tĩnh, vì vậy chúng tôi không thay đổi gì.

Nếu chúng ta có thì đoạn mã sau cũng sẽ vi phạm luật:

ExplicitType x;
var b = A.B;
if (b == null)
  x = null;
else
{
  var c = b.C;
  if (c == null)
    x = null;
  else
  {
    var d = c.D;
    if (d == null)
      x = null;
    else
    {
      var e = d.E;
      if (e == null)
        x = null;
      else
        x = e.F;
    }
  }
}

chính xác là như nhau . Mã này đang sử dụng khớp nối của các yếu tố khác nhau cần "biết" về chuỗi khớp nối đầy đủ, nhưng nó sử dụng mã không vi phạm Luật Demeter để làm như vậy, với mỗi đơn vị có khớp nối được xác định rõ với tiếp theo.


3
+1 toán tử mới chỉ là đường cú pháp cho công thức đắng mà bạn đã mô tả.
Ross Patterson

1
Chà, nếu một nhà phát triển viết một mã trông giống như vậy thì tôi nghĩ rằng sẽ dễ dàng nhận thấy rằng điều gì đó có thể không đúng. Tôi biết rằng toán tử là đường tổng hợp 100%, tuy nhiên, tôi nghĩ rằng mọi người có xu hướng nhận được nhiều thứ dễ viết var x = A?.B?.C?.D?.E?.Fhơn so với tất cả những thứ nếu / cuối cùng ngay cả khi cuối cùng chúng giống nhau.
Arthur Rizzo

2
Dễ dàng nhận thấy điều gì đó không đúng A?.B?.C?.D?.E?.Fvì có ít điều có thể sai; hoặc chúng ta nên cố gắng vượt Fqua con đường đó, hoặc chúng ta không nên, trong khi hình thức dài hơn có thể có lỗi trong đó cũng như lỗi của nó không phải là điều chính xác phải làm.
Jon Hanna

@ArthurRizzo Nhưng nếu bạn liên kết loại mã trên với vi phạm LoD, thì rất dễ bỏ lỡ chúng trong trường hợp không cần kiểm tra null và bạn chỉ có thể làm A.B.C.D. Thật đơn giản hơn nhiều khi có một điều duy nhất để tìm kiếm (truy cập tài sản bị xiềng xích) thay vì hai điều khác nhau phụ thuộc vào một chi tiết khá không liên quan (kiểm tra null)
Ben Aaronson

5

Đối tượng có thể được tạo ra cho mục đích đóng gói các hành vi hoặc lưu giữ dữ liệu và các đối tượng có thể được tạo cho mục đích chia sẻ với mã bên ngoài hoặc được giữ riêng bởi người tạo.

Các đối tượng được tạo ra cho mục đích đóng gói hành vi (dù được chia sẻ hay không) hoặc để được chia sẻ với mã bên ngoài (cho dù chúng đóng gói hành vi hoặc dữ liệu) thường nên được truy cập qua giao diện bề mặt của chúng. Tuy nhiên, khi các đối tượng lưu trữ dữ liệu được tạo ra để sử dụng riêng cho người tạo ra chúng, các lý do Luật pháp thông thường để tránh truy cập "sâu" không được áp dụng. Nếu một phần của lớp lưu trữ hoặc thao tác dữ liệu trong đối tượng bị thay đổi theo cách yêu cầu điều chỉnh mã khác, có thể đảm bảo rằng tất cả các mã đó được cập nhật vì - như đã lưu ý ở trên - đối tượng được tạo cho việc sử dụng độc quyền của một lớp.

Trong khi tôi nghĩ rằng? Toán tử có lẽ đã được thiết kế tốt hơn, có đủ các tình huống trong đó các đối tượng sử dụng các cấu trúc dữ liệu lồng nhau mà toán tử có nhiều trường hợp sử dụng không vi phạm các nguyên tắc được thể hiện bởi Luật Demeter. Không nên coi thực tế là nó có thể được sử dụng để vi phạm LoD như là một đối số chống lại nhà điều hành, vì nó không tệ hơn "." điều hành trong vấn đề đó.


Tại sao Luật Demeter không áp dụng cho các đối tượng lưu trữ dữ liệu?
Telastyn

2
@Telastyn: Mục đích của LoD là để tránh các vấn đề có thể phát sinh nếu một đoạn mã truy cập vào các đối tượng bên trong mà một thứ khác có thể thao túng hoặc quan tâm . Nếu không có gì khác trong vũ trụ có thể thao túng hoặc quan tâm đến trạng thái của các vật thể bên trong, thì không cần phải đề phòng những vấn đề như vậy.
supercat

Tôi không chắc là tôi đồng ý. Không phải những thứ khác có thể quan tâm đến việc sửa đổi dữ liệu, mà là bạn đang ghép nối với đối tượng được chứa thông qua một đường dẫn (về cơ bản là ba điểm khớp nối - hai đối tượng và mối quan hệ của chúng). Đôi khi sự ghép đôi đó sẽ không phải là một vấn đề lớn, nhưng dường như vẫn còn có mùi với tôi.
Telastyn

@Telastyn: Quan điểm của bạn về các con đường là một điều tốt, nhưng tôi nghĩ rằng quan điểm của tôi chỉ tốt. Truy cập một đối tượng thông qua nhiều đường dẫn tạo ra sự ghép nối giữa các đường dẫn đó. Nếu một số truy cập thông qua đường dẫn nông, thì truy cập qua đường dẫn sâu cũng có thể gây ra khớp nối không mong muốn. Tuy nhiên, nếu tất cả quyền truy cập đều thông qua một con đường sâu cụ thể, thì sẽ không có bất cứ điều gì cho con đường sâu đó để kết nối.
supercat

@Telastyn Hoàn toàn ổn khi truy cập cơ sở hạ tầng để truy cập dữ liệu sâu. Nó không giống như các cuộc gọi phương thức lồng nhau. Đôi khi bạn được yêu cầu phải biết cơ sở hạ tầng và cách nó được lồng vào nhau, điều tương tự không xảy ra đối với các đối tượng như một dịch vụ và các dịch vụ / kho lưu trữ lồng nhau của nó, v.v.
Per Hornshøj-Schierbeck
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.