mercoledì 18 giugno 2008
A parole tutti vorremmo
cambiare, è un mantra, che spesso si sente in tv soprattutto in periodo elettorale. Sembrerebbe che la natura umana stia a suo agio nel cambiamento.
In realtà le cose funzionano in maniera completamente diversa,
noi per natura, non vogliamo cambiare; il cambiamento ci costa fatica e per istinto cerchiamo di osteggiarlo.
Riportando queste divagazioni al nostro mestiere di sviluppatori, magari con qualche anno di lavoro alle spalle, proviamo a rispondere alla domanda: Fino a che punto siamo disposti a cambiare? E con cambiare intendo:
- Metodo di approccio allo sviluppo software (ad es: passare da Waterfall ad xp? e viceversa?)
- Tecnologia (sviluppo da diversi anni in .NET passerei a java? e viceversa?)
- Sistema Operativo (ho sempre usato windows xp, passerei a Vista e a linux? e viceversa?)
- Altro?
Quale la cosa che non cambierei per nulla al mondo? (a meno di eventi che ci costringano senza alternative).
A questo proposito segnalo l'interessante post sul
cambiamento che aveva segnalato lorenzo in questo
post e che in parte ha ispirato questo post.
Cosa ne pensate?
lunedì 9 giugno 2008
Segnalo il libro Object-Oriented Reengineering Patterns scaricabile gratuitamente come pdf.
Non ne ho ancora terminato la lettura, ma per ora mi ha fornito alcuni spunti interessanti.
Affronta il problema di reingegnerizzare un software esistente e fornisce come strumenti una serie di pattern.
venerdì 6 giugno 2008
Il multithreading aggiunge complessità al design di un'applicazione. Occorre prestare attenzione alle differenti problematiche legate alla sincronizzazione, race condition, ecc.
In questi giorni sto realizzando un'applicazione in cui devo salvare dei messaggi di log su un database e per non rallentare il chiamante la scrittura sul database avviene in modo asincrono.
Voglio realizzare questa funzionalità in TDD e riporto qui i passi che ho effettuato e la soluzione trovata. Per ogni test scriverò prima il codice del test, introducendo classi e metodi necessari a descrivere lo scenario che sto testando, successivamente scriverò l'implementazione minima per far compilare il test ed infine modificherò il codice per farlo passare.
Partiamo dal LogWriter:
public interface ILogWriter
{
void Write(Log log);
}
L'implementazione concreta di questa interfaccia salva sul database i dati contenuti nella classe Log. La classe che realizzerò in TDD deve chiamare in modo asincrono il metodo Write.
Non utilizzerò librerie di mocking e scriverò delle classi stub che simuleranno i vari scenari da testare; questo renderà più semplice la comprensione del codice. Il primo stub che useremo conta quante volte viene chiamato il metodo Write e salva l'ultimo parametro con cui è stato chiamato:
class LogWriterForTest : ILogWriter
{
public int WriteCallCount = 0;
public Log Log = null;
public void Write(Log log)
{
WriteCallCount++;
Log = log;
}
}
Il test controlla che quando viene accodato un log viene chiamato il metodo Write:
[Test]
public void When_Enqueue_A_Log_The_LogWriter_Is_Invoked()
{
Log log = new Log();
LogWriterForTest writer = new LogWriterForTest();
LogThread thread = new LogThread(writer);
thread.Enqueue(log);
Assert.AreEqual(1, writer.WriteCallCount);
Assert.AreEqual(log, writer.Log);
}
Per far passare il test non è necessario che il metodo Enqueue chiami il metodo Write in modo asincrono, questo sarà compito del prossimo test. Questo concetto è importante nel TDD e lo ribadisco: scrivere solo il codice indispensabile a far passare il test anche se non contiene ancora le funzionalità che effettivamente ci servono. Questo ci consente di ridurre al minimo la complessità del codice e dell'architettura e di arrivare alla soluzione in modo incrementale.
Il primo passo da fare ora è quello di scrivere il codice minimo per compilare, ma che non basta per far passare il test:
public class LogThread
{
public LogThread(ILogWriter writer)
{
}
public void Enqueue(Log log)
{
}
}
Se eseguo ora il test ottengo il messaggio: Expected: 1 But was: 0, in quanto il metodo Write non è stato chiamato; aggiungiamo quindi la chiamata:
public class LogThread
{
private readonly ILogWriter _writer;
public LogThread(ILogWriter writer)
{
_writer = writer;
}
public void Enqueue(Log log)
{
_writer.Write(log);
}
}
Rilanciamo il test che passa con successo.
Passiamo al prossimo scenario: quando viene chiamata la Write il metodo Enqueue la esegue in modo asincrono. Ci serve un meccanismo di sincronizzazione che ci permetta di bloccare il metodo write e sbloccarlo successivamente con un comando; creiamo a questo scopo un nuovo stub:
class ManualResetLogWriter : ILogWriter
{
public int WritingCount = 0;
public Log Log = null;
public readonly ManualResetEvent ResetEvent = new ManualResetEvent(false);
public void Write(Log log)
{
WritingCount++;
Log = log;
ResetEvent.WaitOne();
WritingCount--;
}
}
Quando chiamiamo il metodo Write del ManualResetLogWriter l'esecuzione si blocca sull'istruzione ResetEvent.WaitOne() fin quando da un altro thread non viene chiamato il metodo Set della classe del framework .NET ManualResetEvent.
La classe ManualResetLogWriter è utile nel prossimo test, in cui voglio verificare che le chiamate al metodo Write avvengono in modo asincrono. Sarà necessario modificare il metodo Enqueue affinché invochi il metodo write in un theread dedicato, evitando così che l'esecuzione dell'Enqueue si blocchi sull'istruzione WaitOne. Verifichiamolo:
[Test]
public void The_Calls_To_The_Writer_Are_Asynchronous()
{
Log log = new Log();
ManualResetLogWriter writer = new ManualResetLogWriter();
LogThread thread = new LogThread(writer);
thread.Enqueue(log);
writer.ResetEvent.Set();
Assert.AreEqual(1, writer.WriteCallCount);
Assert.AreEqual(log, writer.Log);
}
Se eseguiamo il test con l'implementazione attuale della Enqueue vediamo che il test si blocca sull'istruzione: thread.Enqueue(log). Modifichiamo il codice in modo da far passare questo secondo test:
public class LogThread
{
private readonly ILogWriter _writer;
public LogThread(ILogWriter writer)
{
_writer = writer;
}
public void Enqueue(Log log)
{
Thread thread = new Thread(
delegate()
{
_writer.Write(log);
});
thread.Start();
}
}
Per maggiore compattezza del codice la chiamata _writer.Write(log) è stata fatta all'interno di un anonymous method.
Rieseguiamo il test che ora passa.
Chiaramente non abbiamo ancora finito, perchè due thread in contemporanea potrebbero eseguire la Write creando una Race Condition (o corsa critica in italiano).
Il prossimo test si occuperà proprio di testare che non ci sia una race condition, cioè dobbiamo creare uno scenario in cui il test fallisce nel caso in cui più thread eseguono contemporaneamente il metodo Write. Scriviamo il test e poi commentiamolo un pezzo per volta:
[Test]
public void Two_Logs_Enqueued_Are_Served_One_At_Time()
{
ManualResetLogWriter writer = new ManualResetLogWriter();
LogThread thread = new LogThread(writer);
Log log1 = new Log();
thread.Enqueue(log1);
while (writer.WritingCount < 1)
{
Thread.Sleep(0);
}
Log log2 = new Log();
thread.Enqueue(log2);
while(thread.RunningThreadCount < 2)
{
Thread.Sleep(0);
}
Assert.AreEqual(1, writer.WritingCount);
writer.ResetEvent.Set();
while (writer.WritingCount > 0)
{
Thread.Sleep(0);
}
}
Nel codice del test precedente ho usato più volte la chiamata Thread.Sleep(0), il metodo Sleep interrompe il thread corrente per x millisecondi, se invece viene passato zero si permette al thread corrente di essere sospeso; questo per evitare di bloccare il PC con CPU al 100% nel caso in cui il test si blocchi in uno dei cicli while.
Il primo ciclo while attende che il primo thread sia fermo all'istruzione ResetEvent.WaitOne del ManualResetLogWriter.
Viene successivamente accodato il secondo log (variabile log2); ci serve quindi un meccanismo che mi permetta di verificare che i thread in esecuzione siano effettivamente due. Per questo scopo introduciamo, per ora solo nel codice del test, la property RunningThreadCount ed aspettiamo che raggiunga il valore 2.
Segue la Assert la quale assume che ci sia un solo thread in scrittura mentra l'altro è in attesa (ricordo che lo scopo é quello di far si che il metodo Write venga eseguito in mutua esclusione).
Il test termina sbloccando la Write tramite l'istruzione writer.ResetEvent.Set() ed infine attendiamo che entrambi i thread siano terminati.
Aggiungiamo la property RunningThreadCount alla classe LogThread:
public class LogThread
{
private readonly ILogWriter _writer;
private int _runningThreadCount = 0;
public LogThread(ILogWriter writer)
{
_writer = writer;
}
public int RunningThreadCount { get { return _runningThreadCount; } }
public void Enqueue(Log log)
{
Thread thread = new Thread(
delegate()
{
_runningThreadCount++;
_writer.Write(log);
_runningThreadCount--;
});
thread.Start();
}
}
Eseguendo il test si nota che la assert fallisce in quanto tutte e due i thread eseguono il metodo Write. Dobbiamo ora inserire un meccanismo di sincronizzazione, useremo l'istruzione lock:
public void Enqueue(Log log)
{
Thread thread = new Thread(
delegate()
{
_runningThreadCount++;
lock(_writer)
{
_writer.Write(log);
}
_runningThreadCount--;
});
thread.Start();
}
Ora rilanciamo il test il quale passerà con successo.
A qualcuno potrà sembrare strano che sia stata aggiunta la property RunningThreadCount, in quanto viene usata solo dal test. Questa pratica però è piuttosto comune nel TDD e capita spesso che queste funzionalità, aggiunte solo per poter testare la classe, tornino utili in seguito anche nel codice di produzione.
Se il metodo Write sollevasse un'eccezione la property RunningThreadCount non verrebbe decrementata. Creiamo un nuovo stub il quale sollevi un eccezione:
class ExceptionRaiseLogWriter : ILogWriter
{
public bool IsWriting = false;
public void Write(Log log)
{
IsWriting = true;
throw new ArgumentException();
}
}
}
Ed il test che verifichi che effettivamente venga descrementata la property:
public void When_The_Writer_Throws_An_Exception_The_QueueLength_Is_Decreased()
{
ExceptionRaiseLogWriter writer = new ExceptionRaiseLogWriter();
LogThread thread = new LogThread(writer);
thread.Enqueue(new Log());
while(!writer.IsWriting)
{
Thread.Sleep(0);
}
while(thread.RunningThreadCount > 0)
{
Thread.Sleep(0);
}
}
Proviamo ad eseguirlo: il test si blocca all'interno dell'ultimo while.
Per far passare il test aggiungiamo un try / finally al metodo Enqueue della classe LogThread:
public void Enqueue(Log log)
{
Thread thread = new Thread(
delegate()
{
_runningThreadCount++;
try
{
lock (_writer)
{
_writer.Write(log);
}
}
finally
{
_runningThreadCount--;
}
});
thread.Start();
}
A questo punto anche questo test è a posto.
Mancano ancora altre cose, come la gestione delle eccezioni, ecc. ma credo che il modo di procedere ora sia chiaro.
Un altro test interessante da sviluppare è quello relativo alla gestione delle richieste secondo una politica FIFO mediante una coda, vale a dire che la prima richiesta accodata sia effettivamente la prima ad essere eseguita, mentre nell'implentazione derivata dai test finora scritti l'ordine di scrittura dipende da quale thread viene sbloccato prima e quindi non è deterministico.
Se avete suggerimenti, consigli o segnalazioni di errori scrivetemeli nei commenti.
mercoledì 4 giugno 2008
In questi giorni un mio amico mi ha chiesto un softwarino (chissà perchè quando si chiede un favore si usa il diminutivo
) che partendo da una tabella di Access generasse una serie di Moduli F24 compilati.
Il modulo dell'F24 da compilare si può scaricare dal sito dell' agenzia delle entrate
A questo punto mi manca una libreria, possibilmente open source, per aggiungere le informazioni necessarie.
Dopo un paio di ricerche ho provato iTextSharp porting della libreria Java iText
Riporto una parte del codice che ho scritto per la generazione del pdf:
public class ReportF24
{
BaseFont m_baseFont;
PdfContentByte m_contentByte;
Document m_document;
PdfImportedPage m_templatePage;
public void Open(string templateFileName, string fileName)
{
PdfReader reader = new PdfReader(templateFileName);
Rectangle pageSize = reader.GetPageSize(1);
m_document = new Document(pageSize, 50, 50, 50, 50);
PdfWriter writer = PdfWriter.GetInstance(m_document, new FileStream(fileName, FileMode.Create));
m_document.Open();
m_contentByte = writer.DirectContent;
m_templatePage = writer.GetImportedPage(reader, 1);
m_baseFont = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
}
public void AddF24(ModuloF24 f24)
{
m_document.NewPage();
m_contentByte.AddTemplate(m_templatePage, 0, 0);
m_contentByte.BeginText();
m_contentByte.SetFontAndSize(m_baseFont, 12);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, f24.CodiceFiscale, 125, 720, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, f24.Denominazione, 125, 700, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, f24.Comune, 125, 650, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, f24.Provincia, 330, 650, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, f24.Indirizzo, 370, 650, 0);
float yRigo = 613;
foreach (RigoErario rigoErario in f24.RighiErario)
{
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT,
rigoErario.CodiceTributo, 160, yRigo, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT,
string.Format("{0:0000}", rigoErario.Mese), 230, yRigo, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT,
string.Format("{0:0000}", rigoErario.Anno), 280, yRigo, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_RIGHT,
string.Format("{0:0.00}", rigoErario.Importo), 400, yRigo, 0);
yRigo -= 12;
}
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_RIGHT,
string.Format("{0:0.00}", f24.Totale), 400, 540, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_RIGHT,
string.Format("{0:0.00}", f24.Totale), 575, 540, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_RIGHT,
string.Format("{0:0.00}", f24.Totale), 575, 120, 0);
string giorno = f24.DataPagamento.Day.ToString("00");
string mese = f24.DataPagamento.Month.ToString("00");
string anno = f24.DataPagamento.Year.ToString();
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, giorno, 30, 50, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, mese, 60, 50, 0);
m_contentByte.ShowTextAligned(PdfContentByte.ALIGN_LEFT, anno, 90, 50, 0);
m_contentByte.EndText();
}
public void Close()
{
m_document.Close();
}
}
Il metodo Open Legge il contenuto del pdf del modulo da compilare e crea anche il PdfWriter in cui verranno aggiunte le pagine compilate.
La classe ModuloF24 (qui non riportata) contiene le informazioni necessarie per riempire il modulo.
lunedì 28 aprile 2008
Sto sviluppando un'applicazione che utilizza le Windows Forms e dovrà supportare più lingue. Vorrei condividere la soluzione che sto progettando/implementando.
L'idea è quella di utilizzare la classe
ResourceManager la quale si occupa di leggere
le stringhe in base alla lingua corrente. Vediamolo in pratica:
Per prima cosa creiamo un progetto che chiameremo MultilingualWindowsApplication
ed aggiungiamo due resources file di nome:
- Local.en.resx
- Local.it.resx
Il primo conterrà le stringhe in lingua inglese ed il secondo quelle in italiano,
quindi se un domani dovremmo aggiungere il tedesco basterà creare il file di risorse
(Local.de.resx).
Nella Form1 aggiungiamo un MenuStrip che in base alla lingua del sistema operativo
visualizzerà le varie voci nella lingua corretta:
Ora aggiungiamo una variabile membro alla Form1 di tipo ResourceManager:
public partial class Form1 : Form
{
private ResourceManager _resources;
public Form1()
{
InitializeComponent();
_resources = new ResourceManager("MultilingualWindowsApplication.Local",
Assembly.GetExecutingAssembly());
}
}
Impostiamo la traduzione nelle due lingue utilizzando il resource editor:
Per leggere la stringa nella lingua corretta utilizzeremo il metodo GetString:
private void LocalizeUITexts()
{
fileToolStripMenuItem.Text = _resources.GetString("Menu_File");
apriToolStripMenuItem.Text = _resources.GetString("Menu_Apri");
salvaToolStripMenuItem.Text = _resources.GetString("Menu_Salva");
salvaConNomeToolStripMenuItem.Text = _resources.GetString("Menu_SalvaConNome");
esciToolStripMenuItem.Text = _resources.GetString("Menu_Esci");
modificaToolStripMenuItem.Text = _resources.GetString("Menu_Modifica");
tagliaToolStripMenuItem.Text = _resources.GetString("Menu_Taglia");
copiaToolStripMenuItem.Text = _resources.GetString("Menu_Copia");
incollaToolStripMenuItem.Text = _resources.GetString("Menu_Incolla");
}
Modifichiamo la lingua corrente per vedere se tutto funziona correttamente
anche in inglese:
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
Per evitare di gestire la sincronizzazione dei file di risorse nelle varie lingue
consiglio l'utilizzo di
RESX Synchronizer è una piccola utility che impostando il file di risorse
sorgente es (Local.en.resx) e destinazione crea o cancella tutte le stringhe che
mancano o sono in più.
Un'altra tecnica è quella di far generare il file di risorse dall'IDE. Per far questo
occorre impostare a true la property Localizable della Form, come spiegato in questo
post
delle MSDN.
Non ho usato quell'approccio perchè avrei un file di risorse per ogni form da localizzare,
inoltre se ho la stessa stringa in diverse form devo duplicare la traduzione in
tutte le form, infine le chiavi delle stringhe hanno nomi poco chiari (per chi non
fa lo sviluppatore di mestiere) come:
salvaToolStripMenuItem.Text
Nel mio caso la traduzione nelle varie lingue è fatto da altre persone e quindi
sarebbe complicato anche la sincronizzazione di tutti questi resx a fronte di uno
solo per tutte le form.
mercoledì 16 aprile 2008
Consiglio vivamente di leggere il pdf contenuto in questo post di Karl Seguin
Contiene una serie di concetti che ogni sviluppatore software dovrebbe già conoscere ed applicare, ma sono espressi in maniera sintetica e molto chiara.
Riporto alcuni passi secondo me molto importanti e spesso poco considerati soprattutto da chi ha poca esperienza:
Although simplistic, every programming decision I make is largely based on maintainability. Maintainability is the cornerstone of enterprise development.
The ultimate tool in making your code maintainable is to keep it as simple as possible. A common belief is that in order to be maintainable, a system needs to be engineered upfront to accommodate any possible change request. I've seen systems built on meta-repositories (tables with a Key column and a Value column), or complex XML configurations, that are meant to handle any changes a client might throw at the team. Not only do these systems tend to have serious technical limitation (performance can be orders of magnitude slower), but they almost always fail in what they set out to do (we'll look at this more when we talk about YAGNI). In my experience, the true path to flexibility is to keep a system as simple as possible, so that you, or another developer, can easily read your code, understand it, and make the necessary change
Questo è uno dei punti su cui trovo maggiore resistenza in fase di analisi di un sistema software: è molto diffusa l'idea che un sistema flessibile debba essere complesso per poter prevedere tutte le possibili richieste future del cliente.
Il motivo principale di questa resistenza credo sia dovuto al fatto che il cliente viene visto come un problema e quindi si debba cercare di limitare il più possibile i canali di comunicazione. Quando il cliente chiede una modifica e la nostra architettura è rigida e complessa diventa un problema apportarla e si scarica la propria responsabilità sul cliente.
Gli altri concetti descritti nel pdf sono:
- YAGNI (You Aren't Going to Need)
- Last Responsible Moment
- DRY (Don't Repeat Yourself)
- Explicitness and Cohesion
- Coupling
- Unit Tests and Continuous Integration
Sono tutti concetti legati a minimizzare lo spreco, i quali portano ad un sistema software efficiente e ad un cliente soddisfatto.
mercoledì 12 marzo 2008
Un mio amico esperto di energie alternative (pannelli solari, ecc.) e risparmio energetico ha aperto un nuovo blog R.C.E. consulenze energetiche.
Su questo argomento c'è parecchia confusione e molti presunti esperti che millantano chissà quali miracoli. Lo scopo del blog è fare un pò di chiarezza sull'argomento.
Se lo trovate utile incoraggiatelo in modo che lo aggiorni periodicamente ;-)
mercoledì 27 febbraio 2008
Come già accennato da Simone ed Emanuele ho moderato una breve sessione sulle metriche del codice. Vorrei dare qui qualche coordinata in più a chi interessa il discorso ed in generale l'argomento della qualità del software.
Per la definizione di qualità ci rifacciamo allo standard ISO 9126 (altre informazioni in questo pdf in italiano), brevemente lo standard definisce la qualità di un software secondo un modello a quattro livelli:
Nel primo livello vengono definiti "tre punti di vista" sulla qualità del software:
- In Uso: Esprime l’efficacia ed efficienza con cui il software serve le esigenze dell’utente, ed è correlata alla percezione diretta dell’utente.
- Esterno: Esprime il comportamento dinamico del software, nell’ambiente d’uso.
- Interno: Esprime la misura in cui il codice software possiede una serie di attributi statici, indipendentemente dall’ambiente di utilizzo e dall’utente.
Al secondo livello vengono definiti i principali attributi.
Al terzo livello ci sono le sottocaratteristiche, le quali sono misurabili
Infine vengono definite le metriche per effettuare le misure.
Il tutto può essere riassunto dal seguente schema (primo livello in blu, secondo in verde e terzo in bianco):
Le metriche sul codice, a cui questo post è dedicato, influenzano la qualità interna.
Ogni metrica è definita dai seguenti attributi (maggiori dettagli qui):
- Handle: Un nome breve per identificare la metrica
- Description: Descrizione testuale della metrica e di cosa misura
- Scope: Identifica quale parte del software è coinvolto nella misurazione (es: classe, file, modulo, ecc.)
- Definition: Definizione formale della metrica usando definizioni e formule matematiche
- View: Descrive gli elementi rilevanti, le relazioni semantice e il grammar tree
- Scale: Il tipo di scala (Absolute, Rational, Interval, Ordinal, Nominal)
- Domain
- Highly Related Software Quality Properties
- Related Software Quality Properties
- References
Un esempio di scheda per la metrica LOC (Lines of Code) la potete trovare qui, mentre i link ad altre metriche qui
Come possiamo calcolare le metriche?
Attraverso alcuni software:
Source Monitor: è freeware e fa il parsing del codice sorgente, potrebbe non funzionare se usato con l'ultima versione della sintassi del linguaggio C#
NDepend: fa molto di più, ma nel report che viene generato ci sono alcune metriche interessanti (soprattutto quelle legate alla dipendenza tra i tipi). Attenzione che le metriche sono calcolate sull'IL e non sul codice sorgente.
Visual Studio 2008 infine permette di calcolare alcune metriche ma solo dalla versione Developer in su.
venerdì 25 gennaio 2008
E il tip numero quattro tratto dal libro The Pragmatic Programmer. Continua con la seguente frase:
Fix bad design, wrong decisions, and poor code when you see them
Perchè si parla di Broken Windows? Per capirlo riporto una parte della storia introduttiva legata al tip:
In inner cities, some buildings are beatiful and clean, while others are rotting hulks. Why? Reasearcher in the field of crime and urban decay discovered a fascinating trigger mechanism, one that very quickly turns a clean, intact, inhabitated building into a smashed and abandoned derelict.
A broken window
One broken window, left unrepaired for any substantial length of time, instills in the inhabitants of the building a sense of abandonment - a sense that powers that don't care about the building. People start littering. Graffiti appears. Serious structural damage begins. In a relative short space of time, the building becomes damaged beyond the owner's desire to fix it, and the sense of abandonment becomes reality.
Per il resto della storia vi consiglio, se non l'avete già fatto, di leggere l'ottimo libro.
Ma vediamo qualche caso concreto in cui ho visto situazioni di questo genere:
- Fare commit del codice con uno unit test che non passava. Dopo un pò un altro membro del team si è trovato nella stessa situazione, ma visto che ce n'era già un altro ha fatto anche lui il commit ed in poco tempo la percentuale di test che non passavano è cresciuta notevolmente.
- Un altro caso riguarda i warning di compilazione, fai un commit con un warning perchè in quel momento per pigrizia o urgenza non ti va di metterlo a posto, ad un certo punto un altro membro del team si trova nella stessa situazione e compie lo stesso misfatto. Da qui a decine di warning ignorati il passo è breve.
- Non parliamo poi del design basta che uno rompe l'incapsulamento dei layer e sembra aver aperto la strada per compiere i più efferati accoppiamenti tra classi, un groviglio da cui poi è veramente difficile uscirne.
Potrei continuare, con la Coding Convention, non segnare i bug su un sistema di traking, non scrivere il test prima del codice, ecc., ecc.
Anche a voi è capitata una situazione del genere? Come ne siete usciti? Avete imparato come team la lezione per il futuro?
mercoledì 23 gennaio 2008
Un mio amico non sviluppatore mi ha segnalato questo sito su:
- Design Patterns
- AntiPatterns
- Refactoring
Ogni Pattern ha una scheda con: Intent, Problem, Discussion, Structure, Example, Check list, Rule of Thumb
Inoltre c'è anche un video dove viene presentato un esempio di codice, e degli esempi in C++ e Java, purtroppo manca .NET ma non credo ci siano particolari problemi a leggere codice Java per un programmatore .NET
Per gli AntiPatterns c'è una scheda che mi piace molto, riporto ad esempio Reinvent The Wheel:
- AntiPattern Name: Reinvent the Wheel
- Also Known As: Design in a Vacuum, Greenfield System
- Most Frequent Scale: System
- Refactored Solution Name: Architecture Mining
- Refactored Solution Type: Process
- Root Causes: Pride, Ignorance
- Unbalanced Forces: Management of Change, Technology Transfer
- Anecdotal Evidence: "Our problem is unique." Software developers generally have minimal knowledge of each other's code. Even widely used software packages available in source code rarely have more than one experienced developer for each program.Virtually all systems development is done in isolation of projects and systems with overlapping functionality. Reuse is rare in most software organizations. In a recent study of more than 32 object-oriented software projects, the researchers found virtually no evidence of successful reuse
Dateci un'occhiata a me sembra molto curato e ben fatto.