Google Analytics

quarta-feira, 19 de outubro de 2011

Código autodocumentado

Prezados, código auto-documentado é diferente de colocar comentários no código explicando o que ele faz ou de colocar documentação doxygen ou afins.

Código auto-documentado é código escrito de forma clara, com nomes descritivos nas entidades (variáveis, funções, constantes, etc) e com uma estruturação lógica que facilita o entendimento. Levando a um exemplo extremo, a seção abaixo não é código auto-documentado:

/* Function f(string s1, string s2)
This function prints a name on the screen, formatting as "surname, name". s1 is the name and s2 is the surname.
*/
f(string s1, string s2)
{
   ...
}

Nem que o comentário fosse no padrão doxygen, isto não seria código auto-documentado!

Agora um exemplo de auto-documentado:

//----------------------------------------------
// This function prints a name on the screen, formatting 
// as "surname, name".
printAsSurnameCommaName(string name, string surname)
{
...
}


domingo, 14 de agosto de 2011

Como documentar um refix no documento de change log de um software

Recentemente um colega me consultou sobre como documentar, no changelog que vai para o usuário, a correção de um bug que foi reaberto. Abaixo eu transcrevo a conversa (com autorização - agradecimentos ao Mateus pela autorização) pois outras pessoas também podem ter a mesma dúvida.

(13:26:31) Mateus: se eu corrigo um bug e coloco no changelog:
(13:26:39) Mateus: fix bug XXX....
(13:26:47) Mateus: mas alguém vai lá e reabre o dito cujo.
(13:26:59) Mateus: aí eu corrijo e libero outra versão, com o novo fix.
(13:27:04) Mateus: como coloco no changelog?
(13:27:07) Mateus: Fix bug xxx
(13:27:12) Mateus: ou Refix bug xxx ?
(13:27:25) lg.moreira: changelog do source control ou do bug tracking?
(13:27:33) Mateus: do pacote. [N.R. - Quer dizer: no documento que vai para o usuário.]
(13:27:40) lg.moreira: ok.
(13:27:41) Mateus: o arquivo changelog que vai pro usuário.
(13:28:06) lg.moreira: Fix of bug xxxx after its reopening by Fulano.
(13:28:50) lg.moreira: Ou, se quiseres mais curto: Refix of bug xxxx: Now I yyyyyyy [descrição do que foi feito] and it should really fix the problem.
(13:29:00) Mateus: hmmm
(13:29:09) Mateus: esse segundo me agrada mais porque to colocando o título do bug também na linha.
(13:29:22) lg.moreira: O importante é te colocares no lugar de um usuário que lê o changelog.
(13:29:50) Mateus: pois é
(13:29:55) lg.moreira: Se tu colocas só refix, o cara vai ficar perguntando WTH he means with refix. What was still broken.
(13:30:34) Mateus: acho q não preciso ser tão verboso, já que o usuário é interno e tem acesso ao bugzila
(13:30:51) Mateus: mas acho bom indicar que foi reaberto e essa é uma "re-correção"
(13:31:17) lg.moreira: No caso do usuário ter acesso ao Bugzilla, o melhor mesmo é só refix of XXXX: Now I yyyyyy.
(13:31:27) Mateus: blz.
(13:31:29) Mateus: valeu.
(13:31:41) lg.moreira: Tu colocas no bugzilla o que foi feito para corrigir?
(13:32:23) Mateus: sim(13:32:37) lg.moreira: ok. Isto é importante também.
(13:32:48) lg.moreira: Agora a cobrança do serviço de consultoria.
(13:33:16) lg.moreira: Eu gostaria de colocar este diálogo no meu blog de programação.
(13:33:29) lg.moreira: Ele exemplifica uma dúvida muito comum.
(13:33:54) lg.moreira: Estou autorizado?
(13:33:57) Mateus: sim
(13:33:59) lg.moreira: ok.
(13:34:00) lg.moreira: ;-)

terça-feira, 9 de agosto de 2011

Tornar a vida do usuário mais simples: Nossa meta

Lembre-se sempre de que programas de computador existem para facilitar a execução de um trabalho. Ou seja, para tornar a vida do usuário mais simples.

