Legacy - decyzja o pisaniu od nowa lub przerabianiu istniejącego systemu.

0

Od paru miesięcy brnę w pewne legacy i właśnie uświadomiłem sobie, że trzeba było to wszystko napisać od nowa.
System nad którym z zespołem siedzę, to typowe spaghetti pisane przez juniorów. Większość rzeczy zarządzane przez edge case.

Budżet jednak i czas był tak skromny, że uznałem, że można wprowadzić wymagane przez klienta zmiany bez orania wszystkiego.
Co więcej, klient nie zrozumie, że zmiany wymagają napisania całego systemu od nowa.

Nigdy więcej legacy.

Jak najlepiej podjąć decyzję, czy coś można reanimować, czy trzeba złożyć do grobu i przebić osikowym kołkiem?
Jest na to jakaś metodologia?

PS. To dziwne, ale nigdy wcześniej nie pracowałem ze złym kodem... i byłem przekonany, że wszystko da się naprawić.

8

O tym są nawet książki: https://www.amazon.co.uk/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

Temat brzmi jakby decyzja miała być radykalna - przepisujemy wszystko od zera albo przerabiamy po trochę. Zwykle poprawną odpowiedzią jest przerabiamy po trochę.

1

A skąd wiesz że nie będzie presji czasu tj .nawet jak zaczniecie przepisywać to ktoś was pogoni żeby to skończyć szybko?
Może warto wprowadzić dużo testów integracyjnych i przepisywać powoli?

0

@Wibowit:

Modikacja to z definicji przepisanie całości i taką drogę wybraliśmy jako zespół, ale moja refleksa po 2 miesiącach jest taka, że trzeba było zrobić dokumentacje starej aplikacji i napisać ją od nowa, od zera, z inną zupełnie architekturą, bo przepisanie tego monolitu w części to proszenie się o... problemy.

5

I tak nie wiesz na jakie problemy natrafisz podczas przepisywania, a życie uczy, że te problemy będą się nieustannie mnożyć. Jeśli przerabiasz po trochę to zawsze możesz przedyskutować z biznesem czy obecne przeróbki są akceptowalne. Jest to łatwo zrobić, bo skoro przepisujesz po kawałku to aplikacja cały czas działa i można sprawdzić i pokazać jak działa - włączając wszelkie interakcje nowo napisanego kawałka z całą resztą. Gdybyś robił big-bang, tzn zmianę wszystkiego naraz, to pełna dyskusja nad wszystkimi zmianami byłaby dopiero pod koniec procesu przerabiania i wtedy mogłoby okazać się, że o sporej liczbie rzeczy zapomnieliście i znowu byłby długi okres przerabiania.

Przerabianie krok po kroku ma też ten plus, że biznes (czyli ludzi płacący za rozwój projektu) widzą efekty pracy na bieżąco i nie ma niepotrzebnych napięć. Gdybyście przepisywali od nowa np pół roku, to przez pół roku biznes nie rozumiałby co się dzieje i nie wiedziałby czy operacja wypali zgodnie z planem.

0
[Wibowit napisał(a)]

Oki, oki.
Ja tez tak myslalem, ale... ten system to jest takie bagno, ze nie da sie tego ogarnac. Nie sadzilem, ze taki rak moze istniec. Ciesze sie, ze nie tylko ja bylem sklonny wejsc na mine pos tytulem przeciez damy rade to zmodyfikowac.

12

Niektóre bezsensowne linijki kodu są tylko po to, żeby załatać jakiegoś nieudokumentowanego buga. Co z tego, że jest tam komentarz skoro komentarze się nie kompilują = nie istnieją. Przepisując musisz wszystkie te rzeczy naprawiać od zera.

0
chalwa napisał(a):

Niektóre bezsensowne linijki kodu są tylko po to, żeby załatać jakiegoś nieudokumentowanego buga. Co z tego, że jest tam komentarz skoro komentarze się nie kompilują = nie istnieją. Przepisując musisz wszystkie te rzeczy naprawiać od zera.

