quinta-feira, 25 de dezembro de 2008

C++: Quando usar cada Smart Pointer

auto_ptr, shared_ptr, scoped_ptr, … Qual utilizar ? E, principalmente, por quê ?

Primeiramente, vamos recordar o que é um Smart Pointer e quais são os recursos que geralmente utilizamos dele. Após isto, ficará mais fácil identificar qual deles usar.

Um Smart Pointer (SP) é uma classe template que guarda um ponteiro para um objeto que é dinamicamente alocado ou, sendo mais explícito, alocado usando new. Quando a instância da classe template é destruída, ela destrói o objeto para o qual aponta. Assim, o objeto apontado é destruído sem que o programador tenha que fazê-lo explicitamente, chamando delete.

Seu uso, logicamente, não fica restrito à esta função. Um Smart Pointer permite se passar pelo ponteiro para o objeto original, através da declaração dos operadores -> e *.

Veja um exemplo:

template< class T >
class SmartPointerSimples
{
public:
SmartPointerSimples(T *ptr = NULL) : _ptr( ptr ) {};
~SmartPointerSimples() { delete _ptr; };
T* operator ->() const { return _ptr; };
T& operator *() const throw( exception ) {
if ( _ptr != NULL ) return *_ptr;
else throw exception( "Ei, o ponteiro é nulo!" );
};
private:
SmartPointerSimples();
SmartPointerSimples(const SmartPointerSimples &);
SmartPointerSimples& operator =(
const SmartPointerSimples &
);

T *_ptr;
};



Suponha que haja uma classe Autor e que seja necessário criar um ponteiro para a mesma:



class Autor
{
public:
Autor() : _nome( "" ) {};
const string& Nome() const { return _nome; };
void DefinirNome(const string &nome)
{ _nome = nome; };
private:
// ...(outros construtores e operadores)
string _nome;
};



Assim, faríamos:



    SmartPointerSimples< Autor > autor( new Autor() );
autor->DefinirNome( "Thiago" );
cout << "Autor: " << autor->Nome() << endl;



Repare que o objeto autor é utilizado da mesma forma como se ele fosse um ponteiro comum. Como visto, isto se deve ao fato de implementarmos o operador –> (veja declaração acima).


Quando o SP autor é destruído, ele destrói a instância de Autor para o qual ele aponta (veja o destrutor de SmartPointerSimples).


Este é o comportamento mais básico de um SP. Ele funciona como um ponteiro, mas libera o programador da preocupação de gerenciar memória, fazendo uma espécie de “coleta automática de lixo”.


Transferência de Posse


E se quisermos passar o objeto apontado pelo Smart Pointer para outro Smart Pointer ou para um ponteiro ?


Para implementarmos a transferência de posse na classe SmartPointerSimples, poderíamos passar o construtor de cópia e o operador de atribuição para a parte pública da classe, implementá-los, acrescentar um método reset para redefinir o valor do ponteiro e mais um método release, para que a classe também pudesse se desfazer do objeto apontado, passando seu controle para outro objeto.


Veja alguns exemplos de como seria sua utilização:


// Transferência através do operador de atribuição
SmartPointerSimples< Autor > autorA( new Autor() );
SmartPointerSimples< Autor > autorB;
autorB = autorA;

// Transferência através de construtor de cópia
SmartPointerSimples< Autor > autorA( new Autor() );
SmartPointerSimples< Autor > autorB( autorA );

// Transferência através do método release...
SmartPointerSimples< Autor > autorA( new Autor() );
SmartPointerSimples< Autor > autorB;
// ...de smart pointer para smart pointer
autorB.reset( autorA.release() );
// ...ou para um ponteiro simples
Autor *pAutor = autorB.release();

A transferência de posse é útil em muitos casos, como quando desejamos passar a responsabilidade de destruição de um objeto para outro, explicitamente. Nestes casos, é altamente recomendado o uso de um SP justamente para indicar que a responsabilidade sobre o objeto foi passada adiante.


Posse Compartilhada


Quando o objeto apontado pelo SP é referenciado por diversos outros objetos em memória, o SP não deve destruí-lo. Isto porque estes outros objetos poderão tentar acessar este objeto posteriormente, o que provavelmente acarretará num erro.


Para impedir a destruição do objeto enquanto outros estiverem fazendo referência ao mesmo, o SP pode controlar seu acesso fazendo uma contagem de uso. Um contador é incrementado cada vez que o objeto é atribuído ou copiado através de seu construtor de cópia e decrementado quando se tenta destruí-lo ou se atribui outro objeto. Esta contagem de referências garante que o delete será chamado apenas quando o contador estiver em zero, indicando que não há mais nenhum outro objeto fazendo referência ao mesmo. Este é o lado mais “smart” de um Smart Pointer.


Transferência de Posse Vs. Posse Compartilhada


Smart Pointers que possuem Transferência de Posse não têm Posse Compartilhada (não têm contagem de referências). Ter Transferência de Posse implica em não poderem ser utilizados em containers padrão: vector, list, deque, etc. Isto porque os containers usam o construtor de cópia ou o operador de atribuição quando você armazena ou acessa algum elemento, respectivamente. Lembrando que, ao usá-los, você transfere a posse.


Desta forma, só use em containers os Smart Pointers que não tiverem Transferência de Posse. A menos que você saiba muito bem o que está fazendo…


Arrays


O uso de arrays é um caso à parte.  A destruição de um array implica em uma chamada a delete diferenciada (com [])


Autor autores[] = new Autor[ 10 ];
...
delete [] autores;

que garante a chamada do destrutor em cada objeto no array.


Assim, Smart Pointers não especializados não executam esse tipo de chamada e por isso não são capazes gerenciar bem arrays.


Analisando alguns Smart Pointers


scoped_ptr

Muitas vezes quando será desejado usar um SP não será preciso nem a transferência de posse nem a contagem de referências. Para estes usos mais simples, quando só queremos nos livrar da dor de cabeça de ter ponteiros não desalocados, podemos utilizar scoped_ptr.


O scoped_ptr é parte do TR1, fazendo-o ficar sob o namespace std::tr1. Após sair o novo padrão do C++ (09) voltará ao namespace std. Atualmente, existe uma implementação na biblioteca boost. Veja seu código aqui.


O scoped_ptr:



  • não é copiável

  • não possui transferência de posse

  • não possui posse compartilhada (contagem de referências) 

  • não gerencia bem arrays. Use scoped_array (abaixo) para isto.


scoped_array

É a versão de scoped_ptr especializada para gerenciamento de arrays. Veja seu código aqui.


auto_ptr

