Czy każdy projekt da się zrefaktoryzować?

0

Mamy projekt w javie który pisany był naście lat, nie ma testów, wszystko lub bardzo dużo jest na statykach z efektami ubocznymi, dokumentacja oczywiście jest dawno nieaktualna, nazwy klas i metod mówią mało, klasy mają po kilka(naście) odpowiedzialności, a powiązania pomiędzy nimi są jak węzeł gordyjski.
Do tego jeszcze wielka baza danych w postaci normalniej ok 2, z webserviceami czy magicznymi polami w stylu "1000010101101011010101010111110111", gdzie każda jedynka oznacza włączoną flagę dla jakiejś aktywności z których część odbywa się bezpośrednio na bazie poprzez procedury i inne natywne ciekawostki, a część w kodzie aplikacji.
Czy jest realna możliwość refaktoryzacji takiego systemu?

Wiadomo, że sporo osób powie, że trzeba przepisać, ale jednak jak coś powstawało naście lat to przepisanie tego też nie zajmie roku czy dwóch, tym bardziej gdy nie wiadomo co to właściwie robi, ani nie ma acceslogów mówiących co jest faktycznie potrzebne, a co jest fanaberią.
Stąd pytanie, czy nie lepiej poświęcić tych ponad dwóch lat na gruntowne sprzątanie?
Nie wiem tylko czy to jest zawsze możliwe.

Tzn nie chodzi mi nawet o doprowadzenie systemu do postaci jak z książek i tutoriali o refaktoringu gdzie kod czyta się jak książkę i od razu wiadomo co dany kawałek robi, ale przynajmniej do postaci pozwalającej na wyodrębnienie kawałków których się nie zmienia i jakoś to wszystko działa do oddzielnych blackboxowych modułów których kod i zależności nie będą zaciemniać i przeszkadzać w tych częściach systemu w których zmiany się dokonują.

0

Dodam od razu, że teraz się takim projektem nie zajmuję więc pytanie jest hipotetyczne, ale pytanie jest inspirowane pewnym konkretnym projektem który widziałem.

2

Wszystko sie da, ale w przypadku który opisujesz refaktoring będzie oznaczał pewnie koniec końców przepisanie tego od nowa. Tzn finalny kod nie będzie miał nic wspólnego z tym co jest teraz. Niemniej można też robić fragmentaryczny refaktoring, startując "od dołu" dla pewnych fragmentów.

0

Moim zdaniem nie da się. czasem lepiej napisać od nowa, ale niech osoby z większym doświadczeniem się wypowiedzą.

0

@Shalom
Z podejściem od dołu jest ten problem, że zanim np ja zdążę porozbijać wielkie metody w klasie i podzielić klasę na kilka innych i jakieś interfejsy, to ktoś w tym czasie może już coś w tej klasie zmienić więc merge będzie później nieziemskie, szczególnie, że starocie stoją często na svn.

Jeżeli jakaś klasa jest paskudna, metody ma wielkie, ale nikt w niej nic nie robi np od 2. lat, to wtedy problemu nie ma, ale wtedy też nie bardzo jest sens to refaktoryzować.
No chyba, że taka refaktoryzacja pozwalałaby rozluźnić powiązania z innymi klasami.

Sądzisz, że jest sens utzrymywać taki kod czy lepiej to przepisać?
Wiem, że pytanie jak do wróżbity Wojciecha, bo nie widziałeś i nie znasz skali zniszczeń, ale czy to się da w ogóle z grubsza określić kiedy przepisanie staje się lepszym wyborem?

@karolinaa
Refaktoring jak starej aplikacji uważasz za sensowny?
Zakładając przeciętny poziom aplikacji z jakimi się spotkałaś.

1

