Se esegui il codice in C, Objective-C o C ++ puoi usare l' CLang Static Analyzer per criticare la tua fonte senza effettivamente eseguendolo.
Sono disponibili alcuni strumenti di debug della memoria: ValGrind, Guard Malloc su Mac OS X, Electric Fence su * NIX.
Alcuni ambienti di sviluppo offrono la possibilità di utilizzare un allocatore di memoria di debugging, che riempie le pagine appena assegnate e le pagine appena liberate con garbage, rileva la liberazione di puntatori non assegnati e scrive alcuni dati prima e dopo ciascun blocco heap, con il debugger viene chiamato se il modello conosciuto di tali dati cambia mai.
Qualche tizio su Slashdot ha detto di avere un sacco di valore dal single-stepping di sempre nuove linee di sorgente in un debugger. "È così", ha detto. Non seguo sempre il suo consiglio, ma quando lo ho è stato molto utile per me. Anche se non si dispone di un test case che stimola un percorso di codice non comune, è possibile spostare una variabile nel debugger per intraprendere tali percorsi, ad esempio allocando memoria, quindi utilizzare il debugger per impostare il nuovo puntatore su NULL anziché su indirizzo di memoria, quindi passando attraverso il gestore degli errori di allocazione.
Usa asserzioni - la macro assert () in C, C ++ e Objective-C. Se la tua lingua non fornisce una funzione di affermazione, scriverne una tu stesso.
L'uso asserisce generosamente, quindi lascialo nel tuo codice. Chiamo assert () "Il test che continua a testare". Li uso più comunemente per controllare le precondizioni al punto di ingresso della maggior parte delle mie funzioni. Questa è una parte di "Programming by Contract", che è integrata nel linguaggio di programmazione Eiffel. L'altra parte è postcondizioni, ovvero utilizzando assert () ai punti di ritorno della funzione, ma trovo che non ne traggo il maggior numero possibile come precondizioni.
Puoi anche usare assert per controllare gli invarianti di classe. Mentre nessuna classe è strettamente necessaria per avere alcuna invariante, la maggior parte delle classi progettate in modo sensibile le ha. Un invariante di classe è una condizione che è sempre vera oltre che all'interno delle funzioni membro che potrebbero collocare temporaneamente l'oggetto in uno stato incoerente. Tali funzioni devono sempre ripristinare la coerenza prima che ritornino.
Quindi ogni funzione membro poteva controllare l'invariante all'entrata e all'uscita e la classe poteva definire una funzione chiamata CheckInvariant che qualsiasi altro codice poteva chiamare in qualsiasi momento.
Utilizza uno strumento di copertura del codice per verificare quali linee della tua fonte vengono effettivamente testate, quindi progetta test che stimolino le linee non testate. Ad esempio, puoi controllare i gestori di memoria bassa eseguendo la tua app all'interno di una VM configurata con poca memoria fisica e nessun file di scambio o molto piccolo.
(Per qualche ragione non sono mai stato a conoscenza, mentre BeOS poteva girare senza file di swap, era altamente instabile in quel modo. Dominic Giampaolo, che ha scritto il file system BFS, mi ha spinto a non eseguire mai il BeOS senza swap. non capisco perché dovrebbe essere importante, ma deve essere stata una sorta di artefatto di implementazione.)
Dovresti anche testare la risposta del tuo codice agli errori di I / O. Prova a memorizzare tutti i tuoi file su una condivisione di rete, quindi disconnetti il cavo di rete mentre l'app ha un carico di lavoro elevato. Analogamente, scollega il cavo o disattiva la rete wireless, se comunichi in una rete.
Una cosa che trovo particolarmente irritante sono i siti web che non hanno un robusto codice Javascript. Le pagine di Facebook caricano dozzine di piccoli file Javascript, ma se uno di essi non riesce a scaricare, l'intera pagina si interrompe. Ci deve essere solo un modo per fornire una certa tolleranza d'errore, ad esempio riprovando un download o per fornire una sorta di fallback ragionevole quando alcuni dei tuoi script non sono stati scaricati.
Prova ad uccidere la tua app con il debugger o con "kill -9" su * NIX mentre è nel bel mezzo della scrittura di un file grande e importante. Se la tua app è ben architettata, l'intero file verrà scritto o non verrà scritto affatto, o forse se è solo parzialmente scritto, ciò che viene scritto non sarà corrotto, con quali dati vengono salvati essendo completamente utilizzabili da l'app dopo la rilettura del file.
I database
hanno sempre I / O disco con tolleranza d'errore, ma praticamente nessun altro tipo di app. Mentre i filesystem journaled prevengono il danneggiamento del filesystem in caso di blackout o interruzioni dell'alimentazione, non fanno nulla per impedire il danneggiamento o la perdita dei dati dell'utente finale. Questa è la responsabilità delle applicazioni utente, ma difficilmente altri oltre ai database implementano la tolleranza d'errore.