É o tipo de SP mais usado, haja vista ser parte do padrão vigente (C++98), estando disponível em qualquer compilador C++. Fica sob o namespace std. Veja sua documentação aqui.



  • possui todas as funcionalidades do scoped_ptr

  • possui transferência de posse (logo, não tem posse compartilhada)

  • não possui suporte a múltiplas threads

  • não gerencia bem arrays


shared_ptr

É parte da TR1 (std::tr1) e da boost. Veja seu código aqui.



  • possui todas as funcionalidades de auto_ptr

  • possui posse compartilhada (contagem de referências)

  • suporta múltiplas threads

  • não gerencia bem arrays. Use shared_array (abaixo) para isto.


shared_array

É a versão de shared_ptr especializada para o gerenciamento de arrays. Veja seu código aqui.


Outros


Separei o weak_ptr e o intrusive_ptr dos demais pois o uso deles ficará restrito à alguns casos. Vejamos:


weak_ptr

Em casos onde há referência cíclica entre ponteiros, eles não são desalocados automaticamente, pois essa relação de dependência é difícil de detectar.


Vamos supor a seguinte situação:


class Pai;
class Filho;

class Pai
{
public:
shared_ptr< Filho > filho;
};

class Filho
{
public:
shared_ptr< Pai > pai;
};

void FazAlgo()
{
shared_ptr< Pai > oPai( new Pai );
shared_ptr< Filho > oFilho( new Filho );

// Esta' feita a referencia circular:
oPai->filho = oFilho;
oFilho->pai = oPai;

...

// Ops! Chegando ao fim do bloco nenhum
// deles sera' desalocado!
}

O código acima irá causar problemas porque a contagem de referências de ambos continuará em 1. Os objetos não irão detectar a referência cruzada, logo, não irão decrementar o contador para que chegue a zero e o objeto apontado possa ser deletado. Este é um caso onde se deve usar weak_ptr.


O weak_ptr pode trabalhar em conjunto com shared_ptr para resolver problemas relacionados à posse e referências circulares. Diferentemente dos SP tradicionais, ele não destrói o objeto para o qual aponta quando seu destrutor é executado. E, principalmente, ele não é levado em consideração pelos shared_ptrs quanto à contagem de referências, ou seja, o shared_ptr irá deletar o objeto para o qual aponta mesmo que hajam weak_ptrs apontando para o mesmo.


Assim, para resolver o problema anterior, por exemplo, poderíamos substituir um dos shared_ptr (o da classe Filho por exemplo) por um weak_ptr. Como ele não seria levado em consideração na contagem de referências pelo shared_ptr, o contador iria chegar a zero e o objeto apontado seria destruído.


O weak_ptr:



  • não destrói o objeto para o qual aponta

  • não influencia na contagem de referências de um shared_ptr

  • não possui transferência de posse

  • possui posse compartilhada (contagem de referências)

  • trabalha junto à shared_ptr para resolver problemas de referência cíclica

  • não possui suporte à múltiplas threads


Veja seu código aqui.


intrusive_ptr

Quando objetos já possuem sua própria contagem de referências, é recomendado o uso de intrusive_ptr. O “intrusive” dele se refere a ele esperar que o objeto para o qual aponta implemente determinados métodos (ou funções amigas) para a contagem de referências:


// Incrementa a contagem
void intrusive_ptr_add_ref(T *p);
// Decrementa a contagem
void intrusive_ptr_release(T *p);

Estes métodos podem ser implementados na classe fazendo uma chamada para seus métodos reais de contagem de referências. Por exemplo, imagine que a classe Autor vista anteriormente possua contagem de referências e os métodos privados IncRef() e DecRef() que respectivamente incrementam e decrementam seu contador.


A implementação dos métodos para o intrusive_ptr poderiam ser simplesmente a chamada destes métodos:


inline void intrusive_ptr_add_ref(Autor *a)
{
a->IncRef();
}

inline void intrusive_ptr_release(Autor *a)
{
a->DecRef();
}

O intrusive_ptr:



  • funciona como um shared_ptr

  • usa a contagem de referências do objeto ao qual aponta ao invés de ter sua própria


Veja seu código aqui. Um uso comum de intrusive_ptr é em objetos COM. Neste modelo, os objetos possuem sua própria contagem de referências, que devem implementar ao herdar da interface IUnknown, através dos métodos AddRef() e Release().


Resumo


Vimos que cada Smart Pointer tem uma finalidade e que seu uso vai depender da necessidade. Para ajudar na decisão de qual utilizar, segue abaixo um pequeno diagrama:


SmartPointerAssinado


Considerações


Espero ter auxiliado a desmistificar um pouco o uso dos vários SP disponíveis. A introdução dos mesmos na C++0x irá abrir um novo leque de opções que auxiliará na resolução dos diversos problemas relacionados ao gerenciamento de memória. É certo que estas classes possam deixar algumas lacunas, precisando da intervenção do programador para a resolução de determinados problemas, mas para a maioria dos casos, as implementações propostas atendem plenamente.

sábado, 13 de dezembro de 2008

Google Mock

A Google liberou seu C++ Mock Framework sob a nova licensa BSD, complementando seu Google C++ Testing Framework (ou somente Google Test). Ao que me parece, eles fizeram um trabalho bem completo, deixando o framework flexível e fácil de usar.

O Google Mock é baseado no EasyMock, no jMock e no Hamcrest, sendo construído para C++ sob o Google Test. Para utilizá-lo você precisará, além do Google Test que já vem incluso, da std::tr1::tuple, encontrada em algumas implementações de novos compiladores e na boost. Por enquanto ele está acoplado ao Google Test, mas há planos de retirar esta dependência.

Já vinha utilizando o MockPP e vendo o Google Mock tenho a impressão de seu uso ser um pouco mais simples. Assim que tiver um tempo (coisa rara), vou analisá-lo melhor e fazer uma breve comparação, talvez postando-a aqui no blog.

Mais informações sobre o Google Mock aqui.

sábado, 6 de dezembro de 2008

Funcionamento dos frameworks xUnit - Parte 1/2

Durante a série de artigos sobre TDD, tenho citado o uso de frameworks de testes unitários baseados na JUnit. Os exemplos que serão exibidos durante a série partem do princípio que o leitor conhece o funcionamento de um framework xUnit.
Sendo assim, coloco aqui uma explicação breve sobre a estrutura da maioria destes frameworks, de forma a facilitar o entendimento dos próximos artigos.


Os chamados frameworks xUnit surgiram do framework SUnit, construído para a linguagem Smalltalk, por Kent Beck e Erich Gamma. A partir daí, o framework foi portado para diversas linguagens (é praticamente certa a existência de um framework para a sua linguagem favorita) e sua adoção cresceu exponencialmente, sobretudo por projetos que adotaram TDD (como a maioria dos projetos XP).

