giovedì 16 luglio 2015

"Wilma dammi l'ABTest"


Immaginate di avere questa riga di codice:

emailSender.Send(email);

chiaro cosa fa questo codice? direi di si... invia un'email.

Cosa si nasconde dietro il metodo Send?

Nella mia esperienza potrei giurare che dietro questo metodo si nascondono una serie (innumerevole) di if e il metodo send molto probabilmente avrà centinaia (o migliaia) di righe e non farà solo il send ma probabilmente molto altre operazioni al contorno!

E pensare che probabilmente tutto è iniziato con una "semplice" if

if(il_dato_ha_questo_valore) { 
 fai questo;
}

semplice no?

e poi si sa come vanno queste cose, una "if" tira l'altra, l'emergenza, il fix "al volo" il poco tempo, sempre di corsa, il caldo... mi stanno chiamandodevoandare, sono le 18, suona la campanella... insomma "piccoli metodi crescono" e si arriva a creare nel tempo e a più mani un blob informe che assume comportamenti strani e diversi, che sembra vivere di vita propria a volte che a metterci mano bisogna prima farsi il segno della croce e quando ti chiedono una modifica bisogna avere la scusa pronta e non è sempre facile... una roba che "è colpa di chi ci ha messo mano prima di me", è codice legacy, non l'ho fatto io, io non c'ero e non ho fatto niente, non è colpa mia... insomma ci siamo capiti, è una storia già sentita vero?

Se qualcuno di voi ha mai sentito parlare della campagna anti-if (http://antiifcampaign.com/)  sa di cosa sto parlando!

Molto spesso (anzi sempre) un "if" sottintende comportamenti diversi e logiche diverse.
Molto spesso (anzi sempre) l'applicazione dei principi SOLID (http://www.wikiwand.com/en/SOLID_(object-oriented_design))  salva la vita.

Facciamo un esempio e facciamo un viaggio nel meraviglioso mondo del metodo "Send" e probabilmente vedremo cose di questo tipo.
...
if(isABTest) {
...
 if(!recipients.isUnsubscribed) {
  doABTest(message);
 }
 ...
 var vetting = EseguiVetting();
 
if(vetting) {
  doABTest(message);
 }
 Logger.debug("fatto AB Test")
}...


Cosa ne pensate se invece avessimo una serie di componenti (oggetti) ognuno dei quali fa una sola cosa (Single Responsibility) e che vengono utilizzati dal nostro EmailSender all'occorrenza abilitandoli o meno magari con un file di configurazione che li abilita solo in ambiente di staging e non li fa vedere in produzione perchè ahimè ci stiamo ancora lavorando?

immaginiamo di scrivere questo codice:

emailSender.Enable(ABTest);
emailSender.Send(email);

e torniamo nel nostro metodo Send dove al posto delle if troviamo una lista di SenderAction da eseguire, infatti il nostro metodo Enable aggiungerà semplicemente ad una lista l'azione da eseguire:

private var workflow = new List<SenderAction>;
...
public void Enable(
SenderAction actionToPerform){
 workflow.Add(actionToPerform);
}

mentre il metodo Send scorrerà semplicemente il nostro workflow e lancerà per ogni elemento il metodo Execute:

private var workflow = new List<SenderAction>;
...
public void Send(
email){
 foreach(actionToPerform in workflow) {
  actionToPerform.Execute();
 }
}

infatti il nostro SenderAction è semplicemente un'interfaccia che espone il metodo Execute

interface SenderAction {
 public void Execute();
}

nell'esempio sopra il nostro parametro ABTest implementa il metodo execute alla sua maniera eseguendo solo l'ABTest ma ognuno ci può fare quello che vuole.

Quali vantaggi ho ottenuto?

L'oggetto che fa  l'AB test fa solo l'AB test(ancora Single Responsibility).
L'ABTest viene passata al EmailSender (Dependency Injection), quindi è mockabile permettendomi di testare il Sender.
L'ABTest è testabile a sua volta.
Ogni metodo ha poche righe e se devo mettere mano so dove andare.
L'EmailSender diventa quindi un semplice  orchestratore, cioè della serie "Miwa lanciami i componenti" (cit. Jeeg Robot)  oppure "Wilma, dammi l'ABTest!".

E se voglio fare il caffè?

Se voglio estendere il mio EmailSender e scopro che tra l'ABTest e l'operazione di Vetting per esempio deve farmi un caffè basterà semplicemente creare una classe AnExpressoPlease che implementa l'interfaccia SenderAction ed eseguire il metodo

emailSender.Enable(new AnExpressoPlease());

e il gioco è fatto! :-)