Co może być przyczyną pojawienia się nowych błędów w innym miejscu po rozwiązaniu znanego błędu?

14

Podczas dyskusji jeden z moich kolegów powiedział, że ma pewne trudności z bieżącym projektem podczas próby rozwiązania błędów. „Kiedy rozwiązuję jeden błąd, coś innego przestaje działać gdzie indziej”, powiedział.

Zacząłem myśleć o tym, jak to się mogło stać, ale nie mogę tego rozgryźć.

  • Czasami mam podobne problemy, gdy jestem zbyt zmęczony / śpiący, aby poprawnie wykonywać pracę i mieć ogólny widok części kodu, nad którym pracowałem. Tutaj wydaje się, że problem występuje przez kilka dni lub tygodni i nie jest związany z celem mojego kolegi.
  • Mogę sobie również wyobrazić ten problem powstający w bardzo dużym projekcie, bardzo źle zarządzanym , w którym członkowie drużyny nie mają pojęcia, kto co robi, i jaki wpływ na pracę innych może mieć zmianę, którą oni robią. Tak też nie jest w tym przypadku: jest to raczej mały projekt z tylko jednym deweloperem.
  • Może to być również problem ze starą, źle utrzymaną i nigdy nieudokumentowaną bazą kodu , gdzie jedyni programiści, którzy naprawdę potrafią sobie wyobrazić konsekwencje zmiany, opuścili firmę lata temu. Tutaj projekt właśnie się rozpoczął, a programista nie korzysta z niczyjej bazy kodu.

Co może być przyczyną takiego problemu na świeżej, niewielkiej bazie kodu napisanej przez jednego programistę, który koncentruje się na swojej pracy ?

Co może pomóc?

  • Testy jednostkowe (nie ma żadnych)?
  • Prawidłowa architektura (jestem prawie pewien, że podstawa kodu w ogóle nie ma architektury i została napisana bez wstępnego myślenia), wymagająca całego refaktoryzacji?
  • Programowanie parowe?
  • Coś innego?
Arseni Mourzenko
źródło
14
Ach, dobry stary wzór kaskadowych fal awarii. :-)
Brian Knoblauch,
1
Porównuję to do bańki w kontakcie. Naciśnij go, wyskakuje gdzie indziej. Im lepiej mój kodowania dostaje, tym mniej ja to widzę
johnc
2
Na marginesie, miałem dokładnie to w systemie wbudowanym. Dodałem wywołanie funkcji, aby naprawić problem. To wywołanie funkcji było zbyt duże dla stosu (mikrokontroler nie wykrył przepełnienia stosu), więc zapisał w pamięci jakieś przypadkowe rzeczy, co oczywiście złamało coś zupełnie innego. Tak więc, coś takiego może się zdarzyć na małej bazie kodu z tylko jednym deweloperem i dobrą architekturą.
rośnie Ciemność
... i to był koszmar do debugowania.
rośnie Ciemność

Odpowiedzi:

38

Nie ma wiele wspólnego z koncentracją, rozmiarem projektu, dokumentacją lub innymi problemami procesowymi. Takie problemy są zwykle wynikiem nadmiernego sprzężenia w projekcie, co bardzo utrudnia izolowanie zmian.

Karl Bielefeldt
źródło
15
w połączeniu ze słabymi testami regresji lub bez nich
Ryathal,
3
To prawda, @Ryathal, chociaż testy regresji nie zapobiegną tego rodzaju błędom, po prostu daj im znać wcześniej.
Karl Bielefeldt,
Jeśli dowiesz się o nich wystarczająco wcześnie (np. W ciągu kilku minut od utworzenia błędów), możesz cofnąć zmiany i skutecznie udawać, że nigdy się nie zdarzyły.
bdsl
14