Idéia

A idéia do framework é facilitar a criação, agrupamento e execução de testes unitários, permitindo sua automatização e a aplicação de seus conceitos. Como seu uso, podemos executar, de uma só vez, os seguintes testes:
  • Teste unitário: verifica se cada pequena parte do programa funciona corretamente.
  • Teste de integração: verifica se as partes do programa funcionam corretamente quando utilizadas conjuntamente.
  • Teste de regressão: verifica se uma alteração numa parte do programa afeta o funcionamento de outras partes, inclusive as não relacionadas diretamente.
Ao permitir executar estes testes de forma simples e rápida, sua adoção como parte do processo de construção do programa se torna indolor. Por exemplo, a execução dos testes pode ser feita após cada linkagem do programa, de forma automática, bastando configurar a ferramenta que gerencia o processo de compilação e linkagem para executar os testes ao final.

Com isso refatoração de partes do código se torna muito mais segura e quaisquer falhas no código são detectadas imediatamente, dando um feedback rápido para o desenvolvedor.

Funcionamento Básico

Um projeto de testes baseado na xUnit, poderá exibir sua saída em modo gráfico ou em modo texto, dependo de sua implementação padrão. A maioria gera saída em modo texto, mas em quase todos existem implementações de saídas em modo gráfico que podem ser adquiridas gratuitamente pela Internet.

O framework funciona a partir da criação de um grupo de testes e da execução dos testes nele contidos. Dentro deste grupo de testes, é permitido adicionar, além dos testes, outros grupos. Ao disparar o método que executa os testes contidos em um grupo, ele executará seus testes e solicitará aos seus subgrupos que também executem seus próprios testes. Assim, todos os testes da hierarquia serão executados. No ponto de entrada do programa, como uma função main, geralmente haverá um grupo de testes principal que iniciará todo o processo.

Como funciona um teste

Cada teste criado deve fazer uma verificação sobre o estado ou comportamento de um determinado código. Nas xUnits, essa verificação é feita utilizando métodos ou macros do tipo assert, que, em sua maioria, recebem uma variável booleana como parâmetro e lançam uma exceção caso o valor da variável seja falso. A exceção dá informações adicionais, como o número e o conteúdo da linha de código onde estava sendo chamado o assert. Por exemplo:

assert( 1 > 2 );

Como o resultado da condição é false, uma exceção é lançada informando algo como "Exceção lançada na linha 1. Condição não cumprida: 1 > 2".

Cada método deverá verificar uma e só uma funcionalidade, que poderá compreender em um ou mais asserts. Por exemplo:

void CalculaMaiorDeDoisCorretamente()
{
assert( 2 == MaiorDeDois( 1, 2 ) );
assert( 0 == MaiorDeDois( 0, 0 ) );
assert( -1 == MaiorDeDois( -1, -2 ) );
}
O teste verifica se a função MaiorDeDois funciona com esperado. Caso algum assert falhe, é exibida a linha de código com a expressão que falhou, como descrito no exemplo anterior.

Originalmente, em praticamente todas as linguagens, um assert interrompe a execução do programa gerando um erro, que decreve a condição não cumprida. Para que o assert não funcione desta maneira, os frameworks geralmente criam um novo assert, como sendo um método da classe de teste a qual herdamos. Esta lança uma exceção ao invés de interromper a execução do programa.

Onde são colocados os testes

Cada teste deve ser colocado em um grupo de testes, que é chamado formalmente de Suíte de Testes (Test Suit). Geralmente nos frameworks xUnit, existe uma classe chamada TestSuit que será como uma lista de (ponteiros para) métodos de teste que você escreverá.
Você deverá adicionar (o endereço de) cada método de teste que você escrever à um objeto de TestSuit. Posteriormente, ela poderá executar todos os métodos que você adicionou - um após o outro. Havendo algum assert dentro deles que não teve sua condição satisfeita, o programa termina com uma exceção (conforme descrito acima).

Na verdade, os métodos que você criou não poderão ser adicionados diretamente no TestSuite. Ao invés disso, você deve criar um objeto de uma classe chamada TestCaller (a "chamadora do teste") que terá o nome do teste e um ponteiro para (o endereço d)ele. Algo como:

TestCaller< MinhaClasseTeste > *chamadaTeste =
new TestCaller< MinhaClasseTeste >(
"NomeDoSeuMetodoDeTeste",
&NomeDoSeuMetodoDeTeste
);

Para simplificar este código, é preferível declarar um tipo que mapeie o TestCaller ao tipo de nossa classe:

typedef TestCaller< MinhaClasseTeste > Chamada;

Agora, substituindo no código anterior, fica:

Chamada *chamadaTeste = new Chamada(
"NomeDoSeuMetodoDeTeste", &NomeDoSeuMetodoDeTeste
);

Agora, para criarmos nossa Suíte de Testes, faremos:

Test* MinhaClasseTeste::suite()
{
Test *suite = new TestSuite( "MinhaClasseTeste" );

Chamada *chamadaTeste = new Chamada(
"NomeDoSeuMetodoDeTeste", &NomeDoSeuMetodoDeTeste
);
suite->addTest( chamadaTeste );

// Ou adiciona diretamente, como neste outro metodo
suite->addTest( new Chamada(
"MeuOutroMetodoDeTeste", &MeuOutroMetodoDeTeste
) );

return ( suite );
}

Existe uma classe chamada TestCase, que agrupa logicamente testes que verificam o comportamento de uma determinada funcionalidade. Ela possui um construtor que recebe uma string que representa o nome do caso de teste, para facilitar sua identificação, os métodos setUp e tearDown para implementação de fixtures, além de um método estático (para quem não conhece métodos estáticos, pense somente como se fosse um método que você pode acessar sem precisar instanciar um objeto da classe) chamado suite, que retorna um (objeto de) TestSuite - o objeto que conterá os nossos testes.
Quando precisarmos criar nossos testes, podemos criar uma classe filha de TestCase. Ela conterá todo o comportamento necessário para a execução de testes e nossos métodos de teste, com seus asserts.

Exemplo prático

Assim, para criar um novo caso de teste chamado TesteContaBancaria, faremos:
  1. A declaração da classe como filha de TestCase;
  2. A declaração nossos testes, preferencialmente na parte privada da classe, pois os mesmos não serão acessados por outras classes;
  3. A implementação dos testes;
  4. A implementação de um método estático suite que criará um TestSuite e adicionará à ele (o endereço de) todos os nossos testes;
Os métodos setUp e tearDown não precisam ser implementados, a menos que você precise fazer um fixture (que veremos mais tarde).

