Jakie zmiany są zbyt duże, aby ułatwić ich prawidłowe zaprojektowanie?


11

To dość niejasne pytanie, ale na pytanie, na które nigdy nie czułem, odpowiedziano w zadowalający sposób, czytając o właściwym projekcie.

Zasadniczo, gdy dowiadujesz się o programowaniu obiektowym, abstrakcji, faktorowaniu itp., Święty graal projektowania - i powód, dla którego zawsze twierdzą, że używasz danych technik programistycznych - jest taki, że twój program będzie „łatwy do zmiany” , „konserwowalne”, „elastyczne” lub dowolny z synonimów użytych do wyrażenia tak produktywnie brzmiącego pojęcia. Oznaczając ivars jako prywatne, dzieląc kod na wiele małych, niezależnych metod, zachowując ogólne interfejsy, prawdopodobnie zyskujesz możliwość modyfikowania programu z całkowitą łatwością i gracją.

W przypadku stosunkowo niewielkich zmian zadziałało to dla mnie dobrze. Zmiany wewnętrznych struktur danych wykorzystywanych przez klasę w celu zwiększenia wydajności nigdy nie były poważną trudnością, podobnie jak zmiany interfejsu użytkownika, niezależne od API, takie jak przeprojektowanie systemu wprowadzania tekstu lub przegląd grafiki dla elementu rozgrywki .

Wszystkie te zmiany wydają się z natury samowystarczalne. Żadna z nich nie wiąże się z żadnymi zmianami w zachowaniu lub projekcie modyfikowanego komponentu programu, w zakresie, w jakim dotyczy to kodu zewnętrznego. Niezależnie od tego, czy piszesz proceduralnie, czy w stylu OO, z dużymi funkcjami lub małymi, są to łatwe zmiany, które możesz wprowadzić, nawet jeśli masz umiarkowanie dobry projekt.

Jednak za każdym razem, gdy zmiany stają się duże i owłosione - to znaczy zmiany w interfejsie API - żaden z moich cennych „wzorów” nigdy nie przychodzi na ratunek. Wielka zmiana pozostaje duża, dotknięty kod pozostaje dotknięty, a wiele godzin pracy nad spawaniem błędów stoi przede mną.

Więc moje pytanie brzmi: Jaką zmianę może usprawnić właściwy projekt? Czy istnieje jakaś inna technika projektowania, która jest mi nieznana lub której nie udało mi się wdrożyć, która naprawdę sprawia, że ​​modyfikacja lepkiego brzmienia jest prosta, czy też ta obietnica (którą słyszałem złożoną przez tak wiele różnych paradygmatów) jest po prostu fajnym pomysłem, całkowicie odłączony od niezmiennych prawd rozwoju oprogramowania? Czy istnieje „narzędzie zmiany”, które mogę dodać do paska narzędzi?

Problem, który napotkał mnie na forach, jest następujący: pracowałem nad wdrożeniem zinterpretowanego języka programowania (zaimplementowanego w D, ale to nie dotyczy) i zdecydowałem, że argumentami moich zamknięć powinny być oparte na słowach kluczowych, a nie pozycyjne, jakie są obecnie. Wymaga to modyfikacji całego istniejącego kodu wywołującego funkcje anonimowe, co na szczęście jest raczej niewielkie, ponieważ jestem na wczesnym etapie rozwoju mojego języka (<2000 linii), ale byłoby ogromne, gdybym podjął tę decyzję na późniejszym etapie. Czy w takiej sytuacji jest jakikolwiek sposób, który dzięki właściwemu przewidywaniu projektu mógłbym ułatwić tę modyfikację, czy też pewne (najbardziej) zmiany są z natury daleko idące? Jestem ciekawy, czy to w jakikolwiek sposób zawodzi moje umiejętności projektowe - jeśli tak, to ja ”

