GitHub Copilot – moje wrażenia

O Copilocie pierwszy raz usłyszałem rok temu, na fali popularności ChataGPT. Przejrzałem pobieżnie informacje o nim na oficjalnej stronie, ale nie miałbym zastosowania prywatnie, więc nie chciałem na to wydawać pieniędzy.

Bodajże w czerwcu w pracy pojawiła się możliwość dołączenia do pilotażowych testów Copilota (a później także Google Gemini). Od razu się zgłosiłem, zwłaszcza, że liczba licencji była ograniczona. Ostatecznie okazało się jednak, że oprócz mnie w moim zespole zainteresowanie nie było duże, co mnie mocno zaskoczyło. Do całego programu testowego dołączyło ostatecznie kilkunastu developerów.

Po instalacji dodatku do Visual Studio i Visual Studio Code oraz konfiguracji (która ze względu na trochę słabe przygotowanie do tych testów od strony infrastruktury sprawiała trochę problemów), zacząłem używać Copilota.

Pierwszych kilka godzin nie robiło na mnie wrażenia. Gdy wpisałem w kodzie komentarz, to przeważnie chcąc zrobić jakąś operacje na strukturach danych, dostawałem sugestie wprawdzie poprawne, ale jakby zupełnie oderwane od kontekstu. Dla jakiekolwiek zadania, które wymagałoby sięgnięcia do kontekstu kodu, który przecież Copilot na bieżaco wysyła, nie dostawałem sugestii. Byłem nieco rozczarowany, bo oglądałem fancy prezentacje na stronie, a tu w zasadzie dostawałem podpowiedzi jakby z góry stack overflow, które oszczędzały mi kilka sekund na wbicie tematu do Google. Jeżeli to ma tak wyglądać, to słabo, bo niewiele mi to daje, a wepchałem się w testy, do których trzeba będzie wypełniać ankiety, uczestniczyć w spotkaniach, itd.

Kolejnego dnia, gdzieś po łącznie 4 godzinach pracy z Copilotem, okazało się, że widocznie na początku zbierał więcej danych z pracy ze mną. W pewnym momencie, pracując nad regularnym taskiem, Copilot nagle zaczął zaskakiwać. Zadanie nie było skomplikowane, rutynowe dodanie nowej kolumny do tabeli, a więc odwzorowanie jej w modelach, użycie w kilku klasach, zareagowanie na jej wartość. Dodałem ją więc do pierwszego modelu i w kolejnych już nie musiałem nic pisać, Copilot od razu sugerował gotową linię do potwierdzania tabem. Nie była to jednak sucha sugestia – kilka modeli ze względu na swoją właściwość miało nieco przeredagowane nazwy pól w zależności od pochodzenia. Copilot ogarnął to i dostosował się do konwencji obowiązujących w bieżących plikach. To wywołało już moje zainteresowanie, bo odbiegało od tego, co znałem dotychczas z sugestii np. IntellisSense.

Dalej było już coraz ciekawiej. Od dawna pracuję w ten sposób, że po analizie tematu dodaję sobie w kodzie komentarze z atomicznymi rzeczami do wykonania, opatrzonymi adnotacją todo. Potem już przeważnie tylko oprogramowuję te komentarze. Copilot bardzo taki przebieg pracy lubi, bo od razu wie, co będzie potrzebne. Po dodaniu tego typu komentarzy, przechodząc pod każdy z nich przeważnie miałem gotową sugestię. I były to sugestie zarówno do wtórnych, powtarzalnych operacji na strukturach danych (np. operacje na listach), jak i zadań, które wymagają już posiadania wiedzy o kontekście (np. wywołanie istniejącego CQRS query z odpowiednimi parametrami i obsłużenie zwrotki).

Z czasem zrobiło się sympatycznie – gdy analizowałem kolejne tematy i dodawałem komentarze do oprogramowania w kodzie, dostawałem sugestie… treści komentarzy po polsku pisanych moim stylem językowym. I nie było to wtórne użycie czegoś, co już wcześniej napisałem – były to nowe słowa, nieużywane wcześniej w kodzie tych projektów, odpowiednio odmienione, w szyku zdania specyficznym dla mnie. To był początek efektu wow, jaki zacząłem odczuwać.

Następnie przeszedłem do rozwijania frontendu. Tu było już grubo. Pisząc w TS z użyciem Angulara+NgRx+RxJS dostawałem sugestię całych, gotowych efektów NgRx, w których nie musiałem poprawić ani jednego znaku. Tak – efekt dla konkretnej akcji, odpowiednio przefiltrowany, dispatchujący inne akcje i wykonujący dodatkową logikę. To wszystko na podstawie kodu zawartego w kontekście i kilku komentarzy. W tym momencie zakochałem się w Copilocie i już wiedziałem, że w najbliższym czasie będę fanatykiem tego narzędzia.

