Google Analytics

quarta-feira, 16 de maio de 2012

Árvore de fontes

Neste post vou falar como eu costumo organizar os arquivos-fonte de meus projetos. A divisão que eu uso obviamente não é a única adequada e nem a melhor, mas tem funcionado relativamente bem. As características que foram buscadas nesta formatação são:
  • Facilidade de localização do material.
  • Formato estruturado e hierárquico.
  • Manter todo o material de entrada necessário para o trabalho versionado e baixável com um comando único.
  • Possibilidade de usar integração contínua e builds CRISP.
 Vamos então à estrutura.

project-x
├── code
│   ├── build
│   │   ├── output (não guardado no sistema de versionamento)
│   ├── internalDocs
│   │   └── TestPlans
│   ├── skels
│   ├── thirdparty
│   │   ├── bin
│   │   ├── dll
│   │   ├── include
│   │   └── lib
│   └── userDeliverables
│       ├── part1
│       ├── part2
│       ├── installers
│       └── userDocs
├── continuous-integration
└── thirdparty-originals

No primeiro nível, temos a divisão em code, continuous-integration e thirdparty-originals. Code vai conter o grosso do trabalho, e será descrito por último.

Em thirdparty-originals,  coloco todos os arquivos de peças de software de terceiros que serão usados no projeto. Importante: eu os coloco do jeito que eles vêm do terceiro, sem nenhum processamento. Todo o processamento que estes arquivos requeiram eu faço através de scripts para garantir a repetibilidade do processo e facilitar no caso de upgrade de uma peça. Os scripts de processamento criados para isto vão ficar em um lugar abaixo do dir code, que será descrito mais para a frente.


Em continuous-integration, coloco os arquivos usados para fazer integração contínua do projeto. Por exemplo, se vai ser usado cruise-control, aqui ficarão um cruise-control-config.xml e um delegating.xml.


O diretório code está organizado do modo abaixo descrito:

No diretório build, coloco os scripts de build que permitem buildar o sistema em linha de comando. Sempre procuro ter um script que permita, rodando um único comando, gerar o pacote pronto para envio para clientes. Gosto que este script crie um sub-dir chamado output e coloque seus produtos ali. Notem que este script de build master deve gerar inclusive a documentação de produto e empacotá-la do jeito necessário para a entrega (zip contendo os documentos e o instalador, por exemplo).

No diretório  internalDocs coloco a documentação de projeto. Por exemplo, diagramas de projeto, instruções de trabalho para programadores, user stories, planos de teste, etc. Vou usando sub-dirs para agrupar coisas relacionadas. Por exemplo, se tenho mais de um plano de teste, crio um sub-dir chamado testPlans, se tenho vários arquivos de user stories, crio um chamado userStories.

Em skels, coloco os arquivos de esqueleto que serão usados no projeto. É interessante que a maioria das pessoas que conheço guardam os esqueletos no armário (ok, normalmente é na pasta da qualidade ou no diretório de políticas da empresa) e não junto de onde eles são usados e, guardando no armário, não dão oportunidade de customização por projeto e muita gente acaba não usando-os por estarem longe. Eu gosto de meus esqueletos evoluindo, versionados e sempre à mão para serem usados de modo efetivo. A propósito, esqueletos são aqueles arquivos modelo que ajudam a ter uma homogeneidade visual no código e também ajudam a gente a se lembrar de detalhes como esconder o copy ctor de classes. Se você quer uns exemplos de esqueletos, de uma olhada aqui.

Em thirdparty ficam os arquivos de peças de software de terceiros, cujos originais estão em thirdparty-originals, no formato em que eles precisam estar para serem usados no projeto. Aqui ficam também os scripts necessários para transformar o que temos em thirdparty-originals para o formato necessário. Um exemplo disto é uma biblioteca de terceiros. Digamos, por exemplo, que eu use a OpenSSL em Windows. O targz dos fontes dela estaria em thirdparty-originals e um script chamado buildOpenSSL, guardado em thirdparty compilaria as libs e as copiaria para thirdparty/dll e os includes públicos para thirdparty/include.

Em UserDeliverables coloco os fontes das várias partes do projeto (ou da única parte, se for o caso) e também a documentação de produto (manual do usuário, readme, history, etc).





quinta-feira, 26 de abril de 2012

Esqueletos

Eu gosto de usar esqueletos, ou seja, aqueles arquivos de modelo. Eles me ajudam a lembrar de coisas que normalmente precisamos e também deixam o código com uma cara homogênea.

