Gdyby obiektywnie popatrzeć na oprogramowanie, które tworzyłem - nawet te większe, latami rozwijane przez wiele osób systemy - to napisany kod, jakkolwiek by mierzyć jego rozmiar, jest jakimś nieszczęsnym ułamkiem całej infrastruktury. Malutkim zbiorem supełków powiązanych ponad masą narzędzi wykorzystywanych do stworzenia działającej aplikacji i do jej uruchomienia.
Bazy danych. Middleware. Serwery webowe, pocztowe, telekomunikacyjne. Języki programowania z całą swą otoczką. Frameworki. Duże i małe biblioteki pomocnicze. Cała warstwa systemowa i sieciowa. I wiele wiele innych... A przecież jeszcze narzędzia developerskie, od IDE i kompilatorów po programy graficzne i wspierające pracę w grupie.
Złożoność (z pułapkami)
W literaturze fantastycznej lat 1990-ych dość często przewijał się motyw oplatającej świat sieci komputerowej, nad którą ludzie już właściwie nie panują i nie rozumieją jak ona działa - za to ich życie od niej zależy. Dla większości zagadka, dla nielicznych lepiej wtajemniczonych możliwość zdobycia ogromnej władzy. Z polskich autorów przywołam choćby Rafała Ziemkiewicza (Pieprzony los kataryniarza i Walc stulecia).
Książki te zresztą bardzo lubię i polecam, podobnie jak stanowiący do nich uwerturę zbiorek opowiadań Czerwone dywany odmierzony krok (koniecznie w wydaniu zawierającym Śpiącą księżniczkę). Wciągająca, dobrze napisana fantastyka (futurystyka?).
Autor budzi w ostatnich latach (zwłaszcza od czasu napisania Polactwa i Michnikowszczyzny) sporo emocji ale te wczesne książki mogą być ciekawe i dla osób, które dziś mają z nim nie po drodze.
Charakterystyczny obrazek to specjalista zdolny obejść nienaruszalne pozornie ograniczenia dzięki umiejętności sięgnięcia pod spód. Co czasem pozwala mu sterować decyzjami rządów, a czasem zapalić papierosa bez aktywowania systemu przeciwpożarowego.
Dzisiaj pisze się o tym mniej, bo rzecz niemalże się spełniła. Dokumenty i informacje przechowywane i przetwarzane w chmurach obliczeniowych - czyli gdzieś i nigdzie (ktoś umie określić lokalizację mojej gmailowej poczty?), ludzie dobrowolnie pozwalający śledzić każdy swój krok (nie piję do usług lokalizacyjnych Google, włączona komórka i używana w każdym sklepie karta płatnicza zupełnie wystarczą), sterowane komputerowo zamki i alarmy, wirtualny pieniądz, coraz większa automatyka sprzętu wojskowego, samochodów, samolotów, nawet urządzeń AGD. A pod tym wszystkim warstwa na warstwie coraz mniej zrozumiałego oprogramowania.
Skoro zacząłem już wspominać fantastykę, przywołam pradawne (a znakomite) Limes inferior Janusza Zajdla i jego klucz. I policjantów śledzących ludzi na podstawie miejsc, w których swoim kluczem płacą...
Oprogramowanie to zawiera zresztą rozmaite luki i furtki. Tych celowych raczej niewiele (choć zainteresowanie Linuksem Chin czy Rosji ma zapewne podłoże i w takich obawach), tych przypadkowych - mnóstwo. Bodajże co dziesiąty z niewinnych domowych komputerów bez wiedzy właścicieli rozsyła spam, wspomaga propagację wirusów, bierze udział w atakach DDOS albo po prostu próbuje wykradać dane.
Audyt
Zwolennicy open-source często twierdzą, że - dzięki swej naturze - programy te są poddawane intensywnym przeglądom kodu. To prawda i kłamstwo. Mogą być. Wbrew obiegowej opinii, mało kto czyta kod używanych przez siebie aplikacji.
Jestem programistą, jestem miłośnikiem Linuksa ale nigdy nie przeglądałem kodu źródłowego Emacsa, Gimpa, Basha, TeXa, Evolution, Xorg i setek innych używanych programów. Jeśli czytam kod (czasem to jednak robię) to albo po to, by móc go uzupełnić (bo czegoś mi bardzo brakuje), albo by lepiej zrozumieć jak działa, albo by się z niego nauczyć jakiejś techniki programowania. W każdym wypadku selektywnie, w takim zakresie, jaki jest mi akurat potrzebny. To wszystko. Zresztą, gdybym chciał przeczytać ze zrozumieniem kod wszystkich używanych aplikacji i ich komponentów, miałbym zajęcie do końca życia.
Dlatego obiegowe stwierdzenia o tysiącach oczu, które weryfikowały każdy program, wydają mi się co najmniej przesadne. Kto konkretnie?
Spektakularny przykład z ostatnich lat to debianowy OpenSSL w którym programista zakomentował instrukcje, na które narzekał analizator kodu, przez co przez ponad półtora roku (od września 2006 do maja 2008) maszyny działające pod kontrolą Debiana i Ubuntu generowały niepełnowartościowe, podatne na łamanie klucze SSL i SSH. Półtora roku, krytyczny ze względów bezpieczeństwa komponent.
W oprogramowaniu komercyjnym nie jest lepiej, oczywiście wszyscy chwalą się procesami wytwórczymi ale wiele firm w ogóle nie robi review kodu, inne wykonują je pobieżnie.
Z mnóstwa potencjalnych przykładów wyłowię mało ważny ale urokliwy i świeży. Odtwarzacze muzyczne Microsoft Zunes efektownie złożyły się w Sylwestra 2008 roku, a błąd naprawdę rzuciłby się w oczy osobie robiącej przyzwoity przegląd tego modułu (a można by jeszcze zauważyć niespójne copy&paste trzysta wierszy niżej).
Wybór
Wszystko powyższe to dygresja. Rzeczywistość jest taka, jaka jest, systemy komputerowe nie są jedynym jej elementem, którego nie mogę w pełni kontrolować. Obrazek przywołałem, by zastanowić się chwilę nad procesem wytwarzania aplikacji.
Skoro rozmaite komponenty stanowią większość budowanego systemu, ich wybór staje się co najmniej tak ważny, jak właściwe kodowanie.
Jak wybierać?
C++, C#, Java, Python, Perl, Ruby, PHP, czy może nawet jakiś język funkcyjny? Windows, Linux, duży Unix, Mac, czy - a co tam - OpenVMS? A jeśli nawet Linux to który - RedHat, Suse, Debian, Ubuntu? PostgreSQL, mySQL, Oracle, DB2, SQLServer, a może jakiś egzotyczny eksperyment od CouchDB po Amazon SimpleDB? Jakie middleware, jaki serwer webowy, jaki parser XML, jaki .... zatrzymam się, bo mógłbym sprokurować kilkunastostronicową litanię.
Nasz ukochany dostawca
Używamy VisualStudio i jego narzędzi, czego w nim nie ma - nie istnieje.
Nie chodzi mi o naturalne pozostawanie przy tym co już znane, oswojone i w czym jesteśmy biegli. To normalne. Mówię o sytuacji, w której - powiedzmy - potrzebując zacząć zarządzać wersjami zespół włącza SourceSafe i już. Przykład celowy, parę razy zdarzyło mi się zdziwić, gdy całkiem rozsądni ludzie w ogóle nie wiedzieli, że są inne możliwości.
Czemu tak bywa dobrze? Cóż, nie tracimy czasu na wybieranie, nie przeżywamy zbędnych wątpliwości, narzędzia Microsoftu, IBM-a czy Oracle do czegoś w końcu się nadają i same ze sobą współdziałają.
To podejście ma także opcje niekomercyjne, choćby dogmatyczny LAMP (Linux, Apache, MySQL, PHP a na doczepkę CVS) albo Ruby on Rails.
Odwrotną stronę medalu widzimy, gdy wśród ogólnie udanego zbioru
produktów trafi się jeden kiepski, którego używanie staje się mordęgą
(SourceSafe przywołałem nieprzypadkowo). Albo gdy trafia się nietypowy
projekt lub komponent i nagle mamy obliczenia numeryczne w
PHP, C++-owy parser logów posługujący się find
i substr
,
generowanie HTML przy pomocy PL/SQL. A przede wszystkim, gdy ważne narzędzia w
ogóle nie są używane, bo do danego stosu się nie załapały (bardzo
długo taki był los issue-trackerów czy mechanizmów komunikacji
zespołowej, na poziomie bibliotecznym wspomnę wyrażenia regularne).
Chybił-trafił
Można wybierać na chybił-trafił. Wbić hasło w Google, otworzyć dwie-trzy odpowiedzi, wybrać tę, która w sercu zagra. Czucie i wiara silniej mówi do mnie niż mędrca szkiełko i oko. Ten CMS jakoś mi się podoba. Ta biblioteka wygląda fajnie. Tę bazę danych mam ochotę wypróbować.
O tym, iż intuicyjne podejmowanie decyzji technologicznych ma poważne wady argumentował niedawno Jan Rychter, pozwolę sobie tematu szerzej nie rozwijać. Dodam jedynie, że uczucia i upodobania bywają szybko zmienne, coś co intuicyjnie gorąco lubiliśmy dwa miesiące temu dziś może budzić irytację.
Jest też i zaleta. Czas poświęcony na wybór.
Statystyka
Skoro cały świat stawia blogi na WordPressie, to i ja na nim postawię. Odwieczne poszukiwanie bezpieczeństwa i dowodu społecznego. Niby logiczne, tylko - czy moje potrzeby naprawdę są takie same, jak wszystkich? Kto to w ogóle jest?
Wybór oparty na popularności jest też zawsze drogą parę kroków z tyłu. Masowo używane produkty nie mogą sobie pozwolić na bycie innowacyjnymi, obciąża je potężny bagaż historii, przyzwyczajeń, zobowiązań, kompatybilności. Główne stado już przeszło, a my dopiero zaczynamy się paść na tej samej łączce.
Szczegółowa analiza
Siadam. Spisuję wymagania. Robię wstępny przegląd wyszukując możliwe kandydatury. Rozpisuję drobiazgowo poszczególne cechy. Rysuję tabelki. Wprowadzam jakieś punktacje. Selekcjonuję czołówkę i dla niej analizuję wymagania jeszcze dokładniej...
Tak się powinno robić, nieprawdaż? Obiektywny, uzasadniony, optymalny wybór.
Wiele razy tak do różnych problemów podchodziłem, dlatego odważę się powiedzieć: lipa. Na pewno jest to sposób na spędzenie dowolnie długiego czasu. A dokonany wybór zwykle jest niewiele lepszy od strzału na chybił-trafił. Przynajmniej, gdy wybieramy coś nowego, narzędzie, w którego używaniu nie mamy doświadczeń.
Bo nie sformułujemy sensownych wymagań. Nie mamy szans. Jeszcze nie wiemy, co tak naprawdę będzie dla nas ważne. Skończymy z listą oczywistości spełnianych przez wszystkie kandydatury (twórca narzędzia CMS nie przegapi konieczności publikowania HTML, a autorzy kompilatora możliwości generowania plików wykonywalnych), spisem niemierzalnych pobożnych życzeń (wygodny w użyciu) i bardzo przypadkową listą spraw totalnie nieistotnych, za to łatwych do weryfikacji (punkty przyznawane edytorom tekstu za ilość formatów graficznych w okienku wstaw obrazek).
Czemu lepiej nie próbować zbyt usilnie? Z wyboru dokonanego na podstawie miesięcznej drobiazgowej analizy bardzo trudno się wycofać.
Głęboka i rozbudowana dyskusja
Przedłużenie poprzedniego kroku. Mądrość zbiorowa. Wymagania gromadzi, spisuje i ocenia zespół. Spotykamy się. Dyskutujemy. Przekonujemy.
Stary slogan mówi, że decyzja podjęta przez komitet jest zwykle nieco gorsza niż decyzja, którą podjąłby najmniej kompetentny jego członek. To przesadna hiperbola ale akurat w warunkach niepewności i niepełnej wiedzy bardzo często tak faktycznie bywa. Z kwestii fundamentalnych spadamy na nieistotne ale zrozumiałe drobiazgi. Urzędnicy Parkinsona debatowali o kosztach papieru, informatycy wybierający system monitorowania sieci skupią się na zgodności strony raportu z XHTML Strict.
Burza mózgów w celu zebrania wymagań i ustalenia scenariuszy wykorzystania jest świetnym pomysłem. Grupowa debata co jest najlepsze, zwłaszcza w dużym gronie, niesłychanie rzadko prowadzi do sensownych rezultatów.
Bikeshed effect: ilość aktywnych dyskutantów i wypowiedzi jest odwrotnie proporcjonalna do złożoności problemu. Było to sformułowane w kontekście dyskusji na listach mailowych i grupach dyskusyjnych, gdzie wątki o ważnych i trudnych decyzjach architektonicznych liczą po kilka wypowiedzi, za to dyskusje o licencjonowaniu, formach zarządzania projektem, sposobach wcinania kodu, nazewnictwie zmiennych czy kolorystyce nawet - po kilkaset, ale sprawdza się i w innych sytuacjach.
Jeśli nad złożonym problemem debatuje dziesięć osób, to zwykle jedna-dwie miały czas przyjrzeć się mu bliżej i głębiej go przemyśleć, pozostali wpadli trzymać rękę na pulsie. Ponieważ chcą jednak jakoś zaznaczyć swój udział, chętnie zaangażują się w debatę o sprawach prostych i zrozumiałych, choć trzeciorzędnych.
No i wychodzimy z wnioskiem, że issue-tracker powinien być zrobiony w Pythonie a nie Perlu (albo odwrotnie), bo to ładniejszy język, musi pokazywać statystyki liczby zgłoszeń jako barcharty, a do tego konieczna jest paczka RPM na Fedorę 8, żeby instalacja poszła szybciej.
Próba
Czas na podejście, w które wierzę. Próba.
Wybieramy jakąś sensownie wyglądającą kandydaturę. Zwykle jest tu potrzebna jakiś minimalna analiza ale ważne, by była zdecydowanie ograniczona czasowo. 2 godziny googlowania przy czymś prostym, dzień-dwa głębszego czytania i notowania przy rzeczy dużej i ważnej.
A potem próbujemy użyć. Nie do hello world. Do czegoś co jest naprawdę. Pobocznego małego projektu, komunikacji w podzespole, czekającego na usprawnienie algorytmu. Trwa to tydzień, dwa, czasem miesiąc, czasem cały mały projekt. Jeśli wybieramy dla zespołu - robimy to w kilka osób.
Różnica między odczuciami wyniesionymi z czytania o narzędziu a wnioskami wynikającymi z jego - nawet krótkiego - używania bywa wręcz szokująca. W obie strony.
Po czym następuje moment decyzji. Jeśli jest dobrze - mamy nasze upragnione narzędzie. Jeśli źle - jesteśmy dużo lepiej przygotowani do wyboru następnego kandydata.
Dwie rzeczy są tu trudne.
Pierwszą jest powiedzenie nie. Już przecież to wdrożyliśmy, zaczęliśmy używać, poznaliśmy - nie szkoda tego czasu i wysiłku? Pomaga zadanie sobie pytania czy naprawdę chcemy z tym pozostać na najbliższe parę lat?
Drugą - powiedzenie tak. A dokładniej: wstrzymanie poszukiwań, pozostanie z rozwiązaniem, które się wstępnie sprawdziło, pogodzenie się z faktem, że wybór pewnie nie jest optymalny, że choć nasza próba jest obiecująca, na pewno istnieje coś lepszego.
Bo tu jest pułapka. Świętego Graala można szukać całe życie, do zrobienia projektu starczy coś, co jest wystarczająco dobre.
Ileż razy tak wpadałem. Instalowałem po kolei osiem programów pocztowych (czytników newsów, IDE, programów do malowania diagramów, systemów zarządzania wersjami, bibliotek sieciowych itd itp), by na koniec stwierdzić, że wszystkie robią niemalże to samo. Testowanie jest przyjemne ale czas mija, a w rękach pozostaje pustka.
Czasem pojawia się pomysł zrobienia kilku równoległych pilotaży. To jest niebezpieczne, efektem może być wojna (szczególnie gdy każdą z prób prowadzi kto inny). Słabsze rozwiązanie akceptowane przez wszystkich lepiej spełni swą rolę niż ideał bojkotowany przez połowę zespołu.
Nie ma żadnego optimum
Ideał wybierającego: wypisać wszystkie możliwości i kryteria, przydzielić punktacje, policzyć, posortować. Mamy! Django zdobyło 7.4 punkta, Ruby on Rails 6.9, Symphony 6.3, a pozostałe frameworki poniżej 6.
Kłamstwo.
Aplikacje posiadają tysiące unikalnych cech, których nijak nie da się sprowadzić do pojedynczego numerka.
Możemy zrobić ładną płachtę na której damy Oracle 2 punkty za cenę, 9 za wydajność, 8 za rozwiązania klastrowe i 4 za jakość bibliotek developerskich, a Postgresowi odpowiedno 10, 7, 1 i 8 (te numerki zresztą też bez sensu, każda poważniejsza próba porównania wydajności dwóch baz danych prowadzi zaraz do setek przypadków w których to jedna, to druga wypada lepiej zależnie od struktur danych, konfiguracji systemowej, wielkości bazy, charakterystyk klientów, ba - prostej ilości RAM). A potem co? Dodać to? Ale w najbliższym projekcie wydajność nie jest dla nas tak ważna, za to cena krytyczna. To może cenie dajmy wagę 10, a wydajności 2? Ale dlaczego akurat tyle? Może 8 i 3? A może 17 i 1?
Patrzyłem kiedyś na rysunek, gdzie dla paru konkurujących systemów rozpisano i oceniono numerycznie setkę cech - i ktoś wpadł na pomysł zaprezentowania ich jako wykresu. Cechy na osi rzędnych, oceny na osi odciętych. Chaotyczne funkcje przecinające się w kilkudziesięciu miejscach.
Która jest większa?