Praca z Copilotem to coś świetnego. Wspomaganie rozwoju oprogramowania na poziomie, którego dotąd nie znałem w kilkunastoletniej karierze zawodowej i ponad 20 latach programowania ogółem. Pomaga na wielu różnych polach. Chcę coś zrobić z kolekcją, czego nie wykonuje się zbyt często? Do tej pory po prostu wbijałem temat w Google, żeby sobie go odświeżyć, trafiałem na stackoverflow, odwzorowywałem odpowiedź w kodzie. Teraz nie muszę, dostaję gotową sugestię. Chcę zrobić coś więcej na danych, czego nigdy wcześniej nie robiłem? Piszę szczegółowy komentarz i dostaję sugestię. Chcę zrefaktoryzować kod? Zmieniam w kilku miejscach i dostaję kilkanaście gotowych sugestii dalszych zmian. Chcę wyciągnąć coś z innego miejsca kodu projektu? Dostaję podpowiedź. Nawet, gdy nie oszczędzały one jakoś znacząco czasu, to ograniczały konieczność ręcznego wklepywania nużących operacji. A to już wpływa widocznie na motywację i efektywność pracy.

Początkowo nie korzystałem praktycznie wcale z chata Copilota. Ot, wpisałem kilka pytań, promptów, ale nie wciągnęło mnie to. Sugestie dostarczały mi wystarczającej liczby wrażeń. To zmieniło się niedawno, gdy próbowałem znaleźć w Google rozwiązanie problemu i wciąż dostawałem nieadekwatne do frazy pytania. Google pod tym kątem jest od co najmniej kilku miesięcy frustrujące. Przykład? Potrzebuję rozwiązania jakiegoś problemu w TypeScripcie. Wpisuję frazę zawierającą typescript i dostaję na pierwszej stronie wyniki dotyczące JavaScriptu z odpowiedziami na stackoverflow sprzed 10 lat, czego właśnie chciałem uniknąć. Wpisuję typescript w cudzysłowie – dalej to samo. Z jakiegoś powodu algorytm zamienia sobie typescript na jego zdaniem synonim i takie wyniki mi sugeruje. Copilot nie miał z tym problemu, dostarczył w kilka sekund rozwiązanie w TS, które opisywało dokładnie to, czego chciałem. Ale znowu na wyższym poziomie: oprócz samego kodu okraszonego linkami do źródeł, dostaję wyjaśnienie linia po linii otrzymanego kodu, a na końcu propozycję integracji go z moim kodem, który mam właśnie otwarty. Jeżeli jest poprawny, wystarczy zaakceptować rozwiązanie i Copilot sam wciśnie zasugerowany kod do pliku, na którym pracuję. Jeżeli odpowiedź mi się nie podoba, mogę napisać to na chacie i odpowiedź zostanie zmieniona tak, aby odpowiadać moim wymaganiom.

Chata używam też do lepszego zrozumienia zastanego kodu. Czasem, gdy trafiam na coś nowego, po prostu zaznaczam to i proszę Copilota na chacie o wyjaśnienie, i dostaję takowe razem z przykładami i odnośnikami do materiałów, które mogą mi się przydać. Mogę też poprosić o znalezienie słabych punktów kodu, który napisałem, zlecić refaktoryzację albo stworzenie testów jednostkowych.

Chat pomaga też w rozwiązywaniu problemów. Kod wpisany z pamięci jest błędny? Pytam o przyczynę na chacie, dostaję mój kod, ale poprawiony. Aplikacja wysypuje się na określonej klasie z jakimś błędem? Podaję ten błąd na chacie wskazując, że występuje w bieżącej klasie. Dostaję odpowiedź, na co mam zwracać uwagę.

No dobra, a co z wadami? Zauważyłem dwie, które mają dla mnie znaczenie. Pierwsza to typowa cecha dla AI – halucynacje. Czasem Copilot zbyt mocno chce przekazać sugestię nawet, gdy ma za mało danych lub z jakiegoś powodu nie potrafi tych danych prawidłowo przetworzyć. Zdarza się więc, że np.

  • tworzę jakąś klasę, powiedzmy CreateBook,
  • używam jej w innym miejscu kodu wraz z przekazaniem odpowiednich danych i obsłużeniem zwrotki,
  • następnie tworzę klasę w stylu ChangeBook,
  • tak samo, jak poprzednio, chcę jej użyć, przekazując wybrane dane i zrobić coś ze zwrotką,
  • Copilot sugeruje poprawny kod z wyjątkiem jednego błędu: w nazwie klasy używa nieistniejącej w projekcie nazwy EditBook zamiast istniejącej ChangeBook.

