YAGNI a „dobry”, SOLIDny, elastyczny kod.

Czemu o tym piszę? Ostatnio czytając internety i sięgając pamięcią do moich doświadczeń doszedłem do wniosku, że czasem ktoś usprawiedliwia przez YAGNI pisanie mniej elastycznego kodu.
Nawet, jeśli sam rozumie dlaczego pisanie takiego kodu jest dobre. Tak więc Panie i Panowie, moja pierwsza techniczna notka:


Najpierw:

Co to jest Yagni? Jeśli nie wiesz, a Ci się nie chce pytać google, o to krótkie podsumowanie:
Chodzi o to, żebyś nie tracił czasu na robienie rzeczy których nie potrzebujesz i pewnie nie będziesz w przyszłości, ale teraz wydaje Ci się że będziesz potrzebować. Jak będziesz potrzebować, dodasz je wtedy, duh! Jak piszesz dobry kod, SOLIDny (czyli zgodny z zasadami SOLID) to nie będzie to problematyczne. Do tego kłania się tutaj też zdrowy rozsądek: nawet jeśli w przyszłości będziesz danej funkcjonalności potrzebować, nie wiesz dziś jaka dokładnie ona będzie, więc możliwe że będziesz musiał coś zmienić i tak, więc tylko stracisz podwójnie czas – teraz i później.

A co to jest ten cały SOLID?
To temat na całą serię, nawet nie jeden wpis (i możliwe, że takowe pojawią się w przyszłości). W bardzo dużym skrócie, są to zasady programowania obiektowego, zebrane przez Wujka Boba jako ładnie brzmiący mnemonik. Także po polsku ładnie się go wypowiada i odmienia. Przypadek ;-)?

Stosowanie SOLID ułatwia tworzenie kodu który jest elastyczny, który można zmieniać wg potrzeb i który jest łatwy do utrzymania. Zasady mówią między innymi o tym, by:
– klasy i metody miały tylko jeden powód do zmiany;
– można je było łatwo rozszerzać i zmieniać, nie psując nic po drodze;
– były ze sobą luźno powiązane;
Uzyskuje się to między innymi przez podział metod i klas na mniejsze oraz wydzielanie klas i delegowanie do nich wykonywania operacji. Używanie abstrakcji (czy to przez klasy bazowe, czy interfejsy) oraz wstrzykiwanie zależności (ang. dependency injection), na przykład jako parametr w konstruktorze również jest tutaj nie tylko pomocne, ale i zawiera się w tych zasadach.


Co pobudziło mnie do tych przemyśleń:

Stosowanie YAGNI może być odbierane przez niektóre osoby, jako pewna „wymówka” do pisania „gorszego kodu” (kwestia dyskusji co jest, i kiedy, gorsze), bo „wydzielenie tego do osobnej klasy/metody nie będzie przecież użyte w innym miejscu naszego projektu, więc nie ma sensu… i ten, bo YAGNI – nie potrzebuję tego”. Ale czy na pewno nie potrzebujesz? Nie dotyczy to małych projektów na zaliczenie czy helper tooli, ale projektów komercyjnych od normalnych średnich do wielgachnych kobył (złooooo 😉 ).
Myślenie typu „nie potrzebuję wydzielać tutaj klasy/metody, wolę to inline”, „nie reużyje tego nigdzie, po ci mi to wydzielać” itd, może skutkować tym, że mamy za długą i nie czytelną metodę. OK, Mamy wszystko w jednym miejscu… ale czy wydzielenie tego do osobnej klasy/metody, nawet mając w głowie pewność, że na dzień dzisiejszy nie użyjemy tego w innym miejscu, nie da nam czegoś więcej?


Co ja o tym myślę:

YAGNI nie usprawiedliwia pisania „gorszego kodu”. W YAGNI chodzi przede wszystkim o oszczędność czasu.

Argument numer jeden:
Nie zmarnujemy sobie czasu ułatwiając komuś (i nam samym) przeczytanie kodu. Kod jest częściej czytany niż pisany, więc chociażby dlatego warto zrobić go bardziej czytelnym.

We wszystkim oczywiście trzeba zachować równowagę. Czasem wydzielenie czegoś do osobnej klasy tylko zaciemnia kod, jeśli to jedna linia. Gdzie leży granica? To zależy. Od naszego doświadczenia, standardu projektu – całość powinna być spójna.

To jest bardzo ważny i jak dla mnie już wystarczający argument. Ale idźmy dalej.

Argument numer dwa:
Oszczędzimy też sobie sporo czasu później, jeśli będziemy wprowadzać poprawki, mając kod ładnie podzielony na klasy i metody.
Zwłaszcza, że często wydzielenie to kwestia kilkunastu sekund, więc nie ma mowy o marnowaniu czasu na to.

Testy – trzeba więcej napisać? Bzdura. Tak czy siak trzeba by było je napisać, teraz mamy dodatkowy wybór, czy każdą wydzieloną klasę testować osobno, czy testować całość integracyjnie, traktując wydzielone klasy jako szczegół implementacyjny. Jeśli wydzielone klasy mają dużo logiki to tym łatwiej i lepiej dla nas, będzie testować je osobno.

To jest w sumie bardziej argument ogólnie na to, żeby pisać SOLIDny kod, ale to notka o połączeniu tych dwóch zagadnień.


Jak moim zdaniem należy podejść do tej kwestii?

Trzeba pisać dobry kod, podzielony, SOLIDny. Ale nie przesadzać. Banalne przykłady z życia:

