piątek, 18 listopada 2011

Jak programować obiektowo cz. 4 - metody klas II

ciągle o tym samym

W dzisiejszym wpisie kontynuuje wątek z poprzedniego wpisu, a więc nadal będzie o metodach:)
Dzisiaj zbiór wszelkich różnych rzeczy dotyczących metod, czyli trochę informacji, a trochę dobrych praktyk, które warto przestrzegać, aby z kodem przyjemniej się pracowało:)

nazewnictwo

Nazwa metody powinna coś mówić o jej przeznaczeniu, czyli wszelkie foo(), boo() bar(), nie powinny być stosowane w kodzie, bo powodują one, że powrót do takiego kodu będzie niezbyt miłym i z pewnością czasochłonnym doświadczeniem, gdy popatrzymy na niego i krzykniemy ze zdziwieniem: WTF?
Tym bardziej w dobie, gdy dobre IDE podpowiada wszystko w locie, użycie odrobinę bardziej wyszukanej nazwy (czyli zapewne dłuższej niż te trzy znaki:) nie powinna być problemem. Z drugiej strony nie powinno się popadać ze skrajności w skrajność i z nazwy metody tworzyć opowiadanie, bo przecież mamy jeszcze komentarze:) Nazwę metody można porównać do nagłówka artykułu, daje jakieś pojęcie o tym, o czym będzie artykuł, ale żeby dowiedzieć się wszystkiego należy go przeczytać.
Druga istotna rzecz to fakt, że nazwa metody powinna (najczęściej) być określeniem jakiejś czynności, bo tak naprawdę do tego sprowadza się tworzenie metod, jest to definiowanie pewnego zachowania, czyli mamy np. changeAddress(), makeCoolThings() etc.

komentarze

Jest to naprawdę bardzo ważna część kodu, chociaż nie wpływa na jego działanie.
Oczywiście komentarze można (i powinno się) stosować w odniesieniu do klas i ich atrybutów, ale tak naprawdę przy metodach można przekonać się, jak wielkie znaczenie one mają.
Po pierwsze bez zagłębiania się w implementację jesteśmy w stanie otrzymać wszystkie niezbędne (jeżeli jest dobrze napisany:) informacje nt. metody, czyli:
  • listę wszystkich parametrów wraz z ich krótkim opisem
  • opis wartości zwracanej
  • wszystkie wyjątki, które może dana metoda wyrzucać wraz z informacjami w jakich przypadkach
  • i oczywiście, opis tego, co dana metoda robi
Po drugie, możemy sobie na tej podstawie w prosty sposób wygenerować dokument z API danej klasy, co również jest przydatne.
Po trzecie, dobre IDE na podstawie tych podpowiedzi jest w stanie ułatwić nam życie:)

długość metod

Chodzi mi o ilość linii kodu, który jeszcze może być traktowany jako pojedyncza metoda, a kiedy należy pomyśleć nad rozbiciem metody na części składowe. Słyszałem kiedyś o tym, że jak metoda nie mieści się na ekranie monitora to powinna zostać rozbita. Moim zdaniem warto wtedy rozważyć podzielenie tej metody, ale pod warunkiem, że ten podział będzie miał jakiś logiczny sens.
Metody, które liczą naprawdę sporo linii kodu zazwyczaj mają również rozbudowaną logikę działania (coś w końcu musi być powodem, dla którego są tak duże:) i szczerze mówiąc nie spotkałem się z przypadkiem, żeby taka logika była tak spójna, że aż nie możliwa do podzielenia. Oczywiście nie twierdzę, że metody, które powstaną w takim procesie powinny być publiczne, bo możliwe, że osobno nie mają zbyt wiele sensu i dopiero użycie ich razem, w określonej kolejności daje jakiś (oczekiwany) efekt.
Jakie przynosi to korzyści? W końcu przecież takie metody będą używane tylko raz? To prawda, ale ułatwi to pracę nad kodem w przyszłości i zwiększy jego czytelność, a jest to na tyle istotny argument, że przynajmniej warto go wziąć pod uwagę:)

ilość parametrów

Ile atrybutów można przekazać? Oczywiście tyle ile jest niezbędne do wykonania określonego działania. Jednak, gdy jest ich rzeczywiście sporo warto jeszcze raz wszystko przemyśleć. Może któryś z nich jest wykorzystywany w innych metodach danej klasy i warto rozważyć dodanie go jako atrybut klasy? Może metoda robi zbyt wiele rzeczy na raz? Może te parametry (wszystkie bądź część) są ze sobą w jakiś sposób logicznie powiązane i warto rozważyć stworzenie klasy, która będzie je w jakiś sposób grupowała?

parametry typu array

Jeżeli tablice są traktowane jako kolekcje wartości/obiektów tego samego typu to wszystko w porządku.
Jeżeli są to tablice asocjacyjne używane w taki sam sposób jak mapy w Javie, czy C++, czyli klucze tego samego typu i wartości tego samego typu, to nadal wszystko jest ok:)
Gorzej jak tablica jest zbiorem wartości i/lub obiektów różnego typu. W takim wypadku z całą stanowczością doradzam stworzenie jakiegoś obiektu, który w logiczny sposób przedstawiałby strukturę takiego tworu bądź zastanowienie się, czy rzeczywiście jest nam potrzebna w tym miejscu tablica.

wywoływanie przez wartość, wywoływanie przez referencje

Są dwie możliwości wywoływania metody: przez wartość i przez referencję. W pierwszym przypadku parametry, których używamy w ciele metody są kopiami parametrów, które przekazujemy, czyli ich modyfikacja w ciele metody nie ma wpływu na ich wartość poza nią np.
<?php
class Math
{
  public function changeValue($x) 
  { 
    $x = 10;
  }
}

$math = new Math();
$x = 13;
$math->changeValue($x);
echo $x; //nadal 13
Drugim sposobem wywołania jest wywoływanie metody przez referencję, czyli instrukcje wykonywane na przekazanym parametrze w ciele metody mają bezpośredni wpływ na jego wartość poza nią, czyli:
<?php
class Math
{
  public function changeValue(&$x) 
  { 
    $x = 10;
  }
}

$math = new Math();
$x = 13;
$math->changeValue($x);
echo $x; //tym razem wynikiem będzie 10
To wszystko ma zastosowanie w odniesieniu do typów prostych. Obiekty natomiast zawsze są przekazywane przez referencję.
Warto pamiętać o tym, gdy mamy obiekt klasy, której atrybutem jest inny obiekt i zależy nam na tym, aby był on prywatny i aby jego stan mógł zmieniać tylko obiekt rodzica. Jeżeli taka sytuacja ma miejsce, ale z jakiegoś powodu potrzebujemy zwrócić ten obiekt (agregowany) jako wynik działania którejś z metod, to należy rozważyć zwrócenie kopii tego obiektu.

that's all

To tyle na temat teorii. Za tydzień postaram się wrzucić jakichś ciekawy przykład, który będzie w stanie pokazać w jaki sposób stosuje się większość z omówionych przeze mnie kwestii.