@Pijany Lew musisz to wtedy robić małymi fragmentami. Zaczynasz np. od 1 klasy i tylko tą jedną biedną klasę zmieniasz i ona jest "zamrożona" dla innych.
Sens jest, bo może jutro trzeba będzie jednak coś do tej klasy dopisać i okaże sie że potrzebujesz na to tydzień a nie 5 minut bo musisz przekopać cię przez co to tam jest? Nie mówiąc już o ryzyku że coś przy okazji zepsujesz bo nie wierze że są do tego jakiekolwiek unit testy ;)

Z przepisywaniem kodu od nowa jest wiele problemów:

  • zmiany w aktualnym kodzie które trzeba uwzględnić
  • defekty które aktualny kod rozwiązał a o których nie wiecie bo specyfikacja o nich nie mówi
  • efekty uboczne z których coś może korzystać a w specyfikacji ich nie ma
  • długi czas zanim ta nowa wersja sie pojawi - kto za to zapłaci? ;)

Dużo sensowniejsze rozwiązanie to "szybkie" rozbicie tego na jakieś moduły, wydzielenie interfejsów a potem refaktorowanie małych elementów przy zgodzie interfejsów.

0

Oczywiście, każdy programista najlepiej napisałby wszystko od początku i od nowa, problem polega na tym że nie zawsze jest to potrzebne albo nie ma czasu i pieniędzy na coś takiego. Hipotetyczne oprogramowanie działa bez zarzutu? Wszystko jest OK? Wydaje mi się że dobrego programistę można poznać właśnie po tym że potrafi radzić sobie w tym "bagnie" i w razie potrzeby coś tam rozbudowywać albo poprawiać.

Przede wszystkim to chyba należałoby znać projekt, jeśli już ktoś zamierza zabierać się za refaktor, nawet z małymi projektami mogą być problemy i jest ryzyko że będą błędy a co dopiero z czymś większym? Pewnie że się da, tylko czy faktycznie jest to potrzebne i czy nie lepiej po prostu ogarnąć ten projekt i w razie potrzeby coś dodać?

0

Trzymaj sie zasady skauta: "Zawsze zostawiaj obozowisko czystsze niż je zastałeś."

0

@Shalom
To rozbicie na moduły to by wyglądało mniej więcej tak?

  1. Napisanie testów integracyjnych - tylko z tym może być problem jak się dokładnie nie zna systemu, a takich systemów nikt nie zna(stąd głównie pytanie czy zawsze się da)
  2. Pogrupowanie gdzieś na karce co z czym lub jakieś inne rozplanowanie co gdzie ma trafić
  3. Wyjęcie klasy do modułu
  4. Odpalenie testów
  5. Jeżeli testy przeszły to commit, jeżeli nie to wracamy
  6. Kroki 3~5 powtarzane do skutku

@Lorem Ipsum
Refaktor po to, że projekt cały czas się rozrast więc coraz trudniej się w nim poruszać i coraz więcej czasu zajmują wszelkie czynności.
Dodatkowo jak wszystko jest naplątane to drobna zmiana w jednym miejscu może popsuć kilka modułów w zupełnie innej części systemu, bo ktoś kiedyś użył hacka.

4

