Ulubione (sprytne) sprawdzone metody programowania obronnego [zamknięte]

148

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) zamiast if(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, finaldopó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ś catchprzeleciał 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ź.

Ryan Delucchi
źródło
Chciałbym reprezentować nieświadomych 30% z nas, którzy mogą nie znać prostych technik. Czy ktoś ma link do „oczywistych” technik, które każdy powinien znać jako podstawę?
elliot42
Związane z: stackoverflow.com/questions/163026/…
Bill the Lizard
Dlaczego wybrałbyś oficjalną odpowiedź? To po prostu brzmi zupełnie nieprzyjemnie.
bzlm
Cóż, jeśli chodzi o programowanie, nawet spryt powinien zająć drugie miejsce na cześć „zasady najmniejszego zdziwienia”. Zerwanie przeze mnie z duchem Stack Overflow polegającym na oznaczaniu najlepszej odpowiedzi naruszyłoby protokół Stack Overflow (stąd złamanie tej zasady). Poza tym: lubię zamknięcie :-)
Ryan Delucchi

Odpowiedzi:

103

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 :

Unikaj programowania obronnego, zamiast tego szybko zawieść.

Przez programowanie obronne rozumiem zwyczaj pisania kodu, który próbuje zrekompensować jakąś awarię danych, pisania kodu, który zakłada, że ​​wywołujący mogą dostarczyć dane, które nie są zgodne z umową między wywołującym a podprogramem i że podprogram musi jakoś sobie poradzić z tym.

Joe Soul-bringer
źródło
8
Programowanie obronne jest próbą radzenia sobie z nielegalnymi warunkami wprowadzonymi przez inne części programu. Obsługa nieprawidłowych danych wejściowych użytkownika to zupełnie inna sprawa.
Joe Soul-bringer
5
Errr ... zauważ, że ta definicja programowania obronnego nie jest nawet bliska definicji domyślnie użytej w pytaniu.
Sol
15
Nigdy nie unikaj programowania obronnego. Nie chodzi o „kompensowanie” błędów w danych, ale ochronę przed złośliwymi danymi zaprojektowanymi po to, aby Twój kod robił rzeczy, których nie powinien. Zobacz Przepełnienie buforu, wstrzyknięcie SQL. Nic nie zawodzi szybciej niż strona internetowa pod XSS, ale nie jest ładna
Jorge Córdoba
6
Twierdziłbym, że procedury „szybkich niepowodzeń” są formą programowania obronnego.
Ryan Delucchi
6
@ryan ma rację, szybkie niepowodzenie to dobra koncepcja obronna. Jeśli stan, w którym się znajdujesz, nie jest możliwy, nie próbuj dalej kuleć, FAIL SZYBKO I GŁOŚNO! Niezwykle ważne, jeśli kierujesz się metadanymi. Programowanie obronne to nie tylko sprawdzanie parametrów ...
Bill K
75

SQL

Kiedy muszę usunąć dane, piszę

select *    
--delete    
From mytable    
Where ...

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 *

John MacIntyre
źródło
Napisałem to na moim iPhonie, gdzie nie mogę zaznaczyć tekstu. Gdyby ktoś mógł oznaczyć mój kod jako kod, byłoby to naprawdę mile widziane. Dzięki.
John MacIntyre
6
Po prostu zawijam to w transakcję, aby móc cofnąć, jeśli
coś schrzanię
36
ROZPOCZNIJ TRANSAKCJĘ | TRANSAKCJA ROLLBACK
Dalin Seivewright
3
+1 Tak, przypuszczam, że można to zastąpić transakcją, ale podoba mi się prostota robienia tego w ten sposób.
Ryan Delucchi
3
Korzystanie z transakcji jest lepsze; oznacza to, że możesz go uruchomić dziesiątki razy i zobaczyć rzeczywiste efekty , aż będziesz pewien, że zrobiłeś to dobrze i możesz się zaangażować.
Ryan Lundy
48

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:

  • Zapisz wszystkie trwałe dane
  • Zamknij wszystkie odpowiednie pliki
  • Zapisuj komunikaty o błędach w pliku dziennika
  • Przedstaw użytkownikowi znaczący błąd