class TesteContaBancaria : public TestCase
{
public:
TesteContaBancaria(std::string nome);

// Suite de testes
static Test* suite();

private:
// Metodo de teste
void ContaTransfereCorretamente();
};


TesteContaBancaria::TesteContaBancaria(
std::string nome
) : TestCase( nome ) // Chama o construtor de TestCase
{
}


Test* TesteContaBancaria::suite()
{
// Cria a suite de testes
Test *suite = new TestSuite( "TesteContaBancaria" );

typedef TestCaller< TesteContaBancaria > Chamada;

// Adiciona o metodo ContaTransfereCorretamente para
// ser chamado pela suite
suite->Add( new Chamada(
&ContaTransfereCorretamente,
"ContaTransfereCorretamente"
));

return ( suite );
}


void TesteContaBancaria::ContaTransfereCorretamente()
{
ContaBancaria minhaConta, suaConta;

minhaConta.DefinirSaldo( 1000.00 );
suaConta.DefinirSaldo( 0.00 );
minhaConta.Transferir( suaConta, 400.00 );

CHECK_EQUALS( 400.00, suaConta.Saldo() );
CHECK_EQUALS( 600.00, minhaConta.Saldo() );
}


Obs: A macro CHECK_EQUALS aqui foi criada sobre assert, e recebe o valor esperado e o valor atual. Se os valores diferirem, uma exceção será lançada. Existem diversas outras macros disponíveis para auxiliar na verificação de estados, dependendo da linguagem utilizada.

Repare que o método de teste ContaTransfereCorretamente é privado. Isto se deve ao fato que ele nunca será acessado diretamente fora da classe e também não se espera que a classe de teste tenha filhas, que irão chamar o método diretamente. Assim, costuma ser um padrão de TDD colocar os métodos de teste como sendo privados.

Este é o funcionamento básico de um framework xUnit. Na parte 2 irei expor o uso de fixtures e mostrarei outros métodos/macros úteis para as verificações de estado. Até lá.

terça-feira, 25 de novembro de 2008

Vídeo-Palestra: Unit Testing

Ótimo vídeo sobre escrita de código limpo e testável, do Google Tech Talks. Se você ainda não percebeu ou não está convencido de que o uso de TDD vale à pena, não deixe de assistí-lo.


FICHA DO VÍDEO
Palestra : Unit Testing
Palestrante : Misko Hevery, Agile Coach do Google
Evento : The Clean Code Talks, do Google Tech Talks
Data : 30 de outubro de 2008
Idioma : Inglês

sábado, 15 de novembro de 2008

Evento: CodeRage 2008 (online)

De 1 à 5 de dezembro rola o 3º CodeRage, evento online promovido pela Embarcadero, que apresenta diversas palestras em sessões onde é possível trocar informações com outros desenvolvedores, analisar ferramentas de desenvolvimento e muitas outras coisas. São mais de 80 sessões, com algumas palestras em Português do Brasil.

Dentro alguns assuntos abordados estão (vou listar em inglês, que é como você deve achar no site do evento):

  • Generics and Anonymous Methods (Closures)
  • C++0x
  • Plugins
  • Database Design Techniques
  • UML
  • Model Driven Development (MDD)
  • SOA
  • New .NET Technology
  • WPF

e muito mais. Claro que a maioria dos eventos estará ligada à uma ferramenta, que será usada para explicar conceitos e elaborar soluções.

Faça seu registro e participe!

quinta-feira, 30 de outubro de 2008

Delphi Generics: Diferenças entre compiladores

Tanto o Free Pascal Compiler (FPC) quanto o CodeGear Delphi possuem suporte a generics, as extensões que permitem definir quais tipos de dados serão usados por classes ou funções (por exemplo) no momento de sua declaração. Hoje há uma tendência da adoção de generics em praticamente todas as linguagens OO. C++ tem seus templates desde o ISO C++94, Java adotou generics a partir de sua versão J2SE 5.0 (em 2004), C# a partir da versão 2.0, e agora é a vez do Object Pascal. (Apesar das diferenças de implementação e uso de generics em cada linguagem, o conceito é basicamente o mesmo.)

Pois bem. A comunidade do FPC terminou implementação de generics para sua versão 2.1.2 (em 2007) . Logo depois, a CodeGear anunciou sua intensão de incluir generics na próxima versão de seu compilador, porém com a sintaxe "ligeiramente" diferente.

Agora, com o lançamento do Delphi 2009, a CodeGear confirma a implementação da sintaxe antes exibida, para desespero da comunidade e, principalmente, dos usuários do FPC...

A sintaxe é "quase" a mesma. Mas como o "quase" faz não compilar, há muita gente de cabelo em pé.

The FPC Way...

type
generic MeuVetor< T > = class
private
_vetor: array of T;
_limite: Integer;
_atual: Integer;
public
constructor Create(tamanho: Integer);
function Adicionar(item: T): Boolean;
function Item(posicao: Integer): T;
end;

constructor MeuVetor.Create(tamanho: Integer);
begin
inherited Create();
SetLength( _vetor, tamanho );
_limite := tamanho;
_atual := 0;
end;

function MeuVetor.Adicionar(item: T): Boolean;
begin
if ( _atual < _limite ) then
begin
_vetor[ _atual ] := item;
Inc( _atual );
Result := True;
end
else
Result := False;
end;

function MeuVetor.Item(posicao: Integer): T;
begin
if ( ( posicao >= 0 ) and ( posicao < _limite ) ) then
Result := _vetor[ posicao ]
else
raise Exception.Create( 'Posição fora da faixa.' );
end;


The Delphi 2009 Way...



type
MeuVetor< T > = class
private
_vetor: array of T;
_limite: Integer;
_atual: Integer;
public
constructor Create(tamanho: Integer);
function Adicionar(item: T): Boolean;
function Item(posicao: Integer): T;
end;

constructor MeuVetor< T >.Create(tamanho: Integer);
begin
inherited Create();
SetLength( _vetor, tamanho );
_limite := tamanho;
_atual := 0;
end;

function MeuVetor< T >.Adicionar(item: T): Boolean;
begin
if ( _atual < _limite ) then
begin
_vetor[ _atual ] := item;
Inc( _atual );
Result := True;
end
else
Result := False;
end;

function MeuVetor< T >.Item(posicao: Integer): T;
begin
if ( ( posicao >= 0 ) and ( posicao < _limite ) ) then
Result := _vetor[ posicao ]
else
raise Exception.Create( 'Posição fora da faixa.' );
end;


