Czy starszy kod powinien zostać zaktualizowany, aby używał nowszych konstrukcji języka, czy też powinien utknąć w przestarzałych konstrukcjach?

15

Chcę wprowadzić pewne ulepszenia w wciąż funkcjonującym kodzie, który został napisany dawno temu, zanim język programowania, w którym jest napisany, zaczął się rozwijać. Teoretycznie w całym projekcie wykorzystywana jest aktualna wersja języka; jednak ten konkretny moduł (i tak naprawdę wiele innych modułów) jest nadal zapisany w starszym dialekcie.

Czy powinienem:

  • Nie dotykać części kodu, których nie muszę dotykać, ale pisać łatkę, korzystając z nowych funkcji językowych, które ułatwiają pisanie łatki, ale nie są używane w analogicznych sytuacjach nigdzie indziej w module? (To rozwiązanie intuicyjnie wybrałbym.)
  • Zignorować fakt, że minęły lata i odzwierciedlić styl używany w pozostałej części kodu podczas pisania łatki, skutecznie zachowując się tak, jakbym wykonywał to samo zadanie wiele lat wcześniej? (To rozwiązanie intuicyjnie uważam za głupie, ale biorąc pod uwagę zamieszanie, każdy, kto mówi o „dobrym kodzie”, stara się zachować spójność za wszelką cenę, być może powinienem to zrobić.)
  • Czy zaktualizować cały moduł, aby korzystać z nowszych konstrukcji i konwencji języka? (Jest to prawdopodobnie najlepsze rozwiązanie, ale może wymagać dużo czasu i energii, które można by lepiej wydać na inne zadanie).
gaazkam
źródło
1
@JerryCoffin Nie będę się kłócił w komentarzach: zamieściłem na meta, gdzie możemy mieć właściwą dyskusję.

Odpowiedzi:

10

Niemożliwe jest jednoznaczne udzielenie odpowiedzi na to pytanie, ponieważ zbyt mocno zależy ono od szczegółów sytuacji.

Spójność stylu bazy kodu jest ważna, ponieważ pomaga w zrozumieniu kodu, co jest jednym z najważniejszych aspektów łatwości konserwacji.
Nie byłbyś pierwszym programistą, który został przeklęty do piekła za utrudnianie zrozumienia kodu przez mieszanie różnych stylów. Może nawet ty sam przeklinasz za lata.

Z drugiej strony użycie nowych konstrukcji języka, które nie istniały przy pierwszym pisaniu kodu, nie oznacza automatycznie, że zmniejszasz łatwość konserwacji lub nawet łamiesz styl kodu. Wszystko zależy od konkretnych funkcji językowych, których chcesz używać, oraz od tego, jak dobrze zespół zna zarówno stary styl kodu, jak i nowe funkcje.

Jako przykład, generalnie nie byłoby wskazane, aby rozpocząć wprowadzanie koncepcji programowania funkcjonalnego, takich jak map / redukcja, do bazy kodu, która jest całkowicie w stylu OO, ale może działać dodawanie funkcji lambda tu i tam do programu, który nie używał coś takiego wcześniej.

Bart van Ingen Schenau
źródło
1
Częściowo się nie zgadzam. Powinniśmy przestrzegać zasady otwartego zamknięcia. Powinniśmy więc spróbować odizolować nowy kod tak bardzo, jak to możliwe, i jednocześnie napisać nowy kod z możliwie najnowszą konstrukcją językową.
Anand Vaidya
3
@AnandVaidya: to jest IMHO nierealne oczekiwanie, ponieważ zakłada, że ​​stary kod podąża za OCP lub można go łatwo zmienić, aby podążał za OCP, co rzadko się zdarza lub po prostu nie jest warte wysiłku.
Doc Brown
1
Kiedy powiedziałem ocp, nie mam na myśli oops ocp, ale ogólnie ocp. Zasadniczo staraj się nie modyfikować istniejącego kodu w jak największym stopniu i izoluj swój własny kod. Jest to nawet możliwe w przypadku starego kodu proceduralnego. Rozumiem, że kodów spaghetti nie można w ten sposób modyfikować, ale w przypadku dobrze zaprojektowanego kodu w dowolnym paradygmacie otwarte zamknięcie powinno być łatwe do zastosowania. Może to być ups, proceduralne lub funkcjonalne.
Anand Vaidya
2
@AnandVaidya: jeśli nie masz na myśli OCP, nie powinieneś tego tak nazywać. Wydaje mi się, że to, co naprawdę masz na myśli, to to, co wywołuje Feather, pisząc nowy kod metodą kiełkową , aby można było ograniczyć nowy styl kodu do nowej, oddzielnej metody. Jeśli jest to możliwe i rozsądne, masz rację, nie ma sprawy. Niestety takie podejście nie zawsze ma zastosowanie, przynajmniej jeśli chcesz zachować stary kod DRY.
Doc Brown,
@DocBrown: Nie sądzę, aby zasada otwartego / zamkniętego ograniczała się do programowania obiektowego. Możesz mieć moduły, w których dodajesz nowe procedury, ale nie modyfikujesz już istniejących, tzn. Nie zmieniasz semantyki istniejącego kodu, a jeśli potrzebujesz nowej funkcjonalności, piszesz nowy kod.
Giorgio
5

W dużym stopniu kod i jego wygląd są nieistotne . Znaczenie ma kod .

Jeśli możesz zagwarantować, że zmiana / przepisanie kodu nie zmieni jego działania, możesz przejść dalej i zmienić kod na odpowiedni dla swojego serca. Ta „gwarancja” jest wyczerpującym zestawem testów jednostkowych, które można przeprowadzić przed i po zmianach, bez zauważalnej różnicy.

Jeśli nie możesz zagwarantować tej stabilności (nie masz tych testów), zostaw to w spokoju.

Nikt nie podziękuje Ci za „złamanie” oprogramowania o kluczowym znaczeniu dla firmy, nawet jeśli starasz się uczynić go „lepszym”. „Praca” przebija „lepiej” za każdym razem.

Oczywiście nic nie stoi na przeszkodzie, abyś przygotował zestaw takich testów w gotowości do takiego ćwiczenia ...

Phill W.
źródło
4

Prawie wszystko można przeanalizować pod kątem kosztów i korzyści, i myślę, że dotyczy to tutaj.

Oczywistymi zaletami pierwszej opcji jest to, że minimalizuje ona pracę w krótkim okresie i minimalizuje szanse na uszkodzenie czegoś przez przepisanie działającego kodu. Oczywistym kosztem jest to, że wprowadza niespójność do podstawy kodu. Kiedy wykonujesz jakąś operację X, odbywa się to w jeden sposób w niektórych częściach kodu, a w inny sposób w innej części kodu.

W przypadku drugiego podejścia zauważyłeś już oczywistą korzyść: spójność. Oczywistym kosztem jest to, że musisz pochylić umysł, aby pracować w sposób, który w przeciwnym razie mógłbyś porzucić lata temu, a kod pozostaje konsekwentnie nieczytelny.

W przypadku trzeciego podejścia oczywistym kosztem jest po prostu dużo więcej pracy. Mniej oczywistym kosztem jest to, że możesz zepsuć rzeczy, które działały. Jest to szczególnie prawdopodobne, jeśli (jak to często bywa) stary kod ma nieodpowiednie testy, aby upewnić się, że nadal działa poprawnie. Oczywistą korzyścią jest to, że (zakładając, że zrobisz to skutecznie) masz ładny, błyszczący nowy kod. Oprócz korzystania z nowych konstrukcji językowych masz szansę na przebudowanie podstawy kodu, co prawie zawsze da ulepszenia samo w sobie, nawet jeśli nadal używałeś języka dokładnie tak, jak istniał w chwili jego napisania - dodaj nowe konstrukcje, które praca łatwiejsza, a może to być duża wygrana.

Jeszcze jedna ważna kwestia: obecnie wydaje się, że moduł ten od dłuższego czasu wymaga minimalnej konserwacji (mimo że cały projekt jest utrzymywany). To wskazuje, że jest dość dobrze napisane i stosunkowo wolne od błędów - w przeciwnym razie prawdopodobnie zostałby w międzyczasie poddany większej konserwacji.

To prowadzi do kolejnego pytania: jakie jest źródło zmian, których dokonujesz teraz? Jeśli naprawiasz mały błąd w module, który ogólnie dobrze spełnia jego wymagania, może to wskazywać, że czas i wysiłek związany z refaktoryzacją całego modułu prawdopodobnie zostanie w dużej mierze zmarnowany - do czasu, gdy ktoś będzie musiał zadzierać to znowu może być mniej więcej w tej samej pozycji, co teraz, utrzymując kod, który nie spełnia „nowoczesnych” oczekiwań.

Możliwe jest jednak, że wymagania się zmieniły i pracujesz nad kodem, aby spełnić te nowe wymagania. W takim przypadku istnieje duża szansa, że ​​Twoje pierwsze próby nie spełnią obecnych wymagań. Istnieje również znacznie większa szansa, że ​​wymagania przejdą kilka rund zmian, zanim ponownie się ustabilizują. Oznacza to, że znacznie bardziej prawdopodobne jest, że będziesz wykonywać znaczącą pracę w tym module w (względnie) bliskiej perspektywie, a znacznie bardziej prawdopodobne jest dokonywanie zmian w pozostałej części modułu, a nie tylko w jednym obszarze, o którym dobrze wiesz teraz. W takim przypadku refaktoryzacja całego modułu jest bardziej prawdopodobne, że przyniesie wymierne, krótkoterminowe korzyści uzasadniające dodatkową pracę.

Jeśli wymagania się zmieniły, być może będziesz musiał sprawdzić, jakiego rodzaju zmiana jest wymagana i co jest przyczyną tej zmiany. Załóżmy na przykład, że modyfikujesz Gita, aby zastąpić jego użycie SHA-1 SHA-256. Jest to zmiana wymagań, ale zakres jest jasno określony i dość wąski. Po skonfigurowaniu i prawidłowym użyciu SHA-256, prawdopodobnie nie napotkasz innych zmian, które wpływają na resztę kodu.

Z drugiej strony, nawet jeśli początkowo jest niewielka i dyskretna, zmiana interfejsu użytkownika ma tendencję do tworzenia balonów, więc to, co zaczęło się jako „dodaj jedno nowe pole wyboru do tego ekranu”, kończy się bardziej: „zmień ten stały interfejs użytkownika do obsługi szablonów zdefiniowanych przez użytkownika, niestandardowych pól, niestandardowych schematów kolorów itp. ”

W poprzednim przykładzie prawdopodobnie najbardziej sensowne jest zminimalizowanie zmian i pomyłka po stronie spójności. W tym ostatnim przypadku pełne refaktoryzacja jest znacznie bardziej prawdopodobne.

Jerry Coffin
źródło
1

Pójdę po ciebie pierwszą opcję. Kiedy koduję, postępuję zgodnie z regułą, aby pozostawić ją w lepszym stanie.

Nowy kod jest zgodny z najlepszymi praktykami, ale nie będę dotykać kodu niezwiązanego z problemami, nad którymi pracuję. Jeśli zrobisz to dobrze, nowy kod powinien być bardziej czytelny i łatwiejszy do utrzymania niż stary kod. Przekonałem się, że całkowita łatwość konserwacji jest coraz lepsza, ponieważ jeśli tak będzie, kod, który musisz dotknąć w swojej pracy, jest coraz częściej kodem „lepszym”. Co prowadzi do szybszego rozwiązywania problemów.

Pieter B.
źródło
1

Odpowiedź, podobnie jak wiele innych rzeczy, zależy . Jeśli istnieją ważne zalety aktualizacji starszych konstrukcji, takie jak znacznie poprawiona długoterminowa łatwość konserwacji (na przykład unikanie piekła wywołania zwrotnego), to skorzystaj z niej. Jeśli nie ma znaczącej przewagi, spójność jest prawdopodobnie twoim przyjacielem

Co więcej, chciałbyś także uniknąć osadzania dwóch stylów w tej samej funkcji bardziej niż chcesz uniknąć w ramach dwóch oddzielnych funkcji.

Podsumowując: ostateczna decyzja powinna opierać się na analizie kosztów i korzyści konkretnego przypadku.


źródło