LeopardSkinPillBoxHat
źródło
Widziałem ten fundusz na deszczowy dzień.
cokół
42

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.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}
Diomidis Spinellis
źródło
2
Lub wrzucenie do nowoczesnego, wachlarzowego języka.
Tom Hawtin - tackline
2
Assert ma tę zaletę, że jego efekty mogą być globalnie wyłączone w czasie kompilacji. Jednak w niektórych sytuacjach rzut może być bardziej odpowiedni, jeśli twój język to obsługuje.
Diomidis Spinellis
2
Assert ma jeszcze jedną zaletę, która sprawia, że ​​jest przydatna w kodzie produkcyjnym: gdy coś pójdzie nie tak, informuje dokładnie, co się nie udało i z której linii programu pochodzi błąd. Taki raport o błędzie jest wspaniały!
Mason Wheeler
2
@Diomidis: Z innego punktu widzenia: Assert ma tę wadę, że jego efekty można globalnie wyłączyć w czasie kompilacji.
Rozczarowany
1
rzut jest rzeczywiście lepszy. Następnie upewniasz się, że pokrycie testu jest odpowiednie.
Dominic Cronin
41

Podczas obsługi różnych stanów wyliczenia (C #):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Następnie, w ramach rutyny ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

W pewnym momencie dodam inny typ konta do tego wyliczenia. A kiedy to zrobię, zapomnę naprawić tę instrukcję przełącznika. Więc Debug.Failwywala się strasznie (w trybie debugowania), aby zwrócić moją uwagę na ten fakt. Kiedy dodam case 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).

Kyralessa
źródło
4
Większość kompilatorów jest na tyle sprytnych, że wydaje ostrzeżenie, jeśli nie obsługujesz niektórych wyliczeń w bloku wielkości liter. Ale ustawienie domyślnego niepowodzenia jest nadal dobrą formą - wyliczenie to tylko liczba i jeśli otrzymasz uszkodzenie pamięci, możesz wyświetlić nieprawidłową wartość.
Adam Hawes
w c użyłem na końcu dodania nieprawidłowego typu konta. jest to czasami przydatne.
Ray Tayek
1
@Adam - kompilatory wyświetlą ostrzeżenie, jeśli wszystko przekompilujesz . Jeśli dodasz nowe klasy i tylko częściowo przekompilujesz, możesz nie zauważyć czegoś podobnego do powyższego, a domyślny przypadek Cię uratuje. Nie zawiedzie po prostu cicho.
Eddie
4
Przerwa jest sugerowana w komentarzach, Slashene. : P
Ryan Lundy
2
Po debugowaniu powinien pojawić się „rzut nowy NotSupportedException ()” dla kodu produkcyjnego.
user7116
35

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:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

Ten brak cudzysłowów %sjest 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:

ERROR: Could not open file

Dlatego zawsze lepiej:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Wtedy przynajmniej użytkownik widzi to:

ERROR: Could not open file ''

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”.

Nik Reiman
źródło
4
dobry, widziałem też ten problem
Alex Baranosky
28

Bezpieczeństwo SQL

Przed napisaniem jakiegokolwiek kodu SQL, który zmodyfikuje dane, zawijam całość w wycofaną transakcję:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

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ć SELECTinstrukcje między SQL a ROLLBACK TRANSACTIONoperatorem, aby upewnić się, że wszystko wygląda dobrze.

Kiedy jesteś całkowicie pewien, że robi to, czego się spodziewałeś, zmień ustawienie ROLLBACKna COMMITi uruchom je naprawdę.

