Czy nowy nieważny operator C # 6.0 jest sprzeczny z prawem Demetera?

29

Prawo Demeter stwierdza co następuje:

  • Każda jednostka powinna mieć ograniczoną wiedzę na temat innych jednostek: tylko jednostki „ściśle” związane z bieżącą jednostką.
  • Każda jednostka powinna rozmawiać tylko z przyjaciółmi; nie rozmawiaj z nieznajomymi.
  • Rozmawiaj tylko z najbliższymi przyjaciółmi.

W C # 6.0 wprowadzono nowy operator o nazwie operator null-warunkowy . IMHO ułatwia kodowanie i poprawia czytelność. Ale ułatwia także pisanie bardziej sprzężonego kodu, ponieważ łatwiej jest poruszać się po polach klas, sprawdzając już pod kątem nieważności (coś podobnego var x = A?.B?.C?.D?.E?.F?).

Czy słusznie jest stwierdzić, że ten nowy operator jest niezgodny z prawem Demetera?

Arthur Rizzo
źródło
2
Dlaczego uważasz, że to A?.B?.C?.D?.E?.F?by go naruszyło - LoD nie polega na tym, ile kropek i jeśli metoda wywołująca zawiera takie informacje o strukturze, która nie narusza jej punktów, takie wywołanie byłoby całkowicie akceptowalne. Że taki kod mogłoby naruszać LoD nie wystarczy powiedzieć, że wszystkie zastosowania nim zrobić naruszać LOD.
14
Czytanie „ Prawo Demetera nie jest ćwiczeniem zliczania kropek ”? Omawia ten dokładny przykład.
poza
@outis: excelent read. Nie twierdzę, że każdy kod w formie stanowi X.Y.Z.W.Unaruszenie „prawa”. Ale z mojego doświadczenia związanego z kodem, 90% czasu jest po prostu brzydko sprzężonym kodem.
Arthur Rizzo,
2
@ArthurRizzo, ale to nie jest problem z zerowym operatorem warunkowym przeciw LoD. To jest kod, który jest winny. Operator jest tylko narzędziem ułatwiającym czytanie przez człowieka. .?Nie więcej niż jest niezgodny LoD +czy -nie.
1
RC Martin rozróżnia klasy czystych danych od klas behawioralnych. Jeśli dostępne właściwości ujawniają wewnętrzne dane klasy behawioralnej, fragment kodu z pewnością narusza LoD, ale nie ma to nic wspólnego z operatorem zerowym. W każdym razie właściwości nie są zobowiązane do ujawnienia wewnętrznych danych, które mogą być zapachem, ale nie naruszają LoD. Według RC Martina schemat może być absolutnie poprawny z czystymi klasami danych.
Paul Kertscher,

Odpowiedzi:

44

Czy słusznie jest stwierdzić, że ten nowy operator jest niezgodny z prawem Demetera?

Nie *


* Operator warunkowy null jest narzędziem w języku i frameworku .NET. Każde narzędzie może być nadużywane i wykorzystywane w sposób, który mógłby zaszkodzić łatwości konserwacji danej aplikacji.

Ale fakt, że narzędzie może być nadużywane, niekoniecznie oznacza, że musi być nadużywane, ani że narzędzie narusza jakąkolwiek konkretną zasadę (zasady), która może być utrzymana.

Prawo Demeter i inne są wytycznymi, jak pisać kod. Jest skierowany do ludzi, a nie do narzędzi. Zatem fakt, że język C # 6.0 zawiera nowe narzędzie, niekoniecznie wpływa na sposób pisania i strukturyzacji kodu.

Z każdym nowym narzędziem, trzeba ocenić go jako ... jeśli facet, który kończy się utrzymując swój kod będzie gwałtowna psychopata ... . Jeszcze raz zauważ, że jest to wskazówka dla osoby piszącej kod, a nie na temat używanych narzędzi.

Społeczność
źródło
foo = new FiveDMatrix(); foo.get(0).get(0).get(0).get(0).set(0,1);byłoby dobrze (i nie gorzej niż foo[0][0][0][0][0] = 1) ... i wiele innych sytuacji, w których nie narusza to LoD.
@MichaelT Kiedy zaczynasz wchodzić w macierze tego wymiaru, wydaje się, że łatwiej byłoby traktować indeksy jako sam wektor / krotkę / tablicę i pozwolić wewnętrznym klasom macierzy martwić się o to, jak dane są faktycznie przechowywane. (Które, teraz, gdy o tym myślę, jest zastosowaniem Prawa Demetera, przynajmniej w odniesieniu do enkapsulacji.)
JAB
(I, oczywiście, tego rodzaju praktyka ułatwia wdrażanie wielowymiarowego krojenia i ma naprawdę potężne narzędzia matrycowe).
JAB
1
@JAB Właśnie próbowałem wymyślić przykład. Lepszym rozwiązaniem byłby prawdopodobnie Dom file = prase("some.xml"); file.get(tag1).getChild().get(tag2).getChild() ...problem przetwarzania struktury jakiegoś głupiego kodu. To nie jest nieznajomy ... jest po prostu głupi. .?Się bardzo użyteczne w takich konstrukcjach.
10

