La mia risposta dovrebbe essere considerata complementare alle altre risposte. Ci sono altre cose che puoi fare per ridurre il tempo impiegato nel debugging. I programmatori efficienti dedicano poco tempo al debug, principalmente perché la maggior parte dei bug che si verificano vengono catturati rapidamente, dove sono facili da correggere, non nella catena di chiamate, e non da un cliente o un collega.
Punte:
- Come dice scriptin, evita di mutare i dati: se la tua lingua ha parole chiave per impedire a una variabile o un campo di mutare (assegnandogli un nuovo valore), usa quelle parole il più possibile. Lo stato predefinito per tutti i campi deve essere immutabile, modificato per essere modificato solo quando necessario.
- Quando è necessario modificare i dati, mantenere l'intervallo di mutazione (intervallo di valori possibili) il più piccolo possibile. Identificare gli invarianti (descritti sotto) aiuterà. Lo scopo di questo è di ridurre lo spazio degli stati del programma, che semplifica le prove, il test e il debug.
- Riduci al minimo il numero di posti quando un campo specifico mutabile viene mutato.
- Riduci al minimo il numero di punti nel tempo in cui i campi vengono modificati. Gli approcci di programmazione funzionale ti guideranno naturalmente in questa direzione.
- Semplifica il più possibile il codice raccogliendo codice comune in metodi, funzioni o classi base.
- Mantenere la lunghezza del codice più corta possibile ed evitare un'eccessiva ingegnerizzazione, evitando tecniche di progettazione di tipo cargo-cult. I motivi di progettazione superflui e "principi", applicati indiscriminatamente, sono i principali colpevoli.
- Utilizza prove informali a ogni livello di astrazione, o almeno per le sezioni più critiche ai livelli più bassi.
Per usare le prove informali devi identificare tre cose, più volte:
- Invarianti: per classi e sistemi nel loro insieme, identifica le cose che dovrebbero sempre essere vere. Ad esempio, forse un campo non dovrebbe mai deviare da determinati valori, o mai essere nullo.
- Prerequisiti: per ciascun metodo, identificare ciò che deve essere vero prima dell'esecuzione del metodo.
- Post-condizioni: per ogni metodo, identificare ciò che deve essere vero dopo l'esecuzione del metodo.
Al livello più alto, gli invarianti per il sistema (o sottosistema) nel suo insieme devono essere dimostrati in modo informale. Questo viene fatto esaminando ogni chiamata dall'alto verso il basso, usando le post-condizioni conosciute per dimostrare che gli invarianti hanno tenuto. Gli invarianti non devono essere veri all'interno del codice del metodo. Gli invarianti possono essere temporaneamente sospesi all'interno dei metodi, purché siano veri quando il metodo esce.
In alternativa, puoi iniziare dal fondo della catena di chiamate e determinare se le pre-condizioni saranno vere per ogni chiamata in aumento.
Idealmente fai qualche prova informale delle catene di chiamata prima di scrivere il codice.
Scrivi il codice per asserire (crash o generare eccezioni se non è vero) le pre-condizioni all'inizio di ogni metodo e le post-condizioni alla fine di ogni metodo. Se una di queste cose è troppo costosa da controllare in fase di esecuzione, fallo solo nelle build di debug.
Il risultato finale è che il tuo codice si bloccherà immediatamente non appena inizi a scriverlo. Questi sono errori o errori di specifica. L'arresto anomalo potrebbe richiedere una modifica agli invarianti, alle condizioni preliminari o alle condizioni successive. Ma tieni questi fatti il più "stretti" possibile; limitare il più possibile gli intervalli di stati del programma.
Troverai bug non appena avvii l'esecuzione del codice. Saranno comunque necessari test per esercitare completamente il codice.
Idealmente avremmo tutti compilatori di prova di controllo formali automatici, ma quel giorno sembra essere ancora distante in futuro.
Se questo sembra troppo lavoro, fallo solo per le sezioni di codice di basso livello e più critiche.