Mas eu não gosto de esqueletos mortos, daqueles que estão estáticos e não podem ser mudados, ou que são iguais para todos os projetos. Gosto de esqueletos vivos, que evoluem, sendo customizados para cada projeto conforme vão sendo usados e gosto também que eles estejam versionados no sistema de versionamento DO PROJETO EM QUESTÃO.

Costumo usar os seguintes esqueletos:

header:
//*****************************************************************************
// Version/Revision log :
// ====================
//$Log: $
//
//*****************************************************************************

#ifndef TODO_NAME_H_INCLUDED
#define TODO_NAME_H_INCLUDED

//################################# Includes ##################################


//################################## Types ####################################


//################################ Constants ##################################


//############################## Public Variables #############################


//############################# ForwardDeclarations ###########################


//################################# Classes ###################################


//####################### Public Function Declarations ########################


#endif // TODO_NAME_H_INCLUDED

//*************************    E n d   F i l e    *****************************



cpp:

//*****************************************************************************
// Version/Revision log :
// ====================
//$Log: $
//
//*****************************************************************************

//################################# Includes ##################################
#include "TODO_NAME.h"


//################################## Types ####################################


//######################### Private Classes Declaration  ######################


//################################ Constants ##################################


//############################# Private Variables #############################


//############################## Public Variables #############################


//############################# Private Functions #############################


//###################### Private Classes Implementation #######################


//###################### Public Classes Implementation ########################


//##################### Public Functions Implementation #######################


//-------------------------------------------------
// Unit tests
//-------------------------------------------------
#ifdef UNIT_TESTS
#include <iostream>
#include <assert.h>
using namespace std;

int main ()
{
    cout << "Unit Testing for TODO_NAME class" << endl;
    Logger::logTostdout();
       
    cout << "Testing TODO" << endl;
    {
        assert(!"No UNITTESTS implemented yet!");
    }

    cout << ">>>>>> Tests finished with no errors" << endl;
    return 0;
}

#endif

//*************************    E n d   F i l e    *****************************


declaração de classe:
//=============================================================================
// Class: TODO_NAME
// Remarks: TODO
//=============================================================================
class TODO_NAME
{
public:
    TODO_NAME() {}
    ~TODO_NAME() {}
private:
    TODO_NAME(const TODO_NAME& x);    // Hide copy ctor to avoid implicit calling
    TODO_NAME& operator=(const TODO_NAME& x);    // Hide = operator to avoid implicit calling
}; // End of class TODO_NAME



Implementação de clase:
//=============================================================================
// Class: TODO_NAME
//=============================================================================
//-----------------------------------------------------------------------------
// Method: TODO
// Remarks: TODO
//-----------------------------------------------------------------------------
void TODO_NAME::TODO(TODO)
{
    //TODO
}


Makefile:
#*****************************************************************************
# Version/Revision log :
# ====================
#$Log: $
#
#*****************************************************************************
all:
    <<TODO: Implement all target to build your items in the current dir

clean:
    <<TODO: Implement clean target that undoes what all does...

install:
    <<TODO: Implement install target that installs the itens created by all to the correct places.

uninstall:
    <<TODO: Implement clean target that undoes EVERYTHING that install does...


.PHONY: all clean install uninstall



quinta-feira, 5 de abril de 2012

Uso robusto do default em switches

Noto uma grande tendência a usarem o default do switch para economizar digitação.

Por exemplo:

O cara tem 8 possíveis valores e tem de fazer o mesmo processamento para 6 deles; então coloca 2 cases e cobre os 6 comuns com um default. 

enum {V1, V2, V3, V4, V5, V6, V7, V8} v;
...

switch (v)
{
case V1:
  processV1();
case V2:
  processV2();
 
default:
  process3to8();
}

Funciona no dia em que é implementado. Mas é um pesadelo para manutenção. No dia que alguém incluir novos valores na enumeração, o novo valor vai ser "automagicamente" considerado como devendo ter o mesmo comportamento que o V3 a V8 tinham. O que pode estar errado. Coisas que acontecem automagicamente não são boas em programação. Por isso é uma boa prática se colocar os valores explicitamente e usar o default para logar erro indicando um valor inesperado (e, de preferência, abortar o programa imediatamente):

enum {V1, V2, V3, V4, V5, V6, V7, V8} v;
...


switch (v)
{
case V1:
  processV1();

case V2:
  processV2();

case V3:
case V4:
case V5:
case V6:
case V7:
case V8:
  process3to8();
default:
  /* Logar uma mensagem indicando que um valor inesperado ocorreu na variável v, da função tal... */
/* Sempre que possível, abortar o programa nesta situação. */
}


