Visualizzazione post con etichetta code. Mostra tutti i post
Visualizzazione post con etichetta code. Mostra tutti i post

[10 years] Codice (seconda parte) leggibilità,framework, falsi miti

Passaggio di consegna: una metrica fondamentale!


Per valutare un buon codice sono state proposte un gran numero di metriche. Io ne ho trovata una che reputo saggia:

Se formare una nuova persona nel tuo progetto 
richiede poco tempo, 
il tuo codice è ottimo! 

In teoria sarebbe sufficiente una suite di test di non regressione e una certa leggibilità.
Talvolta i passaggi di consegna sono considerate attività che richiedono mesi. A me pare assurdo, soprattutto considerando la complessità media dei progetti su cui ho lavorato. Quando lavori su un ecommerce, il passaggio di consegna o la formazione di un nuovo membro di un team dovrebbe essere rapida, dell'ordine di qualche giorno. Infatti a parte specificità anomale del progetto il campo ecommerce ha un dominio estremamente semplice. Tutt'altra cosa sarebbe formare una persona su un simulatore di volo o su codice geometrico 3d. 

Perchè nella realtà, nella stra grande maggioranza dei progetti il tempo di inserimento di una nuova persona è lungo ? 

Ho identificato 3 motivi principali: 
  • Ci sono troppe convenzioni orali. Per fare un task il team sa che deve rispettare delle regole, che però né sono scritte né sono automatizzate. Scoprirle tutte richiede tanto tempo e il rischio di infrangerle è grande.
  • Non c'è una fonte di informazione chiara, o non si risce ad accedervi facilmente. Ad esempio potrebbe non aver senso documentare l'architettura di un sistema, se per sua natura cambia ogni pochi mesi. Ma senz'altro ha senso poter ridisegnarla in poco tempo. La documentazione non è importante di per sé, ma è importante definire un modo con cui un nuovo entrato può reperire le informazioni necessarie.
  • Il codice è complesso. Ma il problema che risolve non lo è. Se fosse semplice la formazione sarebbe assai semplificata.

La Leggibilità: il sacro graal 



Fate fare un kata di programmazione a vari sviluppatori, ognuno valuterà l'esercizio altrui meno leggibile del proprio. 
Troppi refactoring inutili si sono fatti per il tema della leggibilità
Anche questa tematica secondo me distingue uno junior da un senior:

Il codice dev'essere leggibile come il sale: quanto basta.

E' invece importante costruire un dizionario comune all'interno del team. Quindi un termine o una tecnica che può apparire ostica ad un nuovo arrivato ha completamente senso all'interno del team consolidato. Poichè il team si allinea volente o nolente su uno stesso linguaggio è importante che esso stesso cresca (con confronti, esercizi etc) al fine di aumentare la sensibilità media. 


La mia opinione sui framework


Non amo i framework, perchè se li si usa cambia la natura del proprio lavoro che diventa da una attività creativa ad una attività organizzativa/burocratica.

Tuttavia sono andato più a fondo della pura motivazione del divertimento. La verità è che quando qualcuno decide di usare un framework per un progetto è perchè ha una scarsa concezione dei programmatori a sua disposizione. 

Scegli un framework se:
  • il tuo team discute tanto di design e poi non combina nulla
  • il tuo team mette troppi bachi in produzione
  • ogni virgola richiesta al team richiede un tempo esagerato per essere implementata
  • ci sono numerose regressioni



Il più delle volte i problemi risolti da un framework sono molto piu grandi e complessi di quanto serve davvero implementare, e il costo di imparare il framework nella mia opinione è maggiore del tempo di riscriversi un sistema custom piccolo e adatto alle proprie esigenze. 
Ho riscritto o visto riscrivere con poche righe di codice: 
  • un rule engine in ruby
  • un router in java
  • una macchina a stati di media complessità
  • un sistema di alerting di eventi asincrono estremamente scalabile
Il mio consiglio non vuole essere di usare o meno framework: non avrebbe senso. Reputo invece importante che sia il team a scegliere

Rimane un ultimo punto da chiarire: 

Come scegliere tra vari framework che risolvono lo stesso problema? 

Io reputo un framework valido se ha previsto un modo per essere testato o per testare. Ad esempio non sceglierei mai un framework per costruire applicazioni web che come *unico* strumento di test richiede Selenium.


[10 years] Codice (prima parte)


La regola delle regole del buon codice




E' simile a quella seguita nella vita day by day: ascolta tutti, ma scegli tu. 

E' importantissimo confrontarsi su buone pratiche di programmazione, sia con blog, libri e colleghi. Lo scambio di punti di vista su cosa sia un 'buon' codice è anche uno degli aspetti più divertenti del lavoro. Tuttavia tutte le regole possono essere infrante, purchè ci sia un ragionamento di base. 

