Dlaczego wielu programistów narusza zasadę otwartego / zamkniętego , modyfikując wiele rzeczy, takich jak zmiana nazw funkcji, które spowodują uszkodzenie aplikacji po aktualizacji?
To pytanie przeskakuje mi do głowy po szybkich i ciągłych wersjach biblioteki React .
Co krótki okres zauważam wiele zmian w składni, nazwach komponentów, itp
Przykład w nadchodzącej wersji React :
Nowe ostrzeżenia o wycofaniu
Największą zmianą jest to, że wyodrębniliśmy React.PropTypes i React.createClass we własnych pakietach. Oba są nadal dostępne za pośrednictwem głównego obiektu React, ale użycie jednego z nich spowoduje zarejestrowanie jednorazowego ostrzeżenia o wycofaniu do konsoli w trybie programowania. Umożliwi to przyszłe optymalizacje rozmiaru kodu.
Te ostrzeżenia nie wpłyną na zachowanie Twojej aplikacji. Zdajemy sobie jednak sprawę, że mogą powodować frustrację, szczególnie jeśli używasz środowiska testowego, które traktuje plik console.error jako błąd.
- Czy zmiany te są uważane za naruszenie tej zasady?
- Jako początkujący coś takiego jak React , jak mogę się tego nauczyć dzięki tym szybkim zmianom w bibliotece (to takie frustrujące)?
źródło
Odpowiedzi:
Odpowiedź IMHO JacquesB, choć zawiera wiele prawdy, pokazuje podstawowe nieporozumienie OCP. Aby być uczciwym, twoje pytanie również wyraża to nieporozumienie - zmiana nazw funkcji narusza wsteczną kompatybilność , ale nie OCP. Jeśli konieczne jest złamanie kompatybilności (lub utrzymanie dwóch wersji tego samego komponentu, aby nie złamać kompatybilności), OCP był już wcześniej uszkodzony!
Jak już wspomniał Jörg W Mittag w swoich komentarzach, zasada nie mówi „nie można modyfikować zachowania komponentu” - mówi, że należy spróbować zaprojektować komponenty w taki sposób, aby były otwarte do ponownego wykorzystania (lub rozszerzenia) na kilka sposobów, bez potrzeby modyfikacji. Można tego dokonać, zapewniając odpowiednie „punkty rozszerzenia” lub, jak wspomniano w @AntP, „rozkładając strukturę klasy / funkcji do punktu, w którym domyślnie znajduje się każdy naturalny punkt rozszerzenia”. IMHO podążając za OCP nie ma nic wspólnego z „utrzymywaniem starej wersji bez zmian dla kompatybilności wstecznej” ! Lub cytując komentarz @ DerekElkin poniżej:
Dobrzy programiści wykorzystują swoje doświadczenie do projektowania komponentów z myślą o „właściwych” punktach rozszerzenia (lub - jeszcze lepiej - w taki sposób, że nie są potrzebne żadne sztuczne punkty rozszerzenia). Jednak, aby zrobić to poprawnie i bez zbędnej inżynierii, musisz wcześniej wiedzieć, jak mogą wyglądać przyszłe przypadki użycia twojego komponentu. Nawet doświadczeni programiści nie mogą wcześniej spojrzeć w przyszłość i poznać wszystkich przyszłych wymagań. I dlatego czasami trzeba naruszać kompatybilność wsteczną - bez względu na to, ile punktów rozszerzeń ma twój komponent lub jak dobrze przestrzega OCP w odniesieniu do niektórych rodzajów wymagań, zawsze będzie wymaganie, którego nie można łatwo wdrożyć bez modyfikacji składnik.
źródło
Zasada otwarta / zamknięta ma zalety, ale ma również poważne wady.
Teoretycznie zasada rozwiązuje problem kompatybilności wstecznej, tworząc kod „otwarty na rozszerzenie, ale zamknięty na modyfikację”. Jeśli klasa ma jakieś nowe wymagania, nigdy nie modyfikujesz kodu źródłowego samej klasy, ale tworzy podklasę, która zastępuje tylko odpowiednie elementy niezbędne do zmiany zachowania. W związku z tym nie ma to wpływu na cały kod napisany w stosunku do oryginalnej wersji klasy, więc możesz mieć pewność, że zmiana nie spowodowała uszkodzenia istniejącego kodu.
W rzeczywistości łatwo kończy się wzdęciem kodu i mylącym bałaganem przestarzałych klas. Jeśli nie jest możliwe zmodyfikowanie niektórych zachowań komponentu poprzez rozszerzenie, należy podać nowy wariant komponentu o pożądanym zachowaniu i zachować starą wersję w niezmienionej formie, aby zachować zgodność wsteczną.
Powiedzmy, że odkryłeś podstawową wadę projektową w klasie bazowej, z której dziedziczy wiele klas. Powiedzmy, że błąd wynika z niewłaściwego typu prywatnego pola. Nie można tego naprawić, zastępując członka. Zasadniczo musisz przesłonić całą klasę, co oznacza, że kończysz na rozszerzeniu,
Object
aby zapewnić alternatywną klasę podstawową - a teraz musisz również zapewnić alternatywy dla wszystkich podklas, w ten sposób uzyskując podwójną hierarchię obiektów, jedną wadliwą hierarchię, jedną ulepszoną . Nie można jednak usunąć wadliwej hierarchii (ponieważ usunięcie kodu jest modyfikacją), wszyscy przyszli klienci będą narażeni na działanie obu hierarchii.Teraz teoretyczną odpowiedzią na ten problem jest „po prostu zaprojektuj go poprawnie za pierwszym razem”. Jeśli kod jest doskonale rozłożony, bez żadnych wad i błędów i zaprojektowany z punktami rozszerzenia przygotowanymi na wszystkie możliwe przyszłe zmiany wymagań, unikniesz bałaganu. Ale w rzeczywistości wszyscy popełniają błędy i nikt nie jest w stanie doskonale przewidzieć przyszłości.
Weźmy coś w rodzaju .NET Framework - wciąż zawiera on zbiór klas kolekcji, które zostały zaprojektowane przed wprowadzeniem generics ponad dziesięć lat temu. Jest to z pewnością dobrodziejstwo dla kompatybilności wstecznej (możesz uaktualnić framework bez konieczności przepisywania czegokolwiek), ale także rozpręża ramę i oferuje programistom duży zestaw opcji, z których wiele jest po prostu przestarzała.
Najwyraźniej programiści React uważali, że nie opłaca się kosztować złożoności i rozdętego kodu, aby ściśle przestrzegać zasady otwartej / zamkniętej.
Pragmatyczną alternatywą dla otwierania / zamykania jest kontrolowane wycofanie. Zamiast łamania wstecznej kompatybilności w jednym wydaniu, stare komponenty są przechowywane przez cykl wydania, ale klienci są informowani za pomocą ostrzeżeń kompilatora, że stare podejście zostanie usunięte w późniejszym wydaniu. Daje to klientom czas na modyfikację kodu. To wydaje się być podejście React w tym przypadku.
(Moja interpretacja tej zasady oparta jest na The Open-Closed Principle Roberta C. Martina)
źródło
Nazwałbym zasadę otwartą / zamkniętą ideałem. Jak wszystkie ideały, nie bierze się pod uwagę realiów tworzenia oprogramowania. Podobnie jak wszystkie ideały, nie da się go osiągnąć w praktyce - dąży się do tego ideału jak najlepiej.
Druga strona tej historii to Złote Kajdanki. Złote Kajdanki są tym, co dostajesz, gdy zbytnio podporządkowujesz się zasadzie otwarcia / zamknięcia. Złote kajdanki mają miejsce, gdy twój produkt, który nigdy nie łamie się wstecz, nie może rosnąć, ponieważ popełniono zbyt wiele błędów z przeszłości.
Słynny przykład tego można znaleźć w menedżerze pamięci systemu Windows 95. W ramach marketingu systemu Windows 95 stwierdzono, że wszystkie aplikacje systemu Windows 3.1 będą działać w systemie Windows 95. Microsoft faktycznie nabył licencje na tysiące programów do testowania ich w systemie Windows 95. Jednym z problemów było Sim City. W Sim City rzeczywiście wystąpił błąd, który spowodował, że zapisał w nieprzydzielonej pamięci. W systemie Windows 3.1, bez „właściwego” menedżera pamięci, było to niewielkie faux pas. Jednak w Windows 95 menedżer pamięci wychwytuje to i powoduje błąd segmentacji. Rozwiązanie? W systemie Windows 95, jeśli nazwa aplikacji to
simcity.exe
, system operacyjny faktycznie rozluźni ograniczenia menedżera pamięci, aby zapobiec usterce segmentacji!Prawdziwym problemem tego ideału są sparowane koncepcje produktów i usług. Nikt tak naprawdę nie robi ani jednego, ani drugiego. Wszystko układa się gdzieś w szarym obszarze między nimi. Jeśli myślisz o podejściu zorientowanym na produkt, otwieranie / zamykanie brzmi jak doskonały ideał. Twoje produkty są niezawodne. Jednak jeśli chodzi o usługi, historia się zmienia. Łatwo jest wykazać, że zgodnie z zasadą otwartej / zamkniętej ilość funkcji, którą musi obsłużyć zespół, musi asymptotycznie zbliżać się do nieskończoności, ponieważ nigdy nie można wyczyścić starej funkcjonalności. Oznacza to, że Twój zespół programistów musi obsługiwać coraz więcej kodu każdego roku. W końcu osiągasz punkt krytyczny.
Większość dzisiejszych programów, szczególnie open source, stosuje wspólną, zrelaksowaną wersję zasady otwartego / zamkniętego. Często zdarza się, że otwarte / zamknięte są niewolniczo wydawane w przypadku mniejszych wydań, ale porzucane w przypadku większych wydań. Na przykład Python 2.7 zawiera wiele „złych wyborów” z Python 2.0 i 2.1 dni, ale Python 3.0 zmieścił je wszystkie. (Również przejście z systemu Windows 95 kodzie w kodzie Windows NT, kiedy wydany Windows 2000 złamał różne rzeczy, ale to nie znaczy, że nie mają do czynienia z menedżera pamięci sprawdzając nazwę aplikacji, aby zdecydować zachowanie!)
źródło
Odpowiedź doktora Browna jest najbliższa dokładności, inne odpowiedzi ilustrują nieporozumienia dotyczące otwartej zasady zamkniętej.
Wyraźnie artykułować nieporozumienie, nie wydaje się być przekonanie, że OCP oznacza, że nie należy wprowadzać zmian niezgodnych wstecz (lub nawet jakieś zmiany lub coś wzdłuż tych linii.) OCP jest o projektowaniu komponentów, dzięki czemu nie trzeba się wprowadzaj w nich zmiany, aby rozszerzyć ich funkcjonalność, niezależnie od tego, czy zmiany te są kompatybilne wstecz, czy nie. Istnieje wiele innych powodów oprócz dodawania funkcji, które możesz wprowadzać zmiany w komponencie, czy są one wstecznie kompatybilne (np. Refaktoryzacja lub optymalizacja) lub wstecznie niezgodne (np. Przestarzałe i usuwane funkcje). To, że możesz wprowadzić te zmiany, nie oznacza, że twój komponent naruszył OCP (i zdecydowanie nie oznacza, że ty naruszają OCP).
Naprawdę, w ogóle nie chodzi o kod źródłowy. Bardziej abstrakcyjne i stosowne stwierdzenie OCP jest następujące: „element powinien umożliwiać rozszerzenie bez potrzeby naruszania jego granic abstrakcji”. Chciałbym pójść dalej i powiedzieć, że bardziej nowoczesna wersja brzmi: „składnik powinien egzekwować swoje granice abstrakcji, ale zezwalać na rozszerzenie”. Nawet w artykule OCP autorstwa Boba Martina, który „opisuje” „zamknięty na modyfikację” jako „kod źródłowy jest nienaruszalny”, później zaczyna mówić o enkapsulacji, która nie ma nic wspólnego z modyfikacją kodu źródłowego i wszystko, co dotyczy abstrakcji Granic.
Tak więc błędnym założeniem w tym pytaniu jest to, że OCP jest (w zamierzeniu) wytyczną dotyczącą ewolucji bazy kodu. OCP jest zwykle sloganizowane jako „element powinien być otwarty na rozszerzenia i zamknięty dla modyfikacji przez konsumentów”. Zasadniczo, jeśli konsument komponentu chce dodać funkcjonalność do komponentu, powinien mieć możliwość rozszerzenia starego komponentu na nowy z dodatkową funkcjonalnością, ale nie powinien mieć możliwości zmiany starego komponentu.
OCP nie mówi nic o twórcy komponentu zmieniającego lub usuwającego funkcjonalność. OCP nie zaleca utrzymywania zgodności błędów na zawsze. Jako twórca nie naruszasz OCP poprzez zmianę lub nawet usunięcie komponentu. Ty, a raczej komponenty, które napisałeś, naruszają OCP, jeśli jedynym sposobem, w jaki konsumenci mogą dodać funkcjonalność do twoich komponentów, jest ich mutacja, np. Łatanie małplub mając dostęp do kodu źródłowego i ponownej kompilacji. W wielu przypadkach żadna z tych opcji nie jest dostępna dla konsumenta, co oznacza, że jeśli Twój komponent nie jest „otwarty na rozszerzenie”, nie ma szczęścia. Po prostu nie mogą użyć twojego komponentu do swoich potrzeb. OCP twierdzi, że nie stawia konsumentów twojej biblioteki w tej pozycji, przynajmniej w odniesieniu do jakiejś możliwej do zidentyfikowania klasy „rozszerzeń”. Nawet jeśli można dokonać modyfikacji kodu źródłowego lub nawet pierwotnej kopii kodu źródłowego, najlepiej „udawać”, że nie można go zmodyfikować, ponieważ może to mieć wiele potencjalnych negatywnych konsekwencji.
Więc, aby odpowiedzieć na twoje pytania: Nie, to nie są naruszenia OCP. Żadna zmiana dokonana przez autora nie może stanowić naruszenia OCP, ponieważ OCP nie jest właściwością zmian. Zmiany mogą jednak powodować naruszenia OCP i mogą być motywowane awariami OCP we wcześniejszych wersjach bazy kodu. OCP jest własnością określonego fragmentu kodu, a nie historii ewolucji bazy kodu.
Dla kontrastu, zgodność wsteczna jest właściwością zmiany kodu. Nie ma sensu mówić, że jakiś fragment kodu jest lub nie jest kompatybilny wstecz. Sensowne jest tylko mówienie o kompatybilności wstecznej jakiegoś kodu w stosunku do starszego kodu. Dlatego nigdy nie ma sensu mówić o tym, że pierwsze cięcie jakiegoś kodu jest kompatybilne wstecz, czy nie. Pierwsze cięcie kodu może spełniać lub nie spełniać OCP, i ogólnie możemy ustalić, czy jakiś kod spełnia OCP bez odwoływania się do jakichkolwiek historycznych wersji kodu.
Jeśli chodzi o twoje ostatnie pytanie, jest to prawdopodobnie nietypowe dla StackExchange, ponieważ jest oparte głównie na opiniach, ale w skrócie jest mile widziane w technologii, a szczególnie w JavaScript, gdzie w ciągu ostatnich kilku lat opisywane zjawisko nazywało się zmęczeniem JavaScript . (Zapraszam do google znaleźć wiele innych artykułów, niektóre satyryczne, rozmowy na ten temat z różnych perspektyw.)
źródło
private
lub nie. Jeśli autor opracujeprivate
metodępublic
później, nie oznacza to, że naruszył kontrolę dostępu, (1/2)private
wcześniej. „Usunięcie opublikowanego komponentu jest przełomową zmianą”, to nie sekwencja. Albo składniki nowej wersji spełniają OCP, albo nie, nie potrzebujesz historii bazy kodu, aby to ustalić. Według twojej logiki nigdy nie mogłem napisać kodu, który spełnia OCP. Łączymy kompatybilność wsteczną, właściwość zmian kodu, z OCP, właściwość kodu. Twój komentarz ma tyle samo sensu, co stwierdzenie, że Quicksort nie jest kompatybilny wstecz. (2/2)