2009-09-03 1 views

risposta

26

L'ampio uso di NSAssert() non trasformerà ObjC in Eiffel, ma è comunque una pratica abbastanza buona se si tiene a mente come viene effettivamente implementato e cosa sta facendo. Cose da tenere a mente su NSAssert():

Xcode non disattiva NSAssert() in modalità di rilascio per impostazione predefinita. Devi ricordare di aggiungere NS_BLOCK_ASSERTIONS a GCC_PREPROCESSOR_DEFINITIONS in te xcconfig. (Sei using xcconfigs, giusto?) Un problema comune è affermare il non-zero nei casi in cui nil funzionerà tranquillamente; questo può significare crash di campo per cose che potrebbero essere recuperate con garbo. Ciò non è correlato alla macro NDEBUG utilizzata da assert() e si deve ricordare di definire entrambi se il codice include entrambi i tipi di asserzioni.

Se si compila NSAssert() in modalità di rilascio, non si ottiene alcuna indicazione in caso di problemi durante l'invio dei registri da parte dei clienti. Concludo personalmente NSAssert() nei miei macro personali che registrano sempre, anche in modalità di rilascio.

NSAssert() spesso forza la logica duplicata. Si consideri il caso di testare un puntatore C++ per NULL. Si utilizza NSAssert(), ma è ancora necessario utilizzare un semplice test if() per evitare arresti anomali nel campo. Questo tipo di codice duplicato può diventare fonte di bug e ho visto codice che fallisce a causa di asserzioni che non sono più valide. (Fortunatamente questo è generalmente in modalità Debug!) Ho discusso molto su come creare una macro che combinasse l'asserzione e if(), ma è difficile da fare senza essere fragile o rendere difficile la comprensione del codice.

A causa dell'ultimo problema, in genere inserisco "NSAssert(NO, ...)" in una clausola else{} anziché eseguire l'asserzione nella parte superiore del metodo. Questo non è l'ideale perché allontana il contratto dalla firma del metodo (riducendo così il suo vantaggio documentario), ma è l'approccio più efficace che ho trovato nella pratica.

+0

non vedo perché si dovrebbe finire con il codice duplicato. Se l'asserzione fallisce, il tuo codice non ha l'obbligo di salvarsi da un arresto anomalo. In effetti non ha senso, perché un'ipotesi che hai fatto è stata infranta, quindi non esiste un modo corretto per salvare il programma dal non crash in seguito, a causa di uno stato incoerente. –

+0

Il codice ha sempre l'obbligo di comportarsi bene per l'utente. Mentre in alcuni casi la preoccupazione per la corruzione dei dati significa che l'arresto immediato è l'unico approccio. Ma in molti casi questo non è vero e il recupero, mostrando un errore o addirittura non facendo nulla è preferibile in Release vs crash e lasciando l'utente fissando Springboard senza informazioni. Ho visto abbastanza asserzioni errate nel codice di produzione da essere molto cauto nei confronti del crash preventivo in Release in assenza di possibili corruzioni di dati. Il debug è una situazione completamente diversa e io sostengo rapidamente il crash. –

+0

Una buona app riconosce che ha dei bug e cerca di contenerli. Solo perché ho un piccolo bug nel sistema di aiuto non dovrebbe significare che l'intero gioco dovrebbe bloccarsi solo perché tecnicamente il "programma è ora indefinito". Ogni vero programma di qualsiasi dimensione coinvolge un sacco di comportamenti indefiniti. È nostra responsabilità minimizzarlo, ma anche vivere all'interno di esso e non schiantarsi ogni volta che qualcosa sembra strano. –

7

Debugging. Ogni volta che scrivi codice, stai quasi sempre facendo delle supposizioni. Presupposti sullo stato dell'ambiente, i valori dei parametri, le variabili e i campi locali, ecc. Spesso queste assunzioni sono semplicemente sbagliate (un vecchio collega mi ha dato una buona massima, che "Assumption è la madre di tutti i fsckups") .

Le asserzioni esistono per convalidare le vostre ipotesi, nel punto in cui le fate. Hai un metodo

void foo(int x) 
{ 
    … 
} 

che sai e hai documentato funziona solo per x> 5? Affermalo!

Le asserzioni vivono insieme ai test unitari e ai metodi formali come parte di una buona pratica di codifica. Mentre alcuni potrebbero pensare che test unitari completi assicurino che le asserzioni siano ridondanti, non è il caso. Ad esempio, potresti avere un'ipotesi su un metodo che, ad esempio, i dipendenti hanno sempre più di 16 e meno di 100 anni - ma che il codice, al momento, non richiede questo. I test unitari che superano questi parametri avranno successo, ma in seguito, quando avrai bisogno di usare la tua ipotesi, avrai codice dappertutto che ha superato i test, ma è sbagliato.

9

L'avvertimento principale sono gli effetti collaterali.Se si scrive:

NSAssert([self.navigationController popViewControllerAnimated:YES]!=nil,@"Something fails"); 

popViewControllerAnimated: verrà eseguito nella versione di debug, ma non nella versione che mette a nudo NSAssert(). Ciò significa che la versione di rilascio si comporterà in modo diverso rispetto alla versione di debug.

Questo problema scompare se si sta attenti abbastanza:

UIViewController* vc = [self.navigationController popViewControllerAnimated:YES]; 
NSAssert(vc!=nil,@"Something fails"); 
+0

+1 perspicace, anche se non ho mai chiamato metodi in asserzioni. –