Oki, dlatego wlasnie uwazam, ze lepiej jest zrobic dokumentacje, a pozniej napisac system od nowa.

15

Parafrazując stare dowcipy, "miałem gówniany system, więc zacząłem go przepisywać od zera. Teraz mam dwa gówniane systemy".

Warto w tym miejscu zalinkować klasyk od Joela.

10

Oprócz historii Netscape trzeba też wziąć pod uwagę to, że systemy biznesowe są robione typowo pod klienta, a klient używa masy nietypowych funkcji i zna je na wylot. To tak jakby porównać przestawienie się między przeglądarkami Chrome i Firefox, a przestawieniem się między Photoshopem i Gimpem. Przesiadka między Chrome, a Firefox jest trywialna, bo mało kto grzebie w środku. Przesiadka między Photoshopem i Gimpem to uczenie się wielu rzeczy od nowa. Użytkownicy systemu biznesowego, którzy siedzą z nim kilka godzin dziennie znają go bardzo dobrze, włącznie z niedoróbkami i dziwactwami, więc do wszelakich zmian muszą się przyzwyczajać, a wielu będzie domagać się starego zachowania, bo takie im pasuje.

https://xkcd.com/1172/
workflow.png

10

Mam za sobą udane większe refaktoringi. W tym 2 dość duże (jeden wręcz absurdalny). W tym najgorszym przypadku to kod to nie było spaghetti - to było spaghetti zjedzone, a potem wyrzygane przez psa, ponownie zjedzone ale razem z torebką foliową itd. Kods był stress testem do IDE (bo niektóre IDE wywalały się na dużych plikach źródłowych, albo analizie zależności)
Niemcy, którzy ten projekt przekazali - mówili wielokrotnie "przepraszamy" (za Warszawe tak nie przepraszali nawet).
Generalnie naprawianie warstwami daje radę - czyli teraz przerabiamy dostęp do bazy danych, teraz gui, itd. Projekty cały czas były w ruchu na produkcji. Istgotną częścią było wykoncypowanie jak napisać testy do projektów, które testowalne nie były, ale się udało (samo pisanie testów do momentu: "teraz możemy refaktorować" czasem trwało miesiącami).

0

Taka ciekawostka: sporo systemów w COBOLu czy innych dziadkach nadal żyje, bo nikomu nie udało się ich przepisać od zera w rozsądnym czasie. Np u mnie w korpo (HSBC). Z drugiej strony, przepisywanie z COBOLa na Javę jest i tak znacznie trudniejsze niż przepisywanie z Javy na Javę.

2

@Wibowit:
A w moich korpach COBOLe nadal się pisze :-) Co gorsza, robią to czasem naprawdę tragiczni programiści. Kiedyś myślałem, że COBOLOwcy to tacy dziadowie, co mają kiepski jezyk, za to piszą dobrą dokumentację i czytają specyfikacje tego co mają zrobić. Ja czasem spotykam partyzantów z taką "kulturą pracy", że w januszsoftach do robienia na wordpressie by ich nie wzięli. (Ale oczywiście znam kilku OK COBOLowców).

Pracowałem w firmie Szwajcarskiej, która była dumna z tego, że między 2005-2010 przepisała cały COBOL na javę... i zdominowali rynek totalnie, między innymi dzięki temu, że mieli dużo tańsze i elastyczniejsze systemy. Nie wiem jak się to udało (przepisanie) - chociaż dużo o tym rozmawiałem z ludźmi.

0

5

Oki, dlatego wlasnie uwazam, ze lepiej jest zrobic dokumentacje, a pozniej napisac system od nowa.