Raczej.

Jeśli wykonujesz tylko jeden dostęp ( a?.Foo), jest to równoważne z:

a == null ? null : a.Foo

co większość ludzi zgodziłaby się, nie stanowi naruszenia Prawa Demeter. W tym momencie jest to cukier syntaktyczny, który poprawia czytelność.

Coś więcej i prawdopodobnie naruszałoby to prawo Demetera, a ta funkcja ma tendencję do promowania tego rodzaju użycia. Powiedziałbym nawet, że powyższe „dobre” użycie nie wystarcza, aby uzasadnić tego rodzaju zmianę języka, więc spodziewam się, że zostało stworzone, by wspierać mniej wyraźnie dobre użycie.

To powiedziawszy, warto pamiętać, że Prawo Demeter nie jest prawem jako takim, ale raczej wytyczną. Dużo kodu narusza go i działa dobrze. Czasami prostota projektu lub kodu jest warta więcej niż ryzyko związane z naruszeniem Prawa Demetera.

Telastyn
źródło
cokolwiek więcej niekoniecznie łamie LoD, np. wzorzec konstruktora
jk.
@Telastyn: Nowa składnia języka, o której mówimy , obsługuje wywołania metod: a?.Func1(x)?.Func2(y) Operator koalescencji zerowej jest czymś innym.
Ben Voigt,
@BenVoigt - ah, odszedłem od artykułu, który wskazał, że działa tylko z polami, właściwościami i indeksatorami. Nie miałem pod ręką MSVS2015 do przetestowania. Masz rację.
Telastyn,
1
a? Foo nie jest całkiem równoważne = = null? null: a.Foo. Pierwsza ocenia tylko raz, druga ocenia ją dwukrotnie. To może mieć znaczenie, jeśli będzie iteratorem.
Loren Pechtel,
9

Nie. Rozważmy zarówno samego operatora, jak i mocno powiązane z nim wykorzystanie.

Samo .?Azależy od takiej samej wiedzy o klasie, jaką jest lewostronna wartość i rodzaju zwracanego przez metodę, jak .A != nullto jest, mianowicie. Musi wiedzieć, że Awłaściwość istnieje i zwraca wartość, którą można porównać null.

Możemy jedynie argumentować, że narusza to prawo Demeter, jeśli robią to właściwości pisane. Nie jesteśmy nawet zmuszeni do posiadania Ajako konkretnego typu (jego wartość może być typu pochodnego). Sprzężenie tutaj jest minimalne.

Teraz zastanówmy się var x = A?.B?.C?.D?.E?.F.

Co oznacza, że Amusi być typu, który może mieć wartość NULL lub może mieć Bwłaściwość, która musi być typu, który może być NULL lub mieć Cwłaściwość, i tak dalej, dopóki typ Ewłaściwości nie będzie miał wartości NULL lub może mieć Fwłaściwość.

Innymi słowy, musimy to robić za pomocą języka o typie statycznym lub nałożyć ograniczenia na typy, które mogą zostać zwrócone, jeśli pisanie jest luźne. C # w większości przypadków używa pisania statycznego, więc nic nie zmieniliśmy.

Gdybyśmy mieli, następujący kod naruszałby również prawo:

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;
    }
  }
}

Co jest dokładnie takie samo . Ten kod, który wykorzystuje sprzężenie różnych elementów, musi „wiedzieć” o pełnym łańcuchu sprzężenia, ale używa kodu, który nie narusza Prawa Demetera, aby to zrobić, ponieważ każda jednostka ma dobrze zdefiniowane sprzężenie z Następny.