Jedną z przyczyn może być ścisłe powiązanie między komponentami oprogramowania: jeśli nie ma prostych, dobrze zdefiniowanych interfejsów między komponentami, to nawet niewielka zmiana w jednej części kodu może spowodować nieoczekiwane skutki uboczne w innych częściach kod.

Jako przykład ostatnio pracowałem nad klasą, która implementuje komponent GUI w mojej aplikacji. Przez tygodnie zgłaszano nowe błędy, naprawiłem je, a nowe błędy pojawiły się gdzie indziej. Uświadomiłem sobie, że klasa ta stała się zbyt duża, robiła zbyt wiele rzeczy, a wiele metod zależało od tego, by inne metody były wywoływane we właściwej kolejności, aby działały poprawnie.

Zamiast naprawienia trzech ostatnich błędów, dokonałem silnego refaktoryzacji: podzieliłem komponent na klasę główną plus klasy MVC (trzy dodatkowe klasy). W ten sposób musiałem podzielić kod na mniejsze, prostsze części i zdefiniować wyraźniejsze interfejsy. Po refaktoryzacji wszystkie błędy zostały rozwiązane i nie zgłoszono żadnych nowych błędów.

Giorgio
źródło
7

Jeden błąd może maskować inny. Załóżmy, że błąd „A” powoduje wywołanie niewłaściwej funkcji do obsługi danych wejściowych. Po naprawieniu błędu „A” nagle wywoływana jest poprawna funkcja, która nigdy nie została przetestowana.

ddyer
źródło
5

Cóż, bezpośrednią przyczyną są dwie krzywdy, które czynią dobro, a przynajmniej sprawiają, że nie jest to oczywiste zło. Jedna część kodu kompensuje nieprawidłowe zachowanie drugiej części. Lub jeśli pierwsza część nie jest „zła” jako taka, istnieje niepisana zgoda między dwiema częściami, która jest naruszana przy zmianie kodu.

Załóżmy na przykład, że funkcje A i B stosują konwencję zerową dla pewnej ilości, więc działają one poprawnie razem, ale C używa jednej, możesz „naprawić” A, aby pracować z C, a następnie odkryć problem z B.

Głębszym problemem jest brak niezależnej weryfikacji poprawności poszczególnych części. Testy jednostkowe mają na celu rozwiązanie tego problemu. Działają również jako specyfikacja odpowiednich danych wejściowych. Np. Dobry zestaw testów wyjaśniłby, że funkcje A i B oczekiwały danych wejściowych opartych na 0 i opartych na C1.

Właściwe specyfikacje można również wykonać na inne sposoby, od oficjalnych dokumentów po dobre komentarze w kodzie, w zależności od potrzeb projektu. Kluczem jest zrozumienie, czego oczekuje każdy element i co obiecuje, abyś mógł znaleźć niespójności.

Dobra architektura pomaga w zrozumieniu kodu, ułatwiając to. Programowanie w parach pomaga przede wszystkim unikać błędów lub szybciej je znaleźć.

Mam nadzieję że to pomoże.

Mike B.
źródło
5

Ścisłe połączenie, brak testów, to prawdopodobnie najczęstsze winowajcy. Zasadniczo wspólnym problemem są po prostu tandetne standardy i procedura. Innym jest po prostu niepoprawny kod zarządzający, aby mieć szczęście przez jakiś czas z prawidłowym zachowaniem. Rozważmy memcpybłąd Linusa Torvaldsa, w którym zmiana jego implementacji ujawniła (nie spowodowała) błędy w klientach, które używały memcpyw miejscach, w których powinny były stosować memmovenakładające się źródło i miejsce docelowe.


źródło
4

Wygląda na to, że te „nowe” błędy nie są tak naprawdę „nowymi” błędami. Nie stanowiły one problemu, dopóki inny złamany kod nie został naprawiony. Innymi słowy, twój kolega nie zdaje sobie sprawy, że tak naprawdę miał dwa błędy przez cały czas. Jeśli kod, który nie okazuje się być złamany, nie został złamany, nie zawiódłby, gdy rzeczywiście naprawiono inny fragment kodu.

