AntonioGanci

Il blog di Antonio Ganci
posts - 157, comments - 365, trackbacks - 106

martedì 28 aprile 2009

Schema-less Databases

Jacopo mi ha segnalato questo post, in cui viene spiegato come FriendFeed memorizza su un database MySQL dati schema-less.

L'idea è quella di memorizzare una versione serializzata delle nostre entities, nel caso di FriendFeed in formato JSON. La tabella ha la seguente struttura:

CREATE TABLE entities (
    added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    id BINARY(16) NOT NULL,
    updated TIMESTAMP NOT NULL,
    body MEDIUMBLOB,
    UNIQUE KEY (id),
    KEY (updated)
) ENGINE=InnoDB;

La cosa, che trovo, interessante è la motivazione che ha spinto ad adottare tale struttura cito dal post:

As our database has grown, we have tried to iteratively deal with the scaling issues that come with rapid growth. We did the typical things, like using read slaves and memcache to increase read throughput and sharding our database to improve write throughput. However, as we grew, scaling our existing features to accomodate more traffic turned out to be much less of an issue than adding new features.

In particular, making schema changes or adding indexes to a database with more than 10 - 20 million rows completely locks the database for hours at a time. Removing old indexes takes just as much time, and not removing them hurts performance because the database will continue to read and write to those unused blocks on every INSERT, pushing important blocks out of memory.

Ayende ne parla nel post Designing a document database: What next? ed esiste anche un'implementazione open source chiamata CouchDB

Questa idea è un'eventuale alternativa ai classici database relazionali, nel caso in cui ci si trovi in condizioni analoghe.

posted @ martedì 28 aprile 2009 14.39 | Feedback (5) |

sabato 25 aprile 2009

Falso ottimismo e falso pessimismo nelle stime

Andamento stime nel tempo

Ho notato, empiricamente, che l'errore di stima delle user story, iterazione per iterazione, oscilla alternativamente tra sottostime e sovrastime.

Nella prima iterazione del progetto si è in uno stato d'animo ottimista, dovuto alla novità del progetto che porta a sottovalutare la complessità. Si scrivono quindi user story troppo grandi, di durata superiore all'iterazione stessa.

Durante la prima iterazione ci si accorge di tale leggerezza, e si iniziano a dividere le user story in storie più piccole e a stimarle. In questa fase, memori dell'insuccesso dell'ultima stima. si sovrastimano le nuove storie.

Nella seconda iterazione si ha così una velocità molto più elevata che porta ad una nuova ristima ottimistica.

Quali sono le cause di questo comportamento?

Ho l'impressione che siano legate al voler fare una buona impressione al cliente, come se, una stima troppo lunga fosse dovuta alla lentezza del team, più che alla complessità del problema.

Ovviamente il cliente ha tutto l'interesse che il tempo di sviluppo sia il più breve possibile, perchè per lui il tempo di sviluppo è un costo da sostenere e quindi, magari incosciamente, tenderà a premiare le stime brevi.

Un modo per ovviare a tale inconveniete, potrebbe essere di far partecipare alle stime, una persona esterna che non ha interessi economici nel progetto e che, aiuti il team, soprattutto nella fase iniziale, a non sottovalutare la complessità del problema.

posted @ sabato 25 aprile 2009 10.07 | Feedback (1) |

giovedì 26 marzo 2009

Alcune considerazioni sulle retrospettive di team

starfish retrospective

