Gdybyś miał wybrać swoje ulubione (sprytne) techniki kodowania obronnego, jakie by one były? Chociaż moje obecne języki to Java i Objective-C (z doświadczeniem w C ++), możesz odpowiedzieć w dowolnym języku. Nacisk zostałby położony na sprytne techniki defensywne inne niż te, o których wie ponad 70% z nas. Czas więc zagłębić się w swoją torbę sztuczek.
Innymi słowy, spróbuj pomyśleć o czymś innym niż ten nieciekawy przykład:
if(5 == x)
zamiastif(x == 5)
: aby uniknąć niezamierzonego przypisania
Oto kilka przykładów intrygujących najlepszych praktyk programowania obronnego (przykłady specyficzne dla języka znajdują się w Javie):
- Blokuj zmienne, dopóki nie będziesz wiedział, że musisz je zmienić
Oznacza to, że możesz zadeklarować wszystkie zmienne, final
dopóki nie będziesz wiedział, że będziesz musiał je zmienić, w którym to momencie możesz usunąć final
. Jednym z powszechnie nieznanych faktów jest to, że dotyczy to również parametrów metody:
public void foo(final int arg) { /* Stuff Here */ }
- Kiedy dzieje się coś złego, zostaw ślad za sobą
Istnieje wiele rzeczy, które możesz zrobić, gdy masz wyjątek: oczywiście zalogowanie się i wykonanie pewnych porządków to kilka. Ale możesz także zostawić ślad dowodu (np. Ustawienie zmiennych na wartości wartownicze, takie jak "UNABLE TO LOAD FILE" lub 99999, byłoby przydatne w debugerze, na wypadek gdybyś catch
przeleciał obok bloku wyjątków ).
- Jeśli chodzi o spójność: diabeł tkwi w szczegółach
Bądź tak spójny z innymi bibliotekami, których używasz. Na przykład w Javie, jeśli tworzysz metodę, która ekstrakty zakres wartości sprawiają, że dolna granica włącznie , a górna granica wykluczają . Dzięki temu będzie spójny z metodami, takimi jak ta, String.substring(start, end)
która działa w ten sam sposób. W Sun JDK znajdziesz wszystkie tego typu metody, które zachowują się w ten sposób, ponieważ wykonuje różne operacje, w tym iterację elementów zgodnych z tablicami, w których indeksy są od zera ( włącznie ) do długości tablicy ( wyłączne ).
Więc jakie są twoje ulubione praktyki obronne?
Aktualizacja: Jeśli jeszcze tego nie zrobiłeś, zadzwoń. Daję szansę na więcej odpowiedzi, zanim wybiorę oficjalną odpowiedź.
źródło
Odpowiedzi:
W c ++ podobało mi się kiedyś przedefiniowanie nowego, tak aby zapewniało dodatkową pamięć do wychwytywania błędów słupków ogrodzenia.
Obecnie wolę unikać programowania defensywnego na rzecz programowania sterowanego testami . Jeśli szybko i zewnętrznie wychwytujesz błędy, nie musisz mylić kodu za pomocą manewrów obronnych, Twój kod jest bardziej SUCHY i kończy się mniejszą liczbą błędów, przed którymi musisz się bronić.
Jak napisała WikiKnowledge :
źródło
SQL
Kiedy muszę usunąć dane, piszę
Kiedy go uruchomię, dowiem się, czy zapomniałem lub nie spartaczyłem klauzuli where. Mam bezpieczeństwo. Jeśli wszystko jest w porządku, zaznaczam wszystko po żetonach komentarza „-” i uruchamiam.
Edycja: jeśli usuwam dużo danych, użyję count (*) zamiast tylko *
źródło
Przydziel rozsądną ilość pamięci podczas uruchamiania aplikacji - myślę, że Steve McConnell nazwał to spadochronem pamięci w Code Complete.
Można to wykorzystać w przypadku, gdy coś poważnego pójdzie nie tak i musisz zakończyć.
Przydzielenie tej pamięci z góry zapewnia zabezpieczenie, ponieważ można ją zwolnić, a następnie wykorzystać dostępną pamięć do wykonania następujących czynności:
źródło
W każdej instrukcji switch, która nie ma przypadku domyślnego, dodaję przypadek, który przerywa działanie programu z komunikatem o błędzie.
źródło
Podczas obsługi różnych stanów wyliczenia (C #):
Następnie, w ramach rutyny ...
W pewnym momencie dodam inny typ konta do tego wyliczenia. A kiedy to zrobię, zapomnę naprawić tę instrukcję przełącznika. Więc
Debug.Fail
wywala się strasznie (w trybie debugowania), aby zwrócić moją uwagę na ten fakt. Kiedy dodamcase AccountType.MyNewAccountType:
, okropna awaria zatrzymuje się ... dopóki nie dodam kolejnego typu konta i nie zapomnę zaktualizować tutaj przypadków.(Tak, polimorfizm jest tutaj prawdopodobnie lepszy, ale to tylko przykład z góry mojej głowy).
źródło
Podczas drukowania komunikatów o błędach za pomocą łańcucha (szczególnie takiego, który zależy od danych wejściowych użytkownika), zawsze używam apostrofów
''
. Na przykład:Ten brak cudzysłowów
%s
jest naprawdę zły, ponieważ powiedzmy, że nazwa pliku jest pustym ciągiem znaków lub po prostu białą spacją lub czymś w tym rodzaju. Wydrukowana wiadomość wyglądałaby oczywiście:Dlatego zawsze lepiej:
Wtedy przynajmniej użytkownik widzi to:
Uważam, że robi to ogromną różnicę pod względem jakości raportów o błędach przesyłanych przez użytkowników końcowych. Jeśli pojawia się śmiesznie wyglądający komunikat o błędzie, taki jak ten, zamiast czegoś, co brzmi ogólnie, to znacznie bardziej prawdopodobne jest, że skopiują / wkleją go zamiast po prostu napisać „nie otworzy moich plików”.
źródło
Bezpieczeństwo SQL
Przed napisaniem jakiegokolwiek kodu SQL, który zmodyfikuje dane, zawijam całość w wycofaną transakcję:
Zapobiega to trwałemu wykonywaniu złego usunięcia / aktualizacji. Możesz też wykonać całość i zweryfikować rozsądną liczbę rekordów lub dodać
SELECT
instrukcje między SQL aROLLBACK TRANSACTION
operatorem, aby upewnić się, że wszystko wygląda dobrze.Kiedy jesteś całkowicie pewien, że robi to, czego się spodziewałeś, zmień ustawienie
ROLLBACK
naCOMMIT
i uruchom je naprawdę.źródło
Dla wszystkich języków:
Zmniejsz zakres zmiennych do możliwie najmniejszego wymaganego. Unikaj zmiennych, które zostały właśnie dostarczone, aby przenieść je do następnej instrukcji. Zmienne, które nie istnieją, to zmienne, których nie musisz rozumieć i nie możesz za nie odpowiadać. Z tego samego powodu używaj lambdy, gdy tylko jest to możliwe.
źródło
W razie wątpliwości zbombarduj aplikację!
Sprawdź każdy parametr na początku każdej metody (bez względu na to, czy zakodujesz go samemu, czy użyjesz programowania opartego na kontraktach) i zbombarduj odpowiednim wyjątkiem i / lub znaczącym komunikatem o błędzie, jeśli jakikolwiek warunek wstępny kodu jest nie spotkał.
Wszyscy wiemy o tych niejawnych warunkach wstępnych, kiedy piszemy kod , ale jeśli nie są one jawnie sprawdzane, tworzymy dla siebie labirynty, gdy coś pójdzie nie tak później, a stosy dziesiątek wywołań metod oddzielają wystąpienie objawu od rzeczywistej lokalizacji gdzie warunek wstępny nie jest spełniony (= gdzie faktycznie jest problem / błąd).
źródło
param.NotNull("param")
zamiastif ( param == null ) throw new ArgumentNullException("param");
W Javie, szczególnie w przypadku kolekcji, korzystaj z interfejsu API, więc jeśli Twoja metoda zwraca typ List (na przykład), spróbuj wykonać następujące czynności:
Nie pozwól, aby uciec przed twoją klasą cokolwiek, czego nie potrzebujesz!
źródło
w Perlu wszyscy to robią
podoba mi się
Powoduje to, że kod umiera z powodu jakiegokolwiek ostrzeżenia kompilatora / środowiska uruchomieniowego. Jest to szczególnie przydatne w przechwytywaniu niezainicjowanych ciągów.
źródło
DO#:
źródło
W c # sprawdzaniu string.IsNullOrEmpty przed wykonaniem jakichkolwiek operacji na łańcuchu, takich jak length, indexOf, mid itp.
[Edytuj]
Teraz mogę również wykonać następujące czynności w .NET 4.0, które dodatkowo sprawdzą czy wartość jest tylko białą spacją
źródło
if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */
.W Javie i C # nadaj każdemu wątkowi znaczącą nazwę. Obejmuje to wątki puli wątków. To sprawia, że zrzuty stosu są znacznie bardziej znaczące. Nadanie znaczącej nazwy nawet wątkom puli wątków wymaga nieco więcej wysiłku, ale jeśli jedna pula wątków ma problem w długo działającej aplikacji, mogę spowodować wystąpienie zrzutu stosu (wiesz o SendSignal.exe , prawda? ), pobrać dzienniki i bez konieczności przerywania działającego systemu mogę stwierdzić, które wątki są ... czymkolwiek. Zakleszczony, przeciekający, narastający, bez względu na problem.
źródło
W przypadku języka VB.NET należy domyślnie włączyć opcję Option Explicit i Option Strict dla całego programu Visual Studio.
źródło
C ++
następnie zamień wszystkie wywołania ' delete pPtr ' i ' delete [] pPtr ' na SAFE_DELETE (pPtr) i SAFE_DELETE_ARRAY (pPtr)
Teraz przez pomyłkę, jeśli użyjesz wskaźnika „pPtr” po jego usunięciu, otrzymasz błąd „naruszenie dostępu”. Jest to o wiele łatwiejsze do naprawienia niż przypadkowe uszkodzenia pamięci.
źródło
delete
. Używanienew
jest w porządku, o ile nowy obiekt jest własnością inteligentnego wskaźnika.W Javie może być przydatne użycie słowa kluczowego assert, nawet jeśli uruchamiasz kod produkcyjny z wyłączonymi asercjami:
Nawet przy wyłączonych asercjach kod przynajmniej dokumentuje fakt, że parametr ma zostać gdzieś ustawiony. Zauważ, że jest to prywatna funkcja pomocnicza, a nie element członkowski publicznego interfejsu API. Ta metoda może być wywołana tylko przez Ciebie, więc możesz przyjąć pewne założenia, jak będzie używana. W przypadku metod publicznych prawdopodobnie lepiej jest zgłosić prawdziwy wyjątek dla nieprawidłowych danych wejściowych.
źródło
Nie znalazłem
readonly
słowa kluczowego, dopóki nie znalazłem ReSharper, ale teraz używam go instynktownie, szczególnie w przypadku klas usług.źródło
W Javie, gdy coś się dzieje i nie wiem dlaczego, czasami używam Log4J w ten sposób:
w ten sposób otrzymuję ślad stosu pokazujący, jak znalazłem się w nieoczekiwanej sytuacji, powiedzmy blokadę, która nigdy się nie otworzyła, coś zerowego, co nie może być zerowe i tak dalej. Oczywiście, jeśli zostanie rzucony prawdziwy Wyjątek, nie muszę tego robić. To wtedy muszę zobaczyć, co dzieje się w kodzie produkcyjnym, nie zakłócając niczego innego. I nie chcą rzucać wyjątek i nie złapać. Chcę tylko, aby ślad stosu został zarejestrowany z odpowiednią wiadomością, aby oznaczyć mnie, co się dzieje.
źródło
Jeśli używasz języka Visual C ++, użyj słowa kluczowego override za każdym razem, gdy zastępujesz metodę klasy bazowej. W ten sposób, jeśli ktoś kiedykolwiek zmieni sygnaturę klasy bazowej, zgłosi błąd kompilatora, zamiast po cichu wywołać niewłaściwą metodę. Uratowałoby mnie to kilka razy, gdyby istniało wcześniej.
Przykład:
źródło
Nauczyłem się w Javie prawie nigdy nie czekać w nieskończoność na odblokowanie blokady, chyba że naprawdę spodziewam się, że może to zająć nieskończenie długo. Jeśli realistycznie blokada powinna się odblokować w ciągu kilku sekund, będę czekać tylko przez określony czas. Jeśli zamek się nie odblokowuje, narzekam i zrzucam stos do kłód i w zależności od tego, co jest najlepsze dla stabilności systemu, albo kontynuuję, jakby zamek był odblokowany, albo kontynuowałem, jakby zamek nigdy się nie odblokował.
Pomogło to wyodrębnić kilka warunków wyścigu i pseudobloków, które były tajemnicze, zanim zacząłem to robić.
źródło
DO#
sealed
dużo na zajęciach, aby uniknąć wprowadzania zależności tam, gdzie ich nie chciałem. Zezwolenie na dziedziczenie powinno odbywać się jawnie, a nie przez przypadek.źródło
Podczas wydawania komunikatu o błędzie spróbuj przynajmniej podać te same informacje, które miał program, gdy podjął decyzję o zgłoszeniu błędu.
„Odmowa uprawnień” oznacza, że wystąpił problem z uprawnieniami, ale nie masz pojęcia, dlaczego i gdzie wystąpił. „Nie można zapisać dziennika transakcji / mojego / pliku: system plików tylko do odczytu” pozwala przynajmniej poznać podstawę podjęcia decyzji, nawet jeśli jest błędna - zwłaszcza jeśli jest błędna: zła nazwa pliku? otworzył się źle? inny nieoczekiwany błąd? - i informuje, gdzie byłeś, kiedy miałeś problem.
źródło
W C # użyj
as
słowa kluczowego do rzutowania.zgłosi wyjątek, jeśli obj nie jest łańcuchem
pozostawi jako null, jeśli obj nie jest łańcuchem
Nadal musisz brać pod uwagę wartość null, ale zazwyczaj jest to prostsze niż szukanie wyjątków rzutowania. Czasami chcesz zachować zachowanie typu „rzutuj lub nadmuchuj”, w takim przypadku
(string)obj
składnia jest preferowana.W moim własnym kodzie stwierdzam, że używam
as
składni w około 75% przypadków, a(cast)
składni w około 25%.źródło
is/instanceof
wpaść do głowy.Przygotuj się na każde wejście, a każde otrzymane wejście, które jest nieoczekiwane, zrzuć do dzienników. (Nie bez powodu. Jeśli czytasz hasła użytkownika, nie zrzucaj tego do dzienników! I nie zapisuj tysięcy tego rodzaju wiadomości w dziennikach na sekundę. Powód dotyczący treści, prawdopodobieństwa i częstotliwości, zanim je zalogujesz .)
Nie mówię tylko o sprawdzaniu poprawności danych wejściowych użytkownika. Na przykład, jeśli czytasz żądania HTTP, które mają zawierać XML, przygotuj się na inne formaty danych. Byłem zaskoczony, widząc odpowiedzi HTML, w których spodziewałem się tylko XML - dopóki nie spojrzałem i nie zobaczyłem, że moje żądanie przechodzi przez przezroczysty serwer proxy, którego nie byłem świadomy, i że klient twierdził, że nie ma pojęcia - i serwer proxy przekroczył limit czasu podczas próby ukończenia żądanie. W ten sposób proxy zwróciło mojemu klientowi stronę błędu HTML, myląc klienta, który oczekiwał tylko danych XML.
Dlatego nawet jeśli myślisz, że kontrolujesz oba końce przewodu, możesz uzyskać nieoczekiwane formaty danych bez udziału nikczemności. Przygotuj się, koduj defensywnie i zapewnij wyniki diagnostyczne w przypadku nieoczekiwanych danych wejściowych.
źródło
Staram się stosować podejście Design by Contract. Może być emulowany w czasie wykonywania w dowolnym języku. Każdy język obsługuje „asercję”, ale łatwo i wygodnie jest napisać lepszą implementację, która pozwoli zarządzać błędem w bardziej użyteczny sposób.
W Top 25 najniebezpieczniejszych błędów programistycznych z „Niewłaściwe Wejście Validation” jest najbardziej niebezpieczny błąd w „niebezpieczne Interakcje pomiędzy komponentami” sekcji.
Dodanie potwierdzeń warunków wstępnych na początku metod jest dobrym sposobem na upewnienie się, że parametry są spójne. Pod koniec metod piszę postconditions , które sprawdzają, które wyjście jest co inteded być.
Aby zaimplementować niezmienniki , w dowolnej klasie piszę metodę sprawdzającą „spójność klas”, która powinna być wywoływana automatycznie przez makro warunku wstępnego i warunku końcowego.
Jestem oceny kontraktu Kod Library .
źródło
Jawa
Interfejs API Java nie ma koncepcji niezmiennych obiektów, co jest złe! Final może Ci w tym pomóc. Oznaczyć każdą klasę, która jest niezmienna ze ostateczna i przygotować klasę odpowiednio .
Czasami warto użyć final na zmiennych lokalnych, aby mieć pewność, że nigdy nie zmienią one swojej wartości. Znalazłem to przydatne w brzydkich, ale niezbędnych konstrukcjach pętli. Po prostu przypadkowe ponowne użycie zmiennej jest zbyt łatwe, nawet jeśli ma być stałą.
Użyj kopiowania obronnego w swoich getterach. Jeśli nie zwrócisz typu pierwotnego lub niezmiennego obiektu, upewnij się, że kopiujesz obiekt, aby nie naruszać hermetyzacji.
Nigdy nie używaj clone, użyj konstruktora kopiującego .
Naucz się kontraktu między equals i hashCode. To jest tak często naruszane. Problem polega na tym, że w 99% przypadków nie wpływa to na Twój kod. Ludzie nadpisują równa się, ale nie przejmują się hashCode. Istnieją przypadki, w których Twój kod może się zepsuć lub zachowuje się dziwnie, np. Użyj obiektów zmiennych jako kluczy w mapie.
źródło
echo
Zbyt wiele razy zapomniałem napisać w PHP:Zajęłoby mi całą wieczność, zanim zrozumiałbym, dlaczego -> baz () nie zwracał niczego, podczas gdy w rzeczywistości po prostu nie powtarzałem tego! : -S Więc stworzyłem
EchoMe
klasę, która może być zawinięta wokół dowolnej wartości, która powinna być powtórzona:A potem w środowisku programistycznym użyłem EchoMe do zawijania rzeczy, które powinny zostać wydrukowane:
Korzystając z tej techniki, pierwszy brakujący przykład
echo
spowodowałby teraz zgłoszenie wyjątku ...źródło
AnObject.ToString
zamiastWriteln(AnObject.ToString)
?C ++
Kiedy piszę nowy, muszę natychmiast wpisać usuń. Szczególnie w przypadku tablic.
DO#
Przed uzyskaniem dostępu do właściwości sprawdź, czy nie ma wartości null, szczególnie w przypadku korzystania ze wzorca Mediator. Obiekty są przekazywane (a następnie powinny być rzutowane przy użyciu as, jak już wspomniano), a następnie sprawdzane pod kątem wartości null. Nawet jeśli myślisz, że nie będzie zerowa, i tak sprawdź. Byłem zaskoczony.
źródło
Użyj systemu rejestrowania, który umożliwia dynamiczne dostosowywanie poziomu dziennika w czasie wykonywania. Często, jeśli musisz zatrzymać program, aby włączyć rejestrowanie, utracisz rzadki stan, w którym wystąpił błąd. Musisz mieć możliwość włączenia większej ilości informacji logowania bez zatrzymywania procesu.
Również 'strace -p [pid]' w Linuksie pokaże, że chcesz wywołać system, który wykonuje proces (lub wątek linuxowy). Na początku może to wyglądać dziwnie, ale kiedy już przyzwyczaisz się do tego, jakie wywołania systemowe są generalnie wykonywane przez wywołania libc, okaże się to nieocenione w diagnostyce terenowej.
źródło