Mówiąc wprost, nie jestem w żaden sposób sceptyczny wobec OOP ani innych powszechnie używanych wzorów. Dla mnie jednak ich zaletami są oryginalne zapisy, a nie utrzymanie baz kodu. Dziedziczenie pozwala dobrze wyodrębnić powtarzające się wzorce, polimorfizm pozwala rozdzielić kod według funkcji zrozumiałej dla człowieka (która klasa), a nie efektu rozumianego maszynowo (która gałąź switchinstrukcji), a małe, samodzielne funkcje pozwalają na pisz w bardzo przyjemnym stylu „oddolnym”. Jestem jednak sceptyczny wobec ich twierdzeń o elastyczności.


Nie widzę, jak zmiana w twoim języku dotyka tylko parsera. Czy możesz to wyjaśnić?
scarfridge

W Smalltalk-jak język, to prawda, że chcesz tylko trzeba zmodyfikować parser, ponieważ byłoby to prosta sprawa traktując foo metarg1: bar metarg2: bazjako foo.metarg1_metarg2_(bar, baz). Jednak mój język wprowadza większą zmianę, mianowicie parametry oparte na liście do parametrów opartych na słowniku, co wpływa na środowisko uruchomieniowe, ale nie na analizator składni, w rzeczywistości ze względu na specyficzne właściwości mojego języka, do którego nie będę teraz wchodził. Ponieważ nie ma wyraźnego odwzorowania między nieuporządkowanym słowem kluczowym a argumentami pozycyjnymi, środowisko wykonawcze jest głównym problemem.
istnieje-ogólnie

Odpowiedzi:


13

Czasami zmiana jest na tyle duża, że ​​trzeba zaprojektować ścieżkę migracji. Nawet jeśli punkty początkowy i końcowy są dobrze zaprojektowane, często nie można po prostu zmienić zimnego indyka. Wielu innych dobrych projektantów nie potrafi zaprojektować dobrych ścieżek migracji.

Właśnie dlatego uważam, że każdy programista powinien robić oprogramowanie do pisania na sztywno dla środowisk produkcyjnych 24/7. Podane straty w kolejności rocznej pensji na minutę są motywowane do nauki pisania solidnego kodu migracji.

To, co ludzie naturalnie robią w przypadku dużej zmiany, to wyrywanie starego sposobu, wprowadzanie nowego sposobu, a następnie spędzanie kilku dni na naprawianiu błędów kompilacji bazillionu, aż kod w końcu się kompiluje, aby można było rozpocząć testowanie. Ponieważ kod był niestabilny przez dwa dni, nagle masz ogromny bałagan splątanych błędów do rozwiązania.

W przypadku dużej zmiany projektu, takiej jak zmiana argumentów pozycyjnych na słowa kluczowe, dobrą ścieżką migracji byłoby najpierw dodanie obsługi argumentów słów kluczowych bez usuwania wsparcia pozycyjnego . Następnie metodycznie zmieniasz kod wywołujący, aby użyć argumentów słów kluczowych. Jest to podobne do tego, w jaki sposób to robiłeś wcześniej, z tym wyjątkiem, że możesz testować podczas pracy, ponieważ niezmieniony kod wywoływania nadal działa. Ponieważ zmiany między testami są mniejsze, błędy łatwiej jest naprawić wcześniej. Następnie, gdy cały kod wywołujący został zmieniony, banalne jest usunięcie obsługi pozycji.

Właśnie dlatego publicznie opublikowane interfejsy API zastępują stare metody zamiast usuwać je z zimnego indyka. Zapewnia płynną ścieżkę migracji do wywoływania kodu. Może wydawać się, że więcej pracy wymaga wsparcia przez chwilę, ale poświęcasz czas na testowanie.

Tak więc, aby odpowiedzieć na twoje pytanie, jak pierwotnie sformułowano, prawie nie ma zmian, które byłyby zbyt duże, aby ułatwić je poprzez odpowiedni projekt. Jeśli zmiana wydaje się zbyt duża, wystarczy zaprojektować tymczasowe etapy pośrednie, aby można było migrować kod.


+1 za wsparcie nowej i starej skrzynki, abyś mógł wycofać się z pracy.
Erik Reppen

9

