Ból informatycznie nieugaszony
3 marca 2005
| Czy zastanawialiście się może, dlaczego w dłuższej perspektywie czasu, tworzenie oprogramowania po prostu boli? Dlaczego jako programiści musimy wykonywać tyle powtarzających się, niepotrzebnych zadań? Dlaczego setki razy piszemy kod robiący w sumie to samo? Dlaczego projektanci i analitycy zaczynają sprowadzać wszystkie modele rzeczywistości do jednego modelu? Jeśli przypadkiem masz szansę być szczęśliwym programistą, przed którym stoją same interesujące wyzwania, albo analitykiem stykającym się z coraz to nowymi przypadkami problemów - możesz spokojnie zakończyć lekturę już tutaj - jeśli jednak zdecydowałeś się czytać dalej - wiedz jedno - jesteś w absolutnej mniejszości. Od kilkudziesięciu już lat języki programowania i inne używane przez nas narzędzia systematycznie zwiększają stopień automatyzacji wykonywania prostych czynności: począwszy od języków asemblerowych, które uwolniły nas od konieczności ręcznego wpisywania kodu maszynowego, poprzez strukturalne języki niskiego poziomu, dzięki którym programowanie zaczęło wreszcie przypominać tworzenie z "klocków", poprzez rozwój bibliotek strukturalnych aż po pojawienie się obiektów i komponentów - cały czas chodziło o eliminację powtarzalnych (prostych jedynie w teorii - a w praktyce podatnych na błędy) instrukcji w kodzie. Jedna prosta klasa po rozwinięciu jej do postaci asemblera zazwyczaj szokuje nas ilością instrukcji, samych w sobie niezbyt skomplikowanych, ale przez swą ilość nieomal niemożliwych do opanowania. Nawet jeśli udałoby się nam te instrukcje samemu napisać, to istnieje olbrzymie prawdopodobieństwo że ilość popełnionych błędów byłaby duża. Paradoksalnie, ilość linii kodu pisanych przez programistów pozostaje na mniej więcej stałym poziomie - przejście do języka wyższego rzędu oznacza zazwyczaj "atakowanie" trudniejszych i bardziej złożonych problemów. Im bardziej zautomatyzowany język, tym większe problemy atakujemy i tym więcej powtarzalnego kodu piszemy. Kółko się zamyka. Jednym z powodów dla których piszemy powtarzalny kod jest istnienie w dzisiejszej (i nie tylko) informatyce miejsc, gdzie stykają się ze sobą dwa koncepcyjnie różne światy, i problem który przypadkiem zahacza o oba z nich w miejscu styku okazuje się być pocerowany takim właśnie nudnym kodem. Na jeden z tych „szwów” zwrócił mi uwagę na grupie dyskusyjnej Aq wskazując na różnicę w świecie obiektów i świecie baz danych - właśnie w tych pozszywanych miejscach galanteria oprogramowania zazwyczaj mocno trzeszczy. Warto sobie uświadomić, jak skomplikowanym i wrednym zadaniem jest mapowanie pełnego modelu obiektowego w aplikacji na rzeczywistość bazy danych. Wystarczy wyobrazić sobie obiekt reprezentujący klienta i posiadający szereg różnego rodzaju atrybutów, cech, które także modelujemy jako obiekty. O ile enkapsulacja i hermetyzacja pozwala nam w bardzo prosty sposób chronić prywatne cechy klienta przed światem, o tyle zapewnienie światu bazy danych takiej samej logiki staje się nieomal niemożliwe. Owszem, można zabezpieczyć się przemyślnymi mechanizmami wyzwalaczy i procedur, lecz zabezpieczenie takie ma dyskusyjną skuteczność, ponadto jego implementacja często nie jest zadaniem trywialnym. To tylko pierwszy z brzegu przykład, a każdy gdy pomyśli dokładniej uświadomi sobie cały szereg problemów wynikających z owej dualności. Oczywiście nie zmienia to faktu że programy tworzy się dziś mniej lub bardziej obiektowo, ale poprawne zapisanie obiektu do bazy danych wymaga mocno strukturalnego i hierarchicznego kodu którego instrukcje muszą być wykonywane wedle założonego z góry porządku. Najpierw zapisujemy rekord nadrzędny, otrzymujemy jego klucz główny, potem tworzymy rekordy podrzędne, następnie podrzędne do podrzędnych. Pilnujemy integralności referencyjnej zarówno w samym obiekcie, jak i na bazie danych burząc tym samym ideę hermetyzacji obiektu... Owszem, da się tworzyć bazy danych obiektowo... tylko dlaczego tak rzadko taka możliwość jest używana? To chyba podsumowuje całą sprawę... Innym szwem jest wspomniana ostatnio dualność podejść analitycznych. Świat jest obiektem, czy procesem? Modelowanie rzeczywistości jako procesu, a następnie przekładanie jej na obiektowy obraz analizy i projektu już samo z siebie stanowi tworzenie kolejnych szwów na zdrowym ciele oprogramowania. Każde „przejście koncepcyjne” jest zazwyczaj stratne, więc musimy bardzo uważać, aby nie stracić dziedziny problemu sprzed oczu. W tym celu zazwyczaj okazuje się konieczne dołożenie do obiektowego modelu kilku klas odpowiedzialnych za śledzenie i nadzór nad procesem, czyli de facto dodawanie "głupiego kodu". Dodajmy tutaj: kodu bardzo podatnego na błędy. O ile łatwo zaobserwować złe zachowanie obiektów odwzorowujących zjawiska (byty) naturalne (np. wynik działania obiektu stoi w sprzeczności z rzeczywistym zachowaniem się bytu), o tyle obiekty implementujące abstrakcyjne byty (żeby mi ktoś nie myślał że piszę o klasach abstrakcyjnych, bo to zupełnie co innego!) są nieomal niemożliwe do weryfikacji. Po pewnym czasie dochodzimy do takiego znudzenia, ze analizując kolejny workflow wiemy, że tu umieścimy notyfikację, a tu rozgałęzienie. Tu znajdzie się podproces, a tu zaimplementujemy obiekt nadzorujący poprawność przebiegu. Projekty zaczynają się powtarzać w swej nieelegancji rozwiązań zszywanych na siłę. Gdyby dłużej się zastanowić, można by znaleźć dodatkowe miejsca, gdzie systemy informatyczne aż trzeszczą - ot, choćby interfejsy użytkownika które najczęściej projektowane są już po określeniu funkcjonalności, i które muszą się wraz z nią zmieniać. W praktyce każda zmiana funkcjonalności oznacza przebudowę interfejsu, lecz tak naprawdę nikt nie zmienia zasady działania programu tylko dlatego, aby interfejs był ergonomiczniejszy. Twórcy interfejsów nie mają najczęściej nic do gadania, pomimo że tak naprawdę odbiór programu zależy w 80% od wygody jego użytkowania - ergo od jego interfejsu. Klasycznym przykładem jest tutaj Linux, który wedle moich szacunków mógłby mieć kilkukrotnie większe udziały w rynku, lecz większość jego potencjalnych użytkowników odstraszają właśnie te szwy, łączące nieomal perfekcyjne mechanizmy systemowe z dość topornie naśladującymi Windowsa interfejsami. Jeśli jednak przyjrzeć się owym problemom bliżej, od razu zauważymy że stworzenie dobrego programu i stworzenie dobrego do niego interfejsu to zadania sprzeczne same w sobie. Dobry interfejs wymaga od nas bowiem zupełnie innego podziału funkcji, niż wynikałoby to z projektu systemu. Tworzymy programy bacząc na możliwości maszyny: czas życia zmiennych, obsługę wyjątków, sterowanie przebiegiem programu, obsługę pętli - to wszystko z punktu widzenia maszyny (bo inaczej programować się nie da), lecz człowiek myśli zupełnie inaczej, i zupełnie inaczej grupuje informacje. Dopasowanie interfejsu do sposobu myślenia człowieka wymagać od nas może rozbijania logiki pomiędzy moduły, aby w ten sposób reagować na ludzką nawigację w aplikacji. Już nie wystarczy wielka pętla for analizująca jakieś dane - być może część owej obróbki trzeba zrobić na zupełnie innym ekranie aplikacji... i zaczynają się schody, czyli kolejne linie kodu, pisanego tylko po to, aby nasi użytkownicy byli szczęśliwi... Na koniec warto postawić pytanie, co zrobić z tymi wszystkim problemami w produkcji oprogramowania? Pozostaje tylko jedna odpowiedź: pokochać. Bo (jak to powiedział Frederic Brooks w The Mythical Man-Month: Essays on Software Engineering, 20th Anniversary Edition) nie ma srebrnych kul, którymi moglibyśmy zastrzelić ową bestię, jaką jest ból tworzenia oprogramowania... MB |
|
|
|
Ostatnia modyfikacja: 7 maja 2005 (c) Michał Bielamowicz, Kraków 2005 - 2006 e-mail: michal(at)bielamowicz.info |