O que isto significa do ponto de vista de um programador? Significa que, ao tomar decisões de projeto, você deve pautá-las considerando o que vai tornar mais simples a vida do usuário, mesmo que isto implique em tornar a sua vida mais complicada. Resumindo: sempre optar pelo que dá menos trabalho para o usuário, mesmo que isto implique em mais trabalho para o programador.

segunda-feira, 8 de agosto de 2011

Command Line Interfaces

Taí um artigo que eu gostaria de ter escrito. Mas já o fizeram muito bem em http://www.antoarts.com/designing-command-line-interfaces/. Recomendo que leiam.

Alguns excertos:
  • "Ease of automation: Most command-line interfaces can easily be automated using scripts."
  • "Fast startup times: Most command-line interfaces beat their graphical counterparts several times at startup time"
  • "Easier to use remotely: Maybe it’s just me, but I prefer to remotely control computers via SSH over VNC"
  • "Higher efficiency: Being able to do a lot by just typing some characters instead of searching in menus increases the efficiency which you are able to do work at"
  • Try to avoid interactivity 
  • Name the program correctly
  • Follow the CLI conventions on arguments
  • "Provide --version and --help"
  • "If a program has nothing of importance to say, then be quiet"
  • "Every program should do one, and only one thing, and do it well"

terça-feira, 26 de julho de 2011

Mais sobre as "Regras de Ouro"

Após a divulgação do artigo "As 'Regras de Ouro' do desenvolvimento de software", me pediram para falar mais sobre cada uma, dando exemplos quando possível. Vou tentar atender ao pedido neste artigo.

Não se repita.

Esta regra não se refere apenas a não dizer a mesma coisa duas vezes. Esta regra não se refere apenas a não dizer a mesma coisa duas vezes. Ela está voltada para coisas mais profundas como não armazenar a mesma informação em mais de um lugar.

Por exemplo, imagine que você tem um conjunto de arquivos que deve ser instalado pelo instalador do seu software e, consequentemente, removido na desinstalação. Seria uma quebra desta regra se você fizesse algo do tipo:
:install
  copy a b c d e f <destDir>
:uninstall
  remove a b c d e f from <destDir>
Notaram a repetição de "a b c d e f"? Neste caso para evitar a repetição, a resposta seria usar uma variável. Em outros casos, pode ser mais complicado e exigir até mesmo um gerador de código automático.

Outro caso comum de repetição acontece quando um programador (normalmente novo na equipe, mas às vezes também um antigo e com preguiça de procurar o que já existe no código) inadvertidamente cria uma função para fazer a mesma coisa que uma que já existe e que ele não conhecia.

Outro exemplo, mais sutil:

Comentários descrevendo o que uma parte de código faz. O código em si deveria ser claro o suficiente para explicar o que ele faz (leitores entenderem), além de fazer o que deve (computadores entenderem). Os comentários devem ser usados apenas para informar sobre decisões e aspectos de mais alto nível relacionados ao código. Como está muito bem dito por Andrew Hunt e David Thomas em The Pragmatic Programmer: "bad code requires lots of comments".

Outro exemplo:

Duplicação entre a especificação e o código. Ok, esta é difícil de eliminar, mas num mundo ideal seria legal ter a informação só na especificação e o código sair sozinho. Uma das ideias com UML é poder especificar usando-a e gerar código automaticamente. Bom, a gente sabe o final da história e não preciso falar a respeito. :-)

Não separe o que deve estar junto.

"O que Deus uniu, o programador não separe."

Exemplos:

Documentação de projeto (especificações, etc) devem estar guardadas junto com o código (também devido à regra de manter as coisas perto de onde são usadas - a propósito, todos sabem que as especificações devem ser usadas durante a codificação?).

cpp e h de um mesmo módulo devem estar no mesmo diretório.

Instalador de produto e código do produto devem estar na mesma árvore.

Código do módulo e unit test do módulo devem estar juntos. Aliás, eu particularmente prefiro que o código de unit test esteja dentro do mesmo arquivo que o código de produção do módulo; assim, se diminui o trabalho para criar casos de teste novos ao criar funcionalidades novas e se evita o "esquecimento" de atualizar os testes. Unit test junto do código de produção também facilita a depuração quando aparece um bug, pois facilmente se coloca o módulo suspeito em teste (lembem-se de "O procedimento de correção de bug")