Widać więc, że mimo, że Copilot posiada w kontekście wymaganą klasę, to nazwę podpowiada raczej na podstawie analizy leksykalnej niż stanu faktycznego w projekcie. Zdarza się to dość rzadko i jest łatwe do wyłapania, ale przypomina, że kodu sugestii trzeba pilnować. Nie wiadomo przecież, czy w przyszłości nie zasugeruje czegoś, co będzie poprawne pod kątem składni, ale spowoduje błąd dopiero w fazie uruchomienia aplikacji.

Drugą wadą jest knowledge cutoff. W przypadku wersji, z której korzystam, przypada on na październik 2023. Przeważnie nie jest to problem, ale staje się nim, gdy potrzebuję rozwiązać problem z dynamicznie zmieniającymi się bibliotekami. I tak na przykład ostatnio pytając o znaczenie składni bloku z Angulara dostałem odpowiedź, że nie jest to prawidłowy kod Angulara i że to na pewno pochodzi z innej technologii. Gdy wskazywałem działający kontekst nadal twierdził swoje i ostatecznie wpadłem na pomysł, że może nie znać tej wersji biblioteki. Tak faktycznie było – gdy spytałem o to wprost, Copilot na chacie podał swoją datę knowledgle cutoff, a dyskutowana składnia została wprowadzona w Angularze 17, który wyszedł kilka miesięcy po tej dacie.

Wadą może wydawać się też negatywny wpływ takiej pracy na programistę. Bo przecież gdy sporo kodu dostajemy w sugestiach, może to rozleniwiać i powodować, że rzeczy, które Copilot regularnie sugeruje, będziemy wypierać z pamięci. Jest takie ryzyko, jednak trzeba pamiętać, że otrzymany kod powinniśmy każdorazowo weryfikować, co powinno nas uchronić przed amnezją w tej kwestii. Przyszło mi do głowy ostatnio, że podobne wątpliwości pojawiały się wraz z rozwojem IDE. Kilkanaście lat temu trafiałem na poważne zarzuty, że rozwój technik wspomagania pracy z kodem jak np. początkowo sugerowanie typów, metod, funkcji, itd., a później IntelliSense, prowadzi do ogłupienia i pogorszenia pamięci, bo przestajemy zapamiętywać argumenty często używanych metod i ich kolejność… Czy dzisiaj jest ktoś, kto z tego nie korzysta? Wydaje mi się, że podobnie będzie za kilka lat (lub nawet kilkanaście miesięcy) ze wspomagaczami AI.

Podsumowując – Copilota mocno polecam. Jeżeli masz możliwość otrzymania licencji w pracy, to nie zastanawiaj się i bierz, zmieni to trwale Twój sposób pracy na plus. Jeżeli masz przestrzeń na jego użycie w prywatnych projektach, to przynajmniej przetestuj. Sądzę, że nie ma od tego odwrotu i niedługo stanie się nieodłącznym elementem otoczenia developerów.

Jak uczyłem się Angulara (i jak tego nie robić)

Zeszły rok był dla mnie zdecydowanie rokiem zawodowego startu z nowym. Oprócz podstawowej zmiany, czyli języka backendowego, z dotychczasowego PHP na C#, zacząłem też przygodę z Angularem. Nie miałem z nim wcześniej do czynienia i w Idea Banku pewnie długo jeszcze bym nie miał, bo nasz główny projekt był aplikacją monolityczną, a ze względu na swój rozmiar nie byłoby nawet rozmowy o jego przepisaniu.

Stack w postaci backendu na mikroserwisach + frontend nie był mi obcy, w tematyce REST-owych API czułem się dość dobrze, jednak nigdy nie dotykałem frontu na nowoczesnych frameworkach, jak właśnie Angular czy React. Bardzo długo miałem też do tego typu podejścia negatywny stosunek, uważając to za ślepą uliczkę w rozwoju technologii. Nie wiem, czy argumenty, którymi sobie to tłumaczyłem, były sensowne, czy była to po prostu racjonalizacja sobie braku znajomości tematu w czasach, gdy od dłuższego czasu świat zaczynał w ten sposób pracować.

Uprzedzenie mogło wpływać na moje podejście do Angulara na początku pracy, a już na pewno negatywny wpływ miał mój sposób myślenia i przyzwyczajenia wynikające z pracy w dotychczasowym stacku technologicznym. I to właśnie początki procesu poznawania Angulara chcę opisać w tym wpisie, póki mam je relatywnie na świeżo w głowie. Jest to przy okazji opis tego, jak nie powinno się do tego podchodzić.

