Podnieś swoje umiejętności inżynierii wstecznej
Richard Feynman, jeden z najwybitniejszych naukowców i fizyków naszych czasów, powiedział kiedyś słynne zdanie: „Czego nie potrafię odtworzyć, tego nie rozumiem”. Miał na myśli to, że zaczynając od czystej kartki papieru i mając jedynie wiedzę, którą już nosi w głowie, potrafi wyprowadzić od nowa dowolny wynik teoretyczny. Właśnie tę zdolność Feynman uważał za prawdziwą oznakę zrozumienia.
Sam mocno wierzę w konieczność znajomości podstaw. Solidne opanowanie istniejących rozwiązań typowych problemów jest absolutnie niezbędne, żeby wymyślać nowe. Trzeba wiedzieć, jak rozwiązać każdy problem, który został już rozwiązany w dziedzinie, w której pracujesz.
Jest jednak pewien kłopot — skąd wziąć taką wiedzę? W dzisiejszym rozpędzonym świecie autorzy technologii praktycznie nie mają czasu na pisanie tekstów odsłaniających fundamenty. Co więc robić? Ja opowiadam się za inżynierią wsteczną.
Jestem znany z analizowania wstecznie Angulara. Ale Angular nie jest jedynym frameworkiem, któremu przyjrzałem się dogłębnie. Badałem Vue.js, Webpack, jQuery i mnóstwo innych bibliotek oraz frameworków webowych. Teraz przechodzę przez Reacta. I sądzę, że zebrałem dość obserwacji, by się nimi podzielić i pomóc Ci zacząć z inżynierią wsteczną.
Dla mnie proces inżynierii wstecznej to magia odkrywania czegoś nowego. To stan ekscytacji nowymi znaleziskami i myślenie jak haker: nieustanna ciekawość. Mam nadzieję, że ten przewodnik pomoże Twojemu umysłowi wejść w ten sam stan.
Swoje obserwacje podzieliłem na dwa artykuły. Ten opisuje wskazówki i zasady, którymi kieruję się przy inżynierii wstecznej. Drugi pokazuje praktyczne zastosowanie tych zasad na rzeczywistym procesie analizy niewielkiej części Reacta. Demonstruje też kilka ciekawych technik debugowania, które przyspieszają pracę.
Ale najpierw zacznijmy od tego, dlaczego w ogóle warto zajmować się inżynierią wsteczną.
DLACZEGO
Spójrzmy prawdzie w oczy: inżynieria wsteczna to ciężka praca. Pochłania czas i zwykle wymaga solidnej bazy wiedzy. Po co więc się w to angażować?
Większość ludzi uważa, że głównym celem inżynierii wstecznej jest pogłębienie wiedzy o technologii, żeby znaleźć lepszą pracę. A ponieważ czas życia nowoczesnych technologii jest dość krótki, inwestowanie czasu w zbyt głębokie nurkowanie wydaje się bez sensu. Przechodząc przez kod źródłowy, niemal na pewno zyskasz doskonałe zrozumienie technologii. Ale to tylko jedna z wielu korzyści.
W miarę czytania źródeł poznasz nowe wzorce projektowe rozwiązujące typowe problemy, które potem będziesz mógł wykorzystać w pracy. Wielokrotnie przekonałem się o tym na własnej skórze. Na przykład analizując Angular Router, nauczyłem się leniwie ładować komponenty i moduły — a to pomogło mi zbudować platformę opartą na wtyczkach.
Poznasz wiele nowych możliwości języka. Gdy analizowałem Angulara, dowiedziałem się o monomorfizmie w JavaScripcie. Poznasz API platformy bazowej, bo większość technologii intensywnie z niego korzysta. Znajdziesz nowe rzeczy do nauki i zobaczysz ich praktyczne zastosowanie, zamiast tylko czytać o nich w książce i zgadywać, gdzie mogłyby się przydać.
A jeśli zdecydujesz się podzielić swoimi znaleziskami ze społecznością, pomoże Ci to zbudować publiczny profil. To sytuacja korzystna dla obu stron: pomagając innym, pomagasz sobie. Moja historia sukcesu zaczęła się od Angular-In-Depth (AiD), gdy postanowiłem pisać o swoich odkryciach. Od tego czasu AiD urósł do największej publikacji o Angularze i znacząco pomógł mi zacząć występować na konferencjach oraz znaleźć świetną pracę w ag-Grid. Poświęcając czas na dogłębne poznanie technologii, pokazujesz przy okazji umiejętność rozwiązywania problemów, determinację i ciekawość. To właśnie tych cech innowacyjne firmy szukają u kandydatów.
Nabierzesz też swobody w czytaniu istniejącego kodu i poznawaniu nowych baz kodu. Robert C. Martin, powszechnie znany jako Wujek Bob, szacuje stosunek czasu spędzonego na czytaniu do czasu pisania na znacznie ponad 10 do 1. Nieustannie czytamy stary kod w ramach pisania nowego. Od tego zaczynają wszyscy programiści, gdy dołączają do istniejącego projektu. A praktykując inżynierię wsteczną, zyskasz tu przewagę.
Jak widzisz, inżynieria wsteczna uczyni Cię lepszym inżynierem.
Wiedza, która ułatwia inżynierię wsteczną
Przyjrzyjmy się najpierw temu, co trzeba wiedzieć, żeby proces inżynierii wstecznej szedł łatwiej i szybciej. Poświęć czas na naukę i opanowanie tych rzeczy. Jest tego sporo i najprawdopodobniej teraz tej wiedzy nie masz. Nie szkodzi. Wygospodaruj godzinę lub dwie dziennie na świadomą naukę i postaw sobie za cel zostanie ekspertem w tych obszarach. Dojdziesz tam.
Solidna znajomość platformy bazowej
Pierwszym wymogiem udanej inżynierii wstecznej jest znajomość API platformy bazowej. Jeśli mówimy o frameworkach i bibliotekach działających w sieci, oto rzeczy, które trzeba dobrze opanować: JavaScript, DOM API i API przeglądarki.
Solidne rozumienie JavaScriptu nie oznacza, że wiesz, czym jest domknięcie albo jak rozwiązywane jest this. Solidne rozumienie oznacza znajomość większości zaawansowanych rzeczy: deskryptorów właściwości (używa ich Vue.js), obiektów Proxy czy masek bitowych (używa ich Angular).
Jeśli chodzi o DOM i API przeglądarki, nie wystarczy umieć utworzyć i dodać węzeł DOM albo wykonać callback asynchronicznie. Trzeba wiedzieć, co się stanie, gdy ponownie dodasz istniejący węzeł potomny, albo jak przeglądarki radzą sobie z nieznanymi elementami. Poznaj istniejące API do wykonywania żądań HTTP i ich niuanse — na przykład kiedy dokładnie wywoływany jest callback błędu w XHR.
Żeby opanować JavaScript, polecam książki Axela Rauschmayera. Żeby opanować DOM i API przeglądarki — MDN web docs oraz Google developers web updates. A jeśli nauczysz się czytać specyfikacje, to oczywiście źródło idealne: EcmaScript dla JavaScriptu i WHATWG dla środowiska przeglądarki.
Narzędzia do debugowania
Znajomość narzędzi deweloperskich ulubionej przeglądarki na wylot jest niezbędna. Moją ulubioną przeglądarką jest Chrome. Pracując w Chrome, powinieneś wiedzieć:
- co oznacza
$0, gdy wpiszesz to w konsoli - jak pracować z warunkowymi punktami wstrzymania
- jak zatrzymać się przed wyjątkiem
- jak pominąć fragment kodu albo wyjść z bieżącej funkcji
- jak znaleźć konkretny tekst w załadowanych źródłach itd.
Najlepszym źródłem wiedzy o Chrome Dev Tools jest oczywiście Google’s Tools for web developers.
Popularne wzorce projektowe i ogólne koncepcje architektoniczne
Technologie czasem korzystają z popularnych wzorców projektowych, więc warto je znać. Na przykład Webpack opiera się na wzorcach asynchronicznego wykonywania JS zaimplementowanych w bibliotece async. Wszystkie frameworki i biblioteki sprzed ES modules używały do dystrybucji formatu pakowania UMD. Im więcej frameworków i bibliotek poznasz, tym lepiej zaczniesz rozpoznawać wspólne wzorce — i tym szybciej będziesz poruszać się po kodzie.
Koncepcje właściwe danej technologii
Pomaga też znajomość koncepcji, którymi posługuje się framework lub biblioteka. Na przykład zanim weźmiesz się za analizę nowoczesnego frameworka, powinieneś wiedzieć, czym jest komponent. Czasem te koncepcje wyłapiesz z dokumentacji, czasem z pogłębionego artykułu albo dokumentu projektowego. Przeczytaj wszystko, co znajdziesz, zanim zaczniesz przeglądać kod. Czytaj o nowych koncepcjach w miarę, jak odkrywasz je w kodzie. Radzę wybierać materiały z konkretnymi szczegółami implementacyjnymi, a nie mocno uproszczone wersje dla szerokiej publiczności. Prelekcje konferencyjne zwykle nie są najlepszym sposobem na poznanie konkretów. Dokumenty projektowe na Githubie i zaawansowane artykuły są znacznie bardziej pomocne.
Wskazówki do eksplorowania źródeł
Przeczytaj kolejny artykuł, żeby zobaczyć, jak zastosowałem te wskazówki przy przechodzeniu przez źródła Reacta. Radzę notować okruchy wiedzy, które odkrywasz po drodze. Później będziesz w stanie poskładać to wszystko i zobaczyć szerszy obraz.
Wskaż tę część technologii, na której się skupisz
Najczęstsze pytanie, jakie dostaję, brzmi: od czego zacząć, gdzie postawić debugger w bazie kodu. Odpowiedź wynika z Twojego celu. Zanim zaczniesz inżynierię wsteczną, zawsze powinieneś mieć pojęcie, którą część technologii chcesz zrozumieć. Na przykład analizując Angulara czy Reacta, najpierw chciałem zrozumieć wykrywanie zmian. To była ta część frameworka, na której musiałem się skupić. Znając nowoczesny proces wykrywania zmian, wiedziałem, że polega on na synchronizowaniu zmian z instancji komponentu do węzłów DOM. Musiałem więc znaleźć miejsce, w którym te frameworki przechowują referencje do utworzonych węzłów DOM. To był cel.
Myśl jak naukowiec
Uważam, że metoda naukowa — obserwacja, formułowanie hipotez (założeń) na podstawie obserwacji i ich eksperymentalne sprawdzanie — jest najskuteczniejszym sposobem zdobywania wiedzy. To także model, którym posługuję się przy inżynierii wstecznej. Oto podstawowe kroki:
- Poczyń obserwację i sformułuj hipotezę.
- Postaw prognozę opartą na hipotezie.
- Sprawdź prognozę.
Gdy masz już wyniki, wykorzystaj je do postawienia nowych hipotez i prognoz. Iteruj, aż rozpracujesz część, na której się skupiłeś.
Do formułowania hipotezy i stawiania prognozy używaj wnioskowania. Wnioskowanie to wykorzystanie obserwacji i wiedzy tła do wyciągnięcia logicznego wniosku. Na przykład jeśli widzisz, jak ktoś je nowe danie i krzywi się, wnioskujesz, że mu nie smakuje. Albo jeśli ktoś trzaska drzwiami, możesz wywnioskować, że jest czymś zdenerwowany.
Poza tym, że daje to uporządkowane podejście do inżynierii wstecznej, uważam, że założenia (hipotezy) i ich weryfikacja tworzą w pamięci punkty zaczepienia, które pomagają dłużej utrzymać to, czego się nauczyłeś, i wydobyć to w potrzebie.
Przełączaj się między debugowaniem, badaniem implementacji w źródłach i czytaniem komentarzy
Inżynieria wsteczna to nie tylko czytanie źródeł. W rzeczywistości poświęcam jedynie około 20% czasu na sprawdzanie konkretnego szczegółu implementacyjnego w kodzie. Około 70% czasu spędzam na debugowaniu przykładowej aplikacji. Dlatego właśnie uważam, że dobre opanowanie narzędzi do debugowania jest nieodzowne, by proces inżynierii wstecznej był efektywny. Pozostałe 10% czasu poświęcam na czytanie komentarzy w kodzie źródłowym albo wyjaśnień koncepcji odkrytych w kodzie. Zwykle komentarze są znacznie bardziej pomocne niż cokolwiek, co znajdziesz w sieci, więc nigdy nie lekceważ ich jako nieistotnych.
Buduj przepływ aplikacji na podstawie stosu wywołań
Idąc naprzód z debugowaniem i rozstawiając punkty wstrzymania w różnych miejscach kodu, regularnie zaglądaj do stosu wywołań. Kolejność wywołań funkcji da Ci pojęcie o przepływie aplikacji. Często pomoże Ci to również zlokalizować w źródłach funkcje odpowiadające za interesującą Cię funkcjonalność.
Nie zniechęcaj się, gdy hipoteza okaże się błędna
Przygotuj się, że większość Twoich założeń okaże się nietrafiona. To normalny i oczekiwany proces. Czasem oznacza to, że warto poświęcić więcej czasu na budowanie wiedzy tła. Ale często znaczy jedynie tyle, że wzorce zaimplementowane we frameworku czy bibliotece są nowatorskie.
Nie znaczy to, że się nie sfrustrujesz. Sfrustrujesz. Ale miej cel przed oczami, przezwyciężaj te frustracje i idź dalej. Myląc się, właśnie nauczyłeś się czegoś nowego.
Daj sobie trochę czasu na przemyślenie tego, co znalazłeś
W książce „Głowa do liczb” Barbara Oakley opisuje dwa naprzemienne stany umysłu — tryb skupiony i tryb rozproszony. Oba są niezbędne przy uczeniu się czegoś nowego. Tryb skupiony to bezpośrednie rozwiązywanie problemów przy użyciu racjonalnego, sekwencyjnego i analitycznego podejścia. Tryb rozproszony pozwala nagle spojrzeć na nowo na problem, z którym się zmagaliśmy, i wiąże się z perspektywą „szerokiego obrazu”. Tryb rozproszony pojawia się, gdy rozluźniasz uwagę i po prostu pozwalasz myślom błądzić. Nie siedź więc godzinami przed komputerem — regularnie rób krótkie przerwy i przemyśl to, co odkryłeś. Ja po prostu chodzę po mieszkaniu. I robię tak przy każdej pracy twórczej, na przykład przy pisaniu, nie tylko przy inżynierii wstecznej.
Zacznij od zdobycia źródeł i przygotowania przykładowej aplikacji
Żeby przeanalizować framework lub bibliotekę, będziesz potrzebować jej źródeł oraz przykładowej aplikacji z tą technologią. Obecnie większość frameworków i bibliotek jest hostowana na Githubie, więc po prostu sklonuj repozytorium. Zawsze chcesz badać konkretną wersję, więc wejdź w zakładkę „releases” w repozytorium i sprawdź najnowsze wydanie. Po sklonowaniu repozytorium przełącz się na to wydanie, uruchamiając git checkout tags/[version].
Drugim krokiem jest przygotowanie przykładowej aplikacji. Zawsze staraj się złożyć możliwie najprostszą konfigurację. Unikaj bundlerów w rodzaju webpacka i narzędzi CLI dostarczanych przez nowoczesne frameworki. Z mojego doświadczenia znacząco komplikują one debugowanie. Idealna konfiguracja to zwykła strona HTML z kodem ładowanym z CDN, na przykład z unpkg.com. Upewnij się tylko, że wersja frameworka lub biblioteki odpowiada wersji wybranej w źródłach.
Słowo o szczęściu
Jak we wszystkim w życiu, szczęście odgrywa swoją rolę. Przechodząc raz za razem przez ten sam fragment funkcjonalności, możesz natknąć się na nowy komentarz, który rozjaśni jakąś koncepcję, albo na wywołanie funkcji o wymownej nazwie, którego wcześniej nie zauważyłeś. Mnie zdarza się to często. Radzę więc przejść debuggerem wielokrotnie przez ten kawałek kodu, którego nie rozumiesz. Albo wrócić do niego później. Nie odpuszczaj go całkiem tylko dlatego, że teraz go nie rozumiesz. Jest spora szansa, że następnym razem znajdziesz tam coś nowego — bo w międzyczasie zdobyłeś nową wiedzę.