[read English version here]

Rzucam wyzwanie standardom kodowania. Postanowiłem po raz kolejny, na bazie nagromadzonych w ciągi ostatnich miesięcy doświadczeń, zrewidować swój sposób pisania kodu. Oto, co wymyśliłem.

Var

Jakiś czas temu przy okazji cyklu dotyczącego czytelności kodu pozwoliłem sobie dosyć mocno skrytykować słowo kluczowe var. Zgadnijcie co się stało? Zmieniłem zdanie. var już mnie nie straszy. Oswoiłem się z nim. Przekonała mnie możliwość stosowania dłuższych i bardziej wyrazistych nazw zmiennych przy zachowaniu tej samej długości linii (staram się unikać linii dłuższych niż 100 znaków).

Regiony

Nadal mają u mnie przerąbane. Stosuję je tylko w wyjątkowych sytuacjach, kiedy inaczej po prostu się nie da. Przykładem jest kod obiektów ValueObject w DDDSample.Net (tu dostępne są źródła),. gdzie nie chcę rozpraszać czytelnika kwestiami infrastrukturalnymi, takimi jak implementacja operatorów.

Implementacje interfejsów

Kiedyś zwykłem (zgodnie z resztą z domyślnymi ustawieniami R#) zamykać implementacje interfejsów w regiony. Ostatnio jednak porzuciłem jednak ten nawyk. Po pierwsze, bardzo rzadko piszę klasy, które implementują więcej niż jeden interfejs nietechniczny. Po drugie, jeśli już jakiś implementują, bardzo rzadko mają metody publiczne nie należące do tego interfejsu. Nie ma więc potrzeby w jakikolwiek sposób wyróżniać tych, które do niego należą. Jest jeszcze jeden argument przeciwko wyróżnianiu i izolowaniu metod implementacji interfejsu: takie postępowanie uniemożliwia odpowiednie pionowe rozmieszczenie metod. Dlaczego?

Funkcje publiczne, funkcje prywatne

Uncle Bob w swojej genialnej książce Clean Code przekonał mnie, iż metody niższego poziomu wykorzystywane przez daną metodę wyższego poziomu powinny być zlokalizowane blisko niej. Jest to praktyczne: nie trzeba przewijać ekranu podczas czytania kodu. Jest to także logiczne: relacja “wywołuje” znacznie bardziej wiąże ze sobą metody, niż posiadanie takiego samego kwalifikatora dostępu (private, public). Aby nie być gołosłownym — przykład. Zamiast wcześniejszego:

public Class1()
{
   public void MethodA() {
   }
   public void MethodB() {
   }

   private void MethodA_1(){}
   private void MethodA_2(){}
   private void MethodB_1() {}
   private void MethodB_2() {}
}

teraz stosuję:

public Class2()
{
   public void MethodA() {}
   private void MethodA_1(){}
   private void MethodA_2(){}

   public void MethodB() {}
   private void MethodB_1() {}
   private void MethodB_2() {}
}

Widać także, że o ile stary styl kopiowania pozwalał na zawijanie implementacji interfejsów w regiony (np. MehodA i MethodB), o tyle nowy jest zupełnie niekompatybilny z tą ideą — trzeba by było zawinąć całą klasę.

Pola

Czytając kod NServiceBus nauczyłem się, że dogmat o kolejności memberów w klasie (najpierw pola, potem metody) może być złamany. W NSB spotkałem się z wzorcem: najpierw metody, potem konstruktor, na końcu pola. Z początku byłem dosyć sceptyczny, jednak po jakimś czasie sam zacząłem go stosować. Nie wiem jaka była oryginalnie motywacja Udiego, aby tak pisać, ale dla mnie plusem tego podejścia jest to, że otwieram klasę i widzę to co mnie interesuje — jej publiczny interfejs. Dzięki możliwości współczesnych IDE jest duża szansa, że podczas czytania kodu w ogóle nie będę potrzebował patrzeć na pola.

Konstruktor

Klasy, które piszę, są albo pełnoprawnymi klasami, albo strukturami danych kompletnie pozbawionymi zachowania. Instancje tych drugich tworzone są zwykle klasycznie, za pomocą operatora new, więc sygnatura konstruktora ma znaczenie. W takich wypadkach umieszczam konstruktor wysoko, zaraz po polach, a przed właściwościami. Można powiedzieć, że stosuję mniej-więcej klasyczny układ memberów.

Zupełnie inaczej jest, jeśli chodzi o “prawdziwe” klasy. Ich instancje zwykle tworzone są za pośrednictwem frameworku DI, więc kompletnie nie interesuje mnie jak zostaną stworzone. Konstruktor ląduje więc na dole klasy, pomiędzy metodami, a polami. Zaglądam do niego tylko wtedy, kiedy muszę dodać jakąś nową zależność do klasy.

Efekt końcowy

Tak wygląda efekt końcowy:

public MyNewClass()
{
   public void DoSomething()
   {
      HelperMethod1();
      HelperMethod2();
   }

   private void HelperMethod1()
   {
   }

   private void HelperMethod2()
   {
   }

   public void DoSomethingElse()
   {
   }

   public void MyNewClass(SomeOtherClass dependency, YetAnotherClass anotherDependency)
   {
      _dependency = dependency;
      _anotherDependency = anotherDependency;
   }

   private readonly SomeOtherClass _dependency;
   private readonly YetAnotherClass _anotherDependency;
}

Co o tym sądzicie? Na wypadek, gdyby ktoś polubił ten styl pisania, tutaj dostępny jest dokument XML w formacie R# ustawiający automatyczne formatowanie zgodnie z tym, co opisałem. Dodatkowo, jeśli w klasie znajduje się pole o nazwie “_logger”, jest umieszczane na samym dole.

VN:F [1.9.13_1145]
Rating: 5.0/5 (1 vote cast)
[PL] Challenge everything, 5.0 out of 5 based on 1 rating