È da un pó di tempo che partecipo alle retrospettive (se volete qualche informazione in piú su questa pratica date un'occhiata a queste slide) e del nostro team. Vorrei condividere alcune cose che ho appreso.

È importante che tutti si sentano liberi di esprimere al loro opinione. Se si ha timore di esporre francamente il proprio pensiere allora conviene evitare la retrospettiva, perchè risulterebbe una perdita di tempo.

La retrospettiva dovrebbe essere timeboxed della durata di circa un'ora, in modo da non gravare eccessivamente sull'orario di lavoro e non divagare in discussioni dispersive.

Ho trovato utile variare la dinamica, ad esempio tra:

  • Glad, Mad, Sad
  • Start doing, Stop Doing, Keep Doing
  • Starfish

È stato efficace farla condurre da un esterno che conoscesse la pratica, ma non il team, in quanto siamo arrivati piú velocemente e precisamente al punto ed alle azioni.

Se non si tiene traccia delle azioni si rischia di dimenticarsene, quindi puó essere utile nominare dei responsabili per ogni azione e verificare alla prossima retrospettiva i risultati raggiunti. In quest'ultimo punto nel nostro team siamo ancora deboli e qualche decisione presa nella retrospettiva si è purtroppo dimenticata.

Consiglio a qualunque team, indipendentemente dalla metodologia di lavoro seguita, di iniziare ad organizzarla periodicamente (una ogni due settimane), se svolta in maniera responsabile porterà miglioramenti nella produttività e nei rapporti tra i membri del team.

posted @ giovedì 26 marzo 2009 17.55 | Feedback (2) |

venerdì 27 febbraio 2009

L'importanza della scelta del primo test da scrivere usando il TDD

Il nostro processo di valutazione di un candidato, per essere assunto nel team agile in azienda, comprende una mezz'ora di pair programming (il cosidetto pomodoro).

E' una pratica molto utile, che consiglio vivamente, e permette di capire molto più a fondo le reali capacità di uno sviluppatore. Anche perchè alla fine della fiera, scriverà codice e quindi perchè non valutare come lo scrive?

Riporto qui la dinamica di una sessione di pair la quale ha fatto emergere un aspetto importate, ma spesso trascurato del TDD. Abbiamo scelto come esercizio da sviluppare in TDD Back to the CheckOut.

Ovviamente in un pomodoro è molto difficile da terminare e non è lo scopo del pair. E' molto più utile capire l'approccio seguito e i test che vengono scritti.

Brevemente: l'esercizio consiste nel calcolare il totale di prodotti scannerizzati con il codice a barre. Ogni prodotto ha un suo prezzo e può avere offerte speciali (tipo se ne compri tre 100$ piuttosto di 120$)

Un esempio di listino prezzi:

  • Prodotto A 50$. Offerta: 3 A per 120
  • Prodotto B 30$. Nessuna offerta

La seguente sequenza: A, A, B, A produce il totale di 150.

Al candidato ho chiesto di scrivere il test più semplice che gli venisse in mente ed il risultato è il seguente:

@Test
public void shouldScanAnItem() {
CheckOut checkOut = new CheckOut();
Item item = new Item("A");
Catalogue catalogue = new Catalogue();
catalogue.add(10, item);

checkOut.scan(item);

assertEquals(1, checkOut.scannedItems());
assertEquals(10, checkOut.total());
}

Il codice è scritto in Java, ma è facilmente comprensibile anche a chi conosce altri linguaggi.

Nel test c'è un errore evidente su cui ritornerò dopo e il candidato non ha visto. Allora gli ho chiesto di scrivere il minor codice possibile affinchè il test passasse ed il risultato è stato:

public class CheckOut {
public int total() {
return 10;
}

public int scannedItems() {
return 1;
}

public void scan(Item item) {
}
}

Il test passa, nonostante l'errore e la discutibile scelta di design del metodo, secondo me non necessario, scannedItems.

Per chi non l'avesse notato la classe Catalogue non è usata.

Tutto questo per dire che se il candidato avesse effettivamente scritto il test più semplice questo errore sarebbe venuto fuori.

Vediamo il primo test che avrei scritto:

@Test
public void theTotalIsZeroWhenNoItemsScanned() throws Exception {
CheckOut checkOut = new CheckOut();
assertEquals(0, checkOut.scannedItems());
assertEquals(0, checkOut.total());
}

A questo punto l'implementazione con il totale fisso dopo la scrittura del secondo test non sarebbe stata sufficiente.

La conclusione di questo post è per dire di non sottovalutare l'importanza del primo test, anche se appare banale, perchè nel design incrementale evita errori che farebbero perdere molto più tempo della scrittura di un test lungo tre righe.

posted @ venerdì 27 febbraio 2009 9.24 | Feedback (4) | Filed Under [ Extreme Programming ]

domenica 18 gennaio 2009

Un post sul pensiero mainstream

Consiglio la lettura del post: Ancora sul pensiero mainstream, è un articolo che fa riflettere ed approfondisce l'argomento.

Ne riporto, le conclusioni per chi non avesse voglia di leggerlo tutto:

Questo e’ un breve riassunto del pensiero di massa, o pensiero mainstream, nei suoi tratti caratteristici:

  • l’irreggimentazione bipolare
  • il dominio della polemica
  • l’illusione di competenza
  • il mito del critico
  • l’improduttivita’ per definizione
  • il sogno irrealizzato.

posted @ domenica 18 gennaio 2009 18.56 | Feedback (0) |

sabato 17 gennaio 2009

Fishbowl conversation - Una tecnica per moderare una discussione tra molte persone

Fishbowl conversation - Una tecnica per moderare una discussione tra molte persone

Questa settimana allo xpug di Milano, che si è tenunto in Sourcense, eravamo in più di venti persone e dovevamo discutere sull' architettura esagonale.

Matteo, il nostro coach, ci ha proposta la Fishbowl conversation, che si è rivelata una tecnica semplice ed efficace.

Si prendono cinque sedie e le si mettono al centro della stanza. Quando ci sono quattro persone sedute, solo quelle quattro, possono parlare, in tutti gli altri casi nessuno può parlare.

Come avrete notato una sedia rimane vuota. In questo modo se uno vuole intervenire alla discussione basta che si sieda e, se si vuole continuare a parlare qualcuno si deve alzare. Mentre, se uno dei quattro che stanno parlando si alza, la discussione va interrotta fin quando qualcuno non si siede.

Se tutti rispettano le regole non è nemmeno necessario un moderatore.

posted @ sabato 17 gennaio 2009 15.19 | Feedback (1) | Filed Under [ Tips ]

domenica 11 gennaio 2009

Configurare una macchina a stati usando una fluent interface

Devo implementare la seguente macchina a stati:

Trade state machine
L'immagine è stata realizzata tramite Graphviz

In pratica è una macchina a stati (semplificata) per immettere un ordine di acquisto e successivamente di vendita, per una serie di azioni, con lo scopo di ricavarne un utile.

Ad esempio supponiamo che vogliamo comprare 1.000 azioni dell'ENI al prezzo di 20 euro e rivenderle, sulla base di una nostra analisi, a 20,5 euro ricavandone un gain di 0,5 euro per azione. Il trade in questo caso avrà un ordine di acquisto (entry order) di 1.000 ENI a 20 euro (stato Entry: NotSubmitted) quando l'ordine di acquisto delle azioni, verrà ricevuto dalla banca (broker nel dominio) allora ci sarà il passaggio di stato a (Entry: Submitted) e così via.

Ho provato ad usare un approccio decisamente top down. Mi sono chiesto: Come vorrei che fosse il codice per configurare la macchina a stati? Ho, successivamente, cercato di implementarlo.

Ecco il risultato:

        private static TradeStateMachine CreateTradeStateMachine(Broker broker)
        {
            return new TradeStateMachineConfigurator(new TradeStateMachine())
                .State("EntryNotSubmitted")
                    .Action((trade) => broker.PlaceOrder(trade))
                        .OnSuccessGoTo("EntrySubmitted")
                        .OnFinecoExceptionGoTo("EntryError")
                .State("EntrySubmitted")
                    .Action((trade) => broker.IsExpired(trade))
                        .OnReturnValue_GoTo(true, "EntryNotExecuted")
                    .Action((trade) => broker.GetOrderStatus(trade))
                        .OnReturnValue_GoTo(OrderStatus.Submitted, "EntrySubmitted")
                        .OnReturnValue_GoTo(OrderStatus.Executed, "ExitNotSubmitted")
                        .OnFinecoExceptionGoTo("EntryError")
                .State("ExitNotSubmitted")
                .StateMachine();
        }

Come linea guida ho preferito avere il minor rumore possibile, ad esempio chiamando il metodo per aggiungere uno stato State anzichè AddState. Se avessi usato il verbo Add tutti i metodi si sarebbero chiamati Add, un'informazione secondo me inutile.

I metodi che vengono chiamati si riferiscono sempre all'ultimo oggetto creato, ad esempio:

                    .Action((trade) => broker.PlaceOrder(trade))

Si riferisce allo stato  EntryNotSubmitted, mentre:

                        .OnFinecoExceptionGoTo("EntryError")

si riferisce alla freccia che nella figura va da Entry: not submitted a Entry: error. I tab servono per rendere evidente in modo visivo questo concetto.

Se guardiamo le Action per lo stato EntrySubmitted vediamo che sono due diverse.
Qui sorge un problema: Quando eseguire la seconda azione definita?
Ho scelto di farlo quando l'esecuzione del codice della prima Action restituisce un risultato non gestito. In questo caso false.

L'ultima chiamata StateMachine() restituisce la state machine, passata nel construttore, pronta per essere eseguita.

Vediamo come implementare il codice per la classe TradeStateMachineConfigurator:

    class TradeStateMachineConfigurator
    {
        private readonly TradeStateMachine m_stateMachine;
 
        public TradeStateMachineConfigurator(TradeStateMachine stateMachine)
        {
            m_stateMachine = stateMachine;
        }
        public TradeStateMachineConfigurator State(string stateName)
        {
            m_stateMachine.Add(new TradeState(stateName));
            return this;
        }
        public TradeStateMachineConfigurator Action(Func<Trade, object> action)
        {
            m_stateMachine.CurrentState.Action(action);
            return this;
        }
        public TradeStateMachineConfigurator OnSuccessGoTo(string stateName)
        {
            m_stateMachine.CurrentState.CurrentAction.OnSuccessGoTo(stateName);
            return this;
        }
        public TradeStateMachineConfigurator OnFinecoExceptionGoTo(string stateName)
        {
            m_stateMachine.CurrentState.CurrentAction.OnFinecoExceptionGoTo(stateName);
            return this;
        }
        public TradeStateMachineConfigurator OnReturnValue_GoTo(object value, string stateName)
        {
            m_stateMachine.CurrentState.CurrentAction.OnReturnValue_GoTo(value, stateName);
            return this;
        }
        public TradeStateMachine StateMachine()
        {
            return m_stateMachine;
        }
    }

Tutti i metodi ritornano la stessa istanza di TradeStateMachineConfigurator, questo permette di effettuare chiamate multiple senza usare una variabile locale.

La property CurrentState restituisce l'ultimo stato aggiunto, in modo che le chiamate successive a State si riferiscano all'ultimo stato aggiunto. Discorso analogo per la property CurrentAction.

Le action sono delle lambda expression; nel nostro caso funzioni che hanno come parametro di input il Trade e un valore di ritorno.

L'implementazione della state machine è piuttosto semplice:

    class TradeStateMachine
    {
        private readonly List<TradeState> m_states = new List<TradeState>();
 
        public TradeState CurrentState { get { return m_states[m_states.Count - 1]; } }
 
        public void Add(TradeState tradeState) { m_states.Add(tradeState); }
        public void Start(Trade trade)
        {
            string nextState = "";
            TradeState current = m_states[0];
            do
            {
                nextState = current.Execute(trade);
                current = GetState(nextState);
            }
            while (!string.IsNullOrEmpty(nextState));
        }
        private TradeState GetState(IEquatable<string> stateName)
        {
            return m_states.Find((state) => stateName.Equals(state.Name));
        }
    }

L'esecuzione della macchina a stati termina quando il metodo Execute non ritorna il nome del prossimo stato.

Il TradeState è una collection di Action:

    class TradeState
    {
        private readonly string m_stateName;
        private readonly List<TradeStateAction> m_actions = new List<TradeStateAction>();
 
        public TradeState(string stateName) { m_stateName = stateName; }
 
        public TradeStateAction CurrentAction { get { return m_actions[m_actions.Count - 1]; } }
        public string Name { get { return m_stateName; } }
 
        public void AddAction(Func<Trade, object> action)
        {
            m_actions.Add(new TradeStateAction(action));
        }
        public string Execute(Trade trade)
        {
            foreach (TradeStateAction action in m_actions)
            {
                string stateName = action.Execute(trade);
                if (!string.IsNullOrEmpty(stateName))
                {
                    return stateName;
                }
            }
            return "";
        }
    }

Smette di eseguire le action nel momento in cui una di queste ritorna il nome del prossimo stato oppure ritorna la stringa vuota in questo caso cessa l'esecuzione della macchina a stati (stato finale).

Infine la classe TradeStateAction:

    class TradeStateAction
    {
        private readonly Func<Trade, object> m_action;
        private string m_finecoExceptionStateName;
        private readonly Dictionary<object, string> m_states = new Dictionary<object, string>();
        private string m_successStateName;
 
        public TradeStateAction(Func<Trade, object> action) { m_action = action; }
 
        public void OnFinecoExceptionGoTo(string stateName) { m_finecoExceptionStateName = stateName; }
        public void OnReturnValue_GoTo(object status, string stateName) { m_states.Add(status, stateName); }
        public void OnSuccessGoTo(string stateName) { m_successStateName = stateName; }
        public string Execute(Trade trade)
        {
            try
            {
                object returnValue = m_action.Invoke(trade);
                if (!string.IsNullOrEmpty(m_successStateName))
                {
                    return m_successStateName;
                }
                if (m_states.ContainsKey(returnValue))
                {
                    return m_states[returnValue];
                }
                return "";
            }
            catch (FinecoException)
            {
                if (string.IsNullOrEmpty(m_finecoExceptionStateName))
                {
                    throw;
                }
                return m_finecoExceptionStateName;
            }
        }
    }

Il metodo Execute ritorna il nome dello stato in base alle condizioni specificate. Viene data priorità nell'ordine a: OnFinecoException, OnSuccess, OnReturnValue.

Il codice l'ho scritto al volo per il post quindi potrebbe esserci qualche errore o mancanza, quello su cui vorrei porre l'attenzione è la leggibilità del codice di configurazione della state machine.

Lo trovate leggibile? Suggerimenti per migliorarlo? Il design rivela le intenzioni?

posted @ domenica 11 gennaio 2009 14.27 | Feedback (5) |

lunedì 17 novembre 2008

Il codice è il design

Ringrazio il mio collega per avermi suggerito i tre essays su Cose as Design di Jack W. Reeves. mi hanno dato alcuni spunti di riflessione e ne consiglio vivamente la lettura.

L'autore sostiene ed argomenta un concetto secondo me tanto semplice quanto vero: Il design è il codice. L'articolo è stato pubblicato nel 1992 sul C++ Journal, un periodo in cui era diffusa l'idea che il coding fosse un'attività di basso profilo. Un'idea passata quasi inosservata e poi ripresa da Ward Cunningham in questa pagina del wiki.

Alcune considerazioni:

Se il codice è il design, qual è il manufatto e chi lo costruisce?

E' il file eseguibile e viene costruito dal compilatore/linker (notare l'uso del termine build anche in VS)