Jon Hanna
źródło
3
+1 nowy operator to tylko cukier syntaktyczny dla gorzkiej receptury, którą opisałeś.
Ross Patterson
1
Cóż, jeśli programista pisze kod, który wygląda tak, myślę, że łatwiej jest zauważyć, że coś może być nie tak. Wiem, że operatorem jest cukier syntetyczny w 100%, ale myślę, że ludzie mają tendencję do wygodniejszego pisania czegoś podobnego var x = A?.B?.C?.D?.E?.Fniż wszystkie inne, nawet jeśli w końcu są tacy sami.
Arthur Rizzo
2
Łatwiej jest zauważyć, że coś jest nie tak, A?.B?.C?.D?.E?.Fponieważ jest mniej rzeczy, które mogą być złe; albo powinniśmy próbować przedostać się Ftą ścieżką, albo nie powinniśmy, podczas gdy dłuższa forma może zawierać w sobie błędy, a także błąd, który jest niewłaściwy.
Jon Hanna,
@ArthurRizzo Ale jeśli powiążesz powyższy kod z naruszeniami LoD, łatwo je przeoczyć w przypadku, gdy nie jest konieczne sprawdzanie wartości zerowej i możesz to zrobić A.B.C.D. O wiele łatwiej jest mieć jedną rzecz, na którą należy zwrócić uwagę (dostęp do powiązanej nieruchomości), niż dwie różne rzeczy, które zależą od dość nieistotnego szczegółu (sprawdzanie wartości zerowej)
Ben Aaronson
5

Obiekt może być tworzony w celu kapsułkowania zachowań lub przechowywania danych, a obiekty mogą być tworzone w celu udostępniania ich zewnętrznemu kodowi lub przechowywania ich prywatnie przez ich twórcę.

Do obiektów tworzonych w celu enkapsulacji zachowania (współdzielonej lub nie) lub w celu współdzielenia z kodem zewnętrznym (niezależnie od tego, czy enkapsulują zachowanie lub dane) należy ogólnie uzyskiwać dostęp poprzez interfejs powierzchniowy. Gdy obiekty przechowujące dane są tworzone do użytku wyłącznie przez ich twórcę, nie mają jednak zastosowania normalne powody Demetera dotyczące unikania „głębokiego” dostępu. Jeśli część klasy, która przechowuje lub manipuluje danymi w obiekcie, zostanie zmieniona w sposób, który wymagałby dostosowania innego kodu, możliwe będzie zagwarantowanie, że cały taki kod zostanie zaktualizowany, ponieważ - jak wspomniano powyżej - obiekt został stworzony dla wyłączne wykorzystanie jednej klasy.

Chociaż myślę, że? operator mógł być może lepiej zaprojektowany, istnieje wystarczająco dużo sytuacji, w których obiekty wykorzystują zagnieżdżone struktury danych, że operator ma wiele przypadków użycia, które nie naruszałyby zasad wyrażonych przez Prawo Demetera. Fakt, że można go wykorzystać do naruszenia LoD, nie powinien być traktowany jako argument przeciwko operatorowi, ponieważ nie jest gorszy niż „.” operator w tym zakresie.

supercat
źródło
Dlaczego prawo Demetera nie ma zastosowania do obiektów przechowujących dane?
Telastyn
2
@Telastyn: Celem LoD jest uniknięcie problemów, które mogą powstać, jeśli fragment kodu uzyskuje dostęp do wewnętrznych obiektów, którymi może manipulować lub o co się martwić . Jeśli nic innego we wszechświecie nie mogłoby manipulować stanem wewnętrznych obiektów ani się nim przejmować, nie ma potrzeby bronić się przed takimi problemami.
supercat,
Nie jestem pewien, czy się zgadzam. Nie chodzi o to, że inne rzeczy mogłyby dbać o modyfikację danych, chodzi o to, że łączysz się z zawartym obiektem ścieżką (zasadniczo trzy punkty sprzężenia - dwa obiekty i ich relacja). Czasami to połączenie nie będzie wielką sprawą, ale nadal wydaje mi się śmierdzące.
Telastyn
@Telastyn: Twoje zdanie na temat ścieżek jest dobre, ale myślę, że mój punkt widzenia jest w porządku. Dostęp do obiektu wieloma ścieżkami tworzy sprzężenie między tymi ścieżkami. Jeśli pewien dostęp odbywa się płytką ścieżką, dostęp również głęboką ścieżką może powodować niepożądane połączenie. Jeśli jednak cały dostęp odbywa się jedną określoną głęboką ścieżką, nie będzie nic, co łączy tę głęboką ścieżkę.
supercat
@Telastyn Przemieszczanie się w strukturze danych pozwala wnikać głęboko w dane. To nie to samo, co zagnieżdżone wywołania metod. Czasami wymagana jest znajomość struktury danych i sposobu jej zagnieżdżania, to samo nie dotyczy obiektów takich jak usługa i własne zagnieżdżone usługi / repozytoria itp.
Per Hornshøj-Schierbeck