Notou as diferenças ? Particularmente, eu prefiro sem a palavra generic na frente do nome da classe. Acho desnecessária. Como outras linguagens, também acho que deixar explícito na implementação dos métodos da classe a indicação de que ela recebe um parâmetro deixa o código mais fácil de entender. Na maneira implementada pelo FPC, se você não olhar a declaração, não vai saber que a classe é um generic. Não vai saber que "T" é um tipo que é parametrizado para a classe.



Em os futuros posts provavelmente darei exemplos de classes implementadas com generics em Object Pascal. Nessas usarei a sintaxe do Delphi 2009.



Espero que em breve possa haver um consenso. De imediato, seria bom se o FPC fizesse com que a palavra "generic" e a indicação dos tipos parametrizados na implementação dos métodos fossem opcionais. Poderia resolver temporariamente algum problema de portabilidade.

terça-feira, 28 de outubro de 2008

C++: Multi-dimensional analog literals

Interessante as coisas que podemos fazer com C++ padrão.
Veja um exemplo de uma biblioteca construída com templates para simular a construção de objetos multi-dimensionais através de seu formato. Criar linhas, retângulos e cubos assim fica até mais divertido. ;)

Multi-Dimensional Analog Literals

segunda-feira, 27 de outubro de 2008

Evento: C e C++ para Sistemas Embarcados

Acontece em São Paulo no dia 8 de novembro, sábado, um evento sobre Sistemas Embarcados, onde serão abordados algumas temas interessantes, como:
  • Técnicas de Programação em C para Sistemas Embarcados
  • Utilização de C++ em Microcontroladores
  • Explorando os 16 bits da Microchip e as ferramentas de trabalho
  • Otimização de código C para sistemas embarcados
  • Desenvolvimento Embedded no Mundo da eLua
As inscrições podem ser feitas pelo site da TempoReal Eventos.

sexta-feira, 26 de setembro de 2008

TDD na Prática - Parte IV: Quem, Onde, Quando, O Que e Como

Após um breve exercício de análise, você faz um levantamento das tarefas que precisará fazer para concluir a criação de uma determinada funcionalidade no software. Desta funcionalidade,  tanto você como o usuário para o qual o software se destina, pode estabelecer as condições mínimas necessárias para que o software seja considerado "satisfatório".

Estas condições poderão fazer parte do Teste de Aceitação, um conjunto de critérios definidos com o usuário, em forma de estórias ou uma descrição formal dos resultados esperados, que servirá como guia na hora de criar os testes e desenvolver o software.

A interação com o usuário é um fator fundamental para o estabelecimento das regras, condições e cenários que o software deverá atender. Sua participação na especificação é decisiva para um software ter sucesso ou o projeto cair por terra.

Se ao desenvolver o software você não interage diretamente com seu usuário final, como por exemplo, em caso do desenvolvimento de um game,  um driver, um serviço (para um sistema operacional), etc., é recomendado que, ainda assim, você reuna a equipe de desenvovimento junto aos especialistas no domínio e crie um conjunto de espectativas sobre o software que devem ser seguidas, para poder avaliar, posteriormente, se o desenvolvimento do mesmo está caminhando na direção certa.

Agora, se você pode ter contato com o usuário final, o faça! Discuta todos os detalhes possíveis e imaginários, para que a solução desenvolvida avalie diversos cenários do "mundo real" que possam vir a ocorrer e qual a solução para cada uma, além de todas as verificações que se espera do software (o que será muito importante para a criação dos testes).

E documente. Amanhã, se o usuário disser que algo no software não atende às suas necessidades, mas tudo o que foi desenvolvido seguiu a fio a definição que ele deu, você terá em mãos um documento assinado que descreve o que ele mesmo disse que esperava.

O óbvio e o implícito

Não espere demais do usuário. O usuário espera o óbvio. Que o programa funcione sem erros de lógica, que os cálculos estejam corretos, que os dados sejam gravados com sucesso, ..., enfim, que tudo ocorra como esperado - apesar de "o esperado" não ter ficado explícito em nenhum documento formal, ou nem mesmo tenha sido mencionado no levantamento de requisitos.

Cabe então à sua equipe especificar quais coisas devem ser verificadas para garantir o mínimo que qualidade na funcionalidade criada. Um brainstorming ajuda muito nestes casos. Começa-se pelo trivial. Depois, à medida em que se pensa no problema, novas verificações possivelmente necessárias vão sendo descobertas, até que se chegue a uma boa cobertura das prováveis falhas.

Nesse ponto, cada membro da equipe começa a implementar a sua parte da funcionalidade, começando pela codificação dos testes que verificarão o código que por ele será implementado.

Após codificar um teste, se escreve o código para passar no mesmo e, então, o refatora. Segue-se o ciclo normal do TDD.

Depois de terminar de implementar todos os testes identificados pela equipe, é hora de você pensar mais um pouco e tentar identificar o que ainda pode ser verificado, o que mais pode dar errado. Também nessa hora, será preciso pesar a relação custo/benefício dessas verificações.

Custo/benefício de uma verificação

Cada verificação feita no software é uma "garantia" que o mesmo funcionará como esperado. Quanto mais verificações o software fizer, supostamente mais estável e seguro ele será. Se os testes verificam à logica do negócio, suas regras, variações e cenários, mais próximo de cumprir as exigências do projeto ele estará.

Verificar "100%" do software, cobrindo todas as variações possíveis, é, então, a saída para a construção de um software de alta qualidade, certo ? Com certeza não.

Cada verificação adicionada no software tem um impacto diferente sobre sua qualidade. E há um custo de esforço e tempo em cada uma delas.

Pensando no Princípio de Pareto, que diz que 80% dos efeitos vem de 20% das causas, quanto mais nos aproximarmos dos 20% principais, maiores serão os benefícios alcançados pelos testes.

Assim, é importante que os testes sejam concentrados no que irá gerar diferencial para seu usuário - que no caso serão os casos levantados pelo Teste de Aceitação e possivelmente o brainstorming feito junto à equipe de desenvolvimento.

Para o resto, o esforço e custo são alto demais comparados aos benefícios trazidos. Assim, eles podem ser descartados.

Existem, claro, projetos que precisam de uma cobertura de 100% dos testes, como aqueles desenvolvidos para aplicações críticas que envolvam risco à segurança ou saúde, como por exemplo um software para controle de um avião. Mas se você não desenvolve software para esses nichos, é produtivo concentrar esforços na regra 80-20.

Não perca tempo com certos testes

Um erro comum que volta e meia vejo pessoas cometendo, principalmente aquelas que estão iniciando a prática do TDD, é o de verificar coisas que trarão pouqüíssimos benefícios e que tomam tempo considerável. Por exemplo:

TEST( DefineNumeroCorretamente )
{
ContaBancaria conta;

conta.DefinirNumero( 1234 );
CHECK_EQUALS( 1234, conta.Numero() );
}


O código acima testa se o número da conta ficou com o valor correto após definí-lo. É absolutamente ineficiente fazer testes como este, que não trarão benefícios relevantes e desperdiçarão tempo do desenvolvedor (pense, por exemplo, em uma classe com 10 atributos...).



Teste o que for relevante



É muito mais importante, por exemplo, checar estados (valores) com alguma lógica ou processamento relacionado:



TEST( ContaTransfereCorretamente )
{
ContaBancaria minhaConta, suaConta;

minhaConta.DefinirSaldo( 1000.00 );
suaConta.DefinirSaldo( 0.00 );

minhaConta.Transferir( suaConta, 400.00 );

CHECK_EQUALS( 400.00, suaConta.Saldo() );
CHECK_EQUALS( 600.00, minhaConta.Saldo() );
}


Verificar as mudanças de comportamento relacionados aos estados são freqüentemente os alvos dos testes importantes:



TEST( NaoConsegueEfetuarSaqueQuandoContaEstaSemFundos )
{
ContaBancaria conta;
conta.DefinirSaldo( 0.00 );

// Verifica se lanca a excecao
// ESaldoContaInsuficiente caso nao
// consiga efetuar o saque
ASSERT_THROW(
conta.EfetuarSaque( 10.00 ),
ESaldoContaInsuficiente
);
}


E, mais ainda, a interação entre os objetos:



TEST( CartaoDebitoNaoConsegueComprarCasoAContaNaoTenhaFundos )
{
ContaBancaria conta;
CartaoDebito cartao( conta );

conta.DefinirSaldo( 0.00 );

Compra *compra = GerarCompraFicticiaNoValorTotalDe( 10.00 );

// Verifica se lanca a excecao
// ESaldoContaInsuficiente caso nao
// consiga efetuar a compra
ASSERT_THROW(
compra->PagarCom( cartao ),
ESaldoContaInsuficiente
);

delete compra; // Nao deve ser executado
}


A maioria dos testes importantes verificam a interação entre objetos. Outros, o comportamento de cada objeto e, alguns, seu estado.



Concluindo



Quem: O usuário define o que será verificado. A equipe acrescenta outras verificações importantes. Você acrescenta ainda o que julgar necessário.



Onde: As verificações necessárias podem ser registradas em diversos tipos de documento, como num cartão onde é descrita a estória do que será implementado, num documento de Teste de Aceitação ou mesmo num detalhamento da Espeficiação de Requisitos. O importante é que estas informações estejam disponíveis ao construir o software.



Quando: Quando a relação custo/benefício for satisfatória. Procura-se testar os 20% que darão os 80% dos benefícios.



O Que: Teste a interação dos objetos principalmente, seu comportamento e por fim seus estados.



Como: Seguindo o ciclo do TDD e criando testes com frameworks como CppUnit (mostrado acima) ou MockPP. Aquele que estiver disponível para sua linguagem de programação e tenha a facilidade de uso e a flexibilidade requerida para o desenvolvimento dos testes do seu software.

terça-feira, 16 de setembro de 2008

sábado, 6 de setembro de 2008

TDD na Prática - Parte III: Abstração, Simplificação e o Ciclo do TDD

Conhecer como verificar as partes do software e estabelecer o nível de abstração para obter os dados a serem verificados é um ponto vital quando se escreve código de teste.

Expor uma interface simples em uma classe e esconder seus detalhes de implementação é imprescindível para alcançar um modelo de fácil reutilização, substituição e que possa ser facilmente testado. Dependências entre classes devem ser tornadas explícitas e bem-definidas, de forma que se possa trocar a classe da qual se depende por outra, sem que haja um esforço adicional.

E, de novo, aplicando os conceitos de acoplamento e coesão e refatorando o código, podemos obter código com a qualidade desejada. A experiência conta muito a favor, é claro, mas tendo em mente a meta de sempre tentar simplificar as coisas ao máximo (KISS- Keep It Small and Simple), é possível chegar a resultados muito satisfatórios.

Só implementar o necessário

O interessante em o design seguir os testes é que você acaba percebendo a quantidade de código que deixa de escrever. Se você se concentra em escrever código que tente resolver um determinado problema da forma mais simples possível, muita coisa que talvez você fosse escrever, pensando que poderia ser útil futuramente, deixa de ser feita - justamente porque você acabou não precisando pra nada (YAGNI - You Ain't Gonna Need It).

Um teste de cada  vez

Às vezes quando estamos criando um teste, pensamos em uma outra coisa que pode ser testada, não exatamente relacionada à parte de código que está sendo testada, e que  podemos adicionar ao teste que está sendo feito agora. Resista a esta tentação. Mantenha cada funcionalidade que precisa ser testada em testes diferentes. Teste uma funcionalidade por vez. Os testes devem permanecer os menores e mais simples possíveis.

Não implemente mais do que o exigido pelo teste

Após escrever o código do teste e executá-lo, ele irá falhar. Afinal, você ainda não escreveu o código que o implementa. Então você começa escrever o código que implementa a funcionalidade e, de repente, bate aquela idéia de escrever um método ou função que você tem certeza absoluta que vai usar no próximo teste. Por que já não deixar pronto o código para o próximo teste, que você tem certeza que vai escrever ? Acontece que você está escrevendo somente o código necessário para passar no teste. Ao escrever um teste de cada vez, você deve implementar uma funcionalidade também a cada vez. Deixe a implementação sempre seguir os testes. Resista a vontade de escrever código sem que algum teste tenha sido escrito para o mesmo antes.

Refatore

Se o código que você irá escrever for muito parecido com algo que você já fez, refatore (DRY - Don't Repeat Yourself). Se você acha que determinado pedaço de código não faz parte da abstração criada (classe, método, função, etc.), refatore. Se um pedaço de código pode ser tornado mais simples e fácil de entender, refatore. Não deixe de aplicar este passo do ciclo. Ele gera um enorme impacto na qualidade e diminui muito o custo da manutenção futura do código.

E outra coisa importante: Refatore os testes também. O código de teste também pode ser simplificado e melhorado, como o código que o implementa.

Em suma, siga o ciclo do TDD

O ciclo do Test-Driven Development se baseia em 3 passos:

  1. Escreva um teste que verifique uma funcionalidade;
  2. Implemente somente o código para passar no teste;
  3. Refatore o código criado;

Respeitando o ciclo, você mantém o código sob controle, não disperdiça tempo criando algo que não vai precisar e mantém o escopo de cada teste sempre pequeno, facilitando seu desenvolvimento e manutenção.

Tente sempre seguir o ciclo. Não sucumba às tentações. ;)

sábado, 23 de agosto de 2008

Vídeo-Palestra: Extreme Programming

Palestra recomendada para quem quer conhecer melhor como é o desenvolvimento ágil utilizando Extreme Programming. Em português (!).

Palestrante: Vinícius Manhães Teles, da ImproveIt
Evento: TDC 2008

quinta-feira, 21 de agosto de 2008

TDD na Prática - Parte II: Verificação e Confiabilidade

Como visto na Parte I, para criar código (que seja facilmente) "testável" é preferível escrever código que o testa primeiro.

Exercitar o código verificando seus limites e estados auxilia na descoberta de como o mesmo deve se comportar e traz um maior controle sobre seu funcionamento. Conseqüentemente, culmina em um maior nível de confiabilidade.

Tornar freqüente a verificação do código, de forma com que a cada alteração do mesmo ele seja novamente verificado, garante a manutenção deste nível de confiabilidade.

Verificar o código continuamente e acrescentar novas verificações à medida em que forem necessárias, além de re-executar todos os testes (teste de regressão), pode ser um processo doloroso se não for automatizado. Assim, existem diversas ferramentas que suavizam o processo, permitindo nos concentrar no que é importante.

Como o uso das ferramentas, em poucos (mili)segundos executamos todas as verificações e temos um maior grau de certeza que:

1) O novo código criado passa nos testes;