Não junte o que deve estar separado ou coisas que não estão relacionadas.

"O que o programador junta, nem o diabo separa."

Exemplo clássico: O programador precisa fazer uma função que retorna nome e sobrenome, então ele concatena os dois dados numa string e retorna a string, na ilusão de que, se alguém precisar do nome e sobrenome separados (ou em um formato de concatenação diferente...), é só "parsear a string". Não caia nesta ilusão; o correto é retornar os dados separadamente, porque isto é uma interface mais genérica. Ou, o que ainda é melhor no caso de linguagem orientada a objetos: Não ter uma função que retorna o nome e o sobrenome e sim métodos que processam o nome e sobrenome internamente e resolvem todo o problema dentro da classe (exemplo: um serializador para stream).

Guarde as coisas perto de onde elas são usadas.

Aqui a questão é guardar as coisas fisicamente próximas de onde elas são usadas.

Por exemplo, eu tive um cliente que seguidamente me contactava perguntando a data em que alguma versão do software tinha sido liberada e o que tinha sido incluído naquela versão. Para dar uma resposta rápida e exata, eu precisava ter estas informações à mão perto de minha estação de trabalho. Solução: Imprimir um resumo de cada release contendo data, novidades e versão liberada e deixar estas folhas presas na parede na frente da minha mesa - guardadas onde elas seriam usadas.

Outros exemplos:

Se há uma instrução de como desligar o ar condicionado, imprima-a e coloque-a perto do aparelho ao invés de deixá-la na rede da empresa. O mesmo vale para instruções de como fechar porta, fazer café, normas de codificação, etc..

Se há um esqueleto padrão de arquivo fonte, ponha-o bem pertinho de onde os arquivos fonte vão ficar. Junto com os fontes, no sistema de versionamento, num diretório chamado skels, por exemplo.

Guarde todo o material necessário para o seu projeto de software no mesmo sistema de versionamento e faça com que seja fácil (com um comando) baixar tudo quando necessário.

Teste tudo que você faz. 
Teste cedo, teste frequentemente. Se tem 3 linhas, já devia ter sido testado. Se tem um if, já devia ter passado por 2 testes.

O que você testa, funciona de primeira; o que você não testa, dá pau. Precisa dizer mais?

sábado, 2 de julho de 2011

Sistemas de Qualidade de Software

Muito se fala em sistemas de Qualidade de Software (em geral, o pessoal gosta de maiúsculas no nome). Um sistema muito discutido e aplicado no Brasil tem sido o CMM. Infelizmente, poucas vezes se retorna às origens deste sistema para ver de onde surgiu e que problema ele visava resolver. Também poucas vezes se fala a respeito do sucesso ou fracasso deste sistema no mundo de desenvolvimento de SW. Vamos dar uma olhada rápida (e com uma dose de parcialidade) nisto.

O CMM começou com o Departamento de Defesa Americano, como uma ferramenta para avaliação da maturidade de seus fornecedores, em meio a uma crise no gerenciamento e na qualidade do que era recebido. Notem que ele foi concebido como uma ferramenta de avaliação (por conseguinte, externa, para ser usada pelo contratante) e não como uma ferramenta de trabalho das empresas contratadas (que produzem o software). Muito tempo se passou desde então. Hoje em dia, não sei se o DOD ainda usa CMM. Mas sei que muita gente propõe que o usemos como metodologia para produzir software melhor. Pode ser que se consiga, mas até hoje, ainda não presenciei casos de sucesso real. E tenho minhas dúvidas se um modelo originalmente concebido como ferramenta de avaliação realmente é suficiente ou adequado para promover melhorias na qualidade.

