fb pixel

Jak programować obiektowo? cz. 6 – wartości stałe

|

Bo w życiu warto wiedzieć, że są rzeczy stałe

Dzisiaj porozmawiamy o tworach niezmiennych, czyli o „constach”, o wartościach stałych. Czym te wartości są? Otóż są to wartości, do których możemy się odwoływać bez konieczności tworzenia instancji danej klasy. Są one od nich niezależne i co ważniejsze, te wartości są niezmienne. Nie mogą ich zmodyfikować ani obiekty zewnętrzne ani instancje danej klasy.

Wartości stałe bezpośrednio wpływają na czytelność kodu, ponieważ dzięki nim zamiast magicznej, nie wiadomo skąd wziętej wartości, mamy twór o czytelnej nazwie, dzięki któremu zrozumienie kodu staje się dużo łatwiejsze.

Ale kiedy się pytam?

Wprowadzenie wartości stałej warto rozważyć zawsze, wtedy gdy mamy do czynienia z wartością, której niezmienności jesteśmy pewni. Wartość, która z jakiegoś powodu jest taka, a nie inna (np. liczba PI) i wiemy, że taka też pozostanie.
Stałą wprowadzamy dla uproszczenia kodu i dla usunięcia duplikacji. Nie musimy nieustannie powielać danej wartości, a przede wszystkim, w przypadku, gdyby jednak nagle trzeba było ją zmienić (np. zwiększyć precyzję PI w kodzie) to mamy tylko jedno miejsce, gdzie tą zmianę należy wprowadzić. Nie trzeba przeszukiwać kodu z nadzieją, że nie pominiemy przypadkiem któregoś wystąpienia. Ponadto, nie będziemy zachodzić w głowę za kilka miesięcy co ta bliżej nieokreślona wartość znaczy. Jeden rzut oka i zapoznanie się z nazwą stałej będzie wystarczające.

Osobnym pytaniem jest to, czy dana stała powinna zostać umieszczona w klasie, które obiekty ją wykorzystują, czy wyniesiona w inne miejsce? Nie ma niestety na nie prostej odpowiedzi i wszystko zależy od tego jak mocno dana wartość jest z instancjami obiektu związana.

Jeszcze jedną istotną wartością, którą stałe wnoszą w nasze życie jest nawigacja. Aby dowiedzieć się, gdzie dana wartość jest wykorzystywana wystarczy w „poprosić” IDE o wskazanie miejsc użycia. I to, co jest obarczone sporym ryzykiem w przypadku wyszukiwania (ze względu np. na inną dokładność bądź literówki) jest teraz niezwykle proste.

Czy na pewno jest potrzebna?

Zapewne równie często jak ja spotykaliście się z deklaracjami stałych w kodzie, z którym przyszło Wam pracować i który sami tworzyliście i tworzycie:

class SpaceFilter
{
  const SEPARATOR = '_';

  public function filter($value) 
  { 
    return str_replace(' ', self::SEPARATOR, $value);
  }
}

W powyższym przykładzie widzimy klasę, która zamienia nam spację na znak podkreślenia. Czy rzeczywiście jest sens tworzyć stałą SEPARATOR? Zakładamy, że nigdy (czyli w rzeczywistości „nie w najbliższym czasie”:) ) się nie zmieni.

Co daje nam istnienie tej wartości? Często słyszę, że takie działanie pozwala nam łatwiej odnaleźć ją, jeżeli będzie trzeba wprowadzić zmiany (np. zmiana „_” na „.”). Z tym się nie można nie zgodzić, ale czy to jest wystarczający powód, aby stworzyć stałą? Nie będzie ona nigdy używana poza klasą, nie dostarcza nam żadnych dodatkowych informacji, a nawet je duplikuje, bo zakładam, że w dokumentacji metody filter() znajdzie się informacja o tym, że zamienia ona spacje na znaki podkreślenia.

Osobiście uważam, że w takich przypadkach (gdy wartość jest wykorzystywana jedynie przez instancje danej klasy) deklarowanie wartości stałej jest nadużywaniem tej konstrukcji. Co gorsza argument, że „ułatwi przyszłe zmiany” jest również błędny, bo taki zabieg tak naprawdę komplikuje ten proces. Dlaczego? Jeżeli jest to stała to może zostać użyta gdziekolwiek indziej, a więc nie mogę zmienić jedynie definicji klasy, muszę również sprawdzić w całym projekcie, czy nie została ona gdzieś wykorzystana, bo teoretycznie jest taka możliwość.
Co gorsza, może się zdarzyć, że ktoś ją wykorzysta nie dlatego, że kontekst się zgadza, tylko dlatego, że wartość jest ta sama. Przynajmniej na chwilę obecną, bo to może się zmienić. Jest to nic innego jak tworzenie sztucznych zależności, które wpływają na zrozumienie kodu.

Dobra, ale co z jego czytelnością?

Osobiście, w takich sytuacjach, tworzę metody prywatne. Eliminuje to wspomniany wyżej problem z możliwością wykorzystania wartości w innych miejscach nie ujmując przy tym nic czytelności kodu:

class SpaceFilter
{
  public function filter($value) 
  { 
    return str_replace(' ', $this->separator(), $value);
  }

  private function separator()
  { 
    return '_';
  }
}