amsimmon
źródło
Udało ci się pokonać mnie tym! :-)
Howard Pinsley
2
Robiłem to cały czas, ale powoduje to tak dużo dodatkowego obciążenia w klastrach DB, że musiałem się zatrzymać.
Zan Lynx
25

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.

le dorfier
źródło
5
Co dokładnie oznacza część dotyczącą unikania? Czasami wprowadzam zmienne, które żyją tylko do następnego wiersza. Służą jako nazwa wyrażenia, dzięki czemu kod jest bardziej czytelny.
zoul
4
Tak, ja też się nie zgadzam. W przypadku bardzo złożonych wyrażeń często dobrym pomysłem jest podzielenie ich na dwa, a czasem więcej, krótsze i prostsze przy użyciu zmiennych tymczasowych. Konserwacja jest mniej podatna na błędy, a kompilator zoptymalizuje nasze temps
Cruachan
1
Zgadzam się, że są wyjątkowe przypadki, w których deklaruję zmienne dla jasności (a czasami w celu utworzenia celu do debugowania). Z mojego doświadczenia wynika, że ​​ogólną praktyką jest błądzenie w przeciwnym kierunku.
dkretz
Zgadzam się z obydwoma punktami widzenia; chociaż kiedy dzielę wyrażenia na zmienne tymczasowe, staram się to robić w oddzielnym zakresie. Świetnie nadają się do tego lambdy, podobnie jak metody pomocnicze.
Erik Forbes
19

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).

peSHIr
źródło
I oczywiście: użyj kodu ogólnego (mała biblioteka, metody rozszerzające w C #, cokolwiek), aby to ułatwić. Możesz więc napisać coś podobnego param.NotNull("param")zamiastif ( param == null ) throw new ArgumentNullException("param");
peSHIr
2
Lub użyj programowania opartego na kontraktach, na przykład Spec #!
bzlm
Zależy, jaka to aplikacja. Mam nadzieję, że ludzie piszący kod dla samolotów typu fly-by-wire i rozruszników serca nie myślą jak peSHIr.
MarkJ,
6
@MarkJ: Naprawdę tego nie rozumiesz, prawda? Jeśli bombarduje wcześnie (= podczas opracowywania i testowania), nigdy nie powinien bombardować podczas produkcji. Mam więc nadzieję, że robią takie programy!
peSHIr
Muszę przyznać, że nie bardzo mi się to podoba, szczególnie w przypadku metod prywatnych i chronionych. Powody: a) zaśmiecasz kod kontrolami, które wcale nie przypominają wymagań biznesowych b) testy te są trudne do przetestowania dla metod niepublicznych c) w wielu przypadkach jest bezużyteczne, np. Ponieważ wartość zerowa spowoduje niepowodzenie metody w każdym razie dwie linie później
Erich Kitzmueller
18

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:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Nie pozwól, aby uciec przed twoją klasą cokolwiek, czego nie potrzebujesz!

David Grant
źródło
+1 W C # istnieją kolekcje tylko do odczytu.
tobsen
1
+1 dla Java. Używam tego cały czas
Fortyrunner
+1 ... Często to robię (chociaż chciałbym, żeby więcej ludzi o tym pamiętało).
Ryan Delucchi
Tylko upewnij się, że nic innego nie ma instancji bazowej zmiennej listy, bo inaczej ci to nie pomoże ...
GreenieMeanie
5
FYI, Collections.unmodifiableList zwraca niezmienny widok listy, a nie niezmienną kopię . Więc jeśli oryginalna lista zostanie zmodyfikowana, widok też!
Zarkonnen
17

w Perlu wszyscy to robią

use warnings;

podoba mi się

use warnings FATAL => 'all';

Powoduje to, że kod umiera z powodu jakiegokolwiek ostrzeżenia kompilatora / środowiska uruchomieniowego. Jest to szczególnie przydatne w przechwytywaniu niezainicjowanych ciągów.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here
Eric Johnson
źródło
Chciałbym, żeby to było trochę bardziej reklamowane ...
DJG
16