Sobre sucessos e fracassos, sempre ouço que a indústria indiana de software tem um grande número de empresas com certificações CMM altas e que isto comprova a importância do CMM. Pode ser. Minha experiência com tais empresas é muito limitada: Resume-se ao fato de que ouvi muitos clientes americanos reclamarem da baixa qualidade do software de companhias indianas, tanto em relação à qualidade intrínseca (correção, desempenho, etc) quanto a prazos. Claro que não devemos generalizar, mas é o que ouvi. A outra experiência que tive foi de uma empresa em que trabalhei e que não tem (ou ao menos não tinha na época) CMM e foi contratada para desenvolver um software que estava há 2 anos nas mãos de uma indiana certificada CMM. Tal software, mesmo após estes 2 anos de trabalho, ainda não era minimamente funcional e apresentava um número elevado de bugs registrados. A decisão tomada logo que minha companhia entrou em cena foi abandonar totalmente a base de código herdada e recomeçar o projeto do zero. Aceitamos o desafio e o projeto foi concluído em aproximadamente 6 meses, com índices de bug baixíssimos (zero críticos, zero graves, três cosméticos) e com funcionalidades mais avançadas que as do projeto original.

E aí me perguntam se eu gosto de Qualidade de Software. Bom, eu prefiro software de qualidade...

LGM

quinta-feira, 16 de junho de 2011

O conjunto básico

E que tal se a gente conversasse hoje sobre o que são as coisas fundamentais num projeto de software?

