giovedì 12 gennaio 2012
Ne avevo già parlato in un precedente post,
ma provo ad illustrare altri aspetti che avevo ignorato.
Una delle cose che viene data per scontato in XP è che si sappia pensare ad oggetti che è un prerequisito per il simple design.
Ma cosa significa veramente?
Nella programmazione tradizionale o formale un oggetto è dato dai suoi dati (attributi) e i metodi che agiscono su tali dati.
Questo però porta ad un esplosione del numero di classi perché basta un solo dato diverso e devo creare una nuova classe.
L'idea centrale del pensare ad oggetti sono i comportamenti cioè la decomposizione di un problema avviene individuando i comportamenti
e assegnandoli agli oggetti.
Ad esempio: prendiamo una tigre e un gatto nella programmazione formale ho una classe unica e due istanze diverse con i valori degli attributi
diversi perché la tigre e il gatto hanno le stesse caratteristiche. Se pensiamo ad oggetti invece sono due classi diverse in quanto dalla tigre
mi aspetto che mi aggredisca mentre un gatto è un animale domestico e quindi hanno diversi comportamenti.
Un modo utile per scoprire gli oggetti adatti a risolvere un problema è quello della metafora, cioè cerco di capire se esiste un qualcosa di
analogo nel mondo reale che si mappa con il mio problema. Bisogna tenere presente che la metafora è un modello del problema e quindi ci possono
essere delle differenze.
Ad esempio supponiamo di voler realizzare un sito per cercare un volo tra due destinazioni. Una metafora potrebbe essere quella dell'agenzia di viaggio
a cui comunico dove voglio andare, in che data e mi viene fornito un catalogo con tutte le informazioni per poter scegliere il volo.
Seguendo la metafora posso iniziare a disegnare l'oggetto agenzia di viaggio a cui mando il messaggio forniscimi il catalogo e gli passo una richiesta.
A quel punto il risultato del messaggio puo' essere l'oggetto catalogo che contiene un elenco delle offerte e così via.
Il punto cruciale è provare a fare una simulazione mentale o facendosi aiutare dai colleghi dell'interazione tra i vari oggetti per capire se il modello
è adatto a risolvere la storia da realizzare. Quindi si ragiona nello spazio del problema o dominio e inizialmente si ignora la soluzione del problema, cioè la sua implementazione.
Una volta individuati gli oggetti si può inziare l'implementazione utilizzando il TDD; ricordandosi che non sempre ad un oggetto di dominio corrisponde
una classe, a volte puo' essere più di una a volte nessuna come nel caso in cui avessi creato, durante l'analisi, l'oggetto utente che fa la richiesta.
Per approfondire l'argomento consiglio la lettura del libro Object Thinking di David West (ringrazio
Matteo per la segnalazione). Mentre per gli effetti
sulla produttività usando il TDD consiglio la lettura del post Flipping the Bit
di Zio Bob.
lunedì 2 gennaio 2012
In questo post vedremo come accedere ad un database sqlite utilizzando Ruby.
La scelta di sqlite è dovuta alla sua estrema semplicità di gestione, in quanto il database consiste in un unico file.
Per prima cosa occorre installare una libreria per poterlo utilizzare da ruby. Basta digitare da terminale gem install sqlite3-ruby
.
Ora creiamo il file sqlite-test.rb con il seguente contenuto:
require 'sqlite3'
require 'test/unit'
class DatabaseTest < Test::Unit::TestCase
def setup
@db = SQLite3::Database.new 'test.db'
end
def teardown
@db.close
end
def test_create
assert File.exist? 'test.db'
end
end
Come si puo' vedere dal test il database consiste in un unico file test.db creato nella stessa directory. Se lanciamo il test abbiamo barra verde.
Proviamo ora a creare una tabella e ad inserire alcuni dati. Aggiungiamo quindi il test:
def test_create_table
sql = <<SQL
create table names (
id int primary key,
name text
);
SQL
@db.execute sql
assert_equal @db.execute('select * from names'), []
end
Proviamo a lanciarlo: Barra verde. Se proviamo però a rilanciarlo otteniamo l'errore:
SQLite3::SQLException: table names already exists
.
Per risolvere il problema cancelliamo la tabella, se esiste, prima di crearla utilizzando il comando:
drop table if exists names;
. Un'altra alternativa potrebbe essere la cancellazione del file di database.
def test_create_table
sql = <<SQL
drop table if exists names;
create table names (
id int primary key,
name text
);
SQL
@db.execute_batch sql
assert_equal @db.execute('select * from names'), []
end
Notare che ho modificato execute con execute_batch perché altrimenti sarebbe stata eseguita solo una delle due query.
A questo punto siamo pronti per inserire alcuni dati e verificare che siano presenti nella tabella
def test_create_table
sql = <<SQL
drop table if exists names;
create table names (
id int primary key,
name text
);
insert into names values(1, 'a');
insert into names values(2, 'b');
SQL
@db.execute_batch sql
assert_equal @db.execute('select * from names'), [[1, 'a'], [2, 'b']]
end
Come si puo' leggere dal test i dati vengono restituiti come un array di array.
Altre informazioni su come usare sqlite le potete trovare qui
mercoledì 28 dicembre 2011
In questo post illustrerò un modo per sviluppare un sito web che usa Sinatra usando la pratica del TDD. Per poter provare gli esempi occorre aver seguito
i passi del mio precedente post.
Sinatra, come altri framework scritti in Ruby, aderisce allo standard Rack. In sintesi basta realizzare un oggetto
che risponde al messagio call, prende un hash come parametro e risponde con un vettore di tre elementi: lo status code, l'header http e il body.
Un esempio di un oggetto che rispetta lo standard è:
class HelloWorld
def call(env)
[200, {"Content-Type" => "text/plain"}, ["Hello world!"]]
end
end
Questo ha il vantaggio di avere degli oggetti già pronti per poter scrivere i test. Prima occorre da terminale eseguire il comando:
gem install rack-test
in modo da installare tutto ciò che ci serve.
A questo punto siamo pronti a scrivere il nostro primo test (il file l'ho chiamato sitetest.rb):
require './site'
require 'test/unit'
require 'rack/test'
class SiteTest < Test::Unit::TestCase
def test_homepage
browser = Rack::Test::Session.new(Rack::MockSession.new(Sinatra::Application))
browser.get '/'
assert browser.last_response.ok?
end
end
Creiamo anche il file (site.rb) dell'applicazione che per ora non ha impostato nessun routing:
require 'sinatra'
Se ora lanciamo il test con il comando ruby sitetest.rb
. Vediamo che l'assert fallisce.
Il codice minimo per la barra verde è:
require 'sinatra'
get '/' do
end
Rilanciamo il test ed ecco la prima barra verde!
A questo punto vogliamo che la nostra pagina abbia il contenuto Hello World. Aggiungiamo quindi un assert:
assert_equal browser.last_response.body, 'Hello World'
Lanciamo nuovamente il test ed otteniamo il messaggio:
1) Failure:
test_homepage(SiteTest) [sitetest.rb:13]:
<""> expected but was
<"Hello World">.
Modifichiamo il codice di produzione per la barra verde:
require 'sinatra'
get '/' do
'Hello World'
end
Alcune note:
- Affinchè un metodo venga eseguito dal test runner deve iniziare per test
Sinatra::Application
è l'oggetto che rispetta lo standard rack
- Maggiori dettagli su come scrivere i test usando Rack e Sinatra sono qui
Buon divertimento.
martedì 27 dicembre 2011
Ho iniziato a studiare lo sviluppo di applicazioni web con Ruby. Volevo qualcosa di più leggero di Rails per riuscire a padroneggiarlo in meno tempo
così la scelta è ricaduta su Sinatra.
Di seguito i passi da fare per poter lavorare su Windows con una macchina virtuale Ubuntu:
- Scaricare VirtualBox per windows da qui
- Scaricare l'iso dell'installazione di Ubuntu da qui. Ho scaricato l'ultima versione 11.10 a 32 bit.
- Da VirtualBox click su Nuova. Poi scegliere Linux e Ubuntu e dare un nome alla macchina virtuale (io ho scelto ubuntu-11.10)
- Come memoria ho impostato 1024Mb. Per il resto ho lasciato le impostazioni di default.
- Click su Avvia. Poi click sul simbolo della cartella e ho scelto il file appena scaricato dell'immagine di Ubuntu.
- Per l'installazione di Ubuntu ho lasciato tutti i default tranne il check di scaricare gli update.
- Una volta ravviata la macchina virtuale. Installare le Guest Additions (Menu di VirtualBox Dispositivi -> Installa Guest Additions..).
A me le ha installate automaticamente. Per verificare che siano installate dal menu Visualizza -> Adatta alla dimensione della finestra deve essere abilitato.
Ora possiamo installare Ruby
- Installiamo l'ultima versione (la 1.9.3) usando RVM
- Aprire Terminal e digitare il comando sudo apt-get update
- Poi installiamo curl con il comando: sudo apt-get curl
- Ora eseguiamo il comando: bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer ).
Non riuscirà ad installarlo perché mancano delle dipendenze. L'output del comando ci dirà cosa installare.
- Copiare quindi il comando: apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
- Rilanciare: bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer )
- Ora chiudere il terminale e riaprirlo
- Eseguire: rvm --default use 1.9.3
- se tutto è andato bene scrivendo ruby --version dovrebbe venire fuori che si sta usando la 1.9.3
- Ora siamo pronti per installare sinatra con: gem install sinatra
- Infine installiamo il webserver consigliato dal readme di Sinatra: gem install thin
A questo punto siamo pronti a scrivere la prima applicazione che usa Sinatra. Apriamo Text Editor e creiamo il file myapp.rb con il seguente contenuto:
require 'sinatra'
get '/' do
'Hello world!'
end
Da terminal lanciamo il comando: ruby -rubygems myapp.rb. Partirà il webserver che si metterà in ascolto sulla porta 4567.
Apriamo un browser e verifichiamo che il tutto funziona collegandoci all'url: locahost:4567.
Have fun!
martedì 20 dicembre 2011
Una delle pratiche più invasive di extreme programming oltre al pair programming è il TDD (Test Driven Development) ed è anche la più difficile da padroneggiare.
In questo post cercherò di analizzare i motivi, fornendo alcuni spunti su come superare le difficoltà.
Le cose più difficili da accettare soprattutto se si ha già una certa esperienza è il fare emergenere il design dai test.
Questo implica di non anticipare eventuali feature successive o test successivi e scrivere il codice minimo indispensabile per fare passare il test appena scritto. Nella fase di refactoring poi si toglieranno tutti gli smell introdotti a partire dal più importante che è la duplicazione.
Quindi non basta scrivere i test prima, magari solo per realizzare un'idea di design che ho già deciso, ma significa sorprendersi del risultato ottenuto.
Una grossa difficoltà è capire quali test scrivere all'inizio e quindi occorre partire molto lentamente, facendo piccoli passi tra un test e il successivo in modo da familiarizzare con la tecnica.
Spesso nell'affrontare il design in questa maniera ci si accorge di avere delle carenze nei concetti di OOP oppure nel riconoscere gli smell e quindi è importante studiare molto bene questi argomenti prima di iniziare.
Bisogna accettare anche il fatto che nel percorso dell'apprendimento si possa sbagliare e il dover rifare la stessa cosa più volte fino ad arrivare ad un risultato soddisfacente.
E' possibile che per ottenere una barra verde del test appena codificato si debba scrivere molto codice, questo è un segno che si è fatto un passo troppo lungo.
Una buona soluzione potrebbe essere quella di revertare il codice e riflettere su come fare un passo più piccolo per arrivare al risultato. Ricordiamoci inoltre di integrare dopo ogni refactoring successivo ad una barra verde per rendere l'operazione più veloce ed indolore possibile.
La disciplina è un alleato potente perché, soprattutto all'inizio, la tendenza ad abbandonare la pratica è molto forte, in quanto cosa molta fatica e si ha la sensazione di perdere tempo e di andare lentamente.
Partire da una codebase con molto codice legacy introduce un'ulteriore difficoltà, quindi all'inizio conviene provare ad implementare una feature da zero limitando il più possibile l'uso del codice esistente.
Ricordiamoci infine che il nostro obiettivo è rilsolvere problemi. Un codice di qualità è il mezzo e le feature sono il risultato del nostro lavoro.
Su questo punto consiglio la visione di del video Hammock-driven Development
mercoledì 7 dicembre 2011
Un collega mi ha suggerito una tecnica interessante quando si è in presenza di codice legacy.
Nella nostra codebase abbiamo creato l'astrazione ICommitCommand per modificare i dati delle risorse esterne nei setup dei un test di accettazione in cui utilizziamo
i sistemi reali e non dei fake. Nel caso specifico la scrittura su un indice di Lucene e la modifica di alcuni dati nel db.
interface ICommitCommand
{
void Execute();
}
Inizialmente avevo creato l'oggetto:
class LuceneCommand : ICommitCommand
{
LuceneClient _client;
public LuceneCommand(LuceneClient client)
{
_client = client;
}
public void Execute()
{
_client.Update();
}
}
LuceneCommand è un adapter per l'interfaccia ICommitCommand. Il problema dell'oggetto è che soffre dello smell
Lazy Class (cioè una classe che fa troppo poco). Un modo per risolverlo è quello di fare implementare l'interfaccia direttamente
a LuceneClient.
Purtroppo il metodo Execute non ha molto significato per LuceneClient. La soluzione che abbiamo adottato è quella di farlo implementare in modo esplicito:
class LuceneClient : ICommitCommand
{
...
void ICommitCommand.Execute()
{
_client.Update();
}
...
}
In questo modo se utilizzo direttamente LuceneClient non posso chiamare il metodo Execute, mentre se ne ho un riferimento come ICommitCommand posso chiamare il metodo.
Questa tecnica viene sfruttata anche nel .net framework per l'oggetto Dictionary che implementa il metodo Add(KeyValuePair) privatamente:
var dictionary = new Dictionary<int, string>();
dictionary.Add(new KeyValuePair<int, string>()); // Error
var d = (IDictionary<int, string>) dictionary;
d.Add(new KeyValuePair<int, string>()); // ok
Il vantaggio di questa soluzione è che riduce la complessità del sistema, evitando il proliferare di adapter che non fanno altro che permettere ad una classe di implementare l'interfaccia desiderata.
mercoledì 23 novembre 2011
Il coach del mio team xp di nome Nautilus
ci ha proposto un'interessante esercizio per mettere alla prova la qualità della nostra codebase.
Scrivere il test dello scenario che si vuole sviluppare come lo vorremmo, ignorando l'attuale architettura, misurando il tempo impiegato.
Provare poi a scriverlo usando i nostri oggetti misurando nuovamente il tempo impiegato.
Nel caso specifico la coppia, in quanto sviluppiamo in pair, ha impiegato 2 minuti a scrivere il test per il primo caso e tre
pomodori (circa un ora e mezza) nel secondo caso.
La differenza tra i due tempi indica quanto la nostra codebase è lontana dalla qualità che vorremmo ottenere.
Questa è l'essenza del TDD, cioè usare il test per comunicare come ci aspettiamo di utilizzare i nostri oggetti ed è anche
un indicatore di quale sia il debito tecnico attualmente presente.
Non solo: ci suggerisce anche come rifattorizzare il codice per migliorarne la comunicatività e chiarezza.
lunedì 21 novembre 2011
Recentemente ho letto un libro sulla dieta alimentare in cui c'è scritto che la mente umana è molto efficiente nel creare delle giustificazioni
per non fare le cose che ci fanno bene.
Il meccanismo è descritto bene in questo post che spiega il problema del procrastinare.
Guardando il video
Is Software Evolution really Effective? di Francesco Cirillo all'ultimo agile day, ci si rende conto che evoluzione e design emergente
non hanno molto significato se non si conosce in quale direzione deve avvenire l'evoluzione o cosa deve emergere.
In particolare nello sviluppo software l'evoluzione deve portare ad un costo descrescente col passare del tempo nell'aggiungere feature al sistema.
Se questo non avviene probabilmente nella nostra codebase stanno crescendo dei code monster
(
qui un esempio).
Che cosa porta alla creazione di un code monster?
Secondo me le cause possono essere solo due:
- Mancanza di volontà
- Mancanza di competenze
Nel primo caso rientrano: la pigrizia, la stanchezza, poca disciplina. Mentre nella seconda semplicemente non si è studiato abbastanza.
Un'obiezione che spesso sento è: non abbiamo abbastanza tempo. Ormai è appurato che il modo più produttivo per aggiungere una feature al nostro
sistema è quello di non farsi sconti sulla qualità; quindi la mancanza di tempo è un motivo in più per non allevare code monster.
Sulla competenza suggerisco un semplice esercizio. Scrivete a memoria tutti gli smell che conoscete, poi prendete il
libro del refactoring di Fowler e verificate
se li avete elencati tutti correttamente, in caso contrario studiateli finché non li conoscete a memoria.
Un altro esercizio è: scrivete, sempre a memoria, quali caratteristiche ha un bad design e confrontateli con questo
paper di zio bob.
Concludendo: entrambe le variabili per scrivere del buon codice sono completamente sotto il nostro controllo.
Smettiamo di trovare scuse ed iniziamo a farlo ora senza procrastinare. I colleghi e i clienti ringrazieranno della vostra scelta.
lunedì 28 marzo 2011
Dopo che ci è scappato un errore nell'html del sito su cui stiamo lavorando, abbiamo deciso di scrivere un test
che validi l'html prodotto.
Per raggiungere l'obiettivo utilizziamo l'utility Tidy che si puo' scaricare da
qui
È un piccolo eseguibile a cui si puo' passare un html e verificare che non contenga errori.
Il codice del test è piuttosto semplice:
[Test]
public void ValidateHtml()
{
var html = new WebClient().DownloadString(@"http://someurl.com");
process = new Process();
process.StartInfo.FileName = "tidy.exe";
process.StartInfo.Arguments = "-e";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.Start();
process.StandardInput.Write(html);
process.StandardInput.Close();
process.WaitForExit();
Assert.That(_process.ExitCode, Is.EqualTo(0), _process.StandardError.ReadToEnd());
}
Il processo viene creato redirigendo lo standard input e lo standard error, il primo usato per inviare l'html, mentre il secondo
come messaggio di errore nel caso l'Assert fallisca. L'output di tidy è piuttosto esplicativo e ci permette di correggere velocemente gli errori individuati.
l'opzione -e indica di mostrare solo gli errori, altrimenti tidy puo' anche provare a correggerli, opzione che non utilizziamo.
La documentazione sulle opzioni supportate da tidy la trovate
qui.
lunedì 14 febbraio 2011
Oggi mi sono imbattuto in un problema imprevisto e voglio condividerlo perché potrebbe tornare utile a chi non padroneggia Javascript.
Supponiamo di avere un oggetto definito come segue:
function AnObject() {
this.value = 1000;
var set_value = function(new_value) {
this.value = new_value;
};
this.start = function() {
set_value(5);
}
}
L'oggetto ha un field pubblico chiamato value, un metodo pubblico chiamato start ed un metodo privato chiamato set_value.
Supponiamo ora che vogliamo testarlo ed il test sia il seguente:
function an_object_test()
{
test("an object", function() {
var obj = new AnObject();
obj.start();
equal(obj.value, 5);
});
}
Il test sarà passerà o fallirà? Se lo eseguiamo otterremo una soprendente barra rossa, con il messaggio: Expected 5 but was 1000,
come se il metodo privato non fosse stato eseguito.
Per quale motivo?
Il this nel metodo privato non si riferisce all'oggetto AnObject, piuttosto all'oggetto globale del browser window.
Questo problema è spiegato nel post Private Members in JavaScript
di Douglas Crockford. In particolare la soluzione proposta è:
function AnObject() {
this.value = 1000;
var that = this;
var set_value = function(new_value) {
that.value = new_value;
};
this.start = function() {
set_value(5);
}
}
Il motivo del field that è spiegato nel post:
By convention, we make a private that variable. This is used to make the object available to the private methods.
This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions.
Tenetelo presente quando lavorate in Javascript perché potrebbe riservare spiacevoli sorprese.