Polecam przeczytać esej Big Ball of Mud .

Zasadniczo chodzi o to, że projekt ma tendencję do pogarszania się w miarę postępów w rozwoju i musisz poświęcić pracę na opanowanie złożoności. Sama złożoność nie może zostać wyeliminowana, a jedynie ograniczona, ponieważ jest nieodłączną częścią problemu, który próbujesz rozwiązać.

Prowadzi to do głównych zasad zwinnego rozwoju. Dwa najbardziej istotne to „nie będziesz go potrzebować”, który mówi, abyś nie przygotowywał projektu pod kątem funkcji, których jeszcze nie zamierzasz wdrożyć, ponieważ jest mało prawdopodobne, aby projekt był poprawny i „bezlitośnie refaktoryzował”, nakazując ci pracę. na temat zachowania rozsądku kodu w całym projekcie.

Wydaje się, że odpowiedź brzmi: żaden projekt nie jest w stanie ułatwić żadnej zmiany poza wymyślonymi przykładami zaprojektowanymi specjalnie w celu pokazania, że ​​projekt jest mocniejszy niż w rzeczywistości.

Na marginesie powinieneś być sceptyczny wobec programowania obiektowego. To świetne narzędzie w wielu kontekstach, ale musisz zdawać sobie sprawę z jego ograniczeń. Szczególnie niewłaściwe wykorzystanie dziedziczenia może prowadzić do niewiarygodnie skomplikowanego kodu. Oczywiście powinieneś być równie sceptyczny wobec każdej innej techniki projektowania. Każdy ma swoje zastosowania i każdy ma swoje słabe punkty. Nie ma srebrnej kuli.


1
+1: nowatorski projekt z góry rzadko odnosi sukcesy. Udane projekty są albo podsumowaniem sprawdzonych projektów, albo zostały iteracyjnie dopracowane.
kevin cline

1
+1 powinieneś być sceptyczny wobec wszystkich koncepcji programowania, tylko poprzez obiektywną krytykę wykorzystujemy nasze umiejętności analityczne, aby znaleźć właściwe rozwiązanie, a nie tylko rozwiązanie .
Jimmy Hoffa

4

Ogólnie rzecz biorąc, istnieją „fragmenty kodu” (funkcje, metody, obiekty, cokolwiek) i „interfejsy między fragmentami kodu” (interfejsy API, deklaracje funkcji, cokolwiek; w tym zachowanie).

Jeśli fragment kodu można zmienić bez zmiany interfejsu, od którego zależą inne fragmenty kodu, zmiana będzie łatwiejsza (i nie ma znaczenia, czy użyjesz OOP, czy nie).

Jeśli fragmentu kodu nie można zmienić bez zmiany interfejsu, od którego zależą inne fragmenty kodu, zmiana będzie trudniejsza (i nie ma znaczenia, czy użyjesz OOP, czy nie).

Główne zalety OOP polegają na tym, że publiczne „interfejsy” są wyraźnie oznaczone (np. Publiczne metody obiektu), a inny kod nie może korzystać z wewnętrznych interfejsów (np. Prywatnych metod obiektu). Ponieważ interfejsy publiczne są wyraźnie oznaczone, ludzie zwykle ostrożniej projektują te interfejsy publiczne (co pomaga uniknąć trudniejszych zmian). Ponieważ interfejsy wewnętrzne nie mogą być używane przez inny kod, możesz je zmienić bez obawy o inny kod w zależności od nich.


Kolejną zaletą OOP jest to, że hermetyzowanie danych za pomocą logiki, która na nich działa, prowadzi do mniejszych publicznych interfejsów (jeśli są dobrze wykonane).
Michael Borgwardt,

2

Nie ma metodologii projektowania, która mogłaby uchronić Cię przed kłopotami związanymi z dużą zmianą wymagań / zakresu. Nie można sobie wyobrazić i uwzględnić wszystkich możliwych zmian, o których można pomyśleć w późniejszym terminie.

Gdzie dobry projekt robi pomoc was jest w pomaga zrozumieć, jak wszystkie części pasują do siebie i to, co będzie miało wpływ proponowanej zmiany.
Ponadto dobry projekt może ograniczyć części, na które wpływa większość proponowanych zmian.

Krótko mówiąc, wszystkie zmiany są łatwiejsze dzięki dobremu projektowi, ale dobry projekt nie może zmienić dużej zmiany w małą. (z drugiej strony zły / brak projektu może zmienić każdą małą zmianę w dużą).


1

Ponieważ projekt ma na celu przeniesienie projektu programistycznego od zera pustego arkusza do działającego niezawodnego produktu końcowego (przynajmniej projektu dla jednego), i jest to największa możliwa zmiana w projekcie, wątpię, że istnieje coś takiego jak zmiana zbyt duża, aby ją obsłużyć.

Jeśli coś jest odwrotnie. Niektóre zmiany są zbyt małe, aby zawracać sobie głowę jakimkolwiek „projektem” lub fantazyjnym myśleniem. Na przykład proste poprawki błędów.

Pamiętaj, że wzorce projektowe mają pomóc w jasnym myśleniu i znaleźć dobre rozwiązania projektowe w typowych sytuacjach, takich jak tworzenie ogromnej liczby małych identycznych obiektów. Żadna lista wzorów nie jest kompletna. Ty i cała reszta będziemy mieć problemy z projektowaniem, które nie są powszechne i nie pasują do żadnej gołębicy. Próby wykonania każdego najmniejszego kroku w procesie tworzenia oprogramowania zgodnie z tym czy innym oficjalnym wzorcem, są zbyt religijnym sposobem robienia rzeczy i prowadzą do niemożliwych do utrzymania bałaganów.

Cała praca w oprogramowaniu wiąże się oczywiście z powstawaniem błędów. Piłkarze doznają kontuzji. Muzycy dostają modzele lub skurcze warg w zależności od instrumentu. (Jeśli podczas gry na gitarze wystąpią skurcze warg), robisz to źle.) Twórcy oprogramowania dostają błędy. Uwielbiamy znajdować sposoby na zmniejszenie ich częstotliwości odradzania, ale nigdy nie będzie to zero.


3
Pozycja początkowa może być również ujemna. Nie lekceważ potęgi Dziedzictwa :)
Maglob

Projektowanie projektu od zera jest łatwą częścią. Ale nawet jeśli zaczniesz nowy projekt od zera, co jest rzadkie samo w sobie, będziesz cieszyć się budowaniem z niczego przez pierwsze kilka dni. Później pierwsza początkowa decyzja projektowa okazała się błędna i stamtąd zacznie się staczać. Projektowanie od zera jest całkowicie nieistotne w praktyce.
Jan Hudec

1

Koszt zamiatania zmian jest często proporcjonalny do wielkości podstawy kodu. Dlatego zmniejszenie rozmiaru podstawy kodu zawsze powinno być jednym z celów każdego właściwego projektu.

W twoim przykładzie prawidłowe projektowanie już ułatwiło pracę: wprowadzanie zmian w 2000 wierszach kodu bazowego zamiast 20000 lub 200000 wierszy kodu bazowego.

Właściwa konstrukcja ogranicza zamiatanie, ale nie eliminuj ich.

Zamiatanie zmian jest dość łatwo zautomatyzowane, jeśli istnieje odpowiednie wsparcie ze strony narzędzi do analizy języka. Zmianę refaktoryzacji koców można zastosować do całej bazy kodu w stylu wyszukiwania i zamiany (przy odpowiednim przestrzeganiu reguł językowych). Programiści muszą jedynie oceniać tak / nie dla każdego trafienia wyszukiwania.


+1 za sugestię automatyzacji. Myślę, że faktycznie użyję tego dla wielu moich funkcji bibliotecznych, które wywołują zamknięcia - to prosta kwestia wykrycia, które funkcje pobierają bloki i modyfikują je, aby miały dodatkowy parametr symbolu dla nazwy argumentu, aby przekazać wartość jako.
istnieje-ogólnie
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.