Rozpoczynając przygodę z nowymi rozwiązaniami dotychczas najlepiej wychodziło mi klasyczne podejście: na początku poznanie suchej teorii przez lekturę dokumentacji, a dopiero potem z tą wiedzą rozpoczęcie programowania. Nie miałem na to czasu zarówno w przypadku opisywanej poprzednio nauki C#, jak i w przypadku Angulara. Po prostu zostałem przydzielony do nowego projektu i po tasku wdrożeniowym trzeba było przejść do prawdziwych zagadnień.

Początek był toporny. Taska wdrożeniowego zrobiłem w zasadzie kopiując istniejące rozwiązanie w całości i refaktoryzując drobne jej części, bez większego zrozumienia. Nie miałem pojęcia o frameworku, praktycznie żadnego o TypeScripcie, więc nie korzystałem samodzielnie z jego dobrodziejstw w stosunku do JS, nie znałem struktury projektu. Rok wcześniej miałem kilkudniowe szkolenie z Angulara, jednak tutaj czułem się, jakby było to coś zupełnie innego (bo w rzeczywistości było, o czym później). Zbieżne z tym szkoleniem były jedynie podstawowe składowe projektu angularowego.

Brak rozpoznania struktury projektu był moim podstawowym błędem. Oprócz Angulara korzystamy z NgRx do obsługi store’a i RxJS do programowania reaktywnego. Nie wiedziałem tego i traktowałem całość jako części Angulara, więc przeszukiwanie internetu pod kątem rozwiązywania problemów w Angularze – co naturalne – zwracało zupełnie niepasujące wyniki. Flow aplikacji z grubsza wyczułem po kilku wykonanych taskach, ale nadal była to znajomość po łebkach i bez solidnych podstaw, więc o ile coś nie było określone jawnie w kodzie, to było dla mnie magią i działo poza świadomością sposobu, w którym przebiegało. To powodowało, że z powtarzalnymi problemami jako tako sobie radziłem, jednak każde odstępstwo od tej „magii” było dla mnie dużym problemem. Więc na początek – rozpoznaj strukturę projektu, jeżeli chcesz to zrobić dobrze i nie popełnić moich błędów.

Zanim w pełni świadomie dowiedziałem się o NgRx i RxJS, frustrował mnie flow aplikacji, którego nie rozumiałem. Całe zawodowe życie byłem przyzwyczajony do podejścia (w uproszczeniu): „pobierz wiersz z modelu (opartego na bazie danych), ewentualnie przetwórz, przekaż do widoku, renderuj”. I z takim podejściem starałem się pracować w Angularze. Tyle, że nie zawsze się dało – tam, gdzie było to możliwe, próbowałem pobierać dane w resolverach, by móc się w komponencie dobrać do statycznego snapshota z interesującymi danymi, a strumieni nie rozumiałem, bo – znowu – nie znałem podstaw.

W międzyczasie, gdy już dowiedziałem się, że mamy w projekcie zaprzęgnięty NgRx i RxJS, dotarcie do odpowiednich materiałów było znacznie prostsze. Wtedy też zacząłem oglądać na YouTube tutoriale z nimi związane. Było to dość późno, po ładnych kilku miesiącach (może niecałej połowie roku?) pracy przy aplikacjach napisanych w Angularze. Ale właśnie wtedy coś mi w głowie zaskoczyło. Gdy rozeznałem się już w RxJS, siłą rzeczy musiałem poznać lepiej observbables i regularne operacje na nich. Zmieniłem sposób myślenia z zafiksowania na „pobieranie” wierszy, zamieniając to na operacje na observables, kolejne komunikaty z nich, ich przetwarzanie, subskrypcje, itd. A przynajmniej przestałem się ich bać. W podobnym czasie zaczęło mi się w głowie klarować podejście do store’a – poza znanymi z zastanego kodu akcjami i efektami też pierwsze operacje na state. A więc kolejna rada, jak oszczędzić sobie czasu – zmień sposób myślenia, poznaj strumienie i podstawy operacji na nich. Nie da się efektywnie (o ile w ogóle) pracować na Angularze z takim podejściem, jak moje inicjalne. Jest to zresztą jedna z największych zalet pracy z Angularem – gdy już to poznasz, większość dynamicznej aktualizacji treści masz z głowy, zrobi się „sama”.

Od ww. momentu pracowało mi się już sprawniej mimo, że nadal świadomie wielu rzeczy nie znałem (i nie znam do tej pory). Pracując jednak na już istniejącym projekcie można sobie na taki luksus pozwolić i część zagadnień na samym początku zignorować, aby wrócić do nich, gdy przyjdzie taka potrzeba lub w ramach własnego harmonogramu nauki. I w taki sposób doszedłem po pewnym czasie do bardziej świadomej pracy ze state’em: do selektorów, reducerów, itd. Z czasem poznawałem sposób realizacji w Angularze formularzy, kolejne komponenty material UI, komunikację między komponentami, itd. Stąd też kolejna rada – ułóż swoje priorytety w nauce. Nie wszystko trzeba (a nawet się da) opanować od razu.