Todo projeto de software deve conter os itens listados abaixo. Quanto mais cedo eles forem criados, melhor. Recomendo até que eles sejam criados / adicionados na ordem em que os listo.

  • Sistema de bug tracking. Exemplos: Bugzilla, Mantis, etc.
    Por que deve ser o primeiro item? Porque bugs ocorrem desde o primeiro dia de trabalho no projeto. Pode ser um erro em algum diagrama, algum problema que um programador encontra no código de outro como resultado de uma revisão, etc. Se ganha muito se isto for registrado de modo centralizado e CONCISO.
    Deve ser um sistema orientado para desenvolvedores, de modo a ajudar não só no controle, mas no processo de correção, ou seja:
    • Deve ter, no mínimo, os estados New (não corrigido ainda), Fixed (bug corrigido, correção comitada no source control) e Verified (um release-build que contém a correção foi criado, testado para o bug e não se viu o dito cujo ocorrer novamente). Outros estados, embora não sejam fundamentais, ajudam muito. Exemplos: Assigned (tem alguém corrigindo o bug - isto evita que, em grupos grandes, dois desenvolvedores trabalhem na mesma coisa sem saber), Closed (confirmado pelo usuário que reportou o bug que ele não acontece mais).
    •  Os desenvolvedores precisam poder criar entradas neste sistema. Parece mentira, mas tem muita equipe que acha que desenvolvedor não acha bug... Surpresa: Eles acham MUITOS. E normalmente os acham MUITO ANTES que o software entre em uma fase de testes.
    • Deve permitir que os desenvolvedores coloquem anotações acerca da investigação das causas do bug no próprio bug report. (As seguintes Regras de Ouro se aplicam: "Não separe o que deve estar junto.", "Guarde as coisas perto de onde elas são usadas."
  • Sistema de versionamento de fontes. Exemplos: subversion, git, cvs.
    Deve ser colocado no ar bem cedo, pois devemos ter o histórico desde o início. Muita gente mostra resistência em colocar material no sistema de versionamento antes de ter "algo que funcione". Não é uma boa idéia. Vença a resistência e comece a versionar no primeiro dia.
    Detalhes importantes: 
    • Não existe um sistema de versionamento que seja absoluto. Cada método de trabalho casa melhor com um sistema diferente. Não se renda a modismos na hora de escolher. Escolha com a cabeça.
    • Um bom versionamento implica em ter tudo no mesmo sistema e na mesma árvore de diretórios. Um comando de checkout único deve baixar todo o material necessário para trabalhar no projeto.
  • Build automatizado e integração contínua. Mesmo antes de ter um software funcional (antes de começar a programar), ponha no ar um sistema de integração contínua e automatize o processo de build. O que se quer é que se produza os deliverables do software com apenas um comando e, ao se commitar algo no sistema de versionamento, o sistema de integração contínua faça o build e testes sozinho. Importante: Considero parte dos deliverables a geração de tags no sistema de versionamento, de modo a permitir a recuperação dos fontes usados em cada build.
    Na fase inicial do projeto, ainda não temos código, mas já devemos fazer um sistema automático que gere o pacote de instaladores, documentos (manual do usuário, etc), mesmo que gere um instalador com um executável tipo "return 0", que não faz nada.
  • Unit tests. Automatizados, de preferência com possibilidade de rodá-los automaticamente durante o processo de build. Os unit tests devem ser aprimorados ao longo do projeto, por exemplo, quando se encontra um bug (ver O procedimento de correção de bug). Importante: Unit test é feito e rodado pelos desenvolvedores. Pode (e deve, sempre que possível) ser rodado automaticamente no build também; mas o fundamental é que os desenvolvedores possam rodar cada unit test de modo simples e na sua cópia de trabalho dos fontes.
  • Número de versão no software. Sem números de versão não tem como ter rastreabilidade do software. Não tem como saber o que foi testado; não tem como saber quando bugs foram corrigidos. Mais detalhes no artigo Identificadores de Versão.
  • Testes funcionais. Deve-se ter uma rotina de testes funcionais bem definida e executá-los seguidamente ao longo do projeto. Detalhes importantes: 
    • O ambiente de teste (máquinas, dispositivos, etc) deve ser diferente do usado no desenvolvimento. Testes na máquina do desenvolvedor sempre funcionam (a não ser que seja um desenvolvedor muito relapso)...
    • Usar sempre o material gerado nos builds automatizados para os testes. Nunca rode testes funcionais oficiais usando material gerado nas máquinas de desenvolvedores.

segunda-feira, 2 de maio de 2011

Assinaturas padrão dos operadores de stream

Sempre esqueço da assinatura padrão dos operadores de inserção e remoção do C++. Então resolvi anotar aqui:

std::ostream& operator<<(std::ostream& s, const MyData& data);
std::istream& operator>>(std::istream& s, MyData& data);


Um exemplo de implementação do >>:


istream& operator>>(istream& s, MyDataEnum& data)
{
    int v;
    s >> v;
    
    if (s) //Conversion OK.
        data = MyDataEnum(v);

    return s;
}

segunda-feira, 7 de março de 2011

Orientação a objetos

Prezados.

O preceito mais básico da Orientação a Objetos é ter dados e operações dentro da mesma unidade lógica (no caso, a classe). Isto também se diz da seguinte forma: Os objetos devem ser responsáveis por si próprios; eles é que devem realizar as operações sobre seus dados internos.

Outro preceito importante é o de encapsulamento de dados ou information hiding. É até anterior à OO, importante também em outros paradigmas de programação. Trata-se de não expor detalhes de como uma entidade é implementada; expor apenas a interface da entidade. Em OO, isto significa, ter poucos (idealmente, nenhum) dados públicos (no sentido semântico de públicos). Um excelente livro sobre programação que eu li, e agora me escapa qual, dizia a este respeito que devemos seguir o conselho ouvido na infância: "Don't show your private parts."

Perco as contas de quantas vezes vejo isto tudo ser deixado de lado. Por exemplo quando fazem algo assim:


class Horario
{
  unsigned hora;
  unsigned minuto;
public:
  unsigned getHora();
  unsigned getMinuto();
  void setHora(unsigned hora);
  void setMinuto(unsigned minuto);
};

main()
{
  Horario t0, t1, t2, t3;
  t0.setHora = 10;
  t0.setMinuto = 10;

  // um pouco mais de código, inicializando tn e fazendo sabe-se mais o que....

  // então imprime alguns t:
  cout << t2.getHora() << ':' << t2.getMinuto() << endl;
  cout << t3.getHora() << ':' << t3.getMinuto() << endl;
}


estão jogando no lixo os preceitos mencionados acima.

O information hiding foi pro vinagre, pois embora os membros não sejam declarados como públicos, semanticamente eles são totalmente públicos: usuários da classe podem fazer o que bem entenderem com eles... Os getXXX e setXXX geram o problema.

Além disso, o preceito básico da OO foi pro espaço também. Notem que as operações (inicialização e "impressão") sobre os objetos estão sendo feitas FORA do objeto (fora da classe).

Melhor seria algo como:


class Horario
{
  unsigned hora;
  unsigned minuto;
public:
  Horario(unsigned h, unsigned m): hora(h), minuto(m) {};
  string paraString()
   {/*converte para string no formato hh:mm, possivelmente usando ostringstream*/};
  //Eventualmente outras operações com Horarios, mas nada de getXXX ou setXXX
 };

main()
{
  Horario t0(10,10);
  Horario t2(5,11);
  Horario t3(23,55);

  // um pouco mais de código, inicializando tn e fazendo sabe-se mais o que....
  // então imprime alguns t:
  cout << t2.paraString() << endl;
  cout << t3.paraString() << endl;
}
Ou, para este caso de "impressão", usando um operador de inserção em stream:
 
class Horario
{
  friend ostream& operator<<(ostream& s, const Horario& h);
  unsigned hora;
  unsigned minuto;
public: 
  Horario(unsigned h, unsigned m): hora(h), minuto(m) {};
  //Eventualmente outras operações com Horarios, mas nada de getXXX ou setXXX
 };

ostream& operator<<(ostream& s, const Horario& h)
{
  s <<  h.hora << ':' << h.minuto;
  return s;
}

main()
{
  Horario t0(10,10);
  Horario t2(5,11);
  Horario t3(23,55);

  // um pouco mais de código, inicializando tn e fazendo sabe-se mais o que....

  // então imprime alguns t:
  cout << t2 << endl;
  cout << t3 << endl;
}

IMPORTANTE!!!!!! Nesta implementação acima, a classe e o operator<< devem estar no mesmo módulo. Ou seja, as declarações de ambas no mesmo .h (horario.h, por exemplo) e as implementações de ambas no mesmo .cpp (horario.cpp, por exemplo). Notem que quebramos um pouco do information hiding nesta implementação, já que o operator<< precisa acessar os dados privados da classe. Quer dizer, a classe não mostra as suas "partes privadas" para todo mundo, mas mostra para os "amiguinhos". Não é o ideal e não me agrada, mas até pode ser aceitável em algumas situações, desde que classe e operador tenham uma "união estável" bem forte e a classe tenha poucos "amiguinhos" (entidades friend).


Eu prefiro uma implementação que inclui o encapsulamento da paraString() e a conveniência do operador<<: Implemento o operator<< chamando o paraString(). Neste caso, a declaração de friend fica desnecessária.



sábado, 26 de fevereiro de 2011

As "Regras de Ouro" do desenvolvimento de software

  1. Não se repita.
  2. Não separe o que deve estar junto.
  3. Não junte o que deve estar separado ou coisas que não estão relacionadas.
  4. Guarde as coisas perto de onde elas são usadas.
  5. Teste tudo que você faz. (O que você testa, funciona de primeira; o que você não testa, dá pau.)

quarta-feira, 23 de fevereiro de 2011

Para quem se escreve o código-fonte?

Bom, todo (ou quase todo) software que escrevemos possui código-fonte. Mas quando estamos escrevendo este código quem devem ser os "leitores" a quem o dirigimos? Costumo dizer que a resposta dada por um programador para esta pergunta (e o quanto ele aplica a resposta na prática) definem o nível de habilidade deste programador.

Os programadores mais iniciantes ou tacanhos pensam que se deve escrever para que o computador entenda e isto basta. Programadores avançados sabem que se precisa escrever para outros programadores lerem. O que passa desapercebido é que devemos ser claros na nossa escrita e, para isso, devemos ter um vocabulário rico.

Em programação, muitas vezes se pode escrever operações ou expressões diferentes que levam a um mesmo resultado. Usar estas diferenças para expressar melhor o que o programa faz e como o programador pensou na solução é o que eu chamo de ter um vocabulário rico. Aumentamos o nosso vocabulário de programação do mesmo jeito que o de português: estudando, lendo bons textos (no caso, lendo programas), escrevendo com cuidado e pedindo para pessoas experientes revisarem nossos "textos".

Em programação, muitas vezes se usa o termo "idioma" para se referir às peças de vocabulário. Em outros artigos, vou dar alguns exemplos de idiomas em C++, procurando mostrar diferentes idiomas cujos resultados são idênticos para o computador, mas que dizem coisas diferentes para quem lê o código.

Isto me lembra de uma discussão recorrente que tenho com programadores: Eu dizendo "Isto está errado." e o programador respondendo "Mas funciona, logo está certo". Nem tudo que funciona está certo, justamente porque o código pode estar fazendo a coisa certa, mas dizendo a coisa errada. Ou seja, está escrito para o computador, mas não para os leitores humanos.

Vamos a alguns exemplos:

int some16BitVal;
sint_16 some16BitVal;

Mesmo que sint_16 seja um define ou typedef para int, a linha de cima está errada. Quando se escreve int, se está dizendo: preciso de um inteiro que seja o mais eficiente possível nesta arquitetura, mas não me importa o seu tamanho. Se precisamos de tamanho definido, o correto é expressar isto com um tipo.

Outra bem comum: Aproveitar uma constante para dois usos só por que, coincidentemente elas têm o mesmo valor. Se você usa uma mesma constante (e.g. const int mySize=33;) em vários pontos do código, você está dizendo que obrigatoriamente e irrevogavelmente todos aqueles pontos estão ligados e que mudando num todos tem de ser mudados juntos, mantendo todos sempre o mesmo valor.




terça-feira, 22 de fevereiro de 2011

Backup de blog

O comando

wget -e robots=off -E -H -k -K -p -r -l 1 http://lg-fotos.blogspot.com

parece fazer um backup completo de um blog.

sexta-feira, 21 de janeiro de 2011

"The miserable programmer paradox"

O artigo hoje não é meu. Este post é para apontar para um post de outrem.

Me enviaram o endereço the-miserable-programmer-paradox há alguns dias e no fim de semana consegui ler. Nada mais verdadeiro do que o que ele diz.

E uma grande verdade que todos os envolvidos com desenvolvimento de software deveriam aprender está escrita lá no meio: "A train of thought is a fragile thing". Programação e interrupções ao programador simplesmente não combinam. ;-)