I class diagram, UML, ecc. cosa sono?

Sono sempre strumenti di design, ma che danno una rappresentazione di più alto livello.

Ed il TDD come si inserisce in questa visione?

Il TDD serve a validare il design, cioè che il codice si comporti nel modo previsto.

In sostanza il codice è il design più dettagliato possibile quello che rende possibile la creazione fisica del nostro software. Questo spiega anche il perchè sia così difficile creare codice di qualità, non è un'attività di manovalanza, ma un'attività ingegneristica.

posted @ lunedì 17 novembre 2008 8.53 | Feedback (6) | Filed Under [ Extreme Programming ]

martedì 21 ottobre 2008

Guida galattica per la lettura degli annunci immobiliari di Milano

Chi ha dovuto cercare un appartamento in affitto a Milano sa di cosa sto parlando, per gli altri, sembreranno un cumulo di stronzate.

Sostituire le parole in grassetto che trovate in un annuncio con il corrispettivo a destra:

Centralissimo: probabilmente in una cabina telefonica c'è più spazio
Luminossisimo:  Sesto piano senza ascensore.
Ideale per studenti: l'arredamento è stato acquistato in una discarica ed una persona sola non se lo può permettere
Ideale per coppie: Una persona sola non se lo può permettere e il padrone non vuole studenti.
Zona XXX (qualunque nome): Come minimo è a Gessate.
Loft: negozio di macelleria trasformato in apparamento.
Solo referenziati: non vogliamo extracomunitari
Palazzo signorile: è stato costruito ai tempi di Napoleone e non è mai stato ristrutturato da allora.
Appena ristrutturato: hanno dato il bianco.
Come nuovo: vedi sopra
Con posto auto: vedi Zona XXX.
Comodo ai mezzi: l'aereo non vale.

Per l'acquisto:
Ottimo per investimento:
probabilmente demoliranno l'edificio tra sei mesi.

Se ne avete altri aggiungeteli ai commenti che aggiornerò il glossario.

posted @ martedì 21 ottobre 2008 19.21 | Feedback (4) | Filed Under [ Tips ]

Il refactoring che non vedrete mai in un libro.

Aprite visual studio (o l'ide che preferite; si anche notepad o TextEdit vanno bene).
Caricate la parte di codice di cui andate più orgogliosi, su cui avete riflettuto per settimane fino ad ottenere la più perfetta sinfonia di design che la vostra mente potesse concepire.
Respirate.
Cancellate.
Riscrivete lo stesso codice in un altro modo.
I vostri colleghi ringrazieranno.

Fonte: Throwing away code

posted @ martedì 21 ottobre 2008 18.54 | Feedback (3) | Filed Under [ Extreme Programming ]

Powered by: