Niniejsze rozważania zacznę od następującej anegdoty, która doskonale oddaje ich sedno.

Pewna mała dziewczynka przyglądała się mamie przygotowującej pieczeń:
– Mamo, dlaczego gdy robisz pieczeń, to zawsze odcinasz końcówki mięsa i wkładasz je po bokach brytfanny?
– Bo tak się piecze kochanie.
– Ale dlaczego tak się piecze?
– Końcówki obcina się, aby mięso lepiej się upiekło.
– Mamo, ale przecież i bez tego mięso by się upiekło.
– Córeczko, twoja babcia tak piekła, ciocia tak piecze i wszyscy powinni tak piec.
Przy najbliższej okazji, gdy dziewczynka odwiedzała babcię, mając w pamięci tę rozmowę, postanowiła ją zapytać:
– Babciu, dlaczego i ty, i mama, gdy pieczecie mięso, to obcinacie jego końce i wkładacie po bokach brytfanny?
– Kochanie, nie wiem dlaczego mama tak robi. Ja tak robiłam, bo miałem za małą brytfannę.

Jak widać stosowana przez mamę konwencja nie przystawała do aktualnego środowiska, w którym była stosowana. Okazała się przestarzała. I nad tym właśnie chciałbym się dzisiaj pochylić – nad bezmyślnym stosowaniem zasad, uleganiu przyzwyczajeniom, w miejsce rozsądnej czujności, która pozwoli w porę wychwycić, że stosowana dotąd konwencja jest w obecnych warunkach przestarzała, a przez to niepotrzebna. Warto przy tej okazji zauważyć, że w świecie .NET taka czujność ma miejsce – zrezygnowano chociażby z notacji węgierskiej (niemniej w jakiejś, zmutowanej postaci, pozostaje ona w sugerowanym nazewnictwie interfejsów – prefiks I, który wskazuje na typ interface).

W .NET istnieją konwencje opisujące dobre praktyki – to bardzo dobrze, ale – moim zdaniem – czasami ich zasadność jest mocno dyskusyjna. Konwencje takie definiuje np. projekt Style Cop (umieszczony na CodePlex), który oprócz zestawu zaleceń potrafi także egzekwować je podczas pisania kodu. Nie tak dawno Piotr Zieliński zaanonsował na swoim blogu swój artykuł z MSDN o dobrych i złych praktykach. Skorzystam z tego artykułu jako źródła zasad, ponieważ nie omawia on ich wszystkich, a mimo to pozwoli wskazać dyskusyjne moim zdaniem konwencje.

Trudno nie zgodzić się z podaną w artykule argumentacją, że choć każdy zespół może ustalić swoją notację, to zgodność z tą przyjętą w .NET przyspieszy wdrożenie się programistom przychodzącym do zespołu. Zgoda, posługując się analogią do powyższej anegdoty, jak najbardziej pieczeń należy doprawić, do jej upieczenia użyć brytfanny, czas pieczenia przyjąć stosowny do wagi pieczeni, ale … czy należy obcinać końcówki pieczeni, mimo że brytfanna tego nie wymusza, czy należy skubać pierze, choć pieczeń go nie posiada? Bezwzględnie nie!

Skoro tak, to twierdzenie, że nazwy metod bezwzględnie powinny być czasownikami, jest prawdziwe tylko wówczas, gdy mówimy o metodach będących poleceniami (patrz: Na dwa rodzaje metod rozbita klasa: poleceń oraz zapytań), ale niekoniecznie, kiedy mamy do czynienia z zapytaniami (czyli funkcjami). W artykule mowa jest m.in. o czytelności kodu, co jak mniemam, oznacza łatwość jego zrozumienia, łatwość odczytania. Moim zdaniem bardziej czytelne jest użycie nazwy Abs() (wartość bezwzględna) w miejsce GetAbs(). Od czasów szkolnych używamy tak nazwanych funkcji (sin, cos, tg, ctg, itd.) i nie potrzebujemy do tego dodatkowych prefiksów. Spójrzmy zresztą na poniższy kod, przestawiający wzór na pole prostokąta, gdy znane są jego przekątna i jej kąt względem boku a, w wydaniu czasownikowym i zwykłym (używam polskiego nazewnictwa dla lepszego uwypuklenia różnic w czytelności):

// nazwy funkcji będące czasownikami
decimal PoleProstokąta =
  Matma.ObliczKwadrat(Przekątna) * 2 +
  Matma.ObliczKwadrat(Matma.DajSinus(Kąt)) +
  Matma.ObliczKwadrat(Matma.DajCosinus(Kąt));

