Więcej niż kod

Hej! Witam po dłuższej przerwie! Od ostatniego wpisu na blogu minęło pół roku z okładem. Pomimo braku nowych wpisów był to intensywny okres, w którym dużo się u mnie działo. Webinary, artykuły, nagrania i intensywna praca nad kursem PythON: Wtajemniczenie. Ale o tym kiedy indziej 😀
Dzisiaj chciałbym zachęcić Was do tego, żeby w trakcie swojej kariery patrzeć trochę szerzej, ponad sam kod i rozwijać się jako inżynier oprogramowania. Będą więc moje przemyślenia na temat tego co tak naprawdę może robić programista i czy (kiedy?) zastąpi nas sztuczna inteligencja. Ok – no to lecimy!

Programista – klepacz kodu?

Gdy wyjaśniam na czym polega praca programisty, zazwyczaj zaczynam od spojrzenia „z lotu ptaka” opisując dwa jej podstawowe elementy. Pierwszy to tłumaczenie. Zamiana konkretnych zdań zapisanych w języku naturalnym na zdania wyrażone za pomocą kodu (np. Pythona). Nasi klienci, szeroko rozumiany „biznes”, komunikuje się z nami wyrażając swoje potrzeby i oczekiwania za pomocą języka polskiego, angielskiego itd. My zaś, rozumiejąc „ich” język, tłumaczymy go na kolejne linijki kodu. Opanowanie tej umiejętności na wysokim poziomie wymaga biegłej znajomości struktur danego języka programowania, rozumienia jego ekspresji oraz panujących w nim standardów. Idąc krok dalej, możemy wliczyć w to również znajomość bibliotek, narzędzi – ogólnie tego jak używa się danej technologii. Tak jak pisarz wie, że do podkreślenia przekazu może użyć hiperboli, tak programista Pythona wie, że do pomnożenia macierzy może użyć numpy, a do integracji z usługą webową pakietu requests.

Umiejętność ta – tłumaczenie – opiera się na znajomości narzędzi które stosujemy do realizacji naszej pracy. Tak jak majster pracujący w wykończeniówce ma do wyboru różnego rodzaju kleje, młotki, klucze tak programista wybiera pomiędzy dostępnymi wyrażeniami i bibliotekami. Pomimo że najczęściej rozpoczynamy naszą przygodę z programowaniem właśnie od nauki języka, bibliotek i technologii, w mojej opinii nie stanowi to clou pracy programisty. Zatrzymanie się tylko na tym elemencie może spowodować, że ograniczamy naszą pracę właśnie do „klepania kodu”.

Żeby było jasne – całkowicie zgadzam się z tym, że świetna znajomość technologii jest niezbędna w pracy programisty. Uważam jednak, że technologia to tylko część tej pracy. W związku z tym twierdzenie, że jedynym obowiązkiem developera jest „zakodowanie zadania” mocno spłyca to, czym w rzeczywistości zajmują się doświadczeni programiści. Akt pisania kodu (w jego ostatecznej wersji) jest tak naprawdę tylko zwieńczeniem całego procesu związanego z rozwiązywaniem danego problemu.

Projektowanie rozwiązań

I tutaj właśnie przechodzimy do drugiej, w mojej opinii kluczowej, części pracy programisty, czy też właśnie inżyniera oprogramowania. Jest nią projektowanie rozwiązań. Ok, ale na czym to tak naprawdę polega?
W dużym skrócie – na wymyśleniu (stworzeniu) takiego rozwiązania, które najlepiej jak to możliwe realizuje cele i priorytety biznesowe, które ma realizować.
Składa się na to:

  • Pomoc biznesowi w zrozumieniu celów i odkryciu potrzeb
  • Zaproponowanie usprawnień/zmian w procesie biznesowym (np. „Najpierw poczekajmy na potwierdzenie płatności, a dopiero potem kontaktujmy się z dostawcami”)
  • Opracowanie „sprytnego rozwiązania” na trudne problemy („wykorzystując te informacje i tę bibliotekę możemy bardzo prosto uzyskać to czego tak naprawdę nam potrzeba”)
  • Jeżeli wybrane rozwiązanie wymaga implementacji (bo nie zawsze wymaga), to takie dobranie narzędzi, komponentów, architektury rozwiązania, żeby wspierało ono jak najwięcej ważnych celów biznesowych (np. inaczej powinniśmy rozwiązywać problem w systemie firmy, która obsługuje 10 użytkowników a inaczej w takiej, która celuje w miliony)

Wszystkie powyższe punkty realizujemy z wykorzystaniem naszej wiedzy technicznej. Pozwala nam ona między innymi identyfikować „nisko wiszące owoce” czyli rozwiązania dające relatywnie dużą wartość biznesową, które dzięki wykorzystaniu dostępnych technologii można wdrożyć niskim kosztem.
Jako inżynierowie oprogramowania jesteśmy predysponowani do projektowania rozwiązań. Do analizy procesów. Do optymalizacji. Dzięki temu wartość, którą wnosimy do projektu nie sprowadza się do roli tłumacza języka naturalnego na język programowania. Aktywnie działamy na rzecz rozwoju danego biznesu, na rzecz poszukiwania rozwiązania. Dopiero na samym końcu tego procesu, jako pewnego rodzaju zwieńczenie działań, następuje ostateczna implementacja.

Poprzez słowo ostateczna mam na myśli fakt, że w ramach samego procesu optymalizacji i analizy mogą powstawać różne fragmenty kodu, które potem zainspirują nas do zadania kolejnych pytań, wprowadzenia usprawnień i w efekcie zmiany tego kodu.
Jednak to nie umiejętność „klepania” jest tutaj tym co intensywnie wykorzystujemy. Ważne jest nastawienie produktowo-biznesowe, kreatywne i analityczne myślenie oraz klarowna komunikacja.

Spróbujmy przeanalizować sobie ten temat na konkretnym przykładzie. Będzie to sytuacja, w której developer musi podjąć decyzję projektową, w trakcie realizacji dość typowego zadania, w dość typowym projekcie.

W systemie przetwarzającym wnioski (np. o przyznanie jakiegoś grantu) dodajemy formularz umożliwiający złożenie uproszczonego wniosku.

W celu dodania tego typu funkcjonalności musimy pokryć wiele obszarów związanych z interfejsem użytkownika, zapisywaniem i przetwarzaniem danych itd. Dla uproszczenia, w naszej analizie skupimy się na jednym tylko elemencie z całej tej układanki. Przeanalizujemy sposób reprezentacji danych, zbieranych przez formularz.
Na początek przyjmijmy dwa założenia dotyczące naszego projektu:

  • W systemie istnieje już formularz pozwalający na złożenie pełnego wniosku
  • Przynajmniej część danych w nowym formularzu powtarza się z formularzem w wariancie pełnym

W tym kontekście, developer implementujący to rozwiązanie, ma do wyboru dwie podstawowe opcje:

  • Traktować formularz pełny i uproszczony jako całkowicie niezależne byty
  • Uznać, że są one częściowo zależne od siebie i współdzielą pewne części

Przykładowa reprezentacja danych według pierwszego modelu mogłaby wyglądać w następująco:

@dataclass
class FullForm:
  applicant_first_name: str
  applicant_last_name: str
  applicant_date_of_birth: date
  project_title: str
  project_area: ProjectArea
  project_description: str
  main_researcher: Researcher
  required_expenditures: List[Expenditure]
  predicted_incomes: List[Income]
  related_university: Optional[University]
  ...

@dataclass
class SimplifiedForm:
  applicant_first_name: str
  applicant_last_name: str
  applicant_date_of_birth: date
  project_title: str
  project_area: ProjectArea
  project_description: str
  main_researcher: Researcher
  estimated_cost: int
  estimated_incomes: int

Natomiast w drugim przypadku mamy do wyboru całkiem sporo wariantów:

  • Wydzielenie części wspólnej do abstrakcyjnej klasy/struktury
  • Dziedziczenie w jedną lub w drugą stronę
  • Wydzielenie części wspólnej jako oddzielnego bloku i zastosowanie kompozycji
  • Podział na mniejsze bloki (potencjalnie w różnych wariantach) i zastosowanie kompozycji

W tym miejscu ktoś mógłby stwierdzić, że dla kilku pól formularza różnica w każdym z tych podejść jest de facto kosmetyczna. To prawda – dla uproszczenia przykładu rozpisałem tylko po kilka pól w każdym z formularzy. Spróbujmy jednak pomyśleć o tym w kontekście systemu, w którym formularz pełny to zagnieżdżona struktura składająca się z 500 pól, a uproszczony ma ich „tylko” 80. Tego typu sytuacja nie jest zresztą czymś nadzwyczajnym 😉
W takim przypadku wybór konkretnego założenia i oparcie na nim wielu zależnych elementów (walidacji, sposobu zapisu danych, mapowania ich przy wysyłaniu żądań do systemów zewnętrznych itd. ) będzie miało wymierny wpływ na koszt i szybkość wprowadzania przyszłych modyfikacji w systemie. Oczywiście możemy zastosować różne mechanizmy żeby zmniejszyć wpływ tej decyzji, aczkolwiek niesie to ze sobą alternatywny koszt. Musimy bowiem pamiętać, że oprogramowanie które piszemy, będzie podlegać nieustannym modyfikacjom. Zmienia się świat w którym działamy, biznes który wspieramy, realia prawne i oczekiwania. Nowe informacje pozwalają nam lepiej reagować na potrzeby klientów. Przyczyn zmian są setki. Modyfikacje z pewnością dosięgną nas prędko, a my powinniśmy być na nie gotowi. W końcu „software” jest „soft” bo w przeciwieństwie do „hardware’u” powinien być łatwy w modyfikacji (1).

Ok – to które rozwiązanie zapewni nam odpowiednią elastyczność? Które jest dobre? Otóż niestety to… zależy. Na przykład od tego jaka jest szansa, że przyszłości zmienimy pole first_name na inne, tylko w jednym z formularzy? Odpowiedź na to i pozostałe „ważne” pytania wymaga zrozumienia działania produktu oraz celu biznesowego.
W jaki sposób możemy uzyskać zrozumienie celu biznesowego? Poprzez rozmowę. W tym momencie z hukiem upada stereotyp programisty siedzącego sobie w piwnicy, sam na sam z komputerem 😀
Otóż klarowna komunikacja jest jednym z podstawowych narzędzi pracy w projekcie IT. Dotyczy to zarówno komunikacji wewnątrz zespołu technicznego, jak i właśnie rozmowy z szeroko rozumianymi „ludźmi biznesu”.

Komunikacja – czyli wysyłam Ci model

Kiedy jako klient chciałbym przekazać komuś mój cel, wyjaśnić jakiego rodzaju rozwiązania potrzebuje, wygląda to następująco:

  1. W głowie mam jakieś myśli (pewien model), abstrakcyjny koncept tego, co chcę przekazać drugiej osobie
  2. Zamieniam ten koncept na słowa – staram się przedstawić za ich pomocą moje myśli
  3. Wypowiadam/piszę te słowa
  4. Druga osoba słucha/czyta
  5. Dokonuje interpretacji moich słów, zamieniając je na swoje myśli
  6. W jej głowie pojawia się jej interpretacja tego, co chciałem przekazać

Co ważne, na każdym z tych kroków początkowa intencja może zostać nieco zniekształcona. Jeżeli jako inżynierowie oprogramowania mamy proponować najlepsze rozwiązania i podejmować racjonalne decyzje, to musimy dobrze się komunikować. Często przyjdzie nam pracować w środowisku, w którym komunikaty płynące od różnych osób „z biznesu”, będą pomiędzy sobą sprzeczne bądź niespójne. W takiej sytuacji mamy swój udział w rozwiązaniu tych niespójności oraz w organizacji komunikacji pomiędzy poszczególnymi osobami. Wszystko po to, aby móc w miarę precyzyjnie określić cel i kierunek, w którym mamy zmierzać.

Program programuje

Intensywnie rozwijana sztuczna inteligencja często zaskakuje nas swoimi umiejętnościami (zobacz np. GPT-3 – OpenAI API). Rozwój ten prowadzi nas do pytania: kiedy nastąpi moment, w którym programiści sami siebie wyeliminują z rynku tworząc program, który ich zastąpi?

Osobiście sądzę, że istnieje spory potencjał na wsparcie programistów przez sztuczną inteligencję. Uważam, że w dającej się przewidzieć przyszłości, potencjał ten będzie stopniowo wykorzystywany. Mam tu na myśli głównie obszar samego „tłumaczenia” czy też wsparcia przy wyborze konkretnego rozwiązania, poprzez szybkie wskazanie dostępnych możliwości. Duże możliwości automatyzacji dostrzegam również w zakresie rozwiązywania problemów przejrzystych ale o dużym stopniu rozbudowania. Dość realna wydaje mi się sytuacja, w której tłumaczymy sztucznej inteligencji jakie dane potrzebujemy zbierać, ta zaś generuje nam kod potrzebny do obsługi formularzy.
Zdecydowanie bardziej odległa wydaje mi się perspektywa przejęcia przez AI komponentu projektowania rozwiązań. Zwłaszcza w kontekście zaawansowanych problemów. Przemawia za tym kilka argumentów:

  • Jest to wyzwanie bardzo trudne i de facto średnio opanowane przez człowieka
  • Często sami do końca nie wiemy, czego dokładnie nam potrzeba
  • Zakładając, że sztuczna inteligencja rozumie nasze potrzeby i jest w stanie wygenerować nam oczekiwane rozwiązanie, podczas współpracy z nią moglibyśmy natrafić na „problem w komunikacji”. Tak jak ludzie czasami (często) się nie dogadują, tak samo moglibyśmy mieć problem żeby AI załapała o co nam tak naprawdę chodzi. Powoduje to poważne ryzyko, że w pewnym momencie nie będziemy w stanie wprowadzić niezbędnych modyfikacji, bo AI po prostu nas nie zrozumie. Ryzyko byłoby na pewno mniejsze gdybyśmy mieli do dyspozycji wiele niezależnych programistycznych AI 😉
  • Emocje są ważnym elementem komunikacji międzyludzkiej, a tym samym procesu zrozumienia celu i zaprojektowania rozwiązania. W kontekście biznesowym grają one zazwyczaj mniejszą rolę, jednak każdy człowiek to przede wszystkim istota emocjonalna. Być może kiedyś AI będzie posiadać emocje podobne do ludzkich aczkolwiek… to brzmi trochę niebezpiecznie 😀

Podsumowując, sądzę, że w niedalekiej przyszłości realne będzie automatyzowanie prostszych elementów pracy programisty za pomocą AI. Być może w postaci narzędzia wpierającego developerów, dzięki czemu będziemy mogli pracować wydajniej i dostarczać więcej wartości w krótszym czasie.
Jeżeli kiedyś powstanie sztuczna inteligencja, która będzie potrafiła rozumieć nasze cele i kreatywnie tworzyć programy – to w sumie super 😀 Nie spodziewałbym się tego w najbliższym czasie ale nie jest wykluczone, że nastąpi to jeszcze za naszego życia.

Co robić, dokąd zmierzać?

Zgodnie z tym co napisałem we wstępie tego artykułu, chciałbym przede wszystkim zachęcić Was do wyjścia poza samą tylko technologię i sięgnięcia krok dalej. Zwłaszcza, jeżeli ktoś obawia się konkurencji ze strony AI 🙂 .
Chciałbym przy tym zaznaczyć, że według mnie dobre poznanie technologii stanowi trudne i wymagające wyzwanie. Zakres wiedzy i doświadczeń, które musimy posiadać by swobodnie poruszać się w świecie frameworków, bibliotek i dziesiątek narzędzi, jest autentycznie olbrzymi. Do tego jeszcze wszystko cały czas się zmienia i rozwija! Samo nauczenie się technologii to bardzo wymagający i praktycznie niekończący się proces. Sądzę jednak, że warto nie poprzestawać tylko na zagadnieniach technicznych. Praca na poziomie developera zaangażowanego w tworzenie produktu i dostarczanie realnej wartości biznesowej, oprócz wielu wyzwań, daje też niesamowitą frajdę.
Co zatem robić, by rozwijać się jako inżynier oprogramowania? Moja wskazówka to:
bądź świetny w technologii, której używasz i rozwijaj się w niej cały czas, ale… nie ograniczaj się tylko do technologii. Staraj się zrozumieć proces w którym pracujesz, rozwijaj kompetencje zarówno twarde jak i miękkie. Zwróć uwagę na tematy takie jak:

  • Bycie częścią rozwiązania a nie problemu (biznes oczekuje, że rozwiążemy problem, a nie będziemy generować nowe 😉 )
  • Cel biznesowy i myślenie biznesowe (wartość leży w możliwości przetworzenia wniosku a nie w kodzie formularza)
  • Architektura oprogramowania i cele jakie mogą wspierać różne style architektoniczne
  • Komunikacja, komunikacja, komunikacja
  • Praca zespołowa, motywacja, bycie liderem
  • Proces wytwarzania oprogramowania

Każdy z tych tematów wart jest oddzielnego poruszenia. Na ten moment polecam moją prezkę o architekturze a być może niebawem nagram coś również w pozostałych obszarach. Tymczasem powodzenia w świadomym i przede wszystkim satysfakcjonującym rozwoju! Cześć!

(1) „Software was invented to be ‚soft’. It was intended to be a way to easily change the behavior of machines. If we’d wanted the behavior of machines to be hard to change, we would have called it hardware.”
Clean Architecture by Uncle Bob.