O ponto é que o default deve ser usado para expressar: Faça isto para todos os outros valores possíveis (tanto conhecidos quanto os desconhecidos). E não é bom programar para fazer coisas com valores desconhecidos.

O mesmo fenômeno ocorre para o else em trechos do tipo:

enum {V1, V2, V3, V4} v;
...
v = getValueFromSomewhere()

if(v== V1|| v==V2)
  doSomething()
else //Tratando o V3 e V4 como genérico (isto não é bom...)
  doOtherStuff();


Melhor seria usar algo como:

if (v== V1|| v==V2)
{
  doSomething()
 }
else if (v== V3 || v == V4)
{

   doOtherStuff();
 }
else
{
  DebugLog <<"Unexpected value received  in function XXXX. Value is " << v;
  abort();
}

Era isto.

quinta-feira, 9 de fevereiro de 2012

Sugestões de leitura


Seguidamente me pedem algumas recomendações de livros. Então, montei esta lista aqui. Ela está dividida em um grupo de livros genéricos, que se aplicam a qualquer linguagem de programação, e outro específico de C++. Recomendo ler os livros mais ou menos na ordem em que aparecem listados. Sobretudo recomendo que todo mundo comece lendo o "The Pragmatic Programmer, From Journeyman to Master."

Vamos às listas:


Desenvolvimento de software em geral, independente de linguagem:

    The Pragmatic Programmer, From Journeyman to Master. David Thomas e Andrew Hunt. Este livro é um divisor de águas na carreira de um programador. Depois de lê-lo, você nunca mais será o mesmo.

    Extreme Programming Explained. Kent Beck. É fundamental conhecer os conceitos apresentados neste livro, mesmo que não se vá aplicar tudo o tempo todo (BTW, o próprio Beck diz que nem todo o tipo de projeto deve ser feito em eXtreme Programming).

    Refactoring: Improving the Design of Existing Code. Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts. Bastante indicado para quem trabalha com código herdado ou com equipes muito heterogêneas.
   
    The Agile Manifesto. Kent Beck et alli. http://agilemanifesto.org/. Recomendo também uma lida nas páginas adjacentes, como a de princípios. É tudo bem curto e se lê em poucos minutos.

    Pragmatic Project Automation: How to Build, Deploy, and Monitor Java Apps. Mike Clark. Embora ele use java nos exemplos, as ideias ajudam em qualquer linguagem.
   
    Agile Software Development (The Agile Software Development Series). Alistair Cockburn.

    Planning Extreme Programming. Kent Beck e Martin Fowler. Interessante para quem pretende gerenciar projetos ou ter uma participação maior na gerência de seu projeto. Mesmo que não trabalhe em projetos XP, vale a pena ler.

    Code Reading: The Open Source Perspective (Effective Software Development Series). Diomidis Spinellis. Ler código é uma habilidade extremamente importante. Apesar dos comentários depreciativos  que a gente encontra em vários sites sobre o estilo do autor, o livro vale a pena; traz ideias bem interessantes e chama a atenção para a necessidade desta habilidade. De quebra, o leitor também pode se sensibilizar e passar a tentar escrever código lembrando que outros humanos o lerão. BTW, li uma frase um dia desses que dizia algo como: "Escreva o código sempre pensando que o próximo programador a dar manutenção nele será um psicopata violento que sabe o seu endereço."

C++:

    Effective C++:50 Specific Ways to Improve Your Programs and Design. Scott Meyers. Leitura (e compreensão) obrigatória para qualquer candidato a programador C++ verdadeiro.

    More Effective C++: 35 New Ways to Improve Your Programs and Designs. Scott Meyers. Mais básico que o anterior. Talvez muita gente não aprenda nada de novo com este.

    Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library. Scott Meyers. Leitura (e compreensão) obrigatória para qualquer candidato a programador C++ verdadeiro.

    The C++ Standard Library: A Tutorial and Reference. Nicolai M. Josuttis. Leitura obrigatória para programadores C++ médios em diante.

    The C++ Programming Language. Bjarne Stroustrup. Leitura obrigatória para programadores C++. Ponto a mais para que pronunciar corretamente o nome do autor ;-).

    Effective C++:50 Specific Ways to Improve Your Programs and Design. Scott Meyers. Não foi engano... Está duplicado de propósito. Quando você tiver chegado aqui, deve reler este livro. Ele é pequeno, rápido para reler. E provavelmente, na primeira vez que você leu, você não entendeu alguma coisa...