Nie, trzeba zrobić porządne testy integracyjne / e2e, które sprawdzają wszystkie funkcje dokładnie, zarówno pozytywne jak i negatywne ścieżki, wszystkie corner-case itd. Dopiero wtedy można w ogóle zacząć myśleć o jakimś refaktoringu. I mówie tu o testach blackboxowych, które polegaja tylko na API systemu bez wchodzenie w jakiekolwiek szczegóły implementacji.
Takie testy są też niejako dokumentacją, bo opisują scenariusze użycia systemu. Ale są lepsze od dokumentacji, bo da sie je odpalić i zobaczyć co system robi. Dodatkowo muszą być spójne z systemem, bo inaczej będą failować.

0

@Shalom:
Rozumiem w pelni koniecznosc napisania takich testow, a nawet sporo tej roboty mam zrobione.

Teraz jest pytanie, czy wziac te testy umiescic w nowej solucji i napisac od zera system, ktory bedzie dobrze napisany, czy refaktoryzowac po kawalku bierzacy projekt, ktory jest ZLY, do tego stopnia, ze zmiana w jednym systemie powoduje, ze testy ze wszystkich systemow wywalaja. Zespol naklania mnie do napisania od zera, ale to ja musze podjac decyzje.

Najgorsze jest to, ze projekt jest takim wiekszym crudem, naprawde nie ma tam jakiejs trudnej logiki, ale calosc projektu jest przeogromna, bo tak zle to jest napisane. W mojej opini 10% tego kodu realizowaloby potrzebne zadania. Ta nadmiarowosc wynika z tego, ze nigdzie nie ma dziedziczenia, ani kompozycji, tylko wszystko pisane jest od zera, dominuje wszedzie refleksja, wiec trudno nawet o statyczna analize kodu.

3

@renderme:
Podstawową zasadą jest przerzucanie użytkowników stopniowo ze starego kodu na nowy. Można to zrobić za pomocą stopniowego refaktoringu starego kodu, ale teoretycznie możesz też pisać nową wersję systemu od zera. Pisząc nową wersję musisz zrobić to tak, żeby mogła współpracować razem ze starą. Implementując kolejną funkcjonalność w nowej wersji wyłączasz ją w starej, wypuszczasz takie coś na produkcję i czekasz na feedback od użytkowników. Jeśli poszło gładko to bierzesz się za kolejny kawałek systemu, jeśli nie to dogadujesz się z użytkownikami jak ma docelowo działać nowy kod. Po wielu takich iteracjach wszystko jest zaimplementowane w sposób akceptowalny dla użytkowników w nowej wersji, a cały stary system jest wyłączony, więc można go usunąć z produkcji.

Kiedyś czytałem o praktyce polegającej na przepisywaniu od zera (w sensie było to bardziej celem niż środkiem). To było coś z "clean-sheet" w opisie, ale nie mogę tego znaleźć. Powodem takiego postępowania nie jest jednak trudność z refaktoringiem, ale kwestie prawne. Jeżeli firma nie może dystrybuować danego produktu na własnej licencji to tworzy go od nowa i ma prawa autorskie do nowej wersji. Bardzo ważnym elementem takiego schematu są dwa zupełnie oddzielne zespoły. Jeden zespół analizuje stary produkt i spisuje wymagania funkcjonalne. Drugi zespół, który nie miał styczności ze starym produktem (a na pewno z jego kodem źródłowym) tworzy nowy produkt na podstawie sporządzonej listy wymagań. W ten sposób firma unika sytuacji, w której autorzy starego produktu mogą oskarżyć firmę tworzącą nowy produkt o np. kradzież kodu.

5

Przepisując musisz wszystkie te rzeczy naprawiać od zera.

No niezupełnie, bo przepisując startujesz z zupełnie innego poziomu zrozumienia problemu. Możesz lepiej system zaprojektować i uniknąć wielu błędów. Przykładowo, gdybym nasz bieżący system w Javie przepisał w Rust, to na starcie jestem do przodu o kilkaset błędów typu data-race i kilkadziesiąt związanych z wyciekami zasobów. Z czego każdy pochłonął kiedyś dni a może tygodnie na naprawę. Po prostu ich nie da się popełnić w innej technologii.

0