2) Após uma alteração no código, ele continua passando nos testes;

3) A alteração numa parte do código não fez outra parte do código parar de funcionar (compilar), nem deixar de funcionar como esperado (mudou o comportamento), pois ela também continua passando nos testes;

Para garantir 1) e 2) temos o Teste Unitário e para 3) o Teste de Integração e o Teste de Regressão.

Diversos frameworks de testes foram criados com esta finalidade, para cada linguagem. Em especial, os baseados na SUnit (como JUnit, CppUnit, DUnit, NUnit, etc.) são os preferidos por serem simples, poderosos e fáceis de adaptar a qualquer ambiente de desenvolvimento ou sistema operacional.

TDD, xUnit e xMock

A maioria dos desenvolvedores que adotam TDD, adotam também uma framework xUnit para execução dos testes. Não é diferente comigo, nem com este blog. Para cada linguagem há uma variedade de opções, com diversas vantagens e desvantagens a se ponderar. Por exemplo, para C++ existe também a Boost.Test, a CppUnitLite, a NanoCppUnit, a Unit++, a CxxTest e por aí vai. Tenho preferência pelas versões com maior adoção (como a CppUnit), apesar de que para determinadas aplicações, certos frameworks trazem vantagens interessantes.

Outra ferramenta importante, recomendada para usuários que já possuam alguma experiência em testes, é a de simulação de comportamento, para os chamados Objetos Substitutos, ou Mock Objects. Frameworks como jMock, MockPP, NMock, etc. etc. etc. trazem maneiras muito úteis de fazer construções que testem o comportamento esperado de objetos.

Enquanto os frameworks xUnit tem foco na verificação de estados de um objeto, os xMock tem foco na verificação do comportamento dos objetos, na interação entre os mesmos. É sempre possível simular os testes sem o auxílio de um framework, mas seu uso traz uma certa padronização na maneira com a qual os testes são construídos.

Com todas as facilidades e opções disponíveis, a adoção de um framework de testes passa a ser de extrema importância na implementação de TDD. Neste blog, você verá alguns deles em ação e saberá como a maioria deles funciona.

quarta-feira, 20 de agosto de 2008

Interação e Substituição de Objetos

Ter uma visão clara de como os objetos interagem é um fator fundamental no desenvolvimento de software orientado a objetos. É até mais importante do que saber como cada objeto funciona isoladamente.

Pensando que um software O. O. é uma espécie de teia de objetos que se interconectam, fazendo de sua interação o fator gerador do comportamento do software, mudar seu comportamento será como plugar novos objetos no lugar dos que estavam, bastando para isso que o plug esteja no formato esperado.

Simular este tipo de comportamento no software requer que tomemos algumas medidas de análise bem pensadas, estudadas, para que tudo se encaixe da maneira apropriada.

Tipos de Substituição

A substituição dos objetos pode não servir apenas para mudar o comportamento do software. Ela pode servir, principalmente, para analisar o comportamento do mesmo.

Se ao invés de plugar um objeto que precise desempenhar determinada tarefa você plugar outro que registre ou exponha como se deu a interação dos outros objetos com ele, você terá uma excelente ferramenta para análise comportamental.

Caso o objeto precise desempenhar tarefas necessitando da colaboração de outros, sendo requerida a passagem de mensagens para os mesmos, seu objeto pode simulá-la sem que seja necessário qualquer esforço adicional.

Você pode substituir o comportamento do objeto (plug) real pelo comportamento que você deseja, apenas para investigar o comportamento dos objetos (plugues) em que ele estará conectado.

Objeto Substituto

Como disse, para que o objeto substituto possa ocupar o espaço do original, basta que ele apresente o mesmo formato. Este formato do objeto pode ser descrito também como a interface do objeto, ou seja, a maneira com a qual é feita a interação com ele.

Mantendo a mesma interface, os objetos interconectados a ele não precisam ser modificados, podendo lhe referir sempre da mesma maneira.

Mock Object, Proxy e outros nomes

Ao objeto substituto são dados diversos nomes, como Mock Object (geralmente como é referenciado em TDD), Proxy (Padrão GoF), Surrogate, etc. Apesar de haverem diversas variações na maneira como são usados (por exemplo, como virtual proxies, remote proxies, smart references, etc.), todos se baseiam no sentido da substituição do objeto original.

Em suma

A substituição dos objetos para análise, teste ou mudança do comportamento é um dos conceitos mais significativos para o entendimento da criação de software testável e de objetos reutilizáveis. Entender as variações possíveis e os benefícios e problemas trazidos por cada implementação traz uma grande maturidade ao desenvolvedor e potencializa o crescimento e a manutenção de software, levando à um custo de alteração mais baixo e a melhoria da "testabilidade" do mesmo.

segunda-feira, 18 de agosto de 2008

TDD na Prática - Parte 1: Influência no Design

Para se ter uma idéia rápida de como é TDD na prática, criarei um joguinho bem simples, e de conhecimento geral, que deve servir como exemplo: o Jogo da Velha. (Se você não teve infância ou sofre de perda grave de memória, consulte as regras aqui. :)