Przykład oczywiście nie należy do najbardziej skomplikowanych, a kod aplikacji, nad którymi pracujecie na co dzień może być dużo bardziej złożony. Przez to decyzja o (nie)stosowaniu stałej może być odpowiednio trudniejsza. Ogólna zasada pozostaje jednak niezmienna - jeżeli wartość nie będzie wykorzystywana na zewnątrz, to nie powinna być ona stała.

Przestańmy teoretyzować

Wróćmy do naszego głównego przykładu. Do tej pory stworzyliśmy klasę Contractor wraz z asocjacjami, atrybutami oraz metodami. I dzisiaj chyba przyszła pora na kolejną funkcjonalność - może edycja danych?

Załóżmy, że dane dotyczące kontrahentów przechowujemy w bazie danych i każdy z nich posiada jakiś unikalny identyfikator (kolumna o nazwie „id”). Jakie kroki musimy wykonać, żeby go edytować?

  • Wybranie z listy kontrahentów tego, którego dane chcemy zmienić.
  • Wyświetlenie jego obecnych danych.
  • Edycja tych danych.
  • Zapis nowych danych.

Tak więc, wiemy już co musimy zrobić. Aby edytować z powodzeniem kontrahenta najpierw musimy wyciągnąć jego dane z bazy danych. Bez tego nie wiadomo co chcemy edytować. Aby ta operacja była jednak możliwa potrzebujemy jakiś unikalny identyfikator, który określi o którego kontrahenta nam chodzi. W naszym przypadku jest to wartość atrybutu „id”. W aplikacjach klient-serwer nie można się bezpośrednio odwołać do metod obiektów na serwerze. Trzeba wysłać żądanie ze zbiorem parametrów. Taki parametr powinien mieć jednoznaczną nazwę np. „contractorId” (nie „id”, bo tych zazwyczaj jest w projekcie dużo).

Kiedy ten identyfikator będzie wysyłany? Przy pobieraniu danych na temat kontrahenta oraz przy jego edycji, wraz z pozostałymi danymi. Tak więc, mamy już dwa miejsca, w których wystąpi parametr „contractroId”, a co z przyszłymi wymaganiami, które wiemy, że będą musiały zostać spełnione? Z funkcjonalnością, która będzie musiała zostać zaimplementowana np. „wyświetl faktury kontrahenta”, „wystaw fakturę”, etc.? Tam również będzie musiał zostać przesłany.

Oczywiście możemy tą nazwę kopiować, ale co w przypadku potrzeby zmiany „contractorId” na np. „cId” lub cokolwiek innego? Będziemy zmuszeni do przeszukiwania całego projekt w celu odnalezienia wystąpień „contractorId”. Dodatkowo, można popełnić literówkę przy wykorzystywaniu tego ciągu i jeżeli nie zostanie ona wyłapana na etapie powstawania kodu to taki błąd później może być trudny do odnalezienia.

I to jest właśnie moment, w którym można pomyśleć o zastosowaniu stałej!
Tylko gdzie ją umieścić? Warto w takich chwilach zastanowić się, która klasa jest najbardziej powiązana z daną stałą. W naszym przypadku odpowiedź nasuwa się sama - klasa Contractor:

class Contractor
{
  const CONTRACTOR_ID = 'contractorId';
  /* ... */
}

Ale czy rzeczywiście jest to dobre miejsce? Do tej pory udało nam się stworzyć całkiem dobrą strukturę tej klasy i nie uwzględniała ona żadnej stałej? Czy coś się zmieniło?

Do czego tak naprawdę wykorzystujemy „contracotrId”? Do pobierania danych konkretnego obiektu z bazy. Tak więc „contarctorId” nie jest czymś powiązanym z klasą Contractor, jest to informacja, która pomaga nam odnaleźć dane w bazie. Dlatego może lepszym miejscem na umiejscowienie tej stałej byłaby klasa odpowiedzialna za pobieranie tych danych? Z drugiej strony, czy w ogóle ta nazwa jest nam tam potrzebna? My do wykonania odpowiednich metod potrzebujemy wartości, która w żądaniu jest z nią powiązana.

No więc gdzie? Tak naprawdę nazwa ta jest wykorzystywana przy wysyłaniu żądania. Służy do określenia co jest wysyłane i jeżeli dany parametr o kluczu „contractorId” znajduje się wśród przesłanych wartości, to wiemy, że jest to id kontrahenta, którym jesteśmy zainteresowani.

I może to jest odpowiednie miejsce? Jak myślicie?

class Request
{
  const CONTRACTOR_ID = 'contractorId';
  /* ... */
}

A na koniec

Czy zwróciliście uwagę na literówki w pisowni parametru „contractorId” w powyższym tekście? Jeżeli tak, to gratuluję, a jeżeli nie, to właśnie doświadczyliście na własnej skórze przed jak łatwo umykającymi błędami potrafią nas ustrzec wartości stałe 🙂

Pozostałe artykuły z cyklu

Dodaj na LinkedIn
Sebastian Malaca
Jestem fanatykiem obiektowego programowania i nieustannie pogłębiam swoją wiedzę we wszelkich tematach z nim związanych. Wszystko czego się dowiem konfrontuję z rzeczywistością, ponieważ teoria, która nie ma odzwierciedlenia w praktyce, traci swój sens tam, gdzie zaczyna się praca programisty :)