Dalsze kroki to już stopniowe poznawanie kolejnych rzeczy, których nie ruszałem wcześniej lub nie miałem świadomości ich istnienia. Ważne jest też zdobywanie doświadczenia przy rozwiązywaniu problemów. Pamiętam swoje zrezygnowanie, gdy pominięcie jednego importu w module skutkowało całą litanią błędów z kaskady powiązanych komponentów. W takich sytuacjach bardzo przydatny jest mentor, który służy swoim doświadczeniem i może nakierować na źródło różnych problemów. W tej kwestii jestem na bardzo dobrej pozycji, bo mamy w zespole kolegę, którego pomoc w zakresie Angulara jest nieoceniona, co na pewno ma kluczowe znaczenie w nauce. Jeżeli pracujesz samodzielnie, warto poszukać mentoringu na zewnątrz. W przypadku konkretnych problemów polecam jak zawsze Stack Overflow – dobre odpowiedzi tam prezentują nie tylko suchy, techniczny kod, ale też nakreślają tło odpowiedzi, co jest też konfrontowane przez innych użytkowników w komentarzach.

Debugowanie JS w Firefoksie

Nie wiem, jak to się stało, ale dopiero niedawno odkryłem debugowanie skryptów JS w przeglądarce (a konkretniej w Firefoksie). Po tylu latach zwróciłem uwagę na taką możliwość w narzędziach deweloperskich, a gdy zacząłem drążyć temat okazało się, że jest to niesamowicie wygodne i proste w użyciu.

W większości przypadków wystarczy po prostu uruchomić narzędzia deweloperskie w przeglądarce, wskazać interesujący nas plik i założyć w nim breakpointy. Przeglądarka po osiągnięciu wskazanego miejsca w kodzie po prostu wstrzyma jego wykonywanie i pozwoli nam na dalsze przetwarzanie krok po kroku, wraz z podglądem kontekstu, śledzeniem określonych zmiennych, itd.

Przy używaniu debuggera zauważyłem, że nie wszystkie pliki są widoczne na liście źródeł. To jednak też łatwo rozwiązać – wystarczy w dynamicznie ładowanym pliku JS umieścić odpowiednią adnotację.

Również w przypadku często stosowanej minifikacji skryptów nie będzie problemów z debugowaniem – firefoksowy debugger ma wbudowany prettyfier, który sformatuje zminifikowany kod do czytelnej konwencji.

A więc .NET

Dawno mnie tu nie było, ale był też istotny tego powód. Niby człowiek wiedział, ale się łudził i fakt przejęcia Idea Banku przez Pekao spadł na mnie dość nieoczekiwanie, zwłaszcza, że miało to miejsce w południe w sylwestra 2020. Do tamtej pory pracowałem rozwijając aplikacje backoffice’owe w PHP, w Pekao ten język praktycznie nie istnieje poza jakimiś marginalnymi zastosowaniami (zresztą nie wiem, czy ma on rację bytu gdziekolwiek indziej w bankowości poza grupą Leszka Czarneckiego), więc pierwsze tygodnie oznaczały dla mnie miotanie się pomiędzy decyzją o pozostaniu w Pekao i zmianą języka, a zmianą pracy na inną i pozostaniu przy PHP. „Miotanie się” to bardzo adekwatne w tym wypadku określenie – nastrój w tej kwestii zmieniał mi się czasem co tydzień w skrajnych kierunkach. W Pekao dominuje szeroko pojęty .NET, więc dla mnie oznaczałoby to nie tylko zmianę języka, ale zaczynanie praktycznie od zera w większości powiązanych technologii. Tak na szybko listując: język ze słabo typowanego, skryptowego na silnie typowany, kompilowany, DBMS z PostgreSQL na SQL Server, serwery HTTP z Apache/nginx na IIS, środowisko uruchomieniowe z linuksowego na windowsowe, IDE z Eclipse na Visual Studio, do tego dużo drobniejszych rzeczy, jak razor pages, inny ORM, powszechnie używane zasoby sieciowe zamiast NFS/SFTP, itd. Dużo tego. Zupełnie inaczej wyobrażałem sobie też przepięcie na nowe technologie – część kolegów zaczęła swoją technologiczną konwersję praktycznie z dnia na dzień. W moim przypadku ostatecznie było inaczej – system, który tworzyliśmy w IB, podlegał migracji do systemów Pekao, więc było to priorytetem i potrwało do listopada 2021, a ja w tym czasie mogłem się spokojnie oswoić z nowym stackiem technologicznym i zacząć przyswajać wiedzę z tego zakresu, jeszcze wtedy na wolnych obrotach.

