JAVA exPress logo Published on JAVA exPress (http://www.javaexpress.pl)

Nie ma nic za darmo - biblioteki Open Source

Tomasz Skutnik

Issue 3 (2009-03-08)

Po co nam Open Source?

Stosowanie bibliotek o otwartym kodzie źródłowym (open source) podczas programowania w języku Java jest codzienną praktyką. Przeciętny system zbudowany w technologii J2EE zawierać może od kilku do kilkudziesięciu bibliotek pomocniczych. Duża część z nich to biblioteki open source, które można zgrać bezpośrednio z Internetu i użyć we własnym projekcie – zwykle bez dodatkowych opłat bądź ograniczeń licencyjnych.

Zalety stosowania bibliotek o otwartym kodzie źródłowym są dość oczywiste:

- brak bądź niewielkie koszty nabycia biblioteki,

- wygoda programowania – szczególnie debugowania,

- możliwość szybkiego naprawiania błędów ujawniających się podczas wdrożenia i eksploatacji systemu,

- lepsze zrozumienie sposobu działania systemu jako całości.

Z doświadczeń firmy e-point SA w tworzeniu i utrzymaniu systemów informatycznych wynika, że główną korzyścią stosowania bibliotek open source jest posiadanie kodu źródłowego. Przede wszystkim ze względu na możliwość szybkiej analizy i usuwania błędów wykrywanych podczas eksploatacji systemu. Pozostałe korzyści mają charakter drugorzędny.

Nie zawsze jednak decydując się na użycie w projekcie biblioteki o otwartym kodzie źródłowym zdajemy sobie w pełni sprawę z konsekwencji, jakie taka decyzja pociąga za sobą. Szczególnie jeśli uwzględni się problemy występujące podczas utrzymania i konserwacji systemów informatycznych w dłuższym horyzoncie czasowym.

Przegląd problemów związanych ze stosowaniem bibliotek open source

Musimy zdawać sobie sprawę, że decyzja o użyciu konkretnej biblioteki ma najczęściej charakter ostateczny. Zjawisko to, kojarzone zwykle z dostawcami oprogramowania komercyjnego, określa się mianem "vendor lock-in". Oznacza to, że również w przypadku oprogramowania open source, potencjalny koszt wymiany dowolnej biblioteki pomocniczej po wdrożeniu systemu jest bardzo duży. Dla istotnych komponentów – na przykład warstwy prezentacji czy warstwy dostępu do danych (O/R mapping) – wymiana taka jest zupełnie nieopłacalna z biznesowego punktu widzenia.

Jednym z najważniejszych zagadnień utrzymania systemów krytycznych biznesowo jest możliwość szybkiej analizy i usuwania błędów napotkanych we wdrożeniach produkcyjnych. Często zdarza się, że błąd ujawnia się nie w oprogramowaniu wytworzonym przez nas, lecz w bibliotece pomocniczej. Posiadanie w takiej sytuacji jej kodu źródłowego jest niezbędne do szybkiego znalezienia i usunięcia problemu.

Podstawowym grzechem popełnianym przez programistów i kierowników projektów jest nadmierne zaufanie pokładane w binarnych wersjach bibliotek o otwartym kodzie źródłowym. W sytuacjach awaryjnych, kiedy potrzeba dostępu do kodu źródłowego staje się paląca, pośpieszne szukanie go w Internecie może zakończyć się niepowodzeniem. Niefrasobliwe uzależnienie projektu od binarnej wersji biblioteki, bez jej minimalnej choćby weryfikacji, powodować może różnorakie problemy.

Najbardziej oczywistym z nich jest brak kodu źródłowego do biblioteki, której używamy. Zwykle po dołączeniu biblioteki do projektu programiści przestają śledzić jej dalszy rozwój. Z czasem, gdy publikowane są kolejne wersje, zarządzający projektami open source mają zwyczaj kasować wersje archiwalne (zarówno binarne i źródłowe) ze stron WWW projektu. Wtedy zdobycie lub odtworzenie kodu źródłowego do binarnej wersji biblioteki używanej w naszym projekcie (o ile zapobiegliwie nie zgraliśmy go wcześniej) nie jest ani łatwe, ani szybkie.

Jeżeli na stronach projektu nie można znaleźć źródeł, to pierwsze kroki kierujemy do repozytorium kodu źródłowego. Tu jednak czekać na nas mogą niezbyt miłe niespodzianki. Pierwszą z nich jest brak samego repozytorium. Dzieje się tak zazwyczaj, gdy autorzy projektu open source decydują się na migrację kodu źródłowego pomiędzy systemami kontroli wersji (np. z CVS do SVN). Jeżeli wersja, której używamy, jest na tyle wiekowa, że była rozwijana jeszcze w starym repozytorium, może się zdarzyć, że nie jest ono już nigdzie dostępne – co w połączeniu z brakiem wersji źródłowej na stronach WWW projektu istotnie utrudnia odnalezienie źródeł. Kolejnym problemem bywa brak w repozytorium tagu odpowiadającego interesującej nas wersji oprogramowania. Najczęściej jest to spowodowane błędem ludzkim, tj. autorzy projektu open source czasem zapominają nadać właściwy tag w repozytorium.

Przyjmijmy, że udało nam się jednak zdobyć wersję źródłową projektu. Nie oznacza to końca naszych problemów. Bywa, że zgrany przez nas kod źródłowy po prostu się nie kompiluje. Zwykle błędy kompilacji wynikają z niezgodności wersji JDK lub są to trywialne błędy syntaktyczne (np. brak średnika itp.). Zagadką pozostaje wtedy, kto i w jaki sposób zbudował binarną wersję biblioteki. Na pewno nie powstała ona ze źródeł, które mamy do dyspozycji.

Czasami zdarza się, że budowana przez nas ze źródeł biblioteka kompiluje się prawidłowo, lecz wykonanie dołączonych do niej automatycznych testów kończy się niepowodzeniem. Teoretycznie moglibyśmy zastąpić pierwotnie używaną przez nas wersję biblioteki nową, skompilowaną ze źródeł. Pojawia się jednak w takiej sytuacji pytanie – czy na pewno źródła, z których skompilowaliśmy własną wersję binarną, są prawidłowe? Kto ma rację? Czy to kod źródłowy działa prawidłowo, a testy jednostkowe są nieaktualne, czy może to autorzy projektu opublikowali nową wersję biblioteki z wprowadzonym błędem, bez wcześniejszego sprawdzenia czy dotychczasowe testy kończą się prawidłowo?

Bywa, że projekt open source buduje się z wersji źródłowej, testy jednostkowe kończą się powodzeniem, natomiast wygenerowany artefakt binarny nie odpowiada oczekiwanej przez nas wersji. Przykładowo: paczka źródłowa oznaczona jako 2.3 buduje artefakt oznaczony jako 2.4. Bardzo trudno w takiej sytuacji dociec, gdzie leży problem – czy w niewłaściwym nazwaniu paczki źródłowej, czy w niespójności źródeł.

Istnieje też cała klasa mniej krytycznych problemów utrudniających zbudowanie prawidłowej wersji binarnej z posiadanych źródeł, takich jak np. brak domyślnego mechanizmu budowania. O ile w przypadku prostej biblioteki własnoręczne napisanie prostego skryptu budującego nie stanowi problemu, to dla bardziej złożonych projektów – kompilujących klasy warunkowo, bądź modyfikujących kod wynikowy – uzyskanie własnej wersji binarnej może stanowić duże wyzwanie. Dzieje się tak zwykle, gdy autorzy projektu nie mają sformalizowanego, zewnętrznego mechanizmu budowania i publikacji biblioteki, a polegają jedynie na konfiguracji własnego, lokalnego IDE.

Kiedy już zbudujemy wersję binarną ze źródeł, testy jednostkowe kończą się sukcesem oraz, na pierwszy rzut oka, artefakt binarny jest tym, co chcieliśmy uzyskać – wciąż nie musi oznaczać to końca naszych kłopotów.

Czasami, po zamianie oryginalnej wersji binarnej biblioteki na wersję skompilowaną ze źródeł, aplikacja przestaje działać sygnalizując awarię wyjątkami typu ClassNotFoundException lub IncompatibleClassChangeError. Przyczyną takiego stanu rzeczy okazują się najczęściej obce klasy (tj. pochodzące z innych projektów) dołączone do pierwotnego artefaktu binarnego . I znowu mamy do rozwiązania zagadkę: kto, w jaki sposób i z jakich źródeł zbudował wersję binarną biblioteki, której używamy, oraz w jaki sposób znalazły się tam klasy z innych bibliotek.

Najbardziej nieprzyjemnym i najtrudniejszym do zdiagnozowania problemem jest sytuacja, w której (pomimo prawidłowej kompilacji, testów i uruchomienia we własnym projekcie) okazuje się, że zbudowana przez nas biblioteka zachowuje się inaczej niż wersja pierwotnie zgrana z Internetu. Przykładowo: dla określonych parametrów wejściowych jakaś funkcja zamiast zwracać wartość null rzuca wyjątek. Takie niespodziewane zmiany kontraktu mają tendencję do nakładania się na błędy, które właśnie próbujemy diagnozować, znacząco komplikując proces naprawy.

Oprócz opisanych powyżej problemów, mających charakter techniczny, projekty open source mają również szereg innych, mniej uciążliwych wad – głównie w sferze zarządzania. Szczególnie dotyczy to małych, jednoosobowych projektów. Zdarza się bowiem, że gdy projekt spoczywa na barkach jednego człowieka – w sytuacji, gdy ten znajdzie mocno angażującą go pracę, zmieni zainteresowania zawodowe (np. ulubiony język programowania), itp. – rozwój biblioteki zamiera. Nie pojawiają się nowe wersje, błędy nie są naprawiane, a poprawki przesyłane przez użytkowników nie są przeglądane i dołączane do istniejącego kodu źródłowego.

Przed użyciem każdej biblioteki open source (zwłaszcza w zastosowaniach komercyjnych!) należy również bezwzględnie sprawdzić licencję, która dołączona jest do projektu. Część bibliotek nie określa jawnie, na jakiej licencji można ich używać – nagłe pojawienie się licencji, której warunki wykluczają użycie jej w naszym projekcie – w momencie, gdy mamy już system wdrożony produkcyjnie – może narazić nas na dużą i kosztowną modyfikację istniejącego już i przetestowanego oprogramowania.

Decydując się na wybór biblioteki warto również zwrócić uwagę na jakość dokumentacji, aktywność użytkowników i autorów oprogramowania (listy dyskusyjne, wiki itp.). Co prawda, posiadanie i dobra znajomość kodu źródłowego biblioteki, której chcemy użyć, pozwala nam uniezależnić się częściowo od dokumentacji i autorów. Niemniej dobra dokumentacja techniczna, samouczki i przykłady użycia znacznie ułatwiają pracę z konkretną biblioteką.

Jak sobie radzić w przypadku problemów?

Opis wszystkich powyższych problemów nie byłby kompletny bez próby oszacowania ich skali i zasugerowania możliwych środków zaradczych. W e-point SA przeprowadziliśmy analizę części aktywnych projektów pod kątem użycia zewnętrznych bibliotek o otwartym kodzie źródłowym, ze szczególnym uwzględnieniem problemów opisanych w poprzedniej sekcji. Przegląd obejmował 22 najbardziej aktywne projekty, rozwijane na 43 gałęziach rozwojowych, które korzystały ze 173 różnych bibliotek o otwartym kodzie źródłowym w 253 różnych wersjach.

Wnioski z analizy były pesymistyczne: 33% binarnych wersji bibliotek o otwartym kodzie źródłowym jest obarczona którymś z wcześniej omówionych problemów.

Co to oznacza? Oznacza to, że w przypadku długoterminowego utrzymania systemów IT, natknięcie się na któryś z powyższych problemów jest jedynie kwestią czasu. Pytanie nie brzmi "czy może nas to spotkać?" lecz "kiedy nas to spotka?".

A co jeżeli jednak nie możemy znaleźć źródeł wcześniejszej wersji biblioteki na stronach projektu? Gdzie ich szukać? Jak sobie radzić, jeśli pomimo poszukiwań nie uda nam się ich znaleźć?

Pierwszym, często najskuteczniejszym krokiem który można wykonać, jest poszukiwanie źródeł w wyszukiwarkach internetowych, takich jak np. Google, Yahoo. Bywa, że gdzieś w Internecie ktoś posiada archiwalną wersję źródeł, których szukamy, a które nie są już dostępne na stronach projektu. Jeżeli takie poszukiwania zawiodą, można również spróbować sprawdzenia w serwisie archive.com. Przechowuje on archiwalne strony WWW i możliwe, że wśród nich znajdziemy także wersję archiwalną biblioteki. Oczywiście, jeżeli jest dostępne publiczne SCM, to jest ono najlepszym miejscem aby zdobyć kod źródłowy. Można zgrać z niego źródła na podstawie oznaczenia tagiem odpowiadającego interesującej nas wersji (pod warunkiem, że taki tag istnieje i jest prawidłowy).

Jeżeli żaden z powyższych sposobów nie doprowadził do znalezienia źródeł, można skorzystać z pomocy dekompilatorów. Nie zawsze uda nam się tak zdekompilować klasę, aby można było w niej naprawić błąd, a następnie ponownie ją skompilować i zaktualizować archiwum biblioteki. Ale można na podstawie zdekompilowanego kodu spróbować przynajmniej znaleźć przyczynę błędu i skonstruować jego tymczasowe obejście.

Jeżeli znaleźliśmy się w sytuacji, w której musimy naprawić błąd w bibliotece, której źródeł nie mamy, możemy również podjąć jedno z dwóch dodatkowych działań rozwiązujących problem: próbować zmigrować oprogramowanie do nowszej wersji biblioteki, której kod źródłowy mamy, bądź całkowicie usunąć daną zależność z projektu. Które z nich wybrać? Zależy to od tego, z jak dużej liczby funkcji danej biblioteki korzysta nasz system oraz potencjalnych niezgodności z nowszymi wersjami biblioteki. Jeżeli korzystamy z niewielu funkcji biblioteki zewnętrznej (w stosunku do całkowitej wielkości naszego systemu), to najkorzystniejsze może być napisanie tych kilku funkcji bibliotecznych na nowo i dołączenie ich do własnego kodu źródłowego.

Jak wobec tego najlepiej zabezpieczyć się przed wszystkimi opisanymi wcześniej kłopotami albo przynajmniej zminimalizować ich niekorzystny wpływ na nasz projekt? Z doświadczeń firmy e-point SA wynika, że jedynym naprawdę skutecznym sposobem na uniknięcie powyższych komplikacji jest kompilowanie ze źródeł wszystkich używanych w projekcie bibliotek open source.

Oczywiście, nie jest to ani tak proste, ani tak łatwe jak zgranie i bezpośrednie użycie skompilowanej wersji binarnej biblioteki open source. Początkowy (wcześniej niemal zerowy) koszt dołączenia biblioteki do naszego rozwiązania nagle zaczyna być zauważalny. Jeśli zamierzamy skorzystać z kilkunastu bibliotek i każdą z nich chcemy skompilować własnoręcznie, to wtedy – w zależności od szybkości budowania poszczególnych bibliotek i napotykanych po drodze przeszkód – czas spędzony przez programistów na dołączaniu bibliotek do projektu zaczyna być zauważalny w budżecie i harmonogramie.

W przypadku większej liczby projektów, czy prowadzenia i utrzymywania wielu projektów na wielu gałęziach rozwojowych równocześnie, bez mechanizmów automatyzujących budowanie i centralne zarządzanie lokalnie skompilowanymi bibliotekami nie da się w zasadzie zapewnić odpowiednio szybkiej reakcji na błędy mogące ujawniać się w systemach produkcyjnych. Ich zaprojektowanie, wdrożenie, utrzymanie i udokumentowanie procesu posługiwania się nimi jest jednak zdaniem długotrwałym i dość kosztownym. Dlatego każdy z zespołów tworzących oprogramowanie sam musi sobie odpowiedzieć na pytanie: w jaki sposób ma zarządzać źródłami zależności projektu oraz jaką część budżetu i harmonogramu może na to poświęcić.

Source: http://www.javaexpress.pl/article/show/Nie_ma_nic_za_darmo__biblioteki_Open_Source