Reputo questa considerazione uno dei punti che distinguono un programmatore junior da un senior. 

Un junior si appassiona a buone regole di programmazione, le applica e le considera di buon senso e funzionanti. Ma non riesce ad adattare strategie diverse a nuove situazioni. 
Un semplice esempio: in un codice scritto con paradigma ad oggetti è buona norma non esporre lo stato interno degli oggetti con metodi 'setter/getter'; tuttavia questi metodi sono molto utili nei test, non metterli implica il giocare su librerie di reflection per modificare a run time il codice compilato. La soluzione di mettere 'setter/getter', usati solo dai test, è senz'altro da preferire in un gran numero di occasioni, pur introducendo una convenzione di team di non usarli in codice di produzione. 


Le buone pratiche di programmazione


Tralascio quelle che si trovano comunemente diffuse, i vari principi SOLID, GRASP etc etc. 
Secondo molti il codice riflette il modo di pensare del programmatore. 
Se il programmatore pensa a lavorare in fretta per chiudere una funzionalità e migliorare il codice 'in futuro' non avrà problemi ad aggiungere commenti stile "TODO" nel codice. 

Ecco una breve lista di punti che reputo validi al fine di scrivere e leggere un buon codice: 
  • No Todo e Fixme nel codice. 
  • No warning del compilatore o dell'Ide. Avere un progetto con piu di una decina di warning vuol dire non usare questo strumento, quindi tanto meglio disabilitarlo. Oppure può valere la pena di configurare l'ide per valutare i warning considerati significativi, per poi correggerli.
  • Nessun commento inutile. La maggior parte dei commenti è inutile. Ma non tutti. Da agilista junior ero abituato a cancellare ogni commento. A volte indicare il perchè di una scelta, un link a un'api o un articolo, può essere utile. Commenti utili sono quelli che specificano una data e il nome dell'autore, oltre al motivo.
  • Il miglior oggetto ha un solo costruttore e un solo metodo. Sicuramente quell'oggetto sai come costruirlo e come usarlo :) certo, è una grossa semplificazione. Ma mi piace far tendere il codice verso oggetti di quel tipo. E' piu probabile che un oggetto con un solo metodo abbia una sola responsabilità. 
  • I log dell'applicazione sono importanti. Non amo i log a debug, mentre mi piace loggare ciò che vorrei leggere durante una fase di analisi. Quindi benvenuto qualsiasi formato utile ai fini di grep dei log o per tracciare tutto il ciclo di vita di un task applicativo. Ad esempio, in un contesto di ecommerce, è importante dato un numero d'ordine o un identificativo utente, poter tracciare tutto l'iter di navigazione di un processo di checkout con semplici grep dei file di log.
  • Spesso non è immediato dare responsabilità agli oggetti. Nel dubbio, meglio crearne uno nuovo, che sicuramente renderà più snelli gli altri. Il ciclo di vita di questo oggetto potrà poi essere breve, collassare o crescere in identità. 
  • Se nel tuo codice c'è anche un solo if X instanceof poniti delle domande sulle tua abilità di programmazione







[Design] Pattern observer


Un pattern assai famoso che abbiamo usato pesantemente nell'ultimo progetto
è l' OBSERVER
Abbiamo osservato tutti gli eventi significativi che succedono; tipicamente: 

  • l'evento scatenante di ingresso che scatena un certo use case
  • tutte le chiamate verso i sistemi esterni e le risposte ottenute
  • la risposta dello use case invocato


Tale struttura è tornata utile per le seguenti implementazioni concrete: 


  • 'database' observer, che a fronte degli eventi interessati esegue delle operazioni sul database, tipicamente l'aggiornamento di timestamp di una macchina a stati
  • 'logger' observer, che stampa su log gli avvenimenti significativi del sistema.
  • 'latency' observer, che calcola il tempo di esecuzione di uno use case
  • 'statistic' observer, che memorizza in stutture dati l'andamento delle chiamatesecondo particolari regole di business (ad esempio quali e quanti use case sono statiscatenati in un intervallo di tempo) 
  • 'audit' observer, per salvare su database quale utente sta eseguendo una operazione sensibile


Tutti questi observer sono richiamati da un observer list iniettato nell'oggetto che incapsula il flusso principale.