Tworzę nowy drobny feature, wszystko (poza bibliotekami, których używam oczywiście) implementuje sam:
Gdy tworzę klasę „DoSomeMagicWork” i częścią jej funkcjonalności będzie czytanie z pliku CSV, i nie wiem (z wymagań) że będę czytać z innych plików w innych miejscach projektu, to czytanie wydzielę do innej klasy (zakładając że już w projekcie takiej nie ma, gotowej do użycia). Nie będę próbować robić jej uniwersalnej, zrobię ją tak, żeby wygodnie z niej skorzystać, prawdopodobnie będę potrzebował jednej – dwóch publicznych metod. Tyle. Nie będę się zastanawiał i implementował czegoś, co ktoś później może chcieć użyć, gdzieś indziej. Pewnie też ograniczę miejsce z którego czytany jest plik i nie będę „na zaś” tworzyć całej struktury (np klasy abstrakcyjnej, interfejsów, np przez template method patern) które pozwolą na lepsze sparametryzowanie np miejsca odczytu czy nazwy pliku. Bo nie wiem czy komukolwiek się to przyda. A na razie wystarczy.

Co prowadzi do przykładu numer dwa, co potem:
Tworzę nowy feature, korzystam z rozwiązania kogoś innego kto podszedł do tematu jak ja wcześniej:
Mamy klasę „DoSomeStuff”, częścią funkcjonalności jest specyficzny zapis do pliku (miejsce, nazwa, typ danych). Wiem już, że będę chciał go mieć w osobnej klasie, nazwijmy ją MyFileSaver. Dopytałem, okazało się, że mamy już taką w projekcie. Nic tylko użyć! Ale jak mówi słynny youtuber „Nic bardziej mylnego!”. Okazuje się bowiem, że klasa ma zahardcodowane pewne parametry, np co i gdzie zapisuje.
Czy to znaczy, że myślę, że gość który ją pisał jest głupim $%^$%#@$? Nie. Mam testy. Zrobię refactoring – przeniosę ją jeśli trzeba do odpowiedniego miejsca w projekcie, zmienię nazwę, sprametryzuję, dodam inne klasy jeśli trzeba. Upewnię się, że nie zepsułem jego funkcjonalności (bo mam testy!), i jednocześnie przygotuję sobie tą klasę do takiego użycia, jakie mnie jest potrzebne.
Człowiek który to pisał, nie mógł przewidzieć czego ja będę potrzebować. Ba pewnie też nie sądził, że pojawi się za dwa lata kolejny feature mający zapisywać plik. I nie wiedział czy będę chciał zapisywać w tym samym miejscu, czy tylko inaczej nazwać plik. Nie inline’ował też zapisu, ale nie stracił czasu na zrobienie uniwersalnego frameworku do zapisywania plików – bo taki już jest out of the box w .Net. Ładnie wydzielił odpowiedzialność. Wydzielanie odpowiedzialności ma sens.


Podsumowując:
Zawsze trzeba zachować równowagę i kierować się rozsądkiem, a także doświadczeniem – to niestety przychodzi wolno :), ale nie ma usprawiedliwienia na pisanie kodu, który nie jest SOLIDny. Nawet jak jest banalnie prosty i łątwo go potem zmienić. Nie ma sensu przesadzanie i tworzenie super uniwersalnego rozwiązania, na to czas będzie później. Jeśli kod od razu będzie dobrze napisany – nawet jak jest banalnie prosty – łatwo go później zmienić. Tylko trzeba znaleźć granicę, bo nie ma kodu którego nie można napisać lepiej. To jest najtrudniejsze.

A co Ty o tym myślisz? Jakie jest Twoje podejście i co Twoje doświadczenie o tym mówi?

4 przemyślenia nt. „YAGNI a „dobry”, SOLIDny, elastyczny kod.

  1. Teksty typu „tego już nie będę wyjaśniał, tutaj odsyłam do google” pachną elektrodą w najgorszym tego słowa znaczeniu, nie pomoże nawet milion głupich emotek. Chcesz by tekst brzmiał profesjonalnie, chcesz sam uchodzić za profesjonalistę to linkuj. Tym bardziej do rzeczy jasno związanych z artykułem. Dla spójności podaj definicje, to nic cię nie kosztuje. To nie tak, że, jak napisałeś, nie chce się używać Google, zwyczajnie wymagasz od czytelników oderwania się od tego co napisałeś, przejścia gdzie indziej. W konsekwencji gubisz czytelnika, na samym początku artykułu. A tego nie chcesz 🙂

    • Dzięki, cenna uwaga! Za chwilę zmodyfikuję nieco ten paragraf. Zmodyfikowałem nieco ten paragraf. Mam nadzieję, że teraz będzie czytelniej i przyjaźniej dla czytającego.

  2. Przez cały czas odkąd usłyszałem o YAGNI to rysuje mi się ono jako a) nie rozszerzaj dokumentacji o własne przemyślenia, b) KISS. Ale nigdy nigdzie nie słyszałem by YAGNI było powodem do ignorowania jakości kodu. Ktoś to chyba pomylił gdzieś, ale do tej pory nie widziałem nikogo takiego 🙂

    • Fajne stwierdzenie „nie rozszerzaj dokumentacji o własne przemyślenia”. Muszę je zapamiętać 🙂
      A samo KISS ma dla mnie znaczenie nieco inne, związane z czytelnością i „jasnością” kodu (ilość WTF na minutę 😉 ).

Dodaj komentarz