W obu przypadkach pomocny może być lepszy schemat testów automatycznych. Wygląda na to, że Twój kolega musi przetestować bieżącą bazę kodu. W przyszłości testy regresji sprawdzą, czy istniejący kod nadal działa.

Ramhound
źródło
0

Popraw zakres swojego automatycznego schematu testowego. ZAWSZE uruchamiaj pełny zestaw testów przed zatwierdzeniem zmian w kodzie. W ten sposób wykryjesz szkodliwy efekt twoich zmian.

Stephen Gross
źródło
0

Właśnie się z tym spotkałem, gdy test był niepoprawny. Test sprawdził, czy dany stan uprawnień jest! Poprawny. Zaktualizowałem kod i uruchomiłem test uprawnień. Zadziałało. Potem przeprowadziłem wszystkie testy. Wszystkie inne testy, które korzystały ze sprawdzonego zasobu, nie powiodły się. Poprawiłem test i sprawdzenie uprawnień, ale początkowo panika była trochę niepokojąca.

Zdarzają się również niespójne specyfikacje. Wtedy jest prawie pewne, że naprawienie jednego błędu stworzy kolejny (ekscytujące, gdy ta konkretna część specyfikacji nie zostanie wykonana do późniejszego etapu projektu).

Ccoakley
źródło
0

Wyobraź sobie, że masz kompletny produkt. Następnie dodajesz coś nowego, wszystko wydaje się w porządku, ale złamałeś coś innego, co zależy od kodu, który zmienisz, aby nowa funkcja działała. Nawet jeśli nie zmienisz żadnego kodu, po prostu dodaj funkcjonalność do istniejącego, może złamać coś innego.

Więc w zasadzie prawie odpowiedziałeś sobie:

  • luźne powiązanie
  • brak testów

Naucz się dostosowywać zasadę TDD (przynajmniej w przypadku nowych funkcji) i spróbuj przetestować każdy możliwy stan, który może się zdarzyć.

Programowanie w pary jest świetne, ale nie zawsze „dostępne” (czas, pieniądze, oba ...). Ale przeglądy kodu (na przykład przez twoich kolegów) raz dziennie / tydzień / zestaw zatwierdzeń również bardzo pomogą, szczególnie, gdy przegląd obejmuje pakiet testowy. (Trudno mi nie pisać błędów w pakietach testowych ... czasami muszę przetestować test wewnętrznie (sprawdzenie poprawności) :)).

Dalibor Filus
źródło
0

Powiedzmy, że programista A napisał kod z błędem. Kod nie jest dokładnie tym, co powinien zrobić, ale coś nieco innego. Deweloper B napisał kod, który polegał na tym, że kod A robi dokładnie to, co jest przeznaczone, a kod B nie działa. B sprawdza, znajduje nieprawidłowe zachowanie w kodzie A i naprawia go.

Tymczasem kod programisty C działał poprawnie tylko dlatego, że polegał na niepoprawnym zachowaniu kodu A. Kod A jest teraz poprawny. A kod C przestaje działać. Co oznacza, że ​​kiedy naprawiasz kod, musisz bardzo dokładnie sprawdzić, kto używa tego kodu i jak ich zachowanie zmieni się wraz ze stałym kodem.

Miałem inną sytuację: niektóre kody źle zachowały się i całkowicie uniemożliwiły działanie funkcji w niektórych sytuacjach X. Więc zmieniłem złe zachowanie i sprawiłem, że funkcja działa. Niefortunnym efektem ubocznym było to, że cała funkcja miała znaczące problemy w sytuacji X i zawiodła w każdym miejscu - nie było to dla nikogo zupełnie nieznane, ponieważ sytuacja nigdy wcześniej się nie pojawiła. Cóż, to trudne.

gnasher729
źródło