Abraços e até o próximo.
Publicar postagem

sábado, 8 de janeiro de 2011

Identificadores de versão

O assunto de hoje é identificadores de versão. Os famosos v1.0, v1.2.5.9, etc.

Todo mudo já viu estes números em software e a maioria acha que entende bem como funcionam e para que servem. Nem todos... Muitos tem uma vaga idéia; alguns tem uma idéia melhor, mas não sabem como incrementar cada campo e poucos realmente sabem qual a real utilidade destes números e quais são os mecanismos lógicos de avançar os números de versão de um software.

Vamos aos detalhes.

Primeiro, por que se tem números de versão? A resposta é simples: Para termos rastreabilidade do software em campo. E o que é isto? É se poder identificar de forma inequívoca qual versão de um programa um usuário estava rodando (por exemplo, para poder auxiliar o usuário ou reportar/corrigir um bug). Sabendo-se o identificador de versão (VID), deve-se ser também capaz de recuperar os arquivos fonte exatos que geraram aquela versão do software (provavelmente usando tags ou baselines de seu sistema de versionamento de fontes). Isto mostra que temos que garantir que nosso software nunca repetirá um VID, ou seja, se mudamos qualquer coisa no software, temos de mudar o VID.

Há várias estratégias e formatos de VID possíveis que garantem a rastreabilidade e alguns ainda dão indicações de compatibilidade entre versões do software. Eu vou explicar o mecanismo de 4 dígitos comumente usado, pois ele é simples de entender e amplamente difundido. Neste mecanismo, os VIDs têm a forma M.n.r.b ou M.n.r-b, onde M, n, r e b são números naturais (0, 1, 2, 3...). M é chamado de Major, n é o minor, r é o release (também chamado de patch level e abreviado como p) e b é o build.