// nazwy funkcji nie będące czasownikami
decimal PoleProstokąta = 
  Matma.Kwadrat(Przekątna) * 2 +
  Matma.Kwadrat(Sinus(Kąt)) +
  Matma.Kwadrat(Cosinus(Kąt));

Proszę sobie szczerze odpowiedzieć, który z tych wzorów jest bardziej czytelny, bardziej intuicyjny i bardziej przystający do teorii matematycznej (wzory matematyczne są wszak bardzo zwięzłe). Te dodatkowe „wtręty”, typu: oblicz, daj, są tu kompletnie niepotrzebne, a wręcz – śmiem twierdzić – zaburzają przekaz.

Ktoś może sobie powiedzieć: ok, w przypadku wzorów matematycznych rzeczywiście może być to nietrafione, ale jeśli będziemy działać na tekstach, to ta konwencja jest jak najbardziej na miejscu. Czy rzeczywiście? Posłużmy się kolejnym przykładem kodu:

// nazwy funkcji będące czasownikami
decimal ProcentSamogłosek =
  Tekst.DajSamogłoski(Treść).Długość /
  (Treść.Długość - Tekst.DajBiałeZnaki(Treść).Długość);

// nazwy funkcji nie będące czasownikami
decimal ProcentSamogłosek =
  Tekst.Samogłoski(Treść).Długość /
  (Treść.Długość - Tekst.BiałeZnaki(Treść).Długość);

No i które z wyrażeń szybciej pozwala na ustalenie, że procent samogłosek w tekście to stosunek ilości samogłosek, do długości treści z pominięciem białych znaków?

Proszę zauważyć – forma czasownika nie jest tu potrzebna z prostej przyczyny – mamy do czynienia z działaniem, a to z definicji operuje na wartościach, zatem użyte w wyrażeniu metody po prostu zwracają wartość, której znaczenie opisane jest przez nazwę tychże metod. Jestem niemalże pewny, że stosowanie zasady czasownikowania funkcji bierze się z przyzwyczajenia przeniesionego z nazewnictwa akcesorów i mutatorów, które z konieczności wskazania na właściwość, której dotyczą zawierają w swojej nazwie jej nazwę, poprzedzoną przedrostkiem Get lub Set. I już po chwili (po napisaniu kilku akcesorów i mutatorów) machinalnie zaczynamy wstawiać owe Get i Set przed inne metody, choć nie ma takiej potrzeby.