Ostatnio zakończyłam refaktoryzację dwóch dużych projektów.
W jednym z nich polegało to na:

  1. wyłuskaniu, co jest w ogóle używane, a co nie (okazało się, że 3/4 kodu może iść do kosza, a z tego co zostało, wiele drabinek ifów można skrócić, bo niektóre przypadki nigdy nie występują),
  2. przepisaniu pozostałych funkcji na inny język (było C++, zostało przeniesione do nowej dllki w C#),
  3. przepięcie wykorzystania w innych systemach,
  4. SKASOWANIE TEGO SZAJSU

Trwało to jakieś 3 lata.

Drugi projekt był znacznie większy i co gorsza - całkiem nieźle działał, więc zewsząd słyszałam, że lepiej nie ruszać. Przez 3 lata nie ruszałam, tylko analizowałam kod (przy okazji wprowadzania zmian i poprawek). W końcu doszłam do tzw. ściany, chciano bym poprawiła coś, co było sednem działania algorytmu. Na tym etapie powiedziałam przełożonym: "Nie da rady już tego poprawić, ale dajcie mi 3 miesiące, a dostaniecie nowy system, który będę w stanie rozbudowywać jak tylko chcecie". Stary kod znałam już na wylot, wiedziałam, gdzie poprzednicy popełnili najgorsze błędy i jak to powinno być, żeby nie dojść więcej do ściany. Przydzielono mi czas tylko i wyłącznie na przepisywanie systemu.

3 miesiące później + pół roku testów + 3 miesiące współdziałania systemów - mam wreszcie piękny, nowy, logiczny system w C#, z testami i bajerami.
Nie jest wciąż idealny, sporo bym napisała teraz lepiej (szczególnie testy mam do bani, no ale chociaż są...). Ale jest na tyle piękny, że niemal od ręki rozbudowałam system o kolejne elementy. Powiedzmy, że miał 3 główne moduły, a teraz ma 5. Do tego skuteczność działania skoczyła, wcześniej wszystkich absolutnie zadowalała skuteczność 80% - teraz mamy jakieś 95-99% (zależnie od modułu).

Także zawsze da się, nie zawsze jednak warto. W moim przypadku było warto, bo nie było szans na to, by projekty po prostu odeszły kiedyś w niebyt - były cały czas intensywnie wykorzystywane, więc i utrzymywane. Mnie bolało ich utrzymywanie, więc pomału, systematycznie, ale pchałam do przodu. Od jakichś dwóch miesięcy szturcham już delikatnie kolejny system, już za jakieś 5 lat może i on trafi do nowej technologii.

user image

edit:

  1. Napisanie testów integracyjnych - tylko z tym może być problem jak się dokładnie nie zna systemu, a takich systemów nikt nie zna(stąd głównie pytanie czy zawsze się da)

Możesz na przykład logować sobie wejście i wyjście funkcji i je analizować. Jak masz logi z kilku miesięcy działania systemu, to może być od razu widać o co chodzi.
Możesz też pisać nową funkcję (metodę, klasę) w taki sposób, by "żywy" system korzystał ze starej wersji, tuż przed zwrotem z funkcji wołasz nową wersję i porównujesz, czy zwraca to samo co stara wersja. Jeśli nie to logujesz, co było na wejściu, co na starym wyjściu a co na nowym wyjściu. Takie coś wrzucasz "na żywo" i niech podziała kilka miesięcy ;)

Tak, jak widzisz, moje podejście do tematu jest takie, że pośpiechu nie ma ;)

0

@aurel
Miałaś acceslogi czy znasz jakiś inny sposób na znalezienie nieużywanego kodu?
W sumie logowanie można zawsze dodać, tylko wtedy trzeba by jeszcze z rok poczekać zanim się refaktoryzację zacznie, ale z drugiej strony, w tym czasie można testy pisać, a one i tak są potrzebne.

Wiadomo, że jak system rozwijało xnaście/xdziesiąt/xset osób ileś lat, to nikt logicznie myślący nie podejżewa refaktoryzacji w ciągu tygodnia czy kilku miesięcy :)

0

@aurel
Miałaś acceslogi czy znasz jakiś inny sposób na znalezienie nieużywanego kodu?

W tych przypadkach akurat nie było żadnego mechanizmu logowania, musiałam samodzielnie dopisać. Wysyłałam sobie od użytkownika po http.

0

W mojej firmie (duzy system ubezpieczeniowy) czesc systemu byla pisana X lat temu przez "specjalistow" z Indii. W efekcie jest teraz kod z nic niemowiacymi nazwami zmiennych, pomieszanymi konwencjami nazw klas i metodami majacymi po kilka tysiecy(!) linii kodu. Nikt nawet nie chce sie bawic w refaktoryzacje wiec w niedlugim czasie ma sie zaczac napisanie wszystkiego od nowa (i przy okazji dopasowanie UI do obecnego look & feel).

0