DO#:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}
CMS
źródło
To samo w Javie i prawdopodobnie w większości języków OO.
MiniQuark
Jest to fajne w Objective-C, gdzie możliwe jest wysyłanie wiadomości do zera („wywołanie metod zerowego obiektu”, z określoną licencją). Wywołanie [nil isEqualToString: @ "Moo"] zwraca fałsz.
zoul
Nie zgadzam się z przykładem C #. Lepszym rozwiązaniem jest użycie "if (myString ==" someValue ")". Brak również zerowego wyjątku odniesienia i na pewno bardziej czytelny.
Dan C.
13
ten przykład pozwala tylko ukryć potencjalnie niebezpieczną sytuację w programie. Jeśli nie spodziewałeś się, że będzie zerowy, CHCESZ, aby zgłosił wyjątek, a jeśli spodziewałeś się, że będzie zerowy, powinieneś sobie z tym poradzić. To zła praktyka.
rmeador
2
W C # zawsze używam po prostu string.Equals (<string1>, <string2>). Nie ma znaczenia, czy którykolwiek z nich jest zerowy.
Darcy Casselman
15

W c # sprawdzaniu string.IsNullOrEmpty przed wykonaniem jakichkolwiek operacji na łańcuchu, takich jak length, indexOf, mid itp.

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[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ą

string.IsNullOrWhiteSpace(myString)
Binoj Antony
źródło
7
To nie jest defensywność, to ignorowanie problemu. Powinien być if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
enashnash
14

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.

Eddie
źródło
I - w systemie Windows - także dla C ++! (włączone ze specjalnym rzutem wyjątku SEH i następującym po nim catch).
Brian Haak
12

W przypadku języka VB.NET należy domyślnie włączyć opcję Option Explicit i Option Strict dla całego programu Visual Studio.

ChrisA
źródło
Chociaż pracuję ze starszym kodem, więc nie mam włączonej opcji ścisłej (powoduje zbyt wiele błędów kompilatora do naprawienia), włączenie obu tych opcji może (i by to w moim przypadku) zaoszczędziło wiele serca ból.
Kibbee
1
Nawiasem mówiąc, dla każdego, kto konwertuje stary kod, który nie używał Option Strict, pamiętaj, że w przypadku elementów, które zostały automatycznie zamienione na String, nie używają ToString (); używają odlewu do sznurka. We wczesnych latach .NET zmieniłem je, aby używały ToString (), i to zepsułoby rzeczy, szczególnie w przypadku wyliczeń.
Ryan Lundy
10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

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.

Nitin Bhide
źródło
1
Lepiej użyj szablonu. Jest ograniczony i przeciążalny.
Właśnie miałem to powiedzieć. Użyj szablonu zamiast makra. W ten sposób, jeśli kiedykolwiek będziesz musiał przejść przez kod, możesz.
Steve Rowe
Dowiedziałem się, co było łatwiejsze do debugowania na własnej skórze w szkole. To błąd, którego od tamtej pory nie popełniłem. :)
Greg D
3
Lub użyj inteligentnych wskaźników ... Albo do cholery, unikaj nowych / usuwaj tak bardzo, jak tylko możesz.
Arafangion,
1
@Arafangion: po prostu unikaj delete. Używanie newjest w porządku, o ile nowy obiekt jest własnością inteligentnego wskaźnika.
Alexandre C.
10

W Javie może być przydatne użycie słowa kluczowego assert, nawet jeśli uruchamiasz kod produkcyjny z wyłączonymi asercjami:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

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.

wyjęty spod prawa programista
źródło
W .NET Debug.Assert robi to samo. Ilekroć masz miejsce, w którym myślisz, że „To odwołanie nie może być tutaj zerowe, prawda?”, Możesz umieścić tam Debug.Assert, więc jeśli może być zerowe, możesz albo naprawić błąd, albo zmienić swoje założenia.
Ryan Lundy
1
+1, publiczne metody powinny rzucać się na niepowodzenia kontraktów,
prywatni
9