Do takiej nomenklatury pcha zresztą samo .NET, oferując takie metody jak chociażby: GetType(), GetEnumerator(), GetHashCode(), GetTypeCode(). Dlaczego to nie są właściwości? Czyli po prostu: Type, Enumerator, HashCode, TypeCode, a nawet jeśli funkcje, to bez tych nieszczęsnych Get? W ogóle, jeżeli obiekt zwraca jakąś swoja cechę, to powinien to robić przez właściwość. Funkcja jest potrzebna wówczas, gdy niezbędny jest jakiś argument, który by precyzował obiektowi o co chodzi. Pisałem niedawno o zasadzie jednolitego dostępu, mając ją na uwadze byłbym skłonny do takiego podejścia, że – skoro już chcemy używać metod, zamiast właściwości, to nie ma sensu używać przedrostków (zauważcie – to nadal jest jakaś mutacja notacji węgierskiej), po prostu nazywajmy rzeczy po imieniu, tym bardziej, że przeciążanie metod umożliwia nam to (tak, mamy już większą brytfannę 😉 ). Możemy zatem (skoro nie lubimy właściwości, lub nie piszemy w C# tylko w C++ bądź Javie) użyć ChannelNumber() jako metody zwracającej numer kanału przechowywanego przez obiekt, zaś ChannelNumber(123), jako metody ustawiającej obiektowi tenże numer kanału.

Podsumowując: stoję na stanowisku odmiennym od przedstawionego w artykule, gdzie funkcję Time() sugeruje się zastąpieniem funkcją GetTime(). Jak wykazałem powyżej, nie daje to żadnej korzyści, a wręcz przeciwnie.

I skoro już przy czasie jesteśmy, to chciałbym poruszyć jeszcze jedną kwestię, która wypłynęła w komentarzach do wpisu promującego artykuł. Jeden z komentujących twierdził, że określenie Time jest niejednoznaczne, albowiem nie określa w jakich jednostkach tenże czas jest przechowywany. Raczyłem nie zgodzić się z tym argumentem, a ponieważ adwersarz nie przedstawił merytorycznego uzasadnienia swojego twierdzenia, zaś cudzy blog nie jest miejscem do dyskusji, tutaj wyłuszczę mój punkt widzenia, licząc na merytoryczne kontrargumenty (nie zaś wycieczki osobiste).

Czego będziemy oczekiwać od (funkcji lub właściwości) nazwanej Time? Czyż nie tego, że będzie on wyrażony w godzinach, minutach i sekundach (ewentualnie setnych sekundy)? Co więcej, możemy także oczekiwać, że będzie on wyrażony dodatkowo w latach, miesiącach i dniach. Jest to całkowicie intuicyjne oczekiwanie, rzekłby, że naturalne. Co będzie oznaczał ten czas? To będzie wynikać z kontekstu – zapewne z nazwy obiektu, którego jest właściwością. Może oznaczać czas w przedziale doby (także doby konkretnego dnia), ale może też oznaczać przedział czasu, czyli czas jaki minął od konkretnego wydarzenia. Będzie to czas wyrażony w godzinach, minutach i sekundach (godzin może być więcej niż 24), jak również w dniach, godzinach, minutach i sekundach (godzin będzie maksymalnie 24, ale dni może być więcej niż dni w roku). Analogicznie będzie jeśli dołożymy miesiące i lata. Taka właściwość Time powinna być de facto klasą, zwracającą czas w naturalnej postaci, ale także taką, która umożliwia uzyskanie dowolnej postaci czasu, czy to jedynie w (odpowiednio zaokrąglonych) sekundach, minutach, godzinach, dniach, miesiącach, latach, ale także w postaciach częściowych (dni z dokładnością do czasu, itd. – jak rozważałem powyżej). Sposób przechowywania czasu wewnątrz klasy go reprezentującej jest wewnętrzną sprawą klasy (kwestią jej implementacji) – ważne, aby na zewnątrz można było takim czasem swobodnie operować. Nie ma zatem sensu przechowywanie czasu w konkretnych jednostkach i sugerowanie tego nazwą zmiennej lub właściwości (funkcji?), która go przechowuje, np: TimeInSecondsBetweenEvents. Nazwa jest zaiste długa i mało elastyczna, bo za chwilę będą potrzebne dodatkowe metody konwertujące na minuty, godziny, itp. W przypadku klasy reprezentującej czas, wszystko jest zdefiniowane na jej poziomie (w niej hermetyzowane) i żadne dodatki nie są potrzebne – czas będzie zwracany w sposób dowolny, zgodny z potrzebami (o wszystko zadba specjalizowana klasa czasu).

Kolejną zaleceniem które chciałbym rozważyć jest to, aby interfejsy nazywać poprzez poprzedzanie przedrostkiem I i kończenie słówkiem able. Zdaję sobie sprawę, że .NET jest sporo interfejsów, których nazwa jest tak skonstruowana, ale to nie oznacza, że trzeba je na siłę do tej reguły naginać. Pal licho początkowe I – choć nie bardzo widzę potrzebę informowania w ten sposób, że to interfejs, bo co jeśli kiedyś okaże się, że klasa byłaby lepsza? Trzeba będzie usuwać I? Niemniej owe końcowe able może prowadzić do powstawania potworków, które nie występują w języku angielskim. Ot choćby wzorzec Polecenia. Należy dla niego utworzyć interfejs, ale czy ma się on nazywać ICommandable? Otóż to. A jeżeli będę miał interfejs coś odczytujący – czy mam go nazwać IReadable? Wówczas sugerować on będzie, że odpowiada za czytelność – nomen omen ;).

Podsumowując – z faktu, że nazwa jakiegoś procentu interfejsów .NET jest zdefiniowana zgodnie z takim, a nie innym wzorcem, nie oznacza, że ma być to zalecenie dla nazw wszystkich interfejsów, bo wówczas może to prowadzić do sytuacji, jeśli nie komicznych, to kuriozalnych.