@Wibowit: Wiem, że w teorii tak właśnie to wygląda.
W praktyce jednak bliżej prawdy jest @Krolik .
Przy przerabianiu pierwszych, z około 20 systemów (właściwie jest to monolit o 20 możliwych do wyodrębnienia funkcjach) wyglądało to następująco:

  1. Czas tworzenia systemu 20 godzin zespołu
  2. Czas integracji z istniejącymi systemami 400 godzin zespołu.
  3. W czasie integracji wystąpiła konieczność pewnych niewłaściwych kompromisów w powstającym systemie.
  4. W bieżącym systemie jest wiele błędów.
  5. Nie ma możliwości pełnego przetestowania systemu, bo wszystkie metody oparte są o wielowymiarową strukturę zewnętrznych zależności.

Wniosek - napisanie wszystkich systemów od nowa zajmie tyle czasu, co zintegrowanie jednego, bo przebijanie się przez gkod poprzedników jest niewiarygodnie czasochłonne.
Co więcej, dopóki nie zastąpie wszystkich systemów, nie będę pewien działania całej aplikacji.

Pisząc system zupełnie od nowa, w oparciu o testy starego systemu i dokumentacje, czuje jakbym robił coś totalnie niespotykanego. Jednak w większości przypadków jest ta logika stopniowego przejścia.
Ciekawe dokąd zaprowadzi mnie ten eksperyment.

0
renderme napisał(a):

@Wibowit: Wiem, że w teorii tak właśnie to wygląda.

W praktyce jednak bliżej prawdy jest @Krolik .

@Krolik napisał głównie o tym, że Rust jest magiczny i że nie masz w nim data-races i wycieków zasobów w takiej ilości jak np. w Javie czy C#.

renderme napisał(a):

Przy przerabianiu pierwszych, z około 20 systemów (właściwie jest to monolit o 20 możliwych do wyodrębnienia funkcjach) wyglądało to następująco:

  1. Czas tworzenia systemu 20 godzin zespołu
  2. Czas integracji z istniejącymi systemami 400 godzin zespołu.
    (...)
    Wniosek - napisanie wszystkich systemów od nowa zajmie tyle czasu, co zintegrowanie jednego, bo przebijanie się przez gkod poprzedników jest niewiarygodnie czasochłonne.