Ad esempio se vogliamo notificare l'evento di 'messaggio in ingresso' : 

 public void execute(IncomingMessage message) {
    observerList.notifyIncomingMessage(message);
    doSomething()...
 }

 dove observerList è una implementazione concreta dell'interfaccia CustomObserver : 

 public class ObserverList implements CustomObserver {
    private final List observers;
    public void notifyIncomingMessage(IncomingMessage message) {
  for (CustomObserver customObserver : observers) {
customObserver.notifyIncomingMessage(message);
}
  }

Grazie a questo pattern il codice principale è ignaro dei dettagli a contorno. 

Mi piace anche l'effetto teorico che puoi sfilare un certo observer in modo che il sistema
continui ad andare
Immagino una situazione estrema dove ad esempio si scopre che il 'logger' observer  per un qualche baco sta rallentando pesantemente la produzione. Nel frattempo che si indaga il baco si potrebbe rimuovere questo observer dalla ObserverList e tutto continuerebbe ad andare, senza causare disservizio.

[Ruby] A Quick Ruby Kata Solution


First solution of the kata described here:

http://railsrx.com/2010/09/27/a-quick-ruby-kata/




PAIRS = [ [1,1], [2,2], [3,3], [4,4], [5,5] ]
SINGLES = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

def single_from_pair(pair)
[pair[0]]
end

def sum(pair)
pair[0] + pair[1]
end

result = []

PAIRS.each do |pair|
(SINGLES - single_from_pair(pair)).each do |y|
(SINGLES - single_from_pair(pair)).each do |k|

z = 15 - sum(pair) - y - k
if z > 0 && z != single_from_pair(pair).first
candidate = [pair, y, k, z].flatten.sort
result << candidate unless result.include?(candidate)
end
end
end
end

[Code] Abilitazione e disabilitazione funzionalità a run time - senza GIT


Una esigenza comune a pressochè tutti i progetti è quella di abilitare o disabilitare funzionalità con facilità.
Un esempio sono le ultime funzionalità sviluppate, non ancora testate nell'ambiente di staging, che non devono finire in produzione.
Ecco quindi che GIT o sistemi analoghi di controllo versione tentano di risolvere questo problema: una branch per ogni funzionalità e quando vuoi comporre il tuo pacchetto di installazione selezioni le branch a piacimento. Eppure mi pare una soluzione di bassissimo livello, che lavora con i file e non per alto livello tipo le funzionalità.

Concettualmente mi piacerebbe una dashboard di installazione, dove sono elencate con delle checkbox tutte le funzionalità dell'applicazione. L'applicazione sta su un solo ramo, senza alcuna branch.
L'amministratore può quindi disabilitare o abilitare funzionalità a run time, senza bisogno di riavvio. Questo è un sistema che mi piacerebbe !

[Rails] paginate with will_paginate plugin


I'm using the will_paginate plugin for rails.
Unluckily, my query aren't in the form of:

MyModel.find(:all..)

in this case the main change is simply replacing 'find' with 'paginate' as described in the site.
I have a collection of MyModel object retrieved in a lot of different ways.
My solution, based on what i can see here , is calling a method (outside the controller class):

def self.paginate(my_collection, params)
current_page =
if params[:page].nil?
1
else
params[:page].to_i
end
per_page = 10
page_results = WillPaginate::Collection.create(current_page, per_page, my_collection.size) do |pager|
start = (current_page-1)*per_page
pager.replace(my_collection[start, per_page])
end
page_results
end

and, in the view:

<%= will_paginate @page_results, :params => params %>

[Code] java quiz



First
Look at the following test code:

@Test
public void quiz1() throws Exception {
assertEquals(9, 01 + 08);
}
The result is:
1) green bar
2) red bar
3) doesn't compile
4) it throws an exception






Second


@Test
public void quiz2() throws Exception {
Integer x = 1288;
Integer y = 1288;
assertTrue(x == y);
int z = 1288;
assertTrue(x == z);
}

1) first assert red, second green
2) green, red
3) green, green
4) red, red
5) doesn't compile
6) it throws an exception

[Code] subset duplication


One of the step in test driven development is "refactoring your code".
One of the main step of refactoring is the act of removing duplication.
You can read from wikipedia: "The concept of removing duplication is an important aspect of any software design".
I think duplication is pretty difficult to identify; there are a LOT of different kind of duplications. Some of them are stated here.
Today i notice another kind of duplication, something i can call like 'subset duplication' where a function does a little less of another bigger function.

For example:
addTicket(id, value) and
addTicket(id, value, comment) and
addTicket(id, value, date)

I think it's better to have only one function without say the same thing in one thousand of little different modalities. I like expose the differences, not hide them.
One solution could be addDefaultTicket() that return a ticket so you can customize with fluent interfaces: addDefaultTicket().withComment("xxx").
I think there are plenty of other solutions, but the point is always the same: trying to find the best and unique solution.