I na koniec. Nie przemawia do mnie konwencja, aby nazwy parametrów zaczynać z małej litery, skoro używać należy PascalCase. Jeżeli będę chciał przekazać NIP w parametrze, to będę musiał podać go jako nip, a skróty piszemy w wielkich liter – chcąc stosować tę zasadę muszę łamać inną. Dlatego bardziej skłonny jestem przedrostkować argumenty metod przedrostkiem a, pola przedrostkiem f (zamiast nieszczęsnego fiutka „_” ;)). Co do PascalCase, to uważam, że powinna być ona stosowana rozważnie, aby np. skróty wyglądały jak należy. Zatem nie SqlConnection, ale SQLConnection, nie XmlNode, ale XMLNode. Jak widać jestem na przekór zapisom stosowanym w .NET ale uważam, że nie powinny one rugować zasad, które są od nich o wiele starsze. Zbyt wielu spotkałem na swej drodze programistów, którzy nie znali zasad pisowni (ortografia, gramatyka), podejrzewam, że albo podobnie było z projektantami .NET (nic o tych zasadach nie wiedzieli), albo postawili PascalCase ponad zasady języka mówionego.

Jako zwieńczenie niniejszych rozważań chciałbym jeszcze przedstawić inną anegdotę, która w dość celny sposób pokazuje, jak można stać się niewolnikiem standardów.

Czy oświadczenie — „Zawsze tak to robimy” nie budzi w Tobie jakiejś wątpliwości?

  1. Amerykański standard szerokości torów kolejowych (odstępu szyn) wynosi 4 stopy, 8.5 cala. Prawda, że to dziwaczna liczba? A czy wiesz dlaczego taki właśnie standard się stosuje?
  2. Ponieważ w ten sposób buduje się linie kolejowe w Anglii, a właśnie angielscy przybysze budowali amerykańskie linie kolejowe. Dlaczego jednak Anglicy budowali je właśnie tak?
  3. Ponieważ pierwsze linie kolejowe, budowali ci sami ludzie, którzy budowali wcześniej linie tramwajowe, a tam właśnie stosowano ten standard. Dlaczego jednak stosowano tam ten standard?
  4. Ponieważ ludzie, którzy budowali linie tramwajowe, używali tych samych szablonów i narzędzi, jakich używali przy budowie wagonów tramwajowych, które miały taki odstęp kół. W porządku! Ale dlaczego wagony miały taki właśnie dziwaczny odstęp kół?
  5. No cóż, gdyby próbowali zastosować jakiś inny rozstęp, łamali by pewną starą zasadę budowy dalekobieżnych dróg angielskich, ponieważ mają one taki właśnie odstęp kolein na koła. Któż to budował te starodawne drogi z koleinami?
  6. Pierwsze dalekobieżne drogi w Europie (i Anglii) zbudowało Imperium Rzymskie dla swoich legionów. Drogi te są w użyciu dotychczas. Ale skąd te koleiny?
  7. Pierwotne koleiny uformowały rzymskie rydwany wojenne; każdy inny użytkownik tych dróg musiał się dostosować do nich, gdyż inaczej uszkodziłby koła swoich pojazdów. Ponieważ rydwany były wykonywane dla Imperium Rzymskiego, były one wszystkie do siebie podobne jeśli idzie o rozstęp kół. Jak widać amerykański standard odstępu szyn kolejowych: 4 stopy i 8.5 cala, pochodzi od parametrów technicznych rzymskich rydwanów bojowych. A biurokracja jest wieczna.

A więc następnym razem, kiedy dostaniecie do ręki jakąś dokumentację techniczną i zaczniecie podejrzewać, że może ona mieć coś wspólnego z końską dupą, będziecie mieli całkowitą rację, ponieważ rzymskie rydwany wojenne były konstruowane tak, aby ich szerokość była dostosowana do sumarycznej szerokości zadów dwu koni bojowych.

A teraz małe rozwinięcie tematu…

Kiedy spoglądacie na kosmiczny wahadłowiec na stanowisku startowym, widzicie dwie duże rakiety startowe przymocowane po bokach głównego zbiornika paliwa. Są to rakiety na paliwo stałe, w skrócie: SRB, produkowane w firmie Thiokol, w Utah, w U.S.A. Inżynierowie, którzy projektowali SRB woleliby, aby były one nieco grubsze, ale… SRB transportuje się z wytwórni na stanowisko startowe koleją. Tak się złożyło, że linia kolejowa biegnie na pewnym odcinku przez tunel wydrążony w górze. Rakiety muszą się zmieścić w tym tunelu. Tunel jest tylko odrobinę szerszy niż linia kolejowa, a linia ta, jak już wiecie, ma szerokość dwu końskich tyłków.

Tak więc jeden z głównych szczegółów konstrukcyjnych kosmicznego wahadłowca, być może najbardziej nowoczesnego systemu transportowego na świecie, został zdeterminowany ponad dwa tysiące lat temu przez szerokość końskiej dupy.