
Od początku 2018 roku pełnię funkcję lidera / kierownika / głównego programisty w zespole — jak to nazwiesz, nie ma znaczenia, ale w istocie odpowiadam w pełni za jeden z modułów oraz za wszystkich programistów, którzy nad nim pracują. Ta pozycja otwiera przede mną nowy sposób postrzegania procesu tworzenia, ponieważ jestem zaangażowany w większą liczbę projektów i aktywniej uczestniczę w podejmowaniu decyzji. Niedawno, dzięki tym dwóm okolicznościom, nagle zdałem sobie sprawę, jak mocno zrozumienie wpływa na kod i na aplikację.
Myśl, którą chcę wyrazić, sprowadza się do tego, że jakość kodu (i ostatecznego produktu) jest ściśle związana z tym, jak bardzo ludzie, którzy zajmują się projektowaniem i piszą kod, są świadomi tego, co tak naprawdę robią.
Możecie teraz pomyśleć: „Dzięki, kapitanie. Oczywiście dobrze byłoby wiedzieć, co się pisze. W przeciwnym razie równie dobrze można zatrudnić grupę małp, aby pisały na przypadkowych klawiszach, i na tym poprzestać”. I macie całkowitą rację. Dlatego przyjmuję jako oczywiste, że zdajecie sobie sprawę, iż ogólne zrozumienie tego, co robicie, jest konieczne. Można to nazwać zerowym poziomem zrozumienia, a tym się nie będziemy szczegółowo zajmować. Szczegółowo omówimy to, co dokładnie trzeba zrozumieć i jak wpływa to na decyzje, które podejmujecie każdego dnia. Gdybym znał te rzeczy wcześniej, zaoszczędziłoby mi to mnóstwo zmarnowanego czasu i wątpliwego kodu.
Chociaż poniżej nie zobaczycie ani jednej linijki kodu, i tak uważam, że wszystko, co tutaj powiedziano, ma ogromne znaczenie dla pisania wysokiej jakości, wyrazistego kodu.
Pierwszy poziom zrozumienia: Dlaczego to nie działa?
Na ten poziom programiści zazwyczaj dochodzą na bardzo wczesnych etapach swojej kariery, czasami nawet bez jakiejkolwiek pomocy ze strony otoczenia — przynajmniej według moich obserwacji. Wyobraźcie sobie, że otrzymaliście zgłoszenie błędu: jakaś funkcja w aplikacji nie działa, trzeba ją naprawić. Jakie działania podejmiecie?
Standardowy schemat wygląda tak:
- Znaleźć fragment kodu, który wywołuje problem (jak to zrobić — to osobny temat, który omawiam w mojej książce o przestarzałym kodzie)
- Wprowadzić zmiany w tym fragmencie
- Upewnić się, że błąd został naprawiony, a regresja nie wystąpiła
Skupmy się teraz na drugim punkcie — wprowadzaniu zmian w kodzie. Istnieją dwa podejścia do tego procesu. Pierwsze: zrozumieć, co dokładnie dzieje się w obecnym kodzie, zidentyfikować błąd i go naprawić. Drugie: działać na czuja — dodać, powiedzmy, +1 w warunku lub pętli, sprawdzić, czy ta funkcja zaczęła działać w odpowiednim scenariuszu, a potem spróbować czegoś jeszcze, i tak w nieskończoność.
Prawidłowe jest pierwsze podejście. Jak wyjaśnia w swojej książce Code Complete Steve McConnell (zresztą bardzo ją polecam), za każdym razem, gdy wprowadzamy zmiany w kodzie, musimy być w stanie z pełnym przekonaniem przewidzieć, jak wpłynie to na aplikację. Przytaczam cytat z pamięci, ale jeżeli naprawa błędu działa inaczej niż oczekiwałeś, powinno to wzbudzić twoje wątpliwości, a cały plan działania powinien zostać poddany rewizji.
Podsumowując, aby wykonać solidną naprawę błędu, która nie pogorszy jakości kodu, należy rozumieć zarówno całą strukturę kodu, jak i źródło konkretnego problemu.
Drugi poziom zrozumienia: Dlaczego to działa?
Ten poziom jest osiągany znacznie mniej intuicyjnie niż poprzedni. Ja, będąc jeszcze początkującym programistą, przyswoiłem go dzięki mojemu szefowi, a później wielokrotnie sam wyjaśniałem tę kwestię nowicjuszom.
Tym razem wyobraźmy sobie, że otrzymujesz od razu dwa raporty błędów: pierwszy dotyczy scenariusza A, drugi — scenariusza B. W obu scenariuszach dzieje się coś nie tak. W związku z tym najpierw zajmujesz się pierwszym błędem. Kierując się zasadami, które ustaliliśmy dla pierwszego poziomu zrozumienia, dokładnie analizujesz kod związany z problemem, ustalasz, dlaczego powoduje on takie a nie inne zachowanie aplikacji w scenariuszu A, i wprowadzasz sensowne korekty, które dają dokładnie ten rezultat, jakiego się spodziewałeś. Wszystko idzie świetnie.
Następnie przechodzisz do scenariusza B. Powtarzasz scenariusz w próbie sprowokowania błędu, ale — niespodzianka! — teraz wszystko działa jak należy. Aby potwierdzić swoje przypuszczenie, cofasz zmiany wprowadzone w trakcie pracy nad błędem A, a błąd B wraca. Twoja naprawa rozwiązała oba problemy. Szczęście!
Nie spodziewałeś się tego. Wymyśliłeś sposób na naprawienie błędu w scenariuszu A i nie masz pojęcia, dlaczego zadziałał również w scenariuszu B. Na tym etapie może być wielka pokusa, by uznać, że oba zadania zostały pomyślnie zakończone. To całkiem logiczne: chodziło przecież o naprawienie błędów, prawda? Ale praca jeszcze się nie skończyła: musisz jeszcze zrozumieć, dlaczego twoje działania naprawiły błąd w scenariuszu B. Po co? Bo może on działa na niewłaściwych zasadach, a wtedy będziesz musiał szukać innego rozwiązania. Oto kilka przykładów takich sytuacji:
- ponieważ rozwiązanie nie było dopasowane celowo pod błąd B z uwzględnieniem wszystkich czynników, możesz, nieświadomie, uszkodzić funkcję C.
- istnieje prawdopodobieństwo, że gdzieś czai się również trzeci błąd związany z tą samą funkcją, a twój fix błędów uzależnia poprawne działanie systemu w scenariuszu B od niego. Teraz wszystko wygląda dobrze, ale pewnego pięknego dnia ten trzeci błąd zostanie zauważony i naprawiony. Wtedy w scenariuszu B znów pojawi się błąd, i dobrze, jeśli tylko tam.
Wszystko to wprowadza chaotyczność w kodzie i kiedyś spadnie ci na głowę — najprawdopodobniej w najmniej odpowiednim momencie. Będzie trzeba zebrać siły, aby zmusić się do poświęcenia czasu na zrozumienie, dlaczego wszystko wydaje się działać, ale warto.
Trzeci poziom zrozumienia: Dlaczego to działa?
Moje ostatnie oświecenie dotyczy właśnie tego poziomu i prawdopodobnie to on przyniósłby mi najwięcej korzyści, gdybym wpadł na ten pomysł wcześniej.
Aby to lepiej zobrazować, rozważmy to na przykładzie: twój moduł musi być kompatybilny z funkcją X. Nie znasz się zbyt dobrze na funkcji X, ale powiedziano ci, że aby zapewnić z nią kompatybilność, musisz używać frameworka F. Inne moduły, które integrują się z X, działają dokładnie z nim.
Twój kod od pierwszego dnia swojej egzystencji w ogóle nie miał styczności z frameworkiem F, więc jego wdrożenie nie będzie proste. To pociągnie za sobą poważne konsekwencje dla niektórych elementów modułu. Niemniej jednak, całkowicie zanurzasz się w rozwój: tygodniami piszesz kod, testujesz, wypuszczasz wersje pilotażowe, otrzymujesz feedback, poprawiasz błędy regresji, napotykasz nieprzewidziane komplikacje, nie mieszcząc się w pierwotnie ustalonych terminach, piszesz jeszcze trochę kodu, testujesz, otrzymujesz opinię zwrotną, poprawiasz błędy regresji — wszystko to po to, aby wdrożyć framework F.
I w pewnym momencie nagle zdajesz sobie sprawę — lub może słyszysz od kogoś — że framework F może w ogóle nie zapewnić Ci kompatybilności z funkcją X. Może wszystkie te czas i wysiłek były skierowane zupełnie w złym kierunku.
Coś podobnego zdarzyło się pewnego razu w trakcie pracy nad projektem, za który byłem odpowiedzialny. Dlaczego tak się stało? Ponieważ źle rozumiałem, na czym polega funkcja X i jak jest związana z frameworkiem F. Co powinienem był zrobić? Poprosić osobę, która stawia zadanie na rozwój, aby klarownie wytłumaczyła, jak planowane działania prowadzą do pożądanego rezultatu, zamiast po prostu powtarzać to, co robiono dla innych modułów, lub wierzyć na słowo, że to jest konieczne dla działania funkcji X.
Doświadczenie tego projektu nauczyło mnie, aby unikać rozpoczynania procesu rozwijania, póki nie mamy jasnego zrozumienia, dlaczego proszą nas o wykonanie określonych działań. Unikać tego wprost. Kiedy otrzymujesz zadanie, pierwszym impulsem jest natychmiastowe zabranie się do pracy, aby nie tracić czasu. Ale polityka „zamrażamy projekt, dopóki nie poznamy wszystkich szczegółów” może znacznie ograniczyć zmarnowany czas.
Nawet jeśli próbują na Ciebie wywierać presję, zmuszać do rozpoczęcia pracy, chociaż nie rozumiesz, z jakiego to powodu — stawiaj opór. Najpierw zrozum, jakie cele stają przed Tobą i zdecyduj, czy to właściwa droga do celu. Musiałem się tego wszystkiego nauczyć na gorzkim doświadczeniu — mam nadzieję, że moim przykładem ułatwię życie tym, którzy to czytają.
Czwarty poziom zrozumienia: ???
Programowanie zawsze ma wiele do zaoferowania, a ja sądzę, że dotknąłem tylko powierzchni tematu zrozumienia. Jakie inne poziomy zrozumienia odkryliście przez lata pracy z kodem? Jakie podejmowaliście decyzje, które pozytywnie wpłynęły na jakość kodu i aplikacji? Jakie decyzje okazały się błędne i przyniosły cenną lekcję? Podzielcie się doświadczeniem w komentarzach.
Źródło: habr.com