Jeżeli twierdzisz, że przepisanie całego systemu ma zająć 400 godzin zespołu to daje to jakieś 3 miesiące pracy (wliczając choroby, urlopy, piłkarzyki itd). 3 miesiące przestoju raczej nie byłoby katastrofą w typowym projekcie biznesowym. Nawet, gdybyś rozciągnął to do np 6 miesięcy żelaznego deadline'u. Tyle, że musiałby to być naprawdę żelazny deadline, bez okazywania żalu jeśli po tych 6 miesiącach nowy system nadal nie nadaje się na produkcję, ale ma dużo (lepiej napisanego) kodu, z którym trzeba się rozstać. Życie pokazuje, że to tak nie działa (jest psychologiczny problem z wyrzucaniem kodu). W agile'u jest coś takiego jak spike, gdzie tworzy się program, po to by zbadać trudność tematu, a potem ten program wyrzucić. Takie jest założenie wprost - program ma być wyrzucony po spełnieniu oryginalnego zadania jakim jest eksploracja problemu. W praktyce jest tak, że tak to może działać co najwyżej jeśli spike to robota na kilka dni (w sumie taka jest chyba teoria za spike'ami - eksperyment na kilka dni, by lepiej oszacować zadanie, które trudno oszacować). Jak rozciąga się do tygodni to już jest dylemat: kierownik zespołu nie chce tego ciągnąć, bo wyrzucanie kilkutygodniowej pracy nie wygląda (przynajmniej z pozoru) na opłacalny ruch, a jeśli już ma być ciągnięte to tak, żeby to dowieźć na produkcję, a więc pomysł traci status eksperymentu i staje się wymaganiem, tak jak każde inne normalne zadanie.

Kolejną sprawą jest to, że trawa u sąsiada jest zawsze bardziej zielona. Teraz się wam wydaje, że przepisywanie od zera byłoby lepsze, ale bardzo możliwe, że gdybyście przepisywali od zera to obecna strategia wydawałaby się lepsza. Podstawowy powód już podałem - rozciągnięcie się eksperymentu w czasie i presja ze strony kierownictwa, by to jak najszybciej dowieźć na produkcję (bo nowe funkcjonalności są potrzebne szybko), a nie wywalać tak kosztownego eksperymentu. Teraz macie tak, że jeden moduł już macie za sobą, a więc macie już pewne doświadczenie w oraniu starego kodu. To powinno przyspieszyć wymianę pozostałych części systemu - im więcej doświadczenia tym bardziej płynna praca. Kolejna sprawa to to, że przepisując możesz od razu dodawać nowe rzeczy. Jeśli dodajesz nowe rzeczy przepisując cały system naraz to mocno zwiększasz napięcie między zespołem, a kierownictwem, bo co z tego, że nowy system będzie fajny, skoro termin oddania go już dawno przekroczył granicę cierpliwości kierownictwa? Jeśli dodajesz nowe rzeczy przepisując system po kawałku to ograniczona przestrzeń zmian zmniejszy owo napięcie. Kierownictwo zobaczy nowy bajer dostarczony w granicach cierpliwości, a więc będzie zadowolone.

0

@Wibowit: Kolejną sprawą jest to, że trawa u sąsiada jest zawsze bardziej zielona.

Jest to kwestia, której boję się najbardziej. Boję się, że ulegam złudzeniu, że napisanie systemu od nowa będzie takie szybkie.Z drugiej strony w naprawie bieżącego systemu najbardziej boli mnie fakt, że biznes nie rozumie, jak trudne to zadanie i że ja NIE POTRAFIĘ (chyba nikt nie umie) oszacować, ile czasu zajmie mi naprawa systemu, bo tego systemu zwyczajnie nie rozumiem (nikt nie jest w stanie go zrozumieć, bo on nie ma sensu, ani żadnej struktury). Czasem biznes woli komunikat pod tytułem - pół roku i będzie gotowe, wiem, bo sam to robię, niż - nie mam pojęcia.

4
renderme napisał(a):

Czasem biznes woli komunikat pod tytułem - pół roku i będzie gotowe, wiem, bo sam to robię, niż - nie mam pojęcia.

To graj na zwłokę. W obu przypadkach potrzebujesz najpierw pokrycia testami E2E żeby mieć pewność jak system działa (przynajmniej czarnoskrzynkowo). Takie testy mogę być osobnym projektem w osobnym repo. Gdy będziecie wiedzieć jak działa systemo (przynajmniej z zewnątrz) możecie zacząć podejmować decyzję czy przepisać czy refaktoryzować

2
Krolik napisał(a):

[…] przepisał w Rust, to na starcie jestem do przodu o kilkaset błędów typu data-race i kilkadziesiąt związanych z wyciekami zasobów. […] Po prostu ich nie da się popełnić w innej technologii.

Da się. Wycieki pamięci są częścią safe-Rust, a z data-race to tak, ale to tylko mały ułamek możliwych wyścigów, i jak najbardziej można mieć deadlocki czy wyścigi w przypadku safe-Rust. Ogólnie zgadzam się z @Wibowit najłatwiejszy sposób IMHO to postawić jakiś gateway, który przekierowuje część endpointów do nowej aplikacji i powoli zastępować coraz więcej i więcej nowym serwisem. W ten sposób można "stopniowo" przenieść użytkowników na nowe i "wygładzić" implementację w bezpieczny sposób. Można nawet każdy endpoint wstawić za flagą, by w razie problemów móc się błyskawicznie przerzucić na starą wersję.

5

@renderme Robiłem jakiś czas temu taki "refaktor", tzn przepisanie pewnego serwisu od zera (a w praktyce przepisanie kawałka monolitu na osobny serwis) przy zachowaniu API. I mimo że use-case wydawał się prosty i niby wszystkie wymagania były z góry znane, w praktyce wyszło klasycznie: 80% funkcjonalności zajęło 80% czasu, a pozostałe 20% funkcjonalności kolejne 80% czasu (w rzeczywistości nadal nie jest w pełni zaimplementowane, bo niektóre corner case wyszły dopiero po wdrożeniu). Nasze "rozwiązanie" to fallback do starego systemu jeśli czegoś "nie obsługujemy" i przepinanie powoli wszystkiego na nowy system. Mamy teraz pewnie 95% obsłużone, ale te pozostałe 5% stanowi spory problem, bo np. wymaga dostępu do jakichś internalsów tego początkowego monolitu.

@Krolik to dość odważne założenie że technologia magicznie sprawi że wiele problemów zniknie. Tzn może tak być, ale obawiam się że to będzie wymiana problemów X na problemy Y ;) Inna sprawa, że pisząc system drugi raz wiesz juz jakie problemy występowały w poprzednim i będziesz pamiętać zeby tych samych błędów nie popełnić.