Como queremos que qualquer mudança no software cause mudanças no VID, vamos incrementar o build number cada vez que buildarmos o software (ou seja, gerou os deliverables -- instaladores, executáveis, etc -- do software, incrementa o build number). Notem que o build number será incrementado SEM TER HAVIDO ALTERAÇÕES NOS FONTES! Alguns vão perguntar: Ué, mas sem alterações nos fontes, então nada mudou e não precisa mudar o VID... Errado! O tempo passou, a máquina em que o build foi realizado envelheceu (pode ter sofrido atualizações de software...), eventualmente até estamos buildando em uma máquina diferente. Ou seja, muita coisa pode ter mudado. Por isso, incrementa-se o build number e assim se tem certeza que não teremos duas cópias com mesmo VID e comportamento diferente. Notem que o processo de incrementar o build number pode e deve ser automatizado. O próprio conjunto de scripts de build ou ferramentas de build deve se encarregar de incrementar o build number, evitando esquecimentos. Detalhe importante: Mesmo que se mude de máquina, o build number tem de ir sempre crescendo, quer dizer, temos de bolar um jeito de que os scripts guardem qual o último build number usado em algum lugar compartilhado e o incrementem. A solução clássica é guardar o buil number (e o resto todo do VID) no repositório de fontes (sistema de versionamento de fontes).