Nie znalazłem readonlysłowa kluczowego, dopóki nie znalazłem ReSharper, ale teraz używam go instynktownie, szczególnie w przypadku klas usług.

readonly var prodSVC = new ProductService();
JMS
źródło
Używam odpowiednika słowa kluczowego „final” w Javie na wszystkich polach, które mogę. To naprawdę oszczędza ci wykonywania ruchów kościołowych, takich jak nie ustawianie pola / pisanie mylących przełączników, które mogą ustawiać pola wiele razy. Rzadziej zaznaczam lokalne zmienne / parametry, ale myślę, że nie zaszkodzi.
Outlaw Programmer
C # nie pozwala na oznaczanie zmiennych lokalnych jako tylko do odczytu, więc nie mamy nawet wyboru ...
Jason Punyon
Nie jestem pewien, co oznacza tutaj tylko do odczytu, ale wersja Java nie jest dla mnie wystarczająca. Oznacza to po prostu, że wskaźnik do obiektu pozostaje niezmieniony, ale nie ma sposobu, aby zapobiec zmianie samego obiektu.
Slartibartfast
W języku C # struktura readonly uniemożliwiłaby jej zmianę.
Samuel
9

W Javie, gdy coś się dzieje i nie wiem dlaczego, czasami używam Log4J w ten sposób:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

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.

Eddie
źródło
Hmm, to całkiem fajne, ale obawiam się, że spowodowałoby to mieszanie kodu funkcjonalnego i obsługi błędów. Chociaż mechanizm try-catch może być niezdarny, zmusza jednego do wykonania wyjątku przekierowania z jednego bloku (spróbuj) do drugiego (złap), gdzie jest cała obsługa błędów
Ryan Delucchi
1
Nie jest to używane do obsługi błędów, ale tylko do diagnostyki . Pozwala to zobaczyć ścieżkę, po której kod znalazł się w nieoczekiwanej sytuacji, bez przerywania jego przepływu. Używam tego, gdy sama metoda poradzi sobie z nieoczekiwaną sytuacją, ale nadal nie powinno się to zdarzyć.
Eddie
2
możesz także użyć nowego wyjątku („wiadomość”). printStackTrace (); Nie jest wymagane rzucanie ani łapanie, ale nadal masz ładny ślad stosu w dzienniku. Oczywiście nie powinno to być w kodzie produkcyjnym, ale może być bardzo przydatne do debugowania.
Jorn
9

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:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}
Steve Rowe
źródło
w przypadku Javy jest to klasa Foo {void doSomething () {}} class Bar {@Override void doSomething () {}} Wyświetla ostrzeżenie, jeśli brakuje adnotacji (więc nie można po cichu zastąpić metody, której nie zrobiłeś znaczy to) i gdy adnotacja jest tam, ale niczego nie zastępuje.
Jorn
Fajnie ... Nie wiedziałem o tym ... szkoda, że ​​wydaje się być specyficzny dla Microsoftu ... ale kilka dobrych #defines może pomóc w uczynieniu go przenośnym
e.tadeu
8

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ć.

Eddie
źródło
1
Oczekiwania z przekroczeniem limitu czasu używane w ten sposób są oznaką innych, gorszych problemów.
dicroce
2
W systemie, który musi mieć długi czas sprawności, lepiej jest przynajmniej uzyskać informacje diagnostyczne, aby można było znaleźć problem. Czasami wolisz wyjść z wątku lub zwrócić odpowiedź do dzwoniącego, gdy system jest w znanym stanie, więc czekasz na semafor. Ale nie chcesz wisieć wiecznie
Eddie
8

DO#

  • Sprawdź wartości inne niż null dla parametrów typu referencyjnego w metodzie publicznej.
  • Używam sealedduż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.
Brian Rasmussen
źródło
8

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.

