Kompatybilność wsteczna
Czy wsteczna kompatybilność faktycznie jest taką dobrą rzeczą? Nad tym że często jest bardzo potrzebna (czasem wręcz niezbędna) nikt nie dyskutuje. Jednak czy zawsze warto walczyć o nią za wszelką cenę?
Co uzyskujemy trzymając się kompatybilności wstecznej? Oczywiście “stabilność”. Klienci/użytkownicy mogą być pewni, że wybierając nasz twór nie zostaną zaskoczeni, ani zostawieni na lodzie. Że rzecz raz zrobiona (plik raz zapisany) będzie działała prawidłowo (otwierał się prawidłowo) niezależnie którą wersję zainstalują (byle tylko była ona nowsza) i ile czasu minie od jej stworzenia. W związku z tym pieniądze raz wydane na stworzenie czegoś w oparciu o naszą technologie nie zostaną utracone przy następnej aktualizacji oprogramowania.
Jakie korzyści mamy z wyboru drugiej drogi? Przede wszystkim rozwój. Nasze dzieło może podlegać bardzo dynamicznej ewolucji, albo nawet daleko idącym rewolucjom. Nie trzeba martwić się złymi decyzjami podjętymi w przeszłości i można skupić się wyłącznie na poprawianiu i ulepszaniu. Oczywiście nie należy przesadzić, aby nie stworzyć zupełnie nowej rzeczy luźno związanej z poprzednimi wersjami. Kolejnym zyskiem jest, że nasz twór nie rozrasta się do niemożliwych rozmiarów, co w pewnym momencie zawsze następuje, jeśli koniecznie chcemy wspierać wszystkie starsze wersje standardów i rozwiązań. Co więcej, w ten sposób nie zawsze zyskujemy na funkcjonalności, ponieważ często nowe funkcje po prostu dublują stare, robią to samo, ale w inny, nie zawsze gwarantujący kompatybilność wsteczną, sposób.
Na początek, jako przykład drugiego podejścia weźmy język programowania Python w wersji 3. Nie jest to przykład ekstremalny jak w drugim przypadku, ale tym się akurat zajmuję.
W języku tym twórcy doszli do wniosku, że czas pewne nie do końca dobrze zrobione rzeczy poprawić raz, a dobrze. I w ten sposób właśnie powstała trzecia wersja języka. Zmiany nie są drastyczne, nadal jest to ten sam język, ale programy napisane dla Pythona 2.x po prostu nie działają w najnowszej wersji interpretera, i vice versa (nie jest to 100% regułą, ale wyjątki tylko ją potwierdzają ;)). W tej chwili, aby umożliwić programistom łagodniejsze przejście do nowej wersji, duża ilość funkcji została backportowana z wersji 3.1 do najnowszej stabilnej 2.7. Ma to również zapewnić jako taką kompatybilność, ale nadal nie jest to 100%.
Co stracono? Olbrzymia większość programów nie działa na nowej wersji kompilatora. Czasem, aby przeportować program na nową wersję wystarczy uruchomić automatyczny konwerter, a czasem zmiany sięgają głęboko w kod. Zwłaszcza dotyczy to bibliotek pisanych w C lub C++, gdyż API Python’a dla C/C++ uległo znacznym zmianom.
Co zyskano? Znacznie oczyszczono język. Przy starszych wersjach pojawiały się komentarze, że Python jest nieelegancki ponieważ jedną rzecz można zrobić na kilka sposobów i nigdy nie wiadomo który z nich jest najlepszy. Dodatkowo poprawiono parę rzeczy które kiedyś wydawały się dobre, a po czasie okazały się już nie tak fantastyczne.
Jako przykład trzymania się kompatybilności wstecznej za wszelką cenę wezmę coś z czym stykamy się na co dzień – architekturę x86. Jest to fantastyczny przykład ponieważ nawet najnowsze procesory, 6-cio rdzeniowe, 64-bitowe, z pamięcią cache trzeciego poziomu, ze zintegrowanymi kontrolerami pamięci, są zgodne z pierwszymi maszynami 8086. Dlaczego? Bo przecież komuś może któregoś dnia przyjśc do głowy, że on chce odpalić MS-DOS’a z dyskietek. I co wtedy? Przecież nie każdy trzyma w szafie starego blaszaka z AMD K6/Pentium Pro którego dostał na komunię.
W związku z tym ta architektura codziennego użytku, cierpi na bardzo wiele rzeczy które kiedyś wydawały się świetnymi pomysłami, a później się okazało, że jednak takie cudowne nie są. Za przykład niech posłuży segmentacja pamięci z której nie korzysta żaden nowoczesny system operacyjny, ale ze względów historycznych cały czas jest obecna. Takich przykładów można by znaleźć wiele, zwłaszcza gdyby zejść na poziom assemblera.
Która droga rozwoju jest zatem moim zdaniem tą właściwą? Prawda jak zawsze leży po środku. Nie można robić rewolucji przy każdym wydaniu, ale i kurczowe trzymanie się przeszłości nie jest dobre. Wydaje mi się, że najlepszym pomysłem są poważne zmiany, ale w ściśle określonym cyklu, np. 3-5 letnim, i pomniejsze zmiany kiedy tylko zajdzie taka potrzeba. Pozwala to co jakiś czas zrobić porządki, aby nasz produkt nie osiągnął statusu bloatware‘u, i poprawić rzeczy ważne, ale jednocześnie daje pewne poczucie stabilności i bezpieczeństwa które również jest niezbędne.
1) “Prawda jak zawsze leży po środku.” – pozwól na zaczepkę z mojej strony. Być może Twój czerwony jest tym samym co mój zielony. Fakt, że nie mogę tego stwierdzić (nie mogę o tym stwierdzić, to be precise) nie czyni tego prawdopodobnym w 50%. Stwierdzenie o prawdziwości najmniej skrajnego stwierdzenia są mniej więcej na tym samym poziomie co “wyjątek potwierdza regułę” i moim zdaniem nie powinny być używane przez tak inteligentnych ludzi jak Ty.
2) Podejście developerów Pythona do backward compatibility nie jest (jak słusznie zauważyłeś) skrajne. Podejście producentów sprzętu rozwijających architekturę x86 jest. To drugie skutecznie minimalizuje koszty wykorzystania technologii, jaką jest x86. Myślę, że gdyby te dwa przykłady były w jakiś sposób porównywalne (np. były językami programowania lub architekturami sprzętu) pierwszy zyskałby uznanie wynalazców, eksperymentatorów, ludzi rozwijających swój warsztat i tworzących innowacyjne konstrukcje. Drugi natomiast byłby uwielbiany przez przedsiębiorców, gdyż może służyć za pewną i stabilną, minimalizującą ryzyko ewentualnych problemów platformą. Obaj jesteśmy wynalazcami, stąd nie dziwi mnie to, że obaj klniemy na ograniczenia i potencjalne możliwości x86, które nigdy nie zostaną zrealizowane. Obaj lubimy Pythona, ale podejrzewam, że żaden z nas nie poleciłby go przedsiębiorcy nie-programiście, który wybiera technologię, na której oprze oprogramowanie do sterowania aparaturą medyczną, które będzie serwisował i za których błędy będzie płacił.
Różnorodność jest wspaniała.