Defensive Coding in C#

_Logo Scrivere codice manutenibile è la chiave per affrontare varie problematiche nello sviluppo di applicazioni reali realizzate in un arco di tempo medio-lungo. Il Defensive Coding è proprio un insieme di tecniche per garantire qualità, stabilità e comprensibilità del proprio codice.

Partiamo innanzitutto dall’obiettivo che si prefiggono le tecniche di Defensive Coding (o Defensive Programming) e in generale tutti gli argomenti trattati nella sezione coding, cioè quello di garantire:

  • La comprensibilità del codice nel tempo da parte di chi l’ha creato ma anche da parte di altri soggetti. Un codice è comprensibile se è chiaro il suo scopo (purpose) e se è semplice, minimale, ragionato, organizzato, validabile e testabile. La soluzione è quella di applicare tecniche di Clean Code.
  • La prevedibilità (in inglese predictability) del codice: dato un input l’output deve essere certo così come deve esser certa la gestione degli errori e di input inaspettati. Le tecniche sono molteplici: guard clauses, validation e error management.
  • La qualità del codice che deve garantire il minor numero di errori e problemi (quel minor numero è legato a tanti fattori, come il dominio della propria applicazione e le risorse dedicate ad essa). Una tecnica è quella di utilizzare gli Automated Code Test.
  • La manutenibilità (maintainability): il codice deve poter essere modificato con facilità sia in fase di cambio dei requisiti sia in fase di correzione degli errori.

Tali concetti possono essere ben definiti dallo schema proposto da Deborah Kurata e dettagliatamente analizzati nel suo corso pubblicato da Pluralsight:

  • Clean Code
    • Migliora la comprensione
    • Semplifica la manutenzione
    • Riduce i bug
  • Testable Code + Unit Test
    • Migliora la qualità
    • Conferma la manutenzione (non si compromette il codice esistente)
    • Riduce i bug
  • Validation + Error Handling
    • Migliora la prevedibilità
    • Garantisce consistenza
    • Riduce i bug

Quando si affronta un nuovo progetto si devono seguire da subito le regole del clean code, mentre per i progetti esistenti che mostrano un Technical Debt le regole si applicano tramite refactoring. Il refactoring ha appunto lo scopo di trasformare smell code in codice manutenibile.

L’obiettivo si può quindi riassumere in tre punti: il software deve essere clean, testable e predictible. Per ottenere ciò bisogna cercare di rendere clean, testable e predictible ogni parte del proprio progetto partendo dalla sua organizzazione in progetti fino alle unità più piccole: i metodi.

Vediamo ora un elenco di alcuni punti da considerare mentre di applicano le tecniche di defensive coding, punti discussi in dettagli negli articoli i questa sezione:

  • Ridurre alla dimensione minima l’elemento considerato (quindi metodi corti, classi con un numero ridotto di membri, progetti con il minor numero di classi).
  • Considerare un unico scopo per ogni elemento trattato (quindi un metodo, uno scopo; una classe uno scopo; un progetto, uno scopo). Naturalmente il concetto di scopo varia a seconda dell’elemento.
  • Scrivere compliance code, validando quindi i dati in input e canonizzare i dati normalizzandoli in base a quanto aspettato dal sistema.
  • Bilanciare chiarezza e protezione del codice affinché il codice non sia troppo sbilanciato alla semplicità a scapito dei controlli e viceversa. Per protezione si intendono ad esempio le varie forme di validazione dell’input.
  • Ridurre al minimo le dipendenze degli elementi (quindi un metodo dovrà utilizzare il minor numero di oggetti e stati globali, così come una classe deve essere più indipendente possibile dalle altre). In generale è bene che ogni elemento sia utilizzabile singolarmente.
  • Applicare, in base alle necessità, i tanti pattern definiti dalla comunità e considerate le linee guida per risolvere problemi già affrontati da altri. Ad esempio, il Repository Pattern definisce di utilizzare classi apposite per l’accesso ai dati ai fini di consolidare le entità.
  • Seguire delle convenzioni da utilizzare in tutti i propri progetti, meglio se uniforme al sistema di riferimento come nel caso del .NET.
  • Riutilizzare il più possibile il proprio codice: Don't repeat yourself! (DRY principle).
  • Non dare mai per scontato che un elemento del codice rimanga invariato. Ad esempio un metodo privato può essere trasformato in pubblico e quindi richiamato in maniera non originaria.
  • Implementare le cosiddette Technical Restrictions per limitare usi non conformi dei metodi, come l’avvio in thread separati.
  • Utilizzare nomi significativi e non sigle o abbreviazione o nomi generici. Questo vale per ogni elemento della soluzione dal nome del progetto al nome di una variabile.

E’ importante anche un altro concetto tipico della programmazione object oriented: il riuso. Se un codice soddisfa i criteri del defensive coding allora si presuppone che sia affidabile, quindi riutilizzabile senza remore.

Le tecniche di defensive programmaing permettono di consolidare nel codice i presupposti che hanno ispirato quel codice e che spesso si dimenticano nel tempo, ancor più se vi sono persone diverse a gestire le modifiche.

Abusare di tecniche di difesa può portare a un deterioramento della leggibilità e un aumento della complessità: devono essere ponderate in relazione al progetto trattato.

Infine, ritengo che una forma di defensive programming siano le code reviews: far verificare il proprio codice ad un’altra persona permette di avere un immediato feedback sulla sua comprensibilità e sulla conformità rispetto alle convenzioni e alle regole definite.

Tag: Defensive coding