Considera un tipico disegno imperativo, con variabili mutanti e numerosi riferimenti a risorse esterne altrettanto mutanti. Quando qualcosa va storto con tale codice, la cosa normale da fare è raggiungere il debugger. La ragione di questo è semplice: questo tipo di codice richiede di attraversare mentalmente ogni fase, cercando nel contempo di ricordare lo stato corrente di tutto lo stato di muting. È difficile da fare nella tua testa, motivo per cui ci affidiamo a debugger, dichiarazioni di stampa e simili.
Se questo viene sostituito da funzioni che si comportano in modo deterministico, con variabili immutabili e nessun effetto collaterale esterno, allora la necessità di tenere una macchina di stato complessa va via. Come dici tu, diventa possibile ragionare su cosa fa il codice, piuttosto che cercare di eseguirlo nella tua mente per risolverlo.
Ecco perché la "programmazione funzionale", con la sua enfasi sull'immutabilità, le funzioni pure e deterministiche e simili sta crescendo in popolarità. Il codice risultante da questa tecnica è più semplice da comprendere e quindi meno incline ai bug.
E uno non ha bisogno di usare un "linguaggio funzionale" per scrivere il codice deterministico. Ho recentemente scritto un articolo, "Perché la programmazione dichiarativa è spesso meglio che imperativa, anche in C #" che mostra come è possibile scrivere un codice dichiarativo e deterministico in quello che viene visto tradizionalmente come linguaggio OO.