Wątpliwości dot. rozwiązań open-source
Oprogramowanie o otwartym kodzie źródłowym, jak by się mogło wydawać, budzi ogromny entuzjazm zarówno wśród programistów, jak i użytkowników. Programiści przykładowo mają możliwość korzystania z potencjału drzemiącego w ogólnodostępnym dla wszystkich kodzie źródłowym. W przypadku zaś darmowych narzędzi i bibliotek – w znaczący sposób zmniejszają one koszty wytwarzania oprogramowania. Jakby tego było mało, można liczyć na pomoc przyjaźnie nastawionej społeczności innych entuzjastów, która gotowa jest pomóc nawet w najcięższych przypadkach. Jak by się mogło wydawać, również użytkownicy oprogramowania o otwartym kodzie źródłowym powinni być wielce usatysfakcjonowani, ponieważ nie dość, że mają produkt za darmo, to jeszcze mają świadomość, iż został on stworzony przez prawdziwych specjalistów, pracujących nad rozwojem programu “z potrzeby serca”.
Niestety, jak to w życiu bywa, pozory mylą i powyższy obraz jest czysto teoretyczny. Nie będę omawiać wątpliwości dot. aspektów użytkowych oprogramowania open-source, ponieważ jest to temat na oddzielny post. Chciałbym skupić się na zagadnieniach istotnych z punktu widzenia programisty oraz podać konkretny przykład, który mnie osobiście zabolał.
W ostatnim czasie spotkałem się z opinią zawodowego programisty, iż korzystanie z rozwiązań open-source w poważnym komercyjnym projekcie może być ryzykowne, kosztowne, a nawet szkodliwe. Uzasadniając swoje zdanie, programista ten przedstawił kilka konkretnych zarzutów:
Nikt osobiście nie bierze odpowiedzialności za zagwarantowanie rozwoju, uaktualnień oraz wsparcia dla rozwiązań open-source. Podany został przykład sytuacji, gdy w pracach nad komercyjnym projektem szeroko wykorzystywano darmową bibliotekę. W pewnym momencie wydana została nowa wersja biblioteki, całkowicie niekompatybilna z poprzednią. Autor biblioteki całkowicie “odciął” się od starej wersji i zaprzestał jej utrzymywania. Zespół stanął przed dramatycznym wyborem: korzystać ze starej wersji narażając się na wszelkie kłopoty z tym związane, czy całkowicie przepisać projekt, tak by korzystał z nowej wersji. Wybrano to drugie rozwiązanie, co z punktu widzenia rozwoju stanowiło półroczny przestój.
Wsparcie społeczności również nie jest czymś, co może być w jakikolwiek sposób zagwarantowane. Czasami bywa też tak, że projekt “umiera” i nikt, z autorem włącznie, już się nim nie interesuje. Wtedy nawet nie wiadomo do kogo zwrócić się w przypadku problemów.
Otwarty kod źródłowy wcale nie oznacza szybszego reagowania na błędy. Znane są przypadki, gdy autorzy całkowicie ignorowali błędy znajdowane przez użytkowników. Programiści musieli poprawiać znaleziony przez nich samych błąd w bibliotece samodzielnie kompilując każdą kolejną jej wersję, tak aby nadawała się do użytku w ich projekcie. Skutkiem tego było to, iż zespół przeznaczony do prac nad projektem de facto pracował nad dwoma projektami: właściwym projektem + biblioteką, która była im potrzebna.
Dokumentacja w przypadku open-source to jest coś, co może istnieć, ale wcale nie musi.
Opublikowany przez autora kod źródłowy może być w postaci kompletnie nieutrzymywalnej. I na przykład może się wcale nie kompilować. Takie przypadki też istnieją.
Wyżej wymieniłem najważniejsze, potencjalne problemy, jakie można na siebie sprowadzić korzystając z rozwiązań open-source. Oczywiście nie należy generalizować i postrzegać wszystkich rozwiązań przez pryzmat tego typu kłopotów. Niemniej jednak zamiast entuzjastycznie skakać z radości, warto mieć świadomość ryzyka.
Osobiście boleśnie doświadczyłem pierwszego i czwartego z opisywanych problemów. W ramach pracy magisterskiej tworzyłem proste narzędzie do symulacji błędów działające w trybie jądra systemu Linux. Nie wnikając za bardzo w szczegóły, jest to rozwiązanie zbliżone koncepcyjnie do klasycznych debugger’ów, jednakże zamiast wykorzystywać API systemu operacyjnego (kwestie wydajnościowe w przypadku narzędzi programowej symulacji błędów) implementuje własne mechanizmy modyfikujące procedury obsługi przerwań poprzez patch’owanie kodu jądra “w locie”. Bazowym mechanizmem w tym rozwiązaniu jest ustawianie brakpoint’ów sprzętowych. Jądro udostępnia do tego celu elegancką funkcję register_user_hw_breakpoint. Super. Tylko, że ta funkcja, choć należy do API jądra od wersji 2.6.33 jest nieudokumentowana. Próżno szukać jej opisu na stronie dedykowanej API na kernel.org/doc. Jedynym dokumentem jest chyba artykuł z Linux Symposium dostępny w wielu miejscach w internecie (m.in. http://kernel.org/doc/ols/2009/ols2009-pages-149-158.pdf), który stanowi wstęp koncepcyjny autorstwa deweloperów odpowiedzialnych za implementację mechanizmu.
Aby funkcja utworzyła breakpoint sprzętowy na instrukcji wykonywalnej, jako wielkość należało jej podać stałą HW_BREAKPOINT_LEN_1. Tak było w wersjach jądra od 2.6.33 do 2.6.35. Problem zaczął się, gdy przetestowałem swój program na nowszych wersjach. Funkcja po prostu przestała działać. Nigdzie nie udało mi się znaleźć żadnych informacji, czy, jak i dlaczego interfejs uległ zmianie. Co gorsze, nigdzie w jądrze nie było wywołań tej funkcji z takim zestawem parametrów, jakiego używałem ja. Nie było więc niczego, na czym można by się wzorować. Przyznam, że straciłem sporo czasu analizując kod jądra w poszukiwaniu jakichkolwiek wskazówek, co mogło się stać. Miałem szczęście. Znalazłem. Okazuje się, że od wersji jądra 2.6.36 w przypadku pułapek sprzętowych, rozmiar musi być ustawiony jako sizeof(long), a nie jako wartość ww. stałej. Świetnie. Taka mała, ale niesamowicie złośliwa, nieudokumentowana, funkcjonalna zmiana w interfejsie API. Chyba jedna z gorszych rzeczy, jakie mogą spotkać programistę. Ale ja i tak miałem szczęście, moje rozwiązanie jest stosunkowo proste. Wywoływałem tę funkcję tylko 2 razy. Aż boję się pomyśleć co by było, gdyby coś takiego zdarzyło się podczas rozwoju naprawdę dużego projektu.
Zwolennicy open-source oraz członkowie społeczności deweloperów jądra mogą w tym miejscu zarzucić mi, że jestem amatorem, bo modyfikacja ta na pewno gdzieś jest opisana w jakimś commicie do repozytorium kodu jądra. A poza tym, to sam sobie jestem winien, bo interfejs programistyczny jądra jawnie nie gwarantuje kompatybilności pomiędzy różnymi wersjami jądra zarówno na poziomie funkcji jak i ABI. Owszem, wiem o tym. Tylko, czy to przypadkiem nie jest kolejne potwierdzenie, że stosowanie rozwiązań open-source może (oczywiście nie musi) wiązać się z dodatkowymi, nieprzewidywalnymi kłopotami?