Voglio distinguere tra due diversi modi di approccio alla programmazione orientata agli oggetti
- Simulatore: i tuoi oggetti rappresentano oggetti di dominio reali, li hai programmati per gestire qualsiasi funzionalità relativa a quel dominio. È probabile che gli oggetti programmati in questo modo contengano molti stati mutevoli e collaboratori nascosti utilizzati per implementare questa funzionalità.
- Record + funzioni: i tuoi oggetti sono solo pacchetti di dati e funzioni che operano su quei dati. Gli oggetti programmati in questo modo sono più adatti per essere immutabili, per assumere meno responsabilità e permettere di iniettare collaboratori.
Una regola empirica è che un oggetto programmato nel primo modo avrà più metodi e più metodi void
rispetto al secondo modo. Diciamo che stavamo per scrivere un simulatore di volo e stavamo progettando una classe di aerei. Avremmo qualcosa di simile
class Plane {
void accelerate();
void deccelerate();
void toggleRightFlaps();
void toggleLeftFlaps();
void turnRudderRight();
void turnRudderLeft();
void deployLandingGear();
void liftLandingGear();
// etc.
void tick() throws PlaneCrashedException;
}
Questo è forse un po 'più estremo di quello che si potrebbe incontrare, ma ottiene il punto attraverso. Se vuoi implementare questo tipo di interfaccia devi tenere dentro l'oggetto
- Tutte le informazioni sullo stato delle apparecchiature dell'aereo.
- Tutte le informazioni sulla velocità / accelerazione del piano.
- La frequenza di aggiornamento della nostra simulazione (per implementare il segno di spunta).
- Dettagli completi sul modello 3d della simulazione e la sua fisica per implementare tick.
Scrivere un test unitario per un oggetto scritto nella modalità è molto difficile perché
- Devi fornire tutti i molti diversi bit di dati e collaboratori di cui questo oggetto ha bisogno all'inizio del test (l'instillazione di questi può essere davvero noiosa).
- Quando si vuole testare un metodo si incontrano due problemi: a) l'interfaccia spesso non espone dati sufficienti per il test (quindi si finisce per dover usare simulazioni / riflessioni per verificare anche le aspettative) b) ci sono molti componenti legati in uno che devi verificare si sono comportati in ogni test.
Fondamentalmente si inizia con un'interfaccia che sembra abbastanza ragionevole e come si adatta bene al dominio, ma la gentilezza della simulazione ti ha appassionato nella creazione di un oggetto che è davvero difficile da testare.
Tuttavia, puoi creare oggetti che soddisfano lo stesso scopo. Dovresti frenare il Plane
in parti più piccole. Avere un PlaneParticle
che tiene traccia dei bit fisici del piano, la posizione, la velocità, l'accelerazione, il rollio, l'imbardata, ecc. Ecc., Esponendo e permettendo a uno di manipolarli. Quindi un oggetto PlaneParts
potrebbe tracciare lo stato di. Spediresti tick()
in un posto completamente diverso, ad esempio hai un oggetto PlanePhysics
parametrizzato da, ad esempio, la forza di gravità, che sa dato un PlaneParticle
e un PlaneParts
come sputare un nuovo PlaneParticle
. Tutto ciò potrebbe essere completamente immutabile, sebbene non sia necessario per alcuni esempi.
Ora hai questi vantaggi in termini di test:
- Ogni singolo componente ha meno da fare ed è più facile da configurare.
- Puoi testare i tuoi componenti separatamente.
- Questi oggetti possono farla franca esponendo i loro interni (specialmente se sono resi immutabili), quindi non c'è bisogno di intelligenza per misurarli.
Ecco il trucco: il secondo approccio orientato agli oggetti che ho descritto è molto vicino alla programmazione funzionale. Forse nei puri programmi funzionali i tuoi dischi e le tue funzioni sono separati e non legati insieme in oggetti, sicuramente un programma funzionale assicurerebbe che tutte queste cose. Quello che penso rende davvero facile il test unitario è
- Piccole unità (piccolo spazio di stato).
- Funzioni con input minimo (nessun input nascosto).
- Funzioni con output minimo.
La programmazione funzionale incoraggia queste cose (ma si possono correggere programmi cattivi in qualsiasi paradigma) ma sono realizzabili in programmi orientati agli oggetti. E vorrei sottolineare ulteriormente che la programmazione funzionale e la programmazione orientata agli oggetti non sono incompatibili.