Joe McMahon
źródło
1
Podczas pisania kodu komunikatu o błędzie przeczytaj wiadomość i zapytaj, co chcesz wiedzieć , a następnie dodaj. Powtarzaj, aż nieuzasadnione. Na przykład: „Poza zakresem”. Co jest poza zakresem? „Foo count poza zakresem”. Jaka była wartość? „Foo count (42) poza zakresem”. Jaki jest zasięg? „Liczba błędów (42) poza zakresem (od 549 do 666)”.
HABO
7

W C # użyj assłowa kluczowego do rzutowania.

string a = (string)obj

zgłosi wyjątek, jeśli obj nie jest łańcuchem

string a = obj as string

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)objskładnia jest preferowana.

W moim własnym kodzie stwierdzam, że używam asskładni w około 75% przypadków, a (cast)składni w około 25%.

Matt Briggs
źródło
Dobrze, ale teraz przed użyciem odwołania musisz sprawdzić, czy nie ma wartości null.
Brian Rasmussen
7
Nie rozumiem. Wydaje mi się, że wybór wartości zerowej wydaje mi się złą decyzją. Będziesz mieć problemy gdzieś podczas działania bez żadnej wskazówki co do pierwotnego powodu.
Kai Huppmann
Tak. Jest to przydatne tylko wtedy, gdy faktycznie chcesz mieć wartość null, gdy nie jest to poprawny typ. Przydatne w niektórych przypadkach: w Silverlight, gdzie logika sterowania i projekt są często oddzielone, logika chce używać kontrolki „W górę” tylko wtedy, gdy jest to przycisk. Jeśli nie, to tak jakby nie istniał (= null).
Sander
2
Wydaje się to tak samo obronne, jak duże okna sali balowej w fortecy. Ale to piękny widok!
Pontus Gagge
1
To przypomina mi kogoś, kto nie używał wyjątków, ponieważ „zepsuli programy”. Jeśli chcesz określonego zachowania, gdy obiekt nie jest klasą, której oczekujesz, myślę, że zakodowałbym to w inny sposób. is/instanceofwpaść do głowy.
Kirschstein
6

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.

Eddie
źródło
6

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 .

Zen
źródło
6

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.

user45947
źródło
5

echoZbyt wiele razy zapomniałem napisać w PHP:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

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 EchoMeklasę, która może być zawinięta wokół dowolnej wartości, która powinna być powtórzona:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

A potem w środowisku programistycznym użyłem EchoMe do zawijania rzeczy, które powinny zostać wydrukowane:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Korzystając z tej techniki, pierwszy brakujący przykład echospowodowałby teraz zgłoszenie wyjątku ...

za dużo php
źródło
Czy destruktor nie powinien sprawdzać $ this-> print! == true?
Karsten
Powinieneś rozważyć użycie systemu szablonów, osadzenie php w html jest nieoptymalne w prawie wszystkich systemach.
Cruachan
Może czegoś mi brakuje? Ale wydaje mi się, że to próbuje zrekompensować kodowanie: AnObject.ToStringzamiast Writeln(AnObject.ToString)?
Rozczarowany
Tak, ale błąd jest znacznie łatwiejszy do zrobienia w PHP
za dużo php
4

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.

mmr
źródło
Podoba mi się twój pierwszy punkt. Robię podobne rzeczy, gdy na przykład piszę metodę, która zwraca kolekcję czegoś. Tworzę kolekcję w pierwszym wierszu i od razu piszę instrukcję return. Pozostało tylko wypełnić informacje o zapełnieniu kolekcji.
Outlaw Programmer
5
W C ++, wpisując new, należy natychmiast przypisać ten wskaźnik do kontenera AutoPtr lub zliczanego odwołania. C ++ ma destruktory i szablony; używaj ich mądrze, aby automatycznie obsługiwać usuwanie.
An̲̳̳drew
4

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.

dicroce
źródło