Para verificar se a implementação do jogo estará correta, escreverei código que a teste. Mas daí vem algumas questões:

  • Como terei certeza de que o código que escreverei poderá ser testado de forma adequada ?
  • Como saber que não vou ter que modificar meu código depois só pra torná-lo "testável" ?
  • O design do código será apropriado o suficiente para me dar toda a informação que precisarei verificar ?

Se escrevo código que implementa uma funcionalidade antes de escrever código que a testa, corro o risco de que seu design não seja adequado para que seja testado. Portanto, a melhor maneira de escrever código "testável" é definindo seu teste primeiro. Assim você garante que a implentação deve seguir o design que você propôs.

"Hum... então TDD é mais do que escrever testes primeiro, tem a ver sobre design também...", você pode estar pensando. Sim, com certeza; TDD é mais sobre design do que propriamente a ordem em que os testes são feitos.

Em geral, você guia o design pelos testes. Essa  abordagem, ligada a noções de conceitos como acoplamento e coesão, levam a um código altamente flexível e testável. Normalmente, o que queremos quando desenvolvemos software de qualidade.

quarta-feira, 13 de agosto de 2008

Glossário Rápido

Eu não espero reinventar a roda e ter que explicar todos os conceitos, metodologias, processos, siglas, etc. que surgirem neste blog. Diversos outros autores já reservaram seu tempo fazendo isso. Meu intuito aqui é prover uma definição suscinta para quem não quer ler todo o conteúdo encontrado no Wikipedia ou no site mais próximo, encontrado talvez numa pesquisa no Google.

Metologias Ágeis

É o nome dado ao conjunto de metologias dissidentes do Manifesto Ágil. Exemplos: Extreme Programming, Scrum, Crystal, Feature-Driven Development, etc.

Manifesto Ágil

Um conjunto de princípios que visa potencializar o relacionamento das pessoas envolvidas no projeto do software (desenvolvedores, gerentes, clientes, etc.) , de forma a trazer benefícios para o mesmo, através de suas relações e práticas.

Práticas Ágeis

Práticas comumente adotadas pelas metodologias ágeis, como Desenvolvimento Incremental, Liberação Frequente, etc.

eXtreme Programming (XP)

Apesar de não parecer, não é nada de novo. Juntou-se práticas antigas de uma forma que pudesse melhorar o desenvolvimento de software, enxugando ao máximo as tarefas burocráticas e que não estavam ligadas diretamente à programação. As práticas foram unidas de forma que uma sustentasse a outra, cobrindo eventuais problemas relacionados à prática isolada das mesmas. O nome "extrema" vem do sentido de que a adoção das práticas é um tanto radical em relação ao desenvolvimento de software tradicional.

Scrum

Outro dissidente do Manifesto Ágil, o Scrum possui uma série de regras, papéis e documentos bem definidos, a serem seguidos pela equipe participante do projeto. Possui terminologia própria (apesar das práticas e papéis não serem novos) que ajudam no entendimento do processo e em sua adoção. Tem foco mais na parte administrativa que na de implementação.

Test-Driven Development (TDD)

Prática em que se escreve código para testar uma funcionalidade no software, antes que o código que implementa esta funcionalidade tenha sido escrito. Envolve diversos outros fatores como design, refactoring, integração, automação de tarefas e diversos tipos de teste, se tornando muito mais abrangente de que a simples definição de sua prática. O TDD é utilizado pela maioria das metodologias ágeis.

Refactoring

Consiste em melhorar o código existente através de sua reorganização, sem que sua funcionalidade seja modificada. A cada tipo de reorganização foi dado um nome (ou frase) para expressar sua intensão e facilitar a comunicação entre os desenvolvedores.

Design Patterns

Os Padrões de Projeto são modelos de organização e interação de classes, percebidos por desenvolvedores de software ao longo do tempo, que foram catalogados e nomeados para facilitar sua indentificação.

Gang of Four (GoF)

Apelido dado ao grupo de autores do livro "Design Patterns - Elements of Reusable Object Oriented Software", composto por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. O livro é uma espécie de catálogo de padrões de projeto de software (Design Patterns) , sendo uma ferramenta indispensável aos bons analistas de sistemas e arquitetos de software.

Acoplamento

Nível de inter-dependência entre módulos (ou outras partes) do software. Quanto menor, melhor (mais reutilização).

Coesão

Grau de "singularidade" de um módulo (ou outras partes) do software. Se um módulo desempenha uma única função ou tem um único propósito, ele é altamente coeso.

KISS

Princípio que tem como objeto sempre manter as coisas bem simples. Sua abreviação possui variações, como Keep It Small and Simple ("mantenha as coisas pequenas e simples"), ou - a minha preferida - Keep It Simple, Stupid. ;)

YAGNI

Princípio cujo objetivo é que não se crie coisas das quais você não tem certeza absoluta se vai precisar. Sua abreviação se refere You Ain't Gonna Need It (algo como "você não vai precisar disto").

DRY

Princípio que visa reduzir a repetição, a duplicação, de trabalho. Vem de Don't Repeat Yourself (algo como "não se repita").

Fixture

Conjunto de dados de teste que são compartilhados entre diversos testes.

Outros termos serão incluídos aqui à medida em que forem sendo referenciados pelos artigos do blog.

O que virá

Uma idéia do que deve ser visto por aqui:

Metodologias e Processos

Muita gente ainda desconhece ou tem uma visão um tanto turva sobre Metodologias Ágeis, Scrum, XP, TDD, etc. e de diversos outros assuntos relacionados a metodologias de desenvolvimento de software.

Espero deixar aqui minha contribuição para esclarecer alguns pontos, com enfoque prático, fazendo um balanço sempre pragmático.

Modelos, Padrões e Princípios

Abrirei espaço para discussão de Design Patterns, modelos de separação em camadas, Refactoring, além de vários princípios que tangem as boas práticas do desenvolvimento de software.

Linguagens

Devo escrever mais sobre C++, Delphi e Java (nessa ordem), por causa de meus amigos e colegas de trabalho, além dos alunos que sempre pedem por um trecho de código no qual eles possam aprender um pouco mais da linguagem (além de adicionar em seus programas para não ter mais que reescrever tanto código). ;)

Ferramentas e Outros

Ferramentas e outros tópicos sempre irão surgir. Aspectos importantes ligados à prática e o dia-a-dia do desenvolvimento de software permearão os artigos blog.

Acompanhem os marcadores do site e deixem seus comentários. Opinião é sempre bem-vinda. Discussão é sempre saudável.

terça-feira, 12 de agosto de 2008

Blog::Start()

{

    if ( YouLikeSoftwareDevelopment() )

       ThatsTheRightPlace();

   else

       GoToMyAnotherSite(); // www.thiagodp.blogspot.com

}