Pracowałem z takim kodem, z pomieszaniem z poplątaniem gdzie nad nim pracowało ponad 200 programistów. Bez testów. Często na raz w jednej klasie. Testy też ciężko napisać do spaghetti metody na 300 linii wywołującej inne spaghetti na 300 linii... Refactoring? Bardzo powolny, bardzo mozolny i tylko przez architektów, którzy mieli szansę znać na co to wpłynie (1 zmiana mogła na 10 rzeczy w systemie) - a i tak za każdym razem coś "zepsuli". Może za 10 alt coś by z tego było, ale w rozsądnym czasie, np roku... nie.
Czy dałoby się? Tak. Ale trzeba by zrobić pauzę i przestać go rozwijać, a to było niemożliwe. Ja wolałem odejść niż się męczyć i uczyć antypaternów.

0

@Pijany Lew z tym rozbiciem na moduły chodzi o to żeby wydzielić jakiś konkretny podzbiór klas którymi chcesz sie zajmować, wyciągasz je w osobny moduł, wszystkie "zewnętrzne" zależności opakowujesz w swoje własne interfejsy (tak zeby cała reszta systemu zależała tylko od tych interfejsów) a następnie rzeźbisz w tym module. Idea jest taka, że możesz teraz dowolnie zmieniać strukturę modułu, o ile interfejsy cały czas są spełnione.
Dodatkowo warto byłoby mieć testy, choćby blackbox dla tego modułu. Czyli dla tych swoich interfejsów piszesz testy ;]

0

Dobre pytanie na dzisiaj, bo właśnie siedzę nad takim projektem.

W dużym skrócie: mamy pewien moduł napisany w nieco przestarzałej technologii - używa interfejsu Cassandra Thrift, wykorzystuje jedynie funkcje jakie były dostępne w Cassandra 1.x (obecnie jest już 3.0), więc pewne rzeczy robi trochę naokoło. Był pisany dosyć szybko, w czasach jak firma miała dosłownie kilku programistów do ogarnięcia wszystkiego, więc kod miejscami niezbyt piękny, robiony na zasadzie "worse-is-better". Moduł oczywiście działa i spełnia swoje zadania, ale ma duże ograniczenia, a jego wydajność i skalowalność pozostawia sporo do życzenia. Poza tym debugowanie go jest bardzo trudne, bo np. użycie standardowych, przyjaznych użytkownikowi narzędzi takich jak cqlsh skuktuje tym, że trzeba ręcznie dekodować binarne bloby z bazy.

Niby można by to jakoś wszystko zrefaktoryzować. Ale podczas dokładniejszej analizy okazało się, że jeśli już i tak ruszać cały moduł,
to można by też zmienić niektóre struktury danych oraz całą architekturę. W rezultacie stanęło na tym, że przepisujemy to na nowo.

Zanim zabraliśmy się za kodowanie, zebraliśmy jednak wszystkie wymagania w jeden dokument i poświęciliśmy dużo czasu na projekt. Wyszedł z tego taki trochę mini waterfall. Projekt jest w fazie testów i, poza zwyczajnymi błędami, których na początku jest dużo, na razie nie wykryliśmy większych problemów na etapie projektowania. Właściwie to na kilkanaście zgłoszonych błędów, na razie tylko dwa wymagały bardzo poważnej zmiany struktury danych w bazie danych w stosunku do projektu, czyli dodania jednej... kolumny.

Napisanie tego kawałka od nowa zamiast poprawiania starego ma też jeszcze taką zaletę, że stary moduł zostaje w kodzie obok nowego, a nowy jest domyślnie wyłączony. Dzięki temu nie ryzykujemy zdenerwowania starych klientów wprowadzeniem błędów w czymś co działało i dajemy czas na przejście na nowy moduł (który ma ten sam interfejs, co stary, więc wystarczy zmiana jednej linijki w konfiguracji).

1 użytkowników online, w tym zalogowanych: 0, gości: 1