Skip to content

Ogólne cz. 2/2 – ZK #4

Zazdrość o funkcje

Klasy oraz metody powinny „interesować się” tylko zmiennymi oraz funkcjami, które zostały zdefiniowane w ich wnętrzu. Oznacza to, że nie powinny one się opierać na zawartości innych klas. Innymi słowy, gdy wykorzystujemy gettery oraz settery obiekty klasy Y wewnątrz klasy X celem modyfikacji stanu tego obiektu, to klasa X zazdrości klasie Y funkcjonalności. Tyczy się to również momentów, w których metoda w danej klasie częściej korzysta z metod klas obcych. W takiej sytuacji powinniśmy rozważyć wyodrębnienie metody do właściwej klasy (w niektórych przypadkach pomocny może być również wzorzec projektowy Visitor). Zazdrość o funkcje jest również nieraz ściśle powiązana z niewłaściwą hermetyzacją oraz niepoprawnym rozmieszczaniem metod, a w efekcie niską spójnością klasy.

Błędne metody statyczne

W swojej książce Robert Martin jako przykład dobrej metody statycznej podaje metodę  
public static double max(double a, double b). Zaglądając do dokumentacji widzimy następujący opis:

Returns the greater of two double values. That is, the result is the argument closer to positive infinity. If the arguments have the same value, the result is that same value. If either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this method considers negative zero to be strictly smaller than positive zero. If one argument is positive zero and the other negative zero, the result is positive zero.

Metody statyczne powinny wszystkie swoje dane pobierać z argumentów, a nie z obiektów na rzecz których można je wywołać. Projektując jednak metodę statyczną musimy rozważyć ewentualną potrzebę wykorzystania polimorfizmu. Możemy w końcu chcieć posiadać więcej niż jedną implementację danego algorytmu, umieszczoną w osobnych klasach pochodnych, w których przeciążamy nasze poszczególne metody składowe.

Wykorzystanie zmiennych opisowych

Jest to podejście, które każdy z nas w mniejszym lub większym stopniu stosował na samym początku swojej przygody z programowaniem. Polega ono na zamknięciu poszczególnych kroków danej operacji w lokalnych zmiennych opisowych. Jako przykład podam fragment kodu z open source’owego framework’a FitNesse. Przykład ten został również wykorzystany w książce Roberta Martina jako idealna reprezentacja dla podejścia omawianego w tym akapicie.

Matcher match = headerPattern.matcher(line);
if(match.find()) {
    String key = match.group(1);
    String value = match.group(2);
    headers.put(key.toLowerCase(), value);
}

Programiści (szczególnie ci początkujący) niestety często mają tendencję do nadmiernego zmniejszania objętości kodu kosztem jego czytelności. Nie jest to dobre podejście. Poświęcając krótką chwilę na napisanie paru dodatkowych linijek oszczędzamy większą ilość czasu w przyszłości, którą prawdopodobnie my lub inni programiści będą musieli poświęcić na zrozumienie sposobu w jaki to wszystko funkcjonuje.

Dane konfigurowalne na wysokim poziomie abstrakcji

Wszelkiego typu stałe, które przechowują wartości domyślne lub dane konfiguracyjne powinny być przechowywane na wysokim poziomie abstrakcji. Oznacza to, że nie powinniśmy ich ukrywać w metodach lub klasach niższego poziomu (chyba, że dotyczą one tylko i wyłącznie określonej klasy). Dane takie należy przekazywać w dół, a nie w górę. Dzięki temu w razie potrzeby nie ma konieczności przeglądania oraz modyfikowania poszczególnych metod (lub klas), a wystarczy spojrzeć na te o najwyższym poziomie abstrakcji.

Prawo Demeter

Jest to prawo traktujące o stosowaniu zależności przechodnich. Mając trzy klasy, z których pierwsza współpracuje z drugą, a druga z trzecią to nie chcemy, aby klasa pierwsza wiedziała o istnieniu tej trzeciej. Na wiki c2 możemy znaleźć następujące opisy Prawa Demeter:

  • You can play with yourself.
  • You can play with your own toys (but you can’t take them apart),
  • You can play with toys that were given to you.
  • And you can play with toys you’ve made yourself.
  • Your method can call other methods in its class directly
  • Your method can call methods on its own fields directly (but not on the fields’ fields)
  • When your method takes parameters, your method can call methods on those parameters directly.
  • When your method creates local objects, that method can call methods on the local objects.

but

  • One should not call methods on a global object (but it can be passed as a parameter ?)
  • One should not have a chain of messages a.getB().getC().doSomething() in some class other than a’s class.
  • governs the communication structure within an object-oriented design
    • restricts message-sending statements in method implementations
    • only talk to your immediate friends
  • message target can only be one of the following objects:
    1. the method’s object itself (C++, Java, C#: this; Smalltalk: self, super; VB.NET: Me)
    2. an object that is an argument in the method’s signature
    3. an object referred to by the object’s attribute
    4. an object created by the method
    5. an object referred to by a global variable

Na pierwszy rzut oka prawo to może wydawać się nieco zawiłe jednak sądzę, że warto o nim pamiętać i stosować chociaż w podstawowym zakresie.


Informacje na temat cyklu i źródeł:

Facebooktwitterredditlinkedinmail
Published inProgramowanie