3

@Shalom:

to dość odważne założenie że technologia magicznie sprawi że wiele problemów zniknie.

To jest zupełnie normalne założenie. Po to właśnie są te nowe technologie. I to nie magia, a nauka :-)

Przykład: Wiadomo, że i w javie można mieć błąd w zarządzaniu pamięcią (wyciek), ale jednak jak się wróci do C to człowiek dopiero wtedy widzi jakie to pole minowe - na każdym kroku.

A co do wymiany problemów X na Y - zawsze są jakieś problemy to prawda. Tyle, że IMO to nie jest podmiana, a odkopanie. Zwykle były oba - tylko jak mieliśmy **tony **problemów X to nie było widać **kilogramów **problemów Y.

Jeżeli widzę, że w starym systemie / języku / frameworku występował jakiś problem to rozwiązaniem może być właśnie taka zmiana technologii, żeby problem był automatycznie rozwiązywany, albo minimalizowany.

1

Już czytałem o tych problemach, których nie ma w Ruście. Nie pamiętam czy miała być gwarancja braku wycieków pamięci, ale std::boxed::Box.leak to oficjalna, stabilna i safe metoda do robienia wycieków pamięci wprost. Gwarancja zamykania zasobów? W Javie 7 jest try-with-resources. Niby nie działa jeśli np. przesyłamy otwarte strumienie między wątkami, ale jak często tak się robi? Zwykle przesyła się java.nio.file.Path, a zasób otwiera i zamyka się tam gdzie się go używa. Jak wejdzie Project Loom to sytuacji, w których trzeba będzie trzymać otwarty zasób gdzieś na boku będzie jeszcze mniej (try-with-resources świetnie się zgrywa z Project Loom). Data-races w Scali zdarzają mi się sporadycznie, a nie stosujemy żadnych sprytnych monad IO czy innych mechanizmów, które miałyby jakoś automatycznie niwelować problemy z wielowątkowym dostępem do danych (oprócz oczywiście niemutowalnych struktur danych, których w Scali pełno).

Dużo poważniejsze problemy to podział odpowiedzialności, stringly typed programming, testowalność, szybkość refaktorowania, itp itd

2

@Wibowit:

Problem z data races jest taki, że praktycznie każdy nietrywialny kod wielowątkowy (nawet w javo-springu, gdzie wątków prawie się nie używa) jest ich pełny!!! Tylko ich nie widać. Przeważnie mamy szczęście i tylko raz na parę miesięcy występuje jakiś heisenbug na produkcji (który zwykle jest olany).

Co gorsza, jaśli się znajdzie przyczynę to pół zespołu będzię się bało wrzucenia poprawki, bo przecież im od lat działało dobrze, a my tylko pierniczymy o jakichś teoretycznych dyrdymałach z Java Language Specification.

