Słusznie lub nie, jestem obecnie przekonany, że zawsze powinienem starać się, aby mój kod był jak najbardziej niezawodny, nawet jeśli oznacza to dodanie zbędnego kodu / czeków, o których wiem, że nie będą w tej chwili przydatne, ale one może być x ilość lat w dół linii.
Na przykład obecnie pracuję nad aplikacją mobilną, która zawiera ten fragment kodu:
public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
//1. Is rows equal to null? - This will be the case if this is the first appointment.
if (rows == null) {
rows = new List<CalendarRow> ();
}
//2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
if(rows.Count == 0)
{
rows.Add (new CalendarRow (0));
rows [0].Appointments.Add (app);
}
//blah...
}
Patrząc konkretnie na drugą część, wiem, że jeśli pierwsza sekcja jest prawdziwa, to druga sekcja również będzie prawdziwa. Nie mogę wymyślić żadnego powodu, dla którego sekcja pierwsza byłaby fałszywa, a sekcja druga oceniała jako prawdę, co sprawia, że drugie if
zdanie jest zbędne.
Jednak w przyszłości może się zdarzyć, że to drugie if
stwierdzenie jest rzeczywiście potrzebne i ze znanego powodu.
Niektórzy ludzie mogą na to spojrzeć początkowo i myślą, że programuję z myślą o przyszłości, co oczywiście jest dobrą rzeczą. Znam jednak kilka przypadków, w których ten rodzaj kodu „ukrył” przede mną błędy. Oznacza to, że jeszcze dłużej zajęło mi ustalenie, dlaczego funkcja xyz
działa, abc
kiedy powinna def
.
Z drugiej strony istniało także wiele przypadków, w których ten rodzaj kodu znacznie, znacznie ułatwia ulepszanie kodu za pomocą nowych zachowań, ponieważ nie muszę wracać i zapewniać, że wszystkie odpowiednie kontrole są na miejscu.
Czy istnieją jakieś ogólne zasada-of-kciuk wytyczne dla tego rodzaju kodu? (Chciałbym również dowiedzieć się, czy byłoby to uważane za dobrą czy złą praktykę?)
Uwaga: Można to uznać za podobne do tego pytania, jednak w przeciwieństwie do tego pytania, chciałbym uzyskać odpowiedź, zakładając, że nie ma żadnych terminów.
TLDR: Czy powinienem posunąć się do dodawania zbędnego kodu, aby uczynić go potencjalnie bardziej niezawodnym w przyszłości?
if(rows.Count == 0)
to się nigdy nie wydarzy, możesz wtedy podnieść wyjątek - i sprawdź, dlaczego twoje założenie się nie spełniło.rows
być zawsze zerowy? Nigdy nie ma dobrego powodu, przynajmniej w .NET, aby kolekcja była pusta. Puste , jasne, ale nie puste . Rzuciłbym wyjątek, jeślirows
jest pusty, ponieważ oznacza to, że dzwoniący ma przerwę w logice.Odpowiedzi:
Jako ćwiczenie najpierw zweryfikujmy logikę. Chociaż, jak zobaczymy, masz większe problemy niż jakikolwiek problem logiczny.
Wywołaj pierwszy warunek A i drugi warunek B.
Najpierw mówisz:
To znaczy: A implikuje B, lub, w bardziej podstawowych terminach
(NOT A) OR B
I wtedy:
Czyli:
NOT((NOT A) AND B)
. Zastosuj prawo Demorgan, aby uzyskać,(NOT B) OR A
co B oznacza A.Dlatego jeśli oba stwierdzenia są prawdziwe, A oznacza B, a B oznacza A, co oznacza, że muszą być równe.
Dlatego tak, kontrole są zbędne. Wygląda na to, że masz cztery ścieżki kodu w programie, ale w rzeczywistości masz tylko dwie.
Więc teraz pytanie brzmi: jak napisać kod? Prawdziwe pytanie brzmi: jaka jest podana umowa metody ? Pytanie, czy warunki są zbędne, to czerwony śledź. Prawdziwe pytanie brzmi: „Czy zaprojektowałem rozsądną umowę i czy moja metoda wyraźnie ją realizuje?”
Spójrzmy na deklarację:
Jest publiczny, więc musi być odporny na złe dane od dowolnych dzwoniących.
Zwraca wartość, więc powinna być użyteczna dla wartości zwracanej, a nie dla efektu ubocznego.
A jednak nazwa metody jest czasownikiem, co sugeruje, że jest przydatna ze względu na efekt uboczny.
Umowa parametru listy wynosi:
Ta umowa jest szalona . Wyobraź sobie pisanie dokumentacji na ten temat! Wyobraź sobie pisanie przypadków testowych!
Moja rada: zacznij od nowa. Ten interfejs API ma napisany interfejs maszyny do cukierków. (Wyrażenie pochodzi ze starej opowieści o automatach do cukierków w Microsoft, gdzie zarówno ceny, jak i wybory są liczbami dwucyfrowymi, i bardzo łatwo jest wpisać „85”, czyli cenę przedmiotu 75, a otrzymasz zła rzecz. Ciekawostka: tak, właśnie to zrobiłem przez pomyłkę, gdy próbowałem wydobyć gumę z automatu w Microsoft!)
Oto jak zaprojektować rozsądną umowę:
Spraw, by twoja metoda była użyteczna ze względu na efekt uboczny lub wartość zwracaną, a nie oba.
Nie akceptuj typów zmiennych jako danych wejściowych, takich jak listy; jeśli potrzebujesz sekwencji informacji, weź IEnumerable. Czytaj tylko sekwencję; nie pisz do przekazanej kolekcji, chyba że jest bardzo jasne, że jest to umowa metody. Biorąc IEnumerable, wysyłasz wiadomość do dzwoniącego, że nie zamierzasz mutować jego kolekcji.
Nigdy nie akceptuj zer; sekwencja zerowa jest obrzydliwością. Wymagaj od dzwoniącego przekazania pustej sekwencji, jeśli ma to sens, nigdy nie ma wartości zerowej.
Awaria natychmiast, jeśli dzwoniący naruszy umowę, aby nauczyć go, że masz na myśli biznes, i aby złapali błędy podczas testowania, a nie produkcji.
Zaprojektuj umowę, aby była jak najbardziej rozsądna, a następnie jasno ją zrealizuj. To sposób na przyszłość.
Teraz mówiłem tylko o twojej konkretnej sprawie i zadałeś ogólne pytanie. Oto kilka dodatkowych ogólnych porad:
Jeśli istnieje fakt, że jako deweloper możesz wywnioskować, ale kompilator nie może tego zrobić, użyj asercji, aby udokumentować ten fakt. Jeśli inny programista, taki jak przyszły ty lub jeden z twoich współpracowników, naruszy to założenie, wówczas stwierdzenie to ci powie.
Uzyskaj narzędzie pokrycia testowego. Upewnij się, że testy obejmują każdą linię kodu. Jeśli jest odkryty kod, oznacza to, że masz brakujący test lub martwy kod. Martwy kod jest zaskakująco niebezpieczny, ponieważ zazwyczaj nie jest przeznaczony do martwego kodu! Niesamowicie okropna luka w zabezpieczeniach Apple „nie udało się” kilka lat temu przychodzi mi na myśl.
Uzyskaj narzędzie do analizy statycznej. Heck, zdobądź kilka; każde narzędzie ma swoją szczególną specjalność i żadne nie jest nadzbiorem innych. Zwróć uwagę, kiedy mówi ci, że jest nieosiągalny lub nadmiarowy kod. Ponownie, są to prawdopodobnie błędy.
Jeśli brzmi to tak, jak mówię: po pierwsze, dobrze zaprojektuj kod, a po drugie, sprawdź, czy jest poprawny, no cóż, tak właśnie mówię. Takie postępowanie znacznie ułatwi radzenie sobie z przyszłością; najtrudniejszą częścią przyszłości jest radzenie sobie z całym błędnym kodem maszynowym cukierków, który ludzie pisali w przeszłości. Zrób to już dziś, a koszty będą w przyszłości niższe.
źródło
CompareExchange
bez takiej możliwości!), A nawet w scenariuszach niesąsiadujących coś takiego jak „dodaj rekord, jeśli nie istnieje, i zwróć przekazane dane record lub ten, który istniał ”może być wygodniejszy niż jakiekolwiek podejście, które nie wykorzystuje zarówno efektu ubocznego, jak i wartości zwrotnej.To, co robisz w kodzie, który pokazujesz powyżej, to nie tyle korekta na przyszłość, co kodowanie defensywne .
Oba
if
stwierdzenia sprawdzają różne rzeczy. Oba są odpowiednimi testami w zależności od potrzeb.Sekcja 1 testuje i koryguje
null
obiekt. Uwaga dodatkowa: Utworzenie listy nie tworzy żadnych elementów potomnych (npCalendarRow
.).Sekcja 2 testuje i koryguje błędy użytkownika i / lub wdrożenia. To, że masz,
List<CalendarRow>
nie oznacza, że masz na liście jakieś elementy. Użytkownicy i realizatorzy będą robić rzeczy, których nie możesz sobie wyobrazić tylko dlatego, że mogą to zrobić, niezależnie od tego, czy ma to dla ciebie sens, czy nie.źródło
Myślę, że to pytanie w zasadzie zależy od gustu. Tak, dobrym pomysłem jest napisanie solidnego kodu, ale kod w twoim przykładzie jest lekkim naruszeniem zasady KISS (jak wiele takich „przyszłych kodów” będzie).
Osobiście prawdopodobnie nie zawracałbym sobie głowy sprawieniem, by kod był kuloodporny na przyszłość. Nie znam przyszłości i dlatego taki „przyszły kuloodporny” kod jest skazany na niepowodzenie, gdy i tak nadejdzie przyszłość.
Zamiast tego wolałbym inne podejście: sformułuj założenia, które wyrażasz wyraźnie, używając
assert()
makra lub podobnych obiektów. W ten sposób, gdy przyszłość nadejdzie za drzwiami, powie ci dokładnie, gdzie twoje założenia już się nie utrzymują.źródło
Inną zasadą, o której warto pomyśleć, jest pomysł szybkiego zawierania . Chodzi o to, że gdy coś pójdzie nie tak w twoim programie, chcesz natychmiast zatrzymać program, przynajmniej podczas jego opracowywania przed jego wydaniem. Zgodnie z tą zasadą chcesz napisać wiele kontroli, aby upewnić się, że twoje założenia się utrzymują, ale poważnie zastanów się nad tym, aby Twój program zatrzymał się na swoich śladach, ilekroć założenia zostaną naruszone.
Mówiąc odważnie, jeśli w twoim programie jest choćby niewielki błąd, chcesz, aby całkowicie się zawiesił podczas oglądania!
Może się to wydawać sprzeczne z intuicją, ale pozwala wykryć błędy tak szybko, jak to możliwe podczas rutynowego programowania. Jeśli piszesz fragment kodu i uważasz, że jest on gotowy, ale zawiesza się podczas testowania, nie ma wątpliwości, że jeszcze nie skończyłeś. Ponadto większość języków programowania oferuje doskonałe narzędzia do debugowania, które są najłatwiejsze w użyciu, gdy program ulega awarii całkowicie, zamiast próbować zrobić wszystko po błędzie. Najczęstszym, najczęstszym przykładem jest to, że jeśli zawiesisz program, rzucając nieobsługiwany wyjątek, komunikat wyjątku powie ci niewiarygodną ilość informacji o błędzie, w tym o tym, który wiersz kodu zawiódł i ścieżkę przez kod, którą program obrał jego droga do tego wiersza kodu (ślad stosu).
Aby uzyskać więcej przemyśleń, przeczytaj ten krótki esej: Nie przybijaj programu do pozycji pionowej
Jest to istotne dla Ciebie, ponieważ możliwe jest, że czasami piszesz kontrole, ponieważ chcesz, aby Twój program próbował nadal działać, nawet jeśli coś poszło nie tak. Rozważmy na przykład krótką implementację sekwencji Fibonacciego:
To działa, ale co, jeśli ktoś przekaże ujemną liczbę do Twojej funkcji? To nie zadziała! Dlatego warto dodać zaznaczenie, aby upewnić się, że funkcja jest wywoływana z nieujemnym wejściem.
Może być kuszące, aby napisać taką funkcję:
Jednak jeśli to zrobisz, a następnie przypadkowo wywołaj funkcję Fibonacciego z negatywnym wejściem, nigdy nie zdasz sobie z tego sprawy! Co gorsza, Twój program prawdopodobnie będzie nadal działał, ale zacznie generować bezsensowne wyniki, nie dając żadnych wskazówek co do tego, gdzie coś poszło nie tak. To najtrudniejsze rodzaje błędów do naprawienia.
Zamiast tego lepiej napisać czek w ten sposób:
Teraz, jeśli kiedykolwiek przypadkowo wywołasz funkcję Fibonacciego z ujemnym wejściem, twój program natychmiast się zatrzyma i poinformuje cię, że coś jest nie tak. Ponadto, podając ślad stosu, program poinformuje cię, która część twojego programu próbowała nieprawidłowo wykonać funkcję Fibonacciego, dając doskonały punkt wyjścia do debugowania tego, co jest nie tak.
źródło
Czy powinieneś dodać zbędny kod? Nie.
Ale to, co opisujesz, nie jest zbędnym kodem .
To, co opisujesz, to programowanie w obronie przed wywoływaniem kodu, który narusza warunki wstępne twojej funkcji. Niezależnie od tego, czy robisz to, czy po prostu pozostawiasz użytkownikom czytanie dokumentacji i unikanie tych naruszeń, jest to całkowicie subiektywne.
Osobiście wierzę w tę metodologię, ale podobnie jak w przypadku wszystkiego, musisz być ostrożny. Weźmy na przykład C ++
std::vector::operator[]
. Odkładając na chwilę implementację trybu debugowania VS, ta funkcja nie wykonuje sprawdzania granic. Jeśli zażądasz elementu, który nie istnieje, wynik jest niezdefiniowany. Użytkownik powinien podać poprawny indeks wektorowy. Jest to dość celowe: możesz „włączyć się” do sprawdzania granic, dodając je na stronie wywoławczej, ale jeślioperator[]
implementacja miałaby to zrobić, nie będziesz w stanie „zrezygnować”. Ma to sens jako funkcja dość niskiego poziomu.Ale jeśli piszesz
AddEmployee(string name)
funkcję dla jakiegoś interfejsu wyższego poziomu, w pełni oczekiwałbym, że ta funkcja rzuci przynajmniej wyjątek, jeśli podasz pustyname
, a także warunek ten zostanie udokumentowany bezpośrednio nad deklaracją funkcji. Być może nie udostępniasz dziś niechcianej informacji o tej funkcji przez użytkownika, ale uczynienie jej „bezpieczną” w ten sposób oznacza, że wszelkie naruszenia warunków wstępnych, które pojawią się w przyszłości, można łatwo zdiagnozować, zamiast potencjalnie wywołać łańcuch domina trudnych do wykryć błędy. To nie jest redundancja: to staranność.Gdybym musiał wymyślić ogólną zasadę (chociaż jako ogólną zasadę staram się jej unikać), powiedziałbym, że funkcja spełniająca którekolwiek z poniższych kryteriów:
… Może skorzystać z programowania obronnego. W innych przypadkach nadal możesz pisać
assert
jony, które strzelają podczas testowania, ale są wyłączone w kompilacjach wersji, aby dodatkowo zwiększyć swoją zdolność do znajdowania błędów.Temat ten jest dalej badany na Wikipedii ( https://en.wikipedia.org/wiki/Defensive_programming ).
źródło
Istotne są tutaj dwa z dziesięciu przykazań programistycznych:
Nie należy zakładać, że dane wejściowe są prawidłowe
Nie będziesz tworzył kodu do wykorzystania w przyszłości
Tutaj sprawdzanie wartości null nie oznacza „tworzenia kodu do wykorzystania w przyszłości”. Tworzenie kodu do wykorzystania w przyszłości przypomina dodawanie interfejsów, ponieważ uważasz, że mogą one być przydatne „pewnego dnia”. Innymi słowy, przykazaniem jest, aby nie dodawać warstw abstrakcji, chyba że są one w tej chwili potrzebne.
Sprawdzanie wartości null nie ma nic wspólnego z przyszłym użyciem. Ma to związek z przykazaniem nr 1: nie zakładaj, że dane wejściowe będą prawidłowe. Nigdy nie zakładaj, że twoja funkcja otrzyma pewien podzbiór danych wejściowych. Funkcja powinna reagować logicznie, bez względu na to, jak fałszywe i pomieszane są dane wejściowe.
źródło
Thou shall not make code for future use
natrafili na problemy z utrzymaniem wcześniej , pomimo intuicyjnej logiki przykazania. Odkryłem, w kodowaniu w prawdziwym życiu, że przykazanie jest skuteczne tylko w kodzie, w którym kontrolujesz listę funkcji i terminy, i upewniam się, że nie potrzebujesz kodu przyszłości, aby do nich dotrzeć ... to znaczy nigdy.Definicja „nadmiarowego kodu” i „YAGNI” często zależy od tego, jak daleko patrzysz w przyszłość.
Jeśli napotkasz problem, będziesz pisał przyszły kod w taki sposób, aby uniknąć tego problemu. Inny programista, który nie doświadczył tego konkretnego problemu, może rozważyć nadmiar kodu.
Moją propozycją jest śledzenie, ile czasu spędzasz na „rzeczach, które jeszcze się nie pomyliły”, jeśli ładuje się ono, a Ty, rówieśnicy, wyrzucasz funkcje szybciej niż ty, a następnie zmniejszaj je.
Jeśli jednak jesteś podobny do mnie, spodziewam się, że po prostu napisałeś to wszystko „domyślnie” i tak naprawdę nie zajęło ci to dłużej.
źródło
Dobrym pomysłem jest udokumentowanie wszelkich założeń dotyczących parametrów. I dobrze jest sprawdzić, czy kod klienta nie narusza tych założeń. Zrobiłbym to:
[Zakładając, że jest to C #, Assert może nie być najlepszym narzędziem do tego zadania, ponieważ nie jest skompilowane w wydanym kodzie. Ale to debata na kolejny dzień.]
Dlaczego to jest lepsze niż to, co napisałeś? Twój kod ma sens, jeśli w przyszłości, gdzie zmienił się twój klient, gdy klient przejdzie na pustą listę, właściwym rozwiązaniem będzie dodanie pierwszego wiersza i dodanie aplikacji do jego spotkań. Ale skąd wiesz, że tak będzie? Lepiej jest teraz przyjmować mniej założeń na temat przyszłości.
źródło
Oszacować koszt dodając ten kod teraz . Będzie to stosunkowo tanie, ponieważ wszystko jest świeże w twoim umyśle, więc będziesz mógł to zrobić szybko. Dodanie testów jednostkowych jest konieczne - nie ma nic gorszego niż użycie jakiejś metody rok później, to nie działa, a okazuje się, że od samego początku było zepsute i nigdy nie działało.
Oszacuj koszt dodania tego kodu, gdy jest on potrzebny. Będzie drożej, ponieważ musisz wrócić do kodu, pamiętać o wszystkim, a to jest o wiele trudniejsze.
Oszacuj prawdopodobieństwo, że dodatkowy kod będzie rzeczywiście potrzebny. Następnie wykonaj matematykę.
Z drugiej strony, kod pełny z założeniami „X nigdy się nie wydarzy” jest okropny do debugowania. Jeśli coś nie działa zgodnie z przeznaczeniem, oznacza to albo głupi błąd, albo błędne założenie. Twoje „X nigdy się nie wydarzy” jest założeniem, a w przypadku błędu jest podejrzane. Co zmusza następnego programistę do marnowania na to czasu. Zwykle lepiej nie polegać na takich założeniach.
źródło
Podstawowe pytanie brzmi: „co się stanie, jeśli zrobisz / nie zrobisz”?
Jak zauważyli inni, tego rodzaju programowanie obronne jest dobre, ale czasami niebezpieczne.
Na przykład, jeśli podasz wartość domyślną, to utrzymasz program. Ale program może teraz nie robić tego, co chcesz. Na przykład, jeśli zapisuje tę pustą tablicę do pliku, być może zmieniłeś swój błąd z „awarii, ponieważ przypadkowo podałem null”, aby „wyczyścił wiersze kalendarza, ponieważ podałem null przez przypadek”. (na przykład jeśli zaczniesz usuwać rzeczy, które nie pojawiają się na liście w tej części, która brzmi „// bla”)
Kluczem jest dla mnie Nigdy nie uszkadzaj danych . Pozwól mi to powtórzyć; NIGDY. SKORUMPOWANY. DANE. Jeśli Twój program się wyjątkuje, otrzymasz raport o błędzie, który możesz załatać; jeśli zapisuje złe dane do pliku, który później, musisz zasiać ziemię solą.
Wszystkie twoje „niepotrzebne” decyzje powinny być podejmowane z uwzględnieniem tej przesłanki.
źródło
To, z czym masz do czynienia, to w zasadzie interfejs. Dodając zachowanie „gdy dane wejściowe są
null
, zainicjuj dane wejściowe”, skutecznie rozszerzyłeś interfejs metody - teraz zamiast zawsze działać na prawidłowej liście, zmusiłeś ją do „naprawy” danych wejściowych. Bez względu na to, czy jest to oficjalna, czy nieoficjalna część interfejsu, możesz założyć się, że ktoś (najprawdopodobniej i ty) użyje tego zachowania.Interfejsy powinny być proste i powinny być względnie stabilne - zwłaszcza w czymś takim jak
public static
metoda. Masz trochę swobody w metodach prywatnych, zwłaszcza w metodach instancji prywatnych. Poprzez niejawne rozszerzenie interfejsu sprawiłeś, że Twój kod jest bardziej złożony w praktyce. Teraz wyobraź sobie, że tak naprawdę nie chcesz używać tej ścieżki kodu - więc unikaj jej. Teraz masz trochę nieprzetestowanego kodu, który udaje, że jest częścią zachowania metody. Mogę ci teraz powiedzieć, że prawdopodobnie ma błąd: kiedy przekazujesz listę, ta lista jest mutowana przez metodę. Jeśli jednak tego nie zrobisz, utworzysz lokalnylisty i wyrzuć ją później. Jest to rodzaj niespójnego zachowania, które sprawi, że za pół roku będziesz płakać, próbując wyśledzić niejasny błąd.Ogólnie rzecz biorąc, programowanie defensywne jest bardzo przydatne. Ale ścieżki kodu dla kontroli obronnych muszą zostać przetestowane, tak jak każdy inny kod. W takim przypadku komplikują kod bez powodu, a zamiast tego wybrałbym taką alternatywę:
Nie chcesz, aby dane wejściowe
rows
były zerowe i chcesz, aby błąd był widoczny dla wszystkich dzwoniących tak szybko, jak to możliwe .Istnieje wiele wartości, które należy pogodzić przy tworzeniu oprogramowania. Nawet solidność sama w sobie jest bardzo złożoną cechą - na przykład nie uważałbym twojej obrony za bardziej solidną niż rzucenie wyjątku. Wyjątki są bardzo przydatne, aby zapewnić bezpieczne miejsce do ponownego spróbowania w bezpiecznym miejscu - problemy z uszkodzeniem danych są zwykle znacznie trudniejsze do wykrycia niż wczesne rozpoznanie problemu i bezpieczne rozwiązanie go. W końcu dają złudzenie solidności, a miesiąc później zauważasz, że zniknęła jedna dziesiąta twoich spotkań, ponieważ nigdy nie zauważyłeś, że inna lista została zaktualizowana. Ojej
Rozróżnij oba. Programowanie defensywne jest przydatną techniką do wychwytywania błędów w miejscu, w którym są one najbardziej istotne, znacznie pomagając w debugowaniu oraz z dobrą obsługą wyjątków, zapobiegając „podstępnej korupcji”. Porażka wczesna, porażka szybka. Z drugiej strony to, co robisz, jest bardziej jak „ukrywanie błędów” - żonglujesz danymi wejściowymi i zakładasz, co miał na myśli rozmówca. Jest to bardzo ważne w przypadku kodu skierowanego do użytkownika (np. Sprawdzanie pisowni), ale należy zachować ostrożność, widząc go w przypadku kodu skierowanego do programisty.
Głównym problemem jest to, że bez względu na to, jaką abstrakcję stworzysz, wycieknie („Chciałem pisać, nie, nie! Głupi moduł sprawdzania pisowni!”), A kod do obsługi wszystkich specjalnych przypadków i poprawek nadal zawiera kod musisz utrzymywać i rozumieć oraz kod, który musisz przetestować. Porównaj wysiłek, aby upewnić się, że lista nie jest zerowa, z naprawieniem błędu, który masz rok później w produkcji - nie jest to dobry kompromis. W idealnym świecie chciałbyś, aby każda metoda działała wyłącznie z własnym wkładem, zwracając wynik i nie modyfikując żadnego stanu globalnego. Oczywiście w prawdziwym świecie znajdziesz wiele przypadków, w których tak nie jestnajprostsze i najczystsze rozwiązanie (np. podczas zapisywania pliku), ale uważam, że utrzymywanie metod „czystych”, gdy nie ma powodu, aby czytały lub manipulowały stanem globalnym, znacznie ułatwiają rozumowanie kodu. Daje też więcej naturalnych punktów za podzielenie metod :)
Nie oznacza to, że wszystko nieoczekiwane powinno spowodować awarię aplikacji, wręcz przeciwnie. Jeśli dobrze wykorzystujesz wyjątki, w naturalny sposób tworzą one bezpieczne punkty obsługi błędów, w których możesz przywrócić stabilny stan aplikacji i pozwolić użytkownikowi kontynuować to, co robią (najlepiej unikając utraty danych przez użytkownika). W tych punktach obsługi zobaczysz możliwości naprawienia problemów („Nie znaleziono numeru zamówienia 2212. Miałeś na myśli 2212b?”) Lub przekazania użytkownikowi kontroli („Błąd połączenia z bazą danych. Spróbuj ponownie?”). Nawet gdy żadna taka opcja nie jest dostępna, przynajmniej da ci szansę, że nic się nie zepsuje - zacząłem doceniać kod, który używa
using
itry
...finally
o wiele więcej niżtry
...catch
, daje wiele możliwości utrzymania niezmienników nawet w wyjątkowych warunkach.Użytkownicy nie powinni tracić swoich danych i pracy. To wciąż musi być zrównoważone kosztami rozwoju itp., Ale jest to całkiem dobra ogólna wskazówka (jeśli użytkownicy decydują, czy kupić oprogramowanie, czy nie - oprogramowanie wewnętrzne zwykle nie ma tego luksusu). Nawet awaria całej aplikacji staje się o wiele mniejszym problemem, jeśli użytkownik może po prostu zrestartować i wrócić do tego, co robił. To prawdziwa niezawodność - program Word stale zapisuje twoją pracę, nie uszkadzając dokumentu na dysku i dając ci opcjęaby przywrócić te zmiany po ponownym uruchomieniu programu Word po awarii. Czy to lepsze niż brak błędu? Prawdopodobnie nie - choć nie zapominaj, że praca poświęcona na złapanie rzadkiego błędu może być lepiej wszędzie. Jest to jednak znacznie lepsze niż alternatywy - np. Uszkodzony dokument na dysku, cała praca od ostatniego zapisu utracona, dokument automatycznie zastąpiony zmianami przed awarią, którym były Ctrl + A i Delete.
źródło
Odpowiem na to w oparciu o twoje założenie, że solidny kod przyniesie ci „lata” od teraz. Jeśli Twoim celem są długoterminowe korzyści, priorytetem byłby projekt i łatwość konserwacji nad solidnością.
Kompromisem między designem a solidnością jest czas i koncentracja. Większość programistów wolałaby mieć zestaw dobrze zaprojektowanego kodu, nawet jeśli oznacza to przejście przez pewne problemy i wykonanie dodatkowych warunków lub obsługę błędów. Po kilku latach użytkowania miejsca, których naprawdę potrzebujesz, zostały prawdopodobnie zidentyfikowane przez użytkowników.
Zakładając, że projekt ma taką samą jakość, łatwiej jest utrzymać mniej kodu. Nie oznacza to, że lepiej nam będzie, jeśli pozwolisz odejść znanym problemom przez kilka lat, ale dodanie rzeczy, o których wiesz, że nie potrzebujesz, utrudnia. Wszyscy spojrzeliśmy na starszy kod i znaleźliśmy niepotrzebne części. Musisz mieć kod zmieniający poziom zaufania, który działał przez lata.
Więc jeśli uważasz, że Twoja aplikacja jest zaprojektowana tak, jak jest, może być łatwa w utrzymaniu i nie zawiera żadnych błędów, znajdź coś lepszego niż dodawanie niepotrzebnego kodu. To najmniej można zrobić z szacunku dla wszystkich innych programistów pracujących godzinami nad bezsensownymi funkcjami.
źródło
Nie , nie powinieneś. W rzeczywistości odpowiadasz na własne pytanie, gdy twierdzisz, że ten sposób kodowania może ukrywać błędy . Nie sprawi to, że kod będzie bardziej niezawodny - raczej zwiększy podatność na błędy i utrudni debugowanie.
Podajesz swoje bieżące oczekiwania co do
rows
argumentu: albo jest on zerowy, albo w przeciwnym razie ma co najmniej jeden element. Pytanie brzmi: czy dobrym pomysłem jest napisanie kodu, który dodatkowo poradzi sobie z nieoczekiwanym trzecim przypadkiem, w którymrows
ma zero pozycji?Odpowiedź brzmi nie. Zawsze powinieneś zgłaszać wyjątek w przypadku nieoczekiwanych danych wejściowych. Zastanów się: jeśli niektóre inne części kodu nie spełniają oczekiwań (tj. Umowy) Twojej metody , oznacza to, że wystąpił błąd . Jeśli istnieje błąd, który chcesz znać jak najwcześniej, aby go naprawić, a wyjątek pomoże ci to zrobić.
To, co obecnie robi kod, to zgadywanie, jak naprawić błąd, który może, ale nie musi istnieć w kodzie. Ale nawet jeśli wystąpi błąd, nie możesz wiedzieć, jak go w pełni odzyskać. Błędy z definicji mają nieznane konsekwencje. Być może jakiś kod inicjujący nie działał zgodnie z oczekiwaniami, może mieć wiele innych konsekwencji niż tylko brakujący wiersz.
Więc twój kod powinien wyglądać tak:
Uwaga: Istnieją pewne szczególne przypadki, w których sensowne jest „odgadnięcie”, jak obsługiwać nieprawidłowe dane wejściowe, a nie tylko zgłaszanie wyjątku. Na przykład, jeśli obsługujesz wejścia zewnętrzne, nie masz nad nimi kontroli. Przeglądarki internetowe są niesławnym przykładem, ponieważ próbują z wdziękiem obsługiwać wszelkiego rodzaju zniekształcone i nieprawidłowe dane wejściowe. Ma to jednak sens tylko w przypadku zewnętrznego wejścia, a nie wywołań z innych części programu.
Edycja: Niektóre inne odpowiedzi mówią, że programujesz w obronie . Nie zgadzam się. Programowanie defensywne oznacza, że nie ufasz automatycznie, że dane wejściowe są prawidłowe. Dlatego sprawdzanie poprawności parametrów (jak wyżej) jest defensywną techniką programowania, ale nie oznacza to, że powinieneś zmienić nieoczekiwane lub nieprawidłowe dane wejściowe poprzez zgadywanie. Solidne podejście obronne polega na sprawdzeniu poprawności danych wejściowych, a następnie zgłoszeniu wyjątku w przypadku nieoczekiwanych lub nieprawidłowych danych wejściowych.
źródło
W żadnym momencie nie należy dodawać zbędnego kodu.
Nie należy dodawać kodu, który będzie potrzebny tylko w przyszłości.
Należy upewnić się, że kod zachowuje się dobrze bez względu na to, co się stanie.
Definicja „zachowuj się” zależy od twoich potrzeb. Jedną z technik, które lubię stosować, są wyjątki „paranoi”. Jeśli jestem w 100% pewien, że określony przypadek nigdy się nie wydarzy, nadal programuję wyjątek, ale robię to w sposób, który a) wyraźnie mówi wszystkim, że nigdy się tego nie spodziewam, oraz b) jest wyraźnie wyświetlany i rejestrowany oraz dlatego nie prowadzi to później do pełzającej korupcji.
Przykładowy pseudo-kod:
To wyraźnie oznacza, że jestem w 100% pewien, że zawsze mogę otworzyć, zapisać lub zamknąć plik, tj. Nie przechodzę do zakresu tworzenia skomplikowanej obsługi błędów. Ale jeśli (nie: kiedy) nie mogę otworzyć pliku, mój program nadal zawiedzie w kontrolowany sposób.
Interfejs użytkownika oczywiście nie wyświetla takich wiadomości użytkownikowi, będą one logowane wewnętrznie wraz ze śledzeniem stosu. Ponownie, są to wyjątki wewnętrzne „paranoi”, które tylko upewniają się, że kod „zatrzymuje się”, gdy wydarzy się coś nieoczekiwanego. Ten przykład jest nieco sprzeczny, w praktyce oczywiście wdrażałbym obsługę błędów podczas otwierania plików, ponieważ zdarza się to regularnie (zła nazwa pliku, pamięć USB podłączona tylko do odczytu, cokolwiek).
Bardzo ważnym pokrewnym wyszukiwanym terminem byłoby „szybko zawieść”, jak zauważono w innych odpowiedziach, i jest bardzo przydatne do tworzenia solidnego oprogramowania.
źródło
Tutaj jest wiele zbyt skomplikowanych odpowiedzi. Prawdopodobnie zadałeś to pytanie, ponieważ nie czułeś się dobrze z tym kodem części, ale nie byłeś pewien, dlaczego lub jak to naprawić. Więc moja odpowiedź jest taka, że problem jest bardzo prawdopodobne w strukturze kodu (jak zawsze).
Najpierw nagłówek metody:
Przypisać spotkanie do jakiego rzędu? Powinno to być natychmiast usunięte z listy parametrów. Bez dalszej wiedzy, spodziewam params metoda wyglądać następująco:
(Appointment app, CalendarRow row)
.Następnie „kontrole danych wejściowych”:
To bzdury.
rows
do metody jest prawdopodobnie błędne (patrz komentarz powyżej), wówczas nie powinna ponosić odpowiedzialności za wywołaną metodęAssignAppointmentToRow
manipulowania wierszami w jakikolwiek inny sposób niż przypisanie spotkania gdzieś.Ale cała koncepcja przypisywania spotkań gdzieś jest dziwna (chyba że jest to część kodu GUI). Twój kod wydaje się zawierać (lub przynajmniej próbuje) jawną strukturę danych reprezentującą kalendarz (to jest
List<CalendarRows>
<- który powinien być zdefiniowanyCalendar
gdzieś, jeśli chcesz iść w ten sposób, wtedy przejdzieszCalendar calendar
do swojej metody). Jeśli pójdziesz tą drogą, spodziewam się, żecalendar
będą one wypełnione przedziałami czasu, w których następnie umieszczasz (przypisujesz) spotkania (np.calendar[month][day] = appointment
Będzie to odpowiedni kod). Ale potem możesz również całkowicie usunąć strukturę kalendarza z głównej logiki i po prostu mieć miejsce, wList<Appointment>
którymAppointment
obiekty zawierają atrybutdate
. A jeśli chcesz wyrenderować kalendarz gdzieś w graficznym interfejsie użytkownika, możesz utworzyć tę strukturę „wyraźnego kalendarza” tuż przed renderowaniem.Nie znam szczegółów twojej aplikacji, więc niektóre z nich mogą nie dotyczyć ciebie, ale obie te kontrole (głównie druga) mówią mi, że coś jest nie tak z rozdzieleniem problemów w twoim kodzie.
źródło
Dla uproszczenia załóżmy, że albo będziesz potrzebować tego fragmentu kodu za N dni (nie później lub wcześniej), albo w ogóle nie będzie potrzebny.
Pseudo kod:
Czynniki dla
C_maint
:C_maint
oczekiwano ujemnegoC_maint
. Ten przypadek wymaga bardziej złożonej formuły, z większą liczbą zmiennych takich jakN
.Tak wielka rzecz, która tylko obciąża kod i może być potrzebna tylko za 2 lata z małym prawdopodobieństwem, powinna zostać pominięta, ale drobiazg, który sugeruje również przydatne twierdzenia i 50%, że będzie potrzebny za 3 miesiące, powinien zostać wdrożony .
źródło