E quando mudamos algo nos fontes? Bom, aí precisamos de um incremento em alguma parte além do build number. Se a alteração foi apenas uma correção de bug ou alteração interna, sem mudança nas funcionalidades do software, se incrementa apenas o release number. Ao incrementar o release number, se volta o build number para zero. Isto faz com que a seqüência de VIDs fique do tipo M.n.0.0, M.n.0.1, M.n.0.2, M.n.1.0, ...

Se houve alteração ou adição de funcionalidades, devemos incrementar o minor ou o major, dependendo do tipo de alteração. O minor é incrementado se podemos garantir que não houve quebra de compatibilidade na nova versão. Isto normalmente ocorre se só houve adições ao software e tudo que se podia fazer antes continua valendo (por exemplo, ler na nova versão os arquivos gerados com a versão anterior, executar as mesmas operações que eram possíveis anteriormente, etc).

Se ocorre quebra de compatibilidade, devemos incrementar o major number. Muitos recomendam também incrementar o major number quando houve muitas alterações no software, mas há que se olhar esta recomendação com certo cuidado, pois é difícil definir o que são "muitas"...

Resumindo a conversa:
  • Os números de versão existem para permitir rastreabilidade. 
  • Há muitos procedimentos (especificações) de como atualizar números de versão que funcionam, garantindo a rastreabilidade (e existem também alguns escritos por aí que não funcionam, portanto, caveat...). 
  • Um mecanismo lógico possível de ser usado estabelece um identificador de versão de 4 números (major, minor, release e build) incrementados da seguinte forma:
    • No mínimo o build number é incrementado sempre que se reconstroi o software.
    • No mínimo o release number é incrementado se houve alterações nos fontes.
    • No mínimo o minor number é incrementado se houve alterações de funcionalidade no software.
    • O major number é incrementado se houve alterações no software com quebra de compatibilidade.
PS.: Se fala em "compatibilidade para trás" (backward compatibility). Para explicar este conceito, o melhor jeito é usando um exemplo. Imagine que você usa a versão 1.1 de um editor de textos para criar alguns arquivos. Logo a seguir, você instala a versão 1.2 deste editor e cria novos arquivos com ela. Se diz que há backward compatibility se a versão 1.2 do editor conseguir abrir sem problemas os arquivos da 1.1. O  contrário, ou seja, abrir os arquivos da 1.2 usando a 1.1 pode não ser possível, e, por isso, se fala em "para trás". No esquema de versionamento descrito neste artigo, alterações no minor number indicam que há compatibilidade para trás.

PS2: No caso das duas versões do software abrirem arquivos uma da outra, independentemente de qual é mais nova, têm-se compatibilidade total. No esquema de versionamento descrito neste artigo, alterações no release number indicam compatibilidade total.

PS3: O exemplo de abrir arquivos de outras versões é APENAS UM EXEMPLO DIDÁTICO para explicar o conceito de compatibilidade. Na realidade, todas as funcionalidades do software devem ser levadas em conta e cada tipo de software tem coisas diferentes a serem avaliadas. Exemplos: Uma biblioteca com backward compatibility significa que programas linkados com uma versão mais antiga vão funcionar com a mais nova; no caso de servidores, os clientes que sejam feitos para a versão mais antiga do servidor devem continuar funcionando com a versão mais nova; e por aí vai.

Abraços e até o próximo artigo.
LGM