Tu jest fajny wywiad z Corecursive https://open.spotify.com/show/6XU1MRwzCfAXD07YHbpjNv - w ósmej minucie jest o bugach w firefox (pełna propaganda Rustowa :-) - ale IMO dobrze trafia w sedno) .

2
jarekr000000 napisał(a):

@Wibowit:

Problem z data races jest taki, że praktycznie każdy nietrywialny kod wielowątkowy (nawet w javo-springu, gdzie wątków prawie się nie używa) jest ich pełny!!!

Dlatego zaznaczyłem, że kodzę w Scali :] Spring to raczej jest usiany wątkami, tylko np. schowanymi za adnotacjami.

Data-race można zrobić nawet mając jakiś tam bat nad sobą. Weźmy na przykład transakcje bazodanowe z poziomem izolacji serializable. Teoretycznie nie ma szans na błąd. Ale coś jeśli sami sobie go stworzymy? W jednym z mikroserwisów mieliśmy taki problem - użytkownik może zmienić walutę limitu dla konta, a przeliczanie waluty odbywa się w innym serwisie. Co można zrobić? Można zaciągnąć kursy wszystkich walut naraz i przeliczać lokalnie, ale wtedy duplikujemy funkcjonalność serwisu i zakładamy, że transfer niepotrzebnych danych nas nie zajedzie. Drugim rozwiązaniem jest zrobienie 3 kroków w jednej transakcji:

  • wczytanie starej waluty i nominału z bazy
  • przeliczenie nominału na nową walutę z użyciem wyspecjalizowanego serwisu
  • zapisanie nowej waluty i nominału do bazy

Problem z powyższym podejściem to potencjalnie długotrwała operacja w czasie trwania transakcji bazodanowej, co może zablokować trochę system. Rozwiązanie? Zróbmy dwie transakcje, a środkowy krok zróbmy poza transakcjami. Teraz musimy jednak w drugiej transakcji upewnić się, że stara waluta i nominał się nie zmieniły, bo inaczej nasze nowe wartości są potencjalnie przestarzałe (nieprawidłowe). Jeśli się zmieniły to powtarzamy cały proces (to jest optimistic concurrency, jak np. w Javowym AtomicInteger i podobnych). Nic nad nami jednak nie stoi i nie trzyma bata (w tej akurat kwestii), więc (jeśli ktoś się mało przejmuje poprawnością) można założyć że prawdopodobieństwo, że ktoś nam zmieni coś w naszym limicie podczas przeliczania nominału jest tak małe, że nie warto się tym przejmować.

Innymi słowy: Rustowe borrow checkery mogą trochę pomagać w stworzeniu poprawnego pessimistic concurrency, ale już nie działają dla lock-free / optimistic concurrency.

Trochę robi się offtop, więc może już nie rozprawiajmy o magicznych mechanizmach :)

2

Dobra,
Przeforsowałem decyzję o pisaniu systemu na nowo z wykorzystaniem testów zbudowanych na starej wersji oraz zrobionej dokumentacji. Za 2 miesiące napiszę swoje wnioski. YOLO

1

Po co dokumentacja?
Wystarczą Ci testy e2e / integracyjne, najlepiej bazujące na plikach (np. JSON) które użytkownicy rozumieją albo można je wprost użyć z istniejącą aplikacją.
Wszelkie JUnity czy mocki można użyć ale na własny użytek. Nie udowodnisz nimi nic użytkownikowi.

Tyle, że nie po jednym na endpoint, a w zasadzie wszystkie możliwe.
Również mocno polecam "Working Effectively with Legacy Code" - tam jest prosto pokazane jak przejść od totalnej kupy (chociaż przykłady w książce nie są tak straszne jak w rzeczywistości).
Jeden z lepszych pro-tipów z tej książki: zacznij od testu który sprawdza wyjście używając aktualnego wyniku (bez wnikania czy dobry czy zły).
Analizę poprawności zachowania zostaw na później. Może być j.,w. że użytkownicy są przyzwyczajeni do nie do końca poprawnego działania.

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