Pracę w projektach .NET-owych zacząłem na poważnie w grudniu 2021, więc ostatnio minęły pełne 4 miesiące pracy przy nich, taki większy okres próbny. Postanowiłem więc, że póki mam to jeszcze na świeżo, to opiszę moje wrażenie z takiej przesiadki.

Pierwszy miesiąc to głównie przyzwyczajanie się do silnie typowanego języka. Po ponad 10 lat pracy w PHP trzeba było zmienić przyzwyczajenia nawet w tak podstawowych kwestiach, jak dobór typów danych (i wyleczenie się ze wszechobecnych w PHP, mieszanych tablic asocjacyjnych). Nie było to jednak jedyne przyzwyczajenie do zmiany: samo środowisko uruchomieniowe rządzi się innymi prawami i niektóre rzeczy są rozwiązane w sposób, który nigdy we wcześniejszym stacku nie przyszedłby mi do głowy (koordynator transakcji rozproszonych jako osobna usługa jest moim faworytem do tego rankingu).

Z silnym typowaniem z perspektywy czasu jest jednak jak z przejściem ze skrzyni manualnej na automatyczną – na początku wkurza zahaczanie nogą o szeroki hamulec i odruchowe wpadanie lewej nogi w dziurę, gdzie powinno być sprzęgło, jednak po czasie docenia się to, że pewne rzeczy wyłapywane są już na etapie kompilacji. Początkowo było to dla mnie nieco uporczywe i zdawało mi się zbędnym narzutem, jednak nie zauważałem pewnego istotnego faktu: wcześniejsze projekty znałem dość dobrze i w przypadku zmian na poziomie modelu wiedziałem, gdzie szukać obszarów do refaktoryzacji. Gdy jednak nie ma się jeszcze takiej wiedzy, to w przypadku PHP często błędy wychodziły dopiero podczas uruchomienia pominiętego kodu. Tutaj projekty są dla mnie nowe, więc takiej wiedzy nie mam, a przeszukiwanie kodu źródłowego nie zawsze jest na tyle efektywne, aby zidentyfikować wszystkie konieczne miejsca.

Silne typowanie ma jeszcze jeden plus – dzięki niemu autouzupełnianie w IDE i ogólnie asystent kodu stoją na zupełnie innym poziomie, niż w PHP. Tutaj na każdym etapie zmienne mają znany edytorowi typ, więc praktycznie wszędzie mamy dostęp do podpowiedzi, co w przypadku PHP – nie było tak oczywiste.

Pisanie aplikacji webowej w C# z użyciem ASP.NET MVC to też pole do zmiany przyzwyczajeń. Dużo się tu dzieje w middleware’ach. W PHP korzystałem z Zenda i operowanie na surowej tablicy wartości z POST, lekko tylko przerobionej (np. przefiltrowanej) przez framework było dla mnie naturalnym podejściem. Tutaj królują modele, które są automatycznie mapowane z danych POST/GET. Za tym idzie mnóstwo innych rzeczy, jak np. sprzęgnięta walidacja, itd. Po krótkim przyzwyczajeniu jest to dużo wygodniejsze, niż praca na surowych danych z POST.

Konstrukcja języka C# mi się podoba. Są rzeczy, których brakowało mi w PHP (część z nich jednak w międzyczasie została wprowadzona w wersji 8 – gdzie one były przez tyle lat mojej pracy? :P), na przykład named parameters. Dłużej musiałem przysiąść nad powszechnie używanymi wyrażeniami lambda, których czasem w PHP się używało, ale w zupełnie inny sposób. Podobnie z typami generycznymi, z których prototypem (szablonami) miałem do czynienia dawno temu, gdy pisałem w C++. Do tego mnóstwo różnych, drobnych rzeczy, których nie znałem, a których używa się na co dzień (jak chociażby postępowanie z Nullable, bardziej zaawansowane używanie list, itd.).

A propos list, nie można zapomnieć o LINQ. Bardzo spodobało mi się to rozwiązanie, po opisanym wcześniej ogarnięciu wyrażeń lambda można wiele zrobić z kolekcjami w łatwy i krótki sposób. Dodatkowo, w zastosowaniach bazodanowych, w połączeniu z IQueryable czułem jak się w domu ze względu na przyzwyczajenie do zendowych selectów.

Polubiłem też wykorzystywany tu ORM i sposób tworzenia modelu. I choć widzę, że mam w tym temacie jeszcze mnóstwo do nauczenia, to jest przede wszystkim dużo szybciej niż w PHP, zarówno z mapowaniem struktury bazy danych na model, jak i chociażby z migracjami bazodanowymi. Pamiętam, że podobnie miałem, gdy w 2012 przesiadłem się z CodeIgnitera na Zenda i jego active record i Zend_Db_Select. Zresztą ostatnie miesiące bardzo przypominają mi 2012 – przyswajam dużo nowej wiedzy w krótkim czasie, wtedy zdaje się, że porównywałem pierwsze pół roku pierwszej pracy na etacie do całego drugiego stopnia studiów pod kątem przyswojonej wiedzy.

Nie mogę w tym zestawieniu pominąć debugowania aplikacji w Visual Studio. Coś, co działa z automatu i czego używa się codziennie to dla mnie zdecydowanie nowość. Wcześniej, w PHP, używałem xdebug do debugowania kodu krok po kroku lub do profilowania tylko w wyjątkowych sytuacjach, gdy logika była na tyle zaawansowana, że ciężko było ją debugować przez wypluwanie wartości lub wtedy, gdy trzeba było przyjrzeć się z wysokiego poziomu wykonywaniu kodu i systemowego i systemowo go zoptymalizować. Zresztą sama konfiguracja xdebug w PHP była dość problematyczna, szczególnie na obwarowanych zabezpieczeniami komputerach bankowych i takowej infrastrukturze sieciowej. Nie mówiąc już o szybkości działania tak debugowanej aplikacji.

Co do samego Visual Studio, to spodobał mi się gitowy merge tool, który jest znacznie wygodniejszy, niż wcześniej preferowane przeze mnie ręczne rozwiązywanie konfliktów – tutaj mam kod z dwóch stron konfliktu na podzielonym ekranie, mogę pojedyncze linie włączać do rozwiązania konfliktu checkboksami, a dodatkowo edytować go w połączonej wersji. Coś wspaniałego. Rozczarował mnie natomiast brak narzędzia do częściowego stage’owania zmian w gicie. Zostało podobno wprowadzone w VS 2022, więc chętnie wypróbuję, jednak póki co korzystam z VS 2019, więc pozostaje mi nadal dobrze znana obsługa gita z konsoli.

Podsumowując, z przesiadką na nowy stack technologiczny jest lepiej, niż przewidywałem. Nadal jestem na samym początku drogi, więc najbliższe miesiące to na pewno czas dalszej nauki w tym kierunku, jednak sam jestem zdziwiony, że wcześniejsze przyzwyczajenia i uprzedzenia w sumie dość szybko odpuściły. Zawsze byłem też z tych, którzy mocno się do takich rzeczy przyzwyczajają i ciężko było mi zwizualizować sobie trwałą, nieincydentalną przesiadkę np. na zupełnie inny język programowania. Nie doceniałem w tej kwestii jednak doświadczenia, które na pewno ma tu spore znaczenie – znając dobrze zasadę działania aplikacji internetowych, język to tylko narzędzie, sam sposób myślenia, programowanie obiektowe i sposób rozwiązywania problemów są w zasadzie takie same i cieszę się, że przekonałem się o tym na własnej skórze.

Czy scrum rujnuje świetnych inżynierów, czy robisz to źle?

Na blogu stackoverflow trafiłem ostatnio na tytułowy wpis: Does scrum ruin great engineers or are you doing it wrong? Traktuje głównie o scrumie, ale jego większość dotyczy ogólniejszych problemów zarządzania projektami i zespołami. Dawno nie czytałem czegoś tak celnego i przystępnego, toteż polecam i wrzucam na blog, żebym sam o nim nie zapomniał.

Traits w PHP

Traits w PHP są obecne już od wersji 5.4, więc od dość długiego czasu (2012). Sam pierwszy raz czytałem o nich parę lat później, jednak nie wydały mi się atrakcyjne – są próbą wypełnienia braku dziedziczenia wielokrotnego, które nigdy w praktyce mi się nie przydawało jeszcze w czasach C++, a rodziło problemy, które komplikowały czasem bardziej niż jego brak. Zapomniałem więc o traits na wiele lat, nie widziałem też w tym czasie dla nich zastosowania (podobnie, jak wyparłem kiedyś dziedziczenie wielobazowe).

Aż do zeszłego roku. Trafiłem wtedy na zagadnienie, w którym przydatne byłoby wykorzystanie tego samego kodu w trzech zupełnie różnych klasach, z których każda miała już swoją dość złożoną strukturę i odpadało stworzenie klasy nadrzędnej, po której dziedziczyłyby wszystkie z nich.

Można było niby skorzystać z klasy helpera i pobierać jego instancję lub wstrzykiwać, jednak to też wprowadzało pewien nieład zwłaszcza, że jedna z tych klas modyfikowała działanie części kodu, którą potrzebowałem „uwspólnić”.

Nie było więc innego wyjścia, niż zainteresować się traitsami. I był to strzał w dziesiątkę. Traitsy to tak naprawdę kawałki kodu do wielokrotnego użytku. Wyglądają jak klasy, jednak nie wprowadzają zmian w strukturze typów. Nie narzucają też praktycznie niczego – używane w nich pola i metody nie muszą istnieć w nich samych, jeżeli wprowadzają konflikty, to można je łatwo rozwiązać. Nie można utworzyć ich samodzielnych instancji. Teoretycznie blisko jest im więc do klas abstrakcyjnych, z tą różnicą, że w PHP możemy dziedziczyć tylko jednej klasie abstrakcyjnej, natomiast traitstów możemy użyć tyle, ile potrzebujemy.

Korzystanie z nich jest więc podobne do skopiowania ich treści do bieżącej klasy. Dodatkowo, możemy aliasować poszczególne traitsy, a także metody z nich tak, aby nie zachodziły konflikty z już istniejącymi metodami, do których zaciągamy trait. Metody zaciągane z traits mogą być nadpisywane lokalnie, działa to wtedy tak, jak byśmy nadpisywali metodę z klasy nadrzędnej. Nadal można przy tym korzystać z nadpisywanej metody z trait.

Jeżeli chodzi o wady, to należy pamiętać o konfliktach w przypadku współistnienia pól.

Przyznam, że od czasu pierwszego użycia, pomijając początkowo neoficką fascynację, zacząłem widzieć coraz więcej obszarów, w których mógłbym stosować traitsy, i coraz częściej pojawiają się one w moim kodzie.

Trigger ON UPDATE i ON DELETE na spartycjonowanej tabeli w PostgreSQL

UWAGA! Ten wpis ma już 5 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Kolejna ciekawostka, na którą się naciąłem ostatnio w pracy. Otóż w przypadku spartycjonowanej tabeli, triggery ON UPDATE i ON DELETE są wykonywane jedynie na tabeli potomnej zawierającej dane wiersza określonego przez warunki w WHERE (o ile oczywiście istnieją).

Oznacza to, że w przypadku partycjonowania tabel na podstawie np. wartości klucza głównego poprzez ograniczenie CHECK, planner najpierw spróbuje wyznaczyć partycje zawierające wiersz(e) spełniające warunki zapytania, a następnie wywoła triggery ON UPDATE/ON DELETE jedynie na tabelach zawierających dane do usunięcia/aktualizacji.

Jest to zachowanie inne niż w przypadku ON INSERT, gdzie trigger wywoływany jest bezpośrednio na tabeli, na której wywołano instrukcje INSERT mimo, że sam trigger ON INSERT na niej może delegować operacje wstawienia do innych tabel potomnych.

Profilowanie aplikacji w PHP

UWAGA! Ten wpis ma już 6 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Krótko i na temat, bo pierwszy raz zacząłem korzystać z profilowania aplikacji w PHP dawno temu, a często przydaje mi się odświeżenie informacji na ten temat.

Po instalacji xdebug wystarczy dodać do .htaccess:

php_value xdebug.profiler_enable 1
php_value xdebug.profiler_output_dir /output/path/
php_value xdebug.profiler_output_name cachegrind.out.%t-%s
php_value xdebug.profiler_enable_trigger 1

Następnie wczytujemy w przeglądarce żądaną stronę (z włączonym profilerem może trwać to nawet kilkanaście razy dłużej), aby we wskazanym wyżej katalogu uzyskać logi profilera. Logi te można wczytać za pomocą np. KCacheGrind (dostępny zarówno pod Linuksem, jak i Windowsem).

W KCacheGrind po podpięciu kodu źródłowego otrzymujemy listę wywołań z kosztem czasowym każdego z nich. Dzięki temu łatwo wyłapać najbardziej czasochłonne części kodu.

Widoczność pól i metod innych obiektów tego samego typu w PHP

UWAGA! Ten wpis ma już 6 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Ostatnio, refaktoryzując kod, dowiedziałem się o pewnej ciekawej właściwości PHP. Otóż obiekty tych samych typów mają dostęp do prywatnych i chronionych metod oraz pól nawet, jeżeli stanowią oddzielne instancje tej samej klasy. Początkowo myślałem, że to jakiś błąd w kodzie, który analizowałem, jednak znalazłem potwierdzenie w manualu PHP:

Objects of the same type will have access to each others private and protected members even though they are not the same instances. This is because the implementation specific details are already known when inside those objects.

http://php.net/manual/en/language.oop5.visibility.php#language.oop5.visibility-other-objects

Co ciekawsze, w innych popularnych językach jest podobnie, więc nie jest to swoistość samego PHP, na co nigdy nie zwróciłem uwagi…