Não conviva com janelas quebradas

August 29, 2010

Um dos conselhos mais interessantes que li no The Pragmatic Programmers é esse: Não conviva com janelas quebradas.

O que isso quer dizer? Bom, os autores se apoiaram em um artigo (WK82) que cita a seguinte experiência: Em 1969, Philip Zimbardo, um psicólogo de Stanford, deixou um automovel não emplacado no Bronx, e outro equivalente em Palo Alto – California. O primeiro começou a ser atacado por vandalos em 10 minutos, e em 24 horas havia sido completamente depenado. Já o carro de Palo Alto permaneceu intocado por uma semana, quando então Zimbardo quebrou uma parte do carro com uma marreta e após isso, em poucas horas o veículo havia sido totalmente destruído por vandalos.

Enquanto algo está sendo bem cuidado, as pessoas tendem a mante-lo assim por que sabem que existe alguem preocupado com a integridade daquilo. Mas à partir do momento em que veem algum problema que foi deixado de lado (uma janela quebrada), as pessoas começam a se preocupar menos por que tem a impressão de que ninguem está se importando.

Em desenvolvimento de software, isso tambem vale. Uma janela quebrada seria as famosas gambiarras, uma falha na arquitetura, na documentação, um ponto do projeto que não se adequa aos padrões estabelecidos pela equipe, um bug, uma decisão tomada equivocadamente no passado, um problema de usabilidade, etc. Se a equipe enxerga um problema e não o resolve imediatamente, outros passarão a ter menos cuidado com o projeto e consequentemente outros problemas aparecerão.

1

Obtenha o máximo de informações das exceções

August 15, 2010

Estou experimentando o Google Guice em um projeto que estou desenvolvendo em Java, estou tentando configura-lo para injetar o Session do Hibernate nos meus serviços, e por algum motivo estou recebendo uma exceção ao instanciar o SessionFactory e esta é a mensagem que estou recebendo:

[ERROR]1) Error in custom provider, org.hibernate.InvalidMappingException: Could not parse mapping document from resource Cliente.hbm.xml
[ERROR] at br.com.crystalpoll.hibernate.HibernateModule.configure(HibernateModule.java:10)
[ERROR] while locating org.hibernate.Session
[ERROR] for parameter 0 at br.com.crystalpoll.domain.cliente.Clientes.(Clientes.java:12)
[ERROR] while locating br.com.crystalpoll.domain.cliente.Clientes
[ERROR] for parameter 0 at br.com.crystalpoll.ui.server.customer.listing.GetCustomersHandler.(GetCustomersHandler.java:45)
[ERROR] while locating br.com.crystalpoll.ui.server.customer.listing.GetCustomersHandler

Tudo que ele me permite saber sobre o erro do Hibernate é “org.hibernate.InvalidMappingException: Could not parse mapping document from resource Cliente.hbm.xml”.

E quanto ao stack trace da exceção? E quando às exceções primárias (a cadeia de exceções que foi sendo lançada e que culminou nessa org.hibernate.InvalidMappingException)?

Não sei se quem engoliu a exceção foi o Guice (aparentemente foi) ou o Gwt-Platform, um outro framework que uso nesse projeto e que em outros momentos já mostrou esse comportamento. De qualquer forma, aí vão duas recomendações:

  • Sempre preserve a causa primária das suas exceções. Isto é, se você capturou uma exceção X e em função disso você disparou uma exceção Y, procure manter uma referência a X em Y. Java sempre suportou isso, e o Delphi recentemente passou a suportar tambem. Isso ajuda muito na hora de interpretar o erro e descobrir o que está errado
  • Se você for logar uma exceção, jamais se esqueça de logar a pilha de chamadas que levou àquela exceção e tambem às que as originaram. O Delphi infelizmente não tem esse recurso out-of-the-box (o que para mim é uma vergonha), mas é possível conseguir isso com pacotes de terceiros como o madExcept ou EurekaLog.
0

Seja poliglota

July 31, 2010

Eu já li em alguns lugares que aprender um novo idioma faz bem para a nossa saúde mental. E aprender uma nova linguagem de programação também pode ser benéfico para a carreira profissional.

A idéia é que linguagens diferentes te obrigam a pensar de formas diferentes para resolver o mesmo problema, ou seja, isso evita que você fique preso a uma única forma de pensar. e eventualmente você aproveitará coisas de uma linguagem na outra. Eu iria pegar a minha edição de “Pragmatic Programmers” para copiar o trecho onde um dos autores comenta sobre isso (Se bem me lembro, ele falava sobre C++ e Smalltalk), mas vou ficar devendo pois o livro está guardado em alguma caixa por aí (Preciso comprar uma estante logo).

Mas se você já sabe Java, não adianta querer estudar C# (ao menos não em relação ao que estamos falando aqui). A idéia é que sejam linguagens de paradígmas diferentes daqueles que você já domina. E não precisa ser algo que você vá aplicar no dia-a-dia, o objetivo é apenas expandir os seus horizontes.

3

Minhas impressões sobre TDD

July 4, 2010
Tags:

Como diz o ditado: Antes tarde do que nunca. O Test-Driven Development (Desenvolvimento orientado a testes) já existe há uns bons anos e eu só lia a respeito sem nunca ter colocado em prática.

Após participar do Agile Brazil 2010 e ter assistido a algumas palestras sobre o tema, decidi começar a aplicar essa técnica em um projeto que já venho desenvolvendo há algum tempo. Após ficar nisso por uma semana, decidi expor as minhas impressões sobre essa experiência:

  • O design do código que eu fiz com TDD é muito superior ao código que eu fazia antes. O código é mais fácil de ler e dar manutenção.
  • TDD requer uma mudança de pensamento. Não dá pra usar TDD mantendo a mesma mentalidade que usávamos antes.
  • Até agora me saí melhor usando TDD em um framework, do que em projetos de aplicações web ou desktop que possuem interface com o usuário. Preciso estudar mais MVP ou algo do gênero!
  • 2

Campanha anti-if

June 18, 2010

A campanha anti-if visa eliminar o uso inadequado da instrução IF. Pode parecer bizarro a princípio, mas se pararmos para pensar a respeito vemos que faz sentido. Vejamos um exemplo retirado do site da campanha.
Primeiro, com if:

/* Exemplo em Java */
// Bond class
double calculateValue() {
  if(_type == BTP)  {
    return calculateBTPValue();
  } else if(_type == BOT) {
    return calculateBOTValue();
  } else {
    return calculateEUBValue();
  }
}
{ Exemplo em Delphi }
// TBond class
function TBond.calculateValue: Extended;
begin
  if _type = BTP then
    Result := calculateBTPValue
  else if _type = BOT then
    Result := calculateBOTValue
  else
    Result := calculateEUBValue;
end;

E agora sem o dito-cujo:

/* Exemplo em Java */
// Bond class
double calculateValue() {
  _bondProfile.calculate();
}

// AbstractBondProfile class
abstract double calculate();

// classe BTPBondProfile >> AbstractBondProfile
double calculate() {
  ...
}

// classe BOTBondProfile >> AbstractBondProfile
double calculate() {
  ...
}

// classe EUBondProfile >> AbstractBondProfile
double calculate() {
  ...
}
{ Exemplo em Delphi }
// TBond class
function TBond.calculateValue: Extended;
begin
  Result := _bondProfile.calculate;
end;

// TAbstractBondProfile class
type
  TAbstractBondProfile = class abstract
  public
    function calculate: Extended; virtual; abstract;
  end;

// classe TBTPBondProfile >> AbstractBondProfile
function TBTPBondProfile.calculate: Extended;
begin
  ...
end;

// classe TBOTBondProfile >> AbstractBondProfile
function TBOTBondProfile.calculate: Extended;
begin
  ...
end;

// classe TEUBBondProfile >> AbstractBondProfile
function TEUBBondProfile.calculate: Extended;
begin
  ...
end;

Através da orientação a objetos, eliminamos a necessidade do IF naquele código. Vantagens? Vamos lá:

  • Reusabilidade: A rotina de cálculo de cada “bond profile” (não me pergunte o que é isso, só estou utilizando a terminologia do exemplo retirado do site da campanha) foi implementada separadamente umas das outras, e separadamente da classe Bond. Isso permite que estes cálculos sejam reutilizados de uma forma que não seria possível no primeiro exemplo.
  • Legibilidade: O método calculateValue() da classe Bond ficou muito mais simples. Antes haviam vários possíveis caminhos de execução, e agora temos apenas um. Isso torna muito mais fácil ler e entender o código.
  • Adaptabilidade: Ficou muito mais simples adaptar o código para a inclusão de novos “bond profiles”. Olhe no método calculateValue() da classe Bond e imagine que aquele _bondProfile é obtido dinamicamente, podemos então adicionar novos tipos de “bond profile” sem modificar a classe Bond, isso torna muito simples adaptar o sistema a novas regras de negócio. Com isso, estamos aderindo ao chamado “Princípio aberto/fechado”, que diz que uma classe deve ser aberta para extensão, mas fechada para modificações. Ou, em outras palavras: Deve ser possível extender uma classe sem a necessidade de modificar o código fonte da mesma.

Vale ressaltar que o objetivo da campanha não é eliminar o uso do IF, pois existem situações onde seu uso é correto. Por exemplo:

/* Extemplo em Java */
public void efetuarVenda(Cliente cliente) {
  if (cliente.bloqueado())
    throw new ClienteBloqueadoException();

  // Prossegue com a venda
 .....
}
procedure TVenda.efetuarVenda(Cliente cliente);
begin
  if cliente.bloqueado then
    raise EClienteBloqueado.Create;

  // Prossegue com a venda
  ....
end;

Para quem se interessar, neste link pode ser obtido o código para inserir o logotipo em seu site.

Editado em 19/06/2010 para adicionar exemplos em Delphi

4

Boas práticas sobre exceções

June 13, 2010

O suporte a exceções se tornou comum nas linguagens de programação modernas. Este mecanismo permite tratar erros e situações inesperadas de forma muito elegante.

Sem esse recurso, as funções sinalizavam erros através do seu retorno ou pelo uso de alguma variável global, era mais ou menos assim:

const
  ERRO_DIVISAO_POR_ZERO = 0;

function Dividir(Dividendo, Divisor: Integer): Extended;
begin
  if Divisor = 0 then
    Result := ERRO_DIVISAO_POR_ZERO
  else
    Result := Dividendo/Divisor;
end;

Ou seja, convencionou-se um valor que ao ser retornado pela função, indica uma condição de divisão por zero.

Toda vez que a função for chamada, devemos testar esse valor. No final, nosso código vira algo como:

procedure Teste;
begin
  Retorno = FazAlgo;
  if Retorno = ERRO then
  begin
    ShowMessage('Ocorreu um erro');
    Exit;
  end;
end;

Eu estou lendo o livro “The Pragmatic Progammer – From journeyman to mater” (Em português, “O Programador Pragmático – De aprendiz a Mestre”). Hoje cheguei num ponto onde ele fala justamente do que eu escrevi acima. Vou transcrever o exemplo do livro:

if (socket.read(name) != OK) {
  retcode = BAD_READ;
}
else {
  processName(name);
  if (socket.read(address) != OK) {
    retcode = BAD_READ;
  }
  else {
    processAddress(address);
    if (socket.read(telNo) != OK) {
      retcode = BAD_READ;
    }
    else {
      // etc, etc...
    }
  }
}
return retcode;

Como diz o livro: “A lógica normal do seu programa pode acabar sendo totalmente obscurecida por tratamentos de erros”.
Usando exceções, isso poderia ser reescrito assim: (Novamente, transcrevendo código do livro)

retcode = OK;
try {
  socket.read(name);
  process(name);

  socket.read(address);
  processAddress(address);

  socket.read(telNo);
  // etc, etc
}
catch (IOException e) {
  retcode = BAD_READ;
  Logger.log("Error reading individual: " + e.getMessage());
}
return retcode;

Eu iria um pouco mais além: Repare que a rotina acima ainda usa códigos de retorno para sinalizar erros. Ou seja, quem estiver chamando ela, terá que fazer um tratamento de condições de erro semelhante ao que vimos antes, o que poderia acabar levando ao mesmo tipo de código confuso do primeiro exemplo do livro. Eu adotaria uma das duas estratégias seguintes:

  • Não capturar IOException. Ou seja, quando alguma daquelas chamadas gerar um IOException, a exceção poderia ser capturada pelo chamador da rotina. Ficaria mais ou menos assim:
    public void processIndividualData(Socket socket) {
      socket.read(name);
      process(name);
    
      socket.read(address);
      processAddress(address);
    
      socket.read(telNo);
      // etc, etc
    }
    
    public void teste() {
      Socket socket = ....
      try {
        processIndividualData(socket);
      }
      catch (IOException e) {
        // trata a exceção aqui
      }
    }
    
  • Encapsular IOException em alguma outra exceção. Ficaria assim:
    public void processIndividualData(Socket socket) {
      try {
        socket.read(name);
        process(name);
    
        socket.read(address);
        processAddress(address);
    
        socket.read(telNo);
        // etc, etc
      }
      catch (IOException e) {
        throw new OutraException(e);
      }
    }
    
    public void teste() {
      Socket socket = ....
      try {
        processIndividualData(socket);
      }
      catch (OutraException e) {
        // trata a exceção aqui
      }
    }
    

    Isso pode parecer desnecessario, mas vou explicar a importancia de esconder certas exceções:
    Imagine que você implementou um DAO (Data Access Object) para abstrair a persistencia de objetos Cliente no seu sistema. Você teria uma interface assim:

    public interface ClienteDao {
      Cliente[] listarTodos();
    }
    

    E digamos que você tenha uma implementação desse DAO que usa um banco de dados relacional como mecanismo de persistencia. Para acessar o banco de dados, utiliza JDBC.

    public class ClienteDaoJdbcImpl implements ClienteDao {
      public Cliente[] listarTodos() {
        // Implementação aqui
      }
    }
    

    As chamadas ao JDBC podem gerar exceções do tipo SQLException. Então o seu método ClienteDaoJdbcImpl vai repassar essa exceção para o chamador? Isso significa que o usuário do seu DAO saberá que o DAO é implementado para bancos de dados relacionais. O que aconteceria se você quisesse utilizar uma implementação da interface que usa arquivos Xml para persistir as informações? Não haveria nenhum SQLException mais, mas aí como ficaria o seu código que já conta com diversos tratamentos de SQLException onde o antigo DAO era utilizado? Uma das grandes vantagens do DAO é abstrair o mecanismo de peristencia (mas isso não é assunto para este post), só que esta vantagem foi perdida nesse caso. Para resolver este problema, podemos fazer algo assim:

    public class ClienteDaoJdbcImpl implements ClienteDao {
      public Cliente[] listarTodos() {
        try {
          // Implementação aqui
        }
        catch (SQLException e) {
          throw new DaoException(e);
        }
      }
    }
    

    Assim, não importa o que a implementação do DAO faça, você irá sempre se preocupar apenas em tratar DaoException.

Como vimos, o uso de exceções ajuda a melhorar a clareza do nosso código ao separar o tratamento de erros do fluxo normal do código.

Para finalizar, algumas recomendações:

  • Jamais “engula” exceções. Se você capturou uma exceção e não é capaz de trata-la, repasse-a para o chamador
  • Tenha um conjunto rico de exceções.
  • Exceções de baixo nível não devem ser passadas para camadas de mais alto nível. É o exemplo do ClienteDao acima.
0

Exemplo real de script de build para Delphi

June 6, 2010

No último post da série sobre integração contínua com Hudson e Delphi, mostrei como criar um script para automatizar o build de projetos Delphi. Aqui vou mostrar um exemplo real, que utilizo para compilar meus componentes.

@echo off
call "%D2010_ROOT%\bin\rsvars.bat"

echo ******************************
echo *** Compilando componentes ***
echo ******************************
msbuild Src\Componentes.dproj /p:config=Release
if %ERRORLEVEL% neq 0 Exit /b 1

echo ****************************************
echo *** Compilando pacote de design time ***
echo ****************************************
msbuild Src\ComponentesDsn.dproj /p:config=Release

Aqui não coloquei o caminho para o Delphi diretamente no script, ao invés disso eu faço referência através de uma variável de ambiente que eu criei, chamada D2010_ROOT. Na minha máquina, ela aponta para “C:\Arquivos de programas\Embarcadero\RAD Studio\7.0\”, sendo que eu posso rodar esse mesmo script em uma máquina com o Delphi instalado em outro caminho (Por exemplo, em um Windows em inglês, onde haverá “Program files” ao invés de “Arquivos de programas”), bastando ter a variável de ambiente declarada no Windows com o valor apropriado.

Isso abre espaço para uma recomendação: Nunca trabalhar com caminhos fixos nos projetos, pois isso pode eventualmente trazer problemas no projeto. Exemplo: Meus projetos Delphi estão todos localizados em “D:\Desenvolvimento\Delphi\Projetos\”, mas eu não posso ter esse caminho hard-coded dentro do meu projeto (mesmo que eu decida adotar o padrão de sempre trabalhar com essas pastas em qualquer máquina onde eu esteja) pois, por exemplo, isso me traria problemas com o Hudson (ele faz o check-out do projeto em uma pasta à escolha dele, ele não segue o seu padrão).

2

Integração contínua com Hudson e Delphi – Parte III

June 4, 2010

Finalmente chegamos na terceira – e última – parte da série “Integração contínua com Hudson e Delphi”.

Agora que já temos algum conhecimento básico do assunto e as ferramentas necessárias já estão instaladas, podemos finalmente colocar tudo em funcionamento.

Para poupar tempo, eu criei um projeto em Delphi sobre o qual poderemos trabalhar. Eu fiz em Delphi 2010, mas provavelmente não haverão grandes problemas em abrir em outras versões do Delphi (Quem encontrar alguma dificuldade pode entrar em contato comigo). Baixe o projeto aqui e descompacte-o na pasta DemoIC que você criou na segunda parte da série.

A integração contínua precisa que o build do projeto seja automatizado, e no momento nós não temos isso. Precisamos de um meio simples de executar o build em um único passo. Felizmente, o Delphi sempre teve um compilador de linha de comando, que poderia ser utilizado justamente para isso, e melhor ainda: Desde a versão 2007 ele trabalha em conjunto com o MSBuild, que é uma ferramenta de automação de builds da Microsoft, que torna o processo ainda mais fácil. Então tudo o que precisamos fazer é criar um script .bat que faça isso. Vamos lá:

  1. Crie um arquivo chamado Build.bat na sua pasta “DemoIC”.
  2. Abra o arquivo no Bloco de Notas e digite o seguinte código

    @echo off
    call "C:\Arquivos de programas\Embarcadero\RAD Studio\7.0\bin\rsvars.bat"
    msbuild "Src\Calculadora.dproj" /p:configuration="Release"

  3. Salve o arquivo

Com isso, temos uma forma simples de executar o build do nosso projeto. Vamos explicar o conteúdo deste arquivo:

  • A primeira linha serve para evitar que os outros comandos do script fiquem aparecendo na tela. É apenas uma questão visual, não interfere no funcionamento do script.
  • A segunda linha chama um arquivo .bat que acompanha o Delphi. Ele ajusta algumas configurações antes que possamos chamar o MSBuild. Eu recomendo que você crie uma variável de ambiente no computador que aponte para a pasta de instalação do Delphi, e edite o script de build para invocar o rsvars.bat com base nesta variável. Isso permite que o mesmo script de build possa ser utilizado em diferentes máquinas, cada uma com o Delphi instalado em um lugar diferente.
  • A terceira linha chama o MSBuild para finalmente compilar o projeto. Repare no parâmetro /p:configuration=”Release”. Você deve saber que os projetos no Delphi podem ter diferentes configurações de build, e este parâmetro serve justamente para especificarmos qual configuração será utilizada.

Agora, vamos adicionar estes arquivos no nosso sistema de controle de versão. Para isso, vá até a pasta DemoIC, selecione todos os arquivos e pastas exceto as pastas DCU, DCUTestes, Out e OutTestes. Clique com o botão direito do mouse em algum dos arquivos ou pastas que esteja selecionado, vá ao menu “TortoiseSVN”, e depois em “Add”.

Deve surgir uma tela como esta:

Remova a seleção das seguintes pastas e arquivos, pois eles não devem ser versionados:

  • DemoCalculadora.groupproj.local
  • Src/_history
  • Src/Calculadora.dproj.local
  • Src/Calculadora.identcache
  • SrcTestes/__history
  • SrcTestes/CalculadoraTests.dproj.local
  • SrcTestes/CalculadoraTests.identcache

Feito isso, clique “OK”. Surgirá uma tela mostrando os arquivos adicionados, e clique “OK” nela tambem. Após isso, clique com o botão direito em alguma área livre da pasta “DemoIC” e clique em “SVN Commit”.

Na tela que se abre, clique “OK”, e depois “OK” na tela seguinte. Pronto, agora todos os fontes do nosso projeto estão no Subversion.

Agora vamos criar uma tarefa no Hudson. Criar uma tarefa é como cadastrar o projeto no Hudson para que este controle o build do projeto.

Vamos lá, abra o Hudson. A tela inicial dele deve ser a seguinte:
Tela inicial do Hudson

Localize, no lado esquerdo da tela, o item “Nova Tarefa” e clique nele.

Você deve ver esta tela agora:

No campo “Nome da tarefa”, informe “DemoIC” (sem as aspas).
Marque o item “Construir um projeto de software free-style”. Isso nos permitirá configurar livremente a rotina de build do projeto. O Hudson conta com outras opções, que não serão utilizadas aqui.

Muito bem, agora clique “OK”.

Na tela seguinte, temos que informar mais alguns parâmetros do projeto. Primeiro, temos que dizer qual servidor de gerenciamento de código fonte será utilizado: Então, localize o item “Gerenciamento de Código Fonte” e selecione “Subversion”.
Ao selecionar “Subversion”, alguns novos campos aparecerão na tela, vamos então configura-los: No campo “URL do Repositório”, informe “https://localhost/svn/DemoIC/trunk” (sem aspas), esse é o endereço para o repositório do projeto, contendo tambem o caminho da pasta onde estão os fontes que o Hudson irá baixar.
Em “Diretório do módulo local (opcional)”, informe “DemoIC”. Quando o Hudson faz o download dos arquivos do repositório, ele irá colocar os arquivos em uma pasta com o nome que foi especificado aqui. Caso não informemos nenhum valor, ele irá utilizar o último nome da URL do repositório, que no nosso caso seria “trunk”.

Muito bem, agora já informamos ao Hudson como ele fará para obter o código fonte do projeto. Agora precisamos informa-lo como ele fará para compilar o projeto. Para isso, localize um combo escrito “Add build step” e clique nele. Será aberto um menu com as seguintes opções:

  • Executar shell
    Instrui o Hudson a executar um comando de shell do Linux
  • Invocar alvos Maven de alto nível
    Faz com que o Hudson invoque o Maven para realizar o build
  • Executar comando do Windows
    Semelhante à primeira opção, mas serve para ser utilizada no Windows.
  • Invocar ant
    Faz com que o Hudson invoque o Ant para realizar o build

Utilizaremos a terceira opção. Após seleciona-la, deve surgir na sua tela um campo como este:

Este campo funciona da seguinte forma: O que você digitar nele, será gravado pelo Hudson em um arquivo .bat que estará localizado em uma pasta um nível acima da pasta “DemoIC” na qual o Hudson colocará os fontes do projeto ao construir o mesmo. Esse arquivo .bat criado pelo Hudson será executado quando for o momento de realizar algum build. Então o que temos que digitar ali é apenas o código necessario para executar o arquivo Build.bat dentro da pasta DemoIC. Então apenas digite o seguinte:

cd DemoIC
call Build.bat

Finalmente, clique em “Salvar”.

Para testar se está tudo correto, clique em “Construir Agora”, isso deverá iniciar um build do projeto.

Se tudo foi feito corretamente, você deverá ver uma tela como esta:

Repare na cor da bolinha no “Histórico de Construção”. Azul indica um build bem sucedido, enquanto vermelho indica o contrário.

Mas precisamos automatizar ainda mais o processo de build, pois isto deve ocorrer a cada commit do código fonte. Felizmente, o Hudson nos dá um mecanismo simples e eficiente de dispararmos o build de forma automatizada. Basta acessar a URL http://localhost:8080/hudson/job/DemoIC/build. Não é necessario nenhuma outra ação, apenas dar um GET nesta URL já faz com que um build seja disparado.

O Subversion nos dá outro recurso que será fundamental neste momento, são os chamados hooks. Hooks são comandos que você pode programar o Subversion para executar automaticamente em determinadas situações. No nosso caso, vamos utilizar um post-commit hook, onde poderemos definir uma ação a ser executada sempre após um commit.

Mas ainda falta uma peça neste quebra-cabeças: Como disparar um GET naquela URL do Hudson que mostrei anteriormente? Para fazer isso, eu uso esta ferramenta. Se preferir, aqui está um link direto para download.

Tendo baixado a ferramenta, coloque-a em uma pasta de sua preferência e renomeie o arquivo para wget.exe caso ele tenha vindo com outro nome. Não é necessário instalar. Após isso, coloque a pasta que você escolheu no path do Windows e reinicie o computador.

Muito bem, agora vamos configurar o hook:

  1. Abra o VisualSVN Server Manager.
  2. No painel da esquerda, localize o item “DemoIC” que está localizado um nivel abaixo do item “Repositories”.
  3. Clique com o botão direito em “DemoIC” e vá em “Properties”.
  4. Na tela que se abre, vá na aba “Hooks”.
  5. Clique sobre “Post-commit hook”.
  6. Clique em “Edit”.
  7. Na tela que se abre, digite o seguinte:
    wget http://localhost:8080/hudson/job/DemoIC/build --spider
  8. Clique OK
  9. Clique OK novamente e feche o VisualSVN

Obs: O parâmetro

--spider

serve para que o wget não faça o download da URL especificada, mas sim apenas uma requisição GET no servidor. Isso evita tráfego de rede desnecessário.

Podemos fazer um teste agora. Abra o Delphi, e edite qualquer arquivo fonte do projeto de forma a introduzir um erro de compilação. Salve o projeto, vá na pasta e dê um commit no Subversion. Agora acesse o Hudson e clique na tarefa “DemoIC” para exibir as informações do nosso projeto. Neste momento, você deve estar vendo no “Histórico de construção” com uma bolinha vermelha, isto corresponde ao build que foi iniciado em função do commit que você acabou de fazer no Subversion. Agora vá no Delphi, desfaça a edição que você fez anteriormente e dê um novo commit.

Após isso, olhe novamente no Hudson, na página de informações do projeto “DemoIC” e você deve ter notado um terceiro item no “Histórico de construção”, desta vez sinalizando um build bem sucedido.

Bom, está quase tudo pronto. Mas ainda falta fazer com que o build teste o projeto, desta forma um build bem sucedido não será simplesmente um build sem erros de compilação, mas sim um build onde tudo foi compilado com sucesso E onde todos os testes automtizados passaram. O que precisamos fazer é editar no nosso Build.bat de forma a faze-lo compilar e executar o projeto de testes unitários, então vamos lá:

  1. Abra o arquivo Build.bat no bloco de notas.
  2. Acrescente o seguinte código ao final do arquivo

    if %ERRORLEVEL% neq 0 Exit /b 1
    msbuild "SrcTestes\CalculadoraTests.dproj" /p:configuration="BuildAutomatizado"
    if %ERRORLEVEL% neq 0 Exit /b 1
    call OutTestes\CalculadoraTests.exe

    A primeira linha serve para terminar o script sinalizando uma condição de erro caso o passo anterior (isto é, a compilação do projeto principal) não tenha sido bem sucedido. A segunda linha irá compilar o projeto de testes unitários, e a terceira linha irá terminar o script sinalizando uma condição de erro caso a compilação dos testes unitários não tenha sido bem sucedida. Por fim, a última linha executa os testes unitários.

Note que para compilar os testes unitários, utilizei uma configuração de build chamada “BuildAutomatizado”, isso por que o projeto de testes unitários precisará ser compilado com algumas características especiais para poder ser efetivamente utilizado no nosso script de build, a saber:

  • Precisa ser um aplicativo console.
  • O aplicativo precisa retornar zero caso todos os testes passem, e retornar qualquer outro valor caso contrário.

Vamos então criar esta configuração. Abra o projeto CalculadoraTests no Delphi e crie uma nova configuração de build (Build configuration) para ele. Chame-a de “BuildAutomatizado”. No Delphi 2010, isso é feito no seguinte menu:

Agora vá nas opções da configuração BuildAutomatizado e defina os seguintes parâmetros:

  • Conditional defines: CONSOLE_TESTRUNNER
  • Unit output directory: ..\DCUTestes\
  • Output directory: ..\OutTestes\

Agora abra o arquivo CalculadoraTests.dpr e modifique-o para ficar assim:


program CalculadoraTests;
{

  Delphi DUnit Test Project
  -------------------------
  This project contains the DUnit test framework and the GUI/Console test runners.
  Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options
  to use the console test runner.  Otherwise the GUI test runner will be used by
  default.

}

{$IFDEF CONSOLE_TESTRUNNER}
{$APPTYPE CONSOLE}
{$ENDIF}

uses
  Forms,
  TestFramework,
  GUITestRunner,
  TextTestRunner,
  UCalculadoraTests in 'UCalculadoraTests.pas';

{$R *.RES}

var
  Results: TTestResult;

begin
  Application.Initialize;
  if IsConsole then
  begin
    Results := TextTestRunner.RunRegisteredTests;
    try
      if Results.FailureCount + Results.ErrorCount = 0 then
        ExitCode := 0
      else
        ExitCode := 1;
    finally
      Results.Free;
    end;
  end
  else
    GUITestRunner.RunRegisteredTests;
end.

Basicamente, essas mudanças no dpr foram apenas para fazer o processo retornar o código 1 caso algum teste não tenha passado, e retornar 0 caso todos os testes tenham passado.

Feito isso, salve tudo, faça um commit no Subversion e observe o DemoIC no Hudson, deve ter sido gerado um novo build bem sucedido lá.

Agora vamos simular uma falha nos testes. Abra o arquivo UCalculadora.pas e adicione um “Exit;” logo após o begin no método TCalculadora.Calcular(). Ele ficará assim:

function TCalculadora.Calcular(A, B: Extended; Operacao: TOperacao): Extended;
begin
  Exit;
  case Operacao of
    oAdicao: Result := A + B;
    oSubtracao: Result := A - B;
    oMultiplicacao: Result := A * B;
    oDivisao: Result := A / B;
  end;
end;

Salve, faça um commit e observe o DemoIC no Hudson. Você deverá ver um build falho agora. Se você clicar sobre o item correspondente a um build “Histórico de construção”, poderá acessar informações específicas sobre um determinado build. Por exemplo, em “Saída do console” você poderá visualizar o que foi gerado no console pelo script de build, isso é útil para identificar o ponto onde o build falhou.

Como pode ver, nós facilmente descobrimos um erro que foi introduzido “acidentalmente” no código, e então teremos a possibilidade de corrigi-lo rapidamente.

Uma coisa interessante, é que o nosso script de build (Build.bat) pode ser utilizado independente do Hudson. Se você mudar o servidor de integração contínua, poderá continuar utilizando. Ele serve inclusive para uso local. Exemplo: Você terminou uma implementação e quer ver se tudo está funcionando bem antes de enviar o código para o controle de versão? Basta executar o script de build.

Para terminar, farei algumas considerações que serão úteis quando você for utilizar na prática (eu espero que você o faça) o que leu aqui:
Eu instalei tudo em uma mesma máquina para simplificar as explicações, mas no mundo real não será assim. Considere que você possuirá os seguintes tipos de computadores no seu ambiente e eu vou explicar quais ferramentas seriam instaladas em cada um, dentre as que utilizamos nesta série:

  • Máquinas de desenvolvimento: Onde os programadores trabalharão. Aqui normalmente seria instalado o Delphi e o cliente do Subversion (No nosso caso, o TortoiseSVN).
  • Servidor de código fonte: Aqui é onde ficarão os fontes do projeto. Instale o servidor Subversion (No nosso caso o VisualSVN) e o WGet.
  • Servidor de integração contínua: É onde ficará instalado o Hudson.
  • Servidor de build: Onde os builds ocorrerão, precisa do Delphi instalado.

Sei que o Hudson suporta realizar builds em máquinas remotas, mas não sei configurar isso. Então para mim o servidor de integração e de build são um só. Pode ser interessante separar isso caso o seu sistema precise ser compilado/testado em diferentes plataformas. Assim você teria um servidor central de integração, que dispararia o build em diversas outras máquinas cada uma com uma plataforma diferente.

O servidor de integração e de código fonte podem ser na mesma máquina ou não, isso fica a seu critério.

O Hudson ainda tem muitos recursos. Por exemplo, ele pode ser configurado para enviar e-mails quando houver alguma mudança relevante no projeto. Ou seja, quando o projeto estava estável e de repente um build falhou, ou quando os builds não seram bem sucedidos e de repente um build passou a funcionar. Isso é importante para os membros da equipe saberem o estado do build sem terem que ficar monitorando o Hudson.

Ele tambem suporta plugins, para adicionar novas funcionalidades ao mesmo. A instalação de plugins é bem simples, dê uma olhada no menu “Gerenciar Hudson/Gerenciar plugins”.

Com isso, chegamos ao final desta série. Espero ter conseguido ser claro e mostrar as vantagens da prática da integração contínua e o quão simples é configurar um ambiente para começar a trabalhar desta forma.

2

Quais são as suas prioridades como desenvolvedor?

May 23, 2010

Quando você está codificando, quais são os fatores que você leva em consideração para decidir como implementar uma determinada situação?

Vamos clarear mais. Considere os seguintes pontos:

  • Legibilidade
  • Performance / Consumo de memória
  • Manutenabilidade (em outras palavras: O quão fácil ou difícil será dar manutenção naquele código futuramente)
  • Tempo de desenvolvimento
  • Quantidade de código

Peço que quem estiver lendo este post, dê uma pausa e faça uma lista destes items partindo do que você acha mais importante até o menos importante, antes de prosseguir.

Agora vamos discutir cada um:

  1. Quantidade de código
    Muitos programadores acreditam que uma solução é melhor que outra apenas por envolver menos linhas de código. Eu digo que este fator, por si só, está entre os menos importantes para mim. Pense: Que vantagens isso tras? Você paga imposto de acordo com o tamanho dos seus fontes? E de que adianta gastar menos tempo digitando o código, se toda vez que você precisar fazer alguma alteração você perder o dobro de tempo pra entender o que o código faz?
  2. Tempo de desenvolvimento
    O cliente está te cobrando a cada cinco minutos, o seu prazo já estourou, e então você implementa a coisa do jeito mais rápido possível, o que quase sempre significa ignorar toda e qualquer boa-prática.

    Ok, mas saiba toda vez que você precisar dar manutenção no código, vai levar mais tempo do que levaria se tivesse gasto um pouco mais de tempo no inicio para fazer algo melhor. Então eu pergunto: Será que o lucro de hoje não se transformará em uma série de prejuízos amanhã? Não seria melhor ter um prejuízo hoje para ter uma série de lucros amanhã?

    Se situações assim são frequentes, sugiro maior atenção na hora de definir prazos. É melhor dar um prazo de 20 dias e entregar o produto em 15, do que dar um prazo de 10 dias e entregar o produto nos mesmos 15 dias. Sempre inclua nos seus prazos um tempo a mais para cobrir imprevistos.

  3. Performance / Consumo de memória
    Sério, vamos deixar as previsões sobre o futuro com os místicos, ok? Não há nada pior do que o programador que fica fazendo previsões sobre se uma determinada rotina ficará muito pesada, pois é bem provável que nossos palpites não causem nenhuma melhora significativa no produto. Estamos na era dos gibabytes de memória RAM, processadores com diversos núcleos, etc, então não adianta muito gastar horas/dias otimizando rotinas para salvar alguns quilobytes ou megabytes de memória RAM, ou alguns ciclos de processamento, por que isso não fará absolutamente nenhuma diferença para o usuário final.
    Se for comprovado, na prática, que uma determinada parte do sistema está consumindo recursos de forma realmente excessiva, você deve encontrar o gargalo com a ajuda de profilers, e só então você dará um jeito de resolver o gargalo.

    Leve em consideração o Princípio de Pareto, que diz que 80% dos problemas se originam em apenas 20% das causas. Trazendo isso para o assunto em questão aqui, podemos dizer 20% do código do seu sistema é responsável por 80% do tempo de execução do mesmo. Ou seja: Por mais que você otimizar esses 80% de código restante, o ganho no final das contas será mínimo.

  4. Manutenabilidade
    Pense no quão facil ou dificil será dar manutenção no código que você está escrevendo. Veja não pelo seu ponto de vista, mas no de algum outro desenvolvedor que eventualmente ficará responsavel por manter este código. De que adianta fazer algo rapidamente hoje, se você se vir no inferno cada vez que precisar alterar algo no código (e isso significa perder muito tempo)? Talvez seja necessário até mesmo refazer tudo, então teria sido melhor fazer algo bom desde o inicio.
  5. Legibilidade
    Quando codificar uma rotina, imagine-se tendo que entende-la depois de um ano inteiro sem olhar para ela. Imagine outro desenvolvedor tendo que entende-la. O seu código deve ser fácil de ler nestas situações

Quando priorizamos performance, quantidade de linhas de código e tempo de desenvolvimento, a tendência é uma redução na legibilidade e na manutenabilidade. Acho que um bom profissional deve ser capaz de balancear tudo isso da melhor forma possível, mas nunca deixando de lado a preocupação com a qualidade do projeto. Muitos dizem que o cliente não vê o código fonte e que o importante é o produto funcionando. Eu não discordo, mas temos que considerar o seguinte: O produto pode estar funcionando e atendendo com maestria as necessidades do cliente, mas se o projeto não foi estruturado de uma maneira saudavel, então essa qualidade deixará de existir cedo ou tarde devido às dificuldades de manter e expandir o projeto e o produto não mais atenderá às necessidades do cliente.

Agora vos apresento a minha lista de prioridades:

  1. Legibilidade
    Se o código não é legível, não poderei trabalhar com ele e entregar um produto de qualidade.
  2. Manutenabilidade
    Se tenho dificuldades de manter o código já escrito, estarei sempre estourando prazos, entregando requisitos mal implementados, bugs, etc.
  3. Performance
  4. Tempo de desenvolvimento
  5. Quantidade de código

Isso quer dizer que eu não me importo em escrever centenas ou milhares de linhas de código? De forma alguma, só quer dizer que eu não abro mão de fatores mais importantes apenas para economizar linhas de código. Mas se for possível escrever menos, sem prejudicar a legibilidade ou a manutenabilidade, é claro que eu prefiro!

Como eu disse antes, se for constatado que o sistema está consumindo muitos recursos, utilizamos um profiler para identificar o gargalo e trabalhamos para resolver este gargalo. Nesse caso, possivelmente terei que abrir mão de fatores mais importantes como a legibilidade do código pois, como eu disse antes, o que importa é ter um produto de qualidade que satisfaça ao cliente.

2

Integração contínua com Hudson e Delphi – Parte II

May 20, 2010

Na primeira parte desta série, apresentei o conceito de integração contínua, mas me mantive apenas na teoria. Agora vamos preparar o terreno para colocarmos a idéia em prática instalando as ferramentas necessárias.

Em primeiro lugar, precisamos de um tipo de software conhecido como servidor de integração contínua. Eu utilizo o Hudson, um servidor escrito em Java mas que pode trabalhar com projetos escritos em qualquer linguagem. Este servidor é que será responsável obter o código fonte do projeto no servidor de controle de versão, iniciar o build, manter um histórico dos builds bem e mal sucedidos, etc.

Sendo escrito em Java, então já sabemos que vamos precisar também da JVM. Como este sistema usa a tecnologia JEE, precisaremos também de um servidor de aplicações. O Apache Tomcat dá conta do recado.

Então a primeira coisa é baixar e instalar o Java SDK e o Apache Tomcat.

Nos dois casos, a instalação segue o modelo NNF (Next, Next, Finish), então não vou entrar em detalhes aqui.

Obs: Daqui para frente, irei considerar que o Tomcat foi instalado em “C:\Arquivos de Programas\Apache Software Foundation\Tomcat 6.0\” e que os valores padrão da instalação foram mantidos, ou seja, a porta utilizada será 8080

O Tomcat é instalado por padrão como serviço. Eu tive alguns problemas para o Hudson compilar meus projetos Delphi quando o Tomcat está rodando como serviço, então passei a utiliza-lo como aplicativo mesmo. Para fazer isso, primeiro temos que desativar o serviço Tomcat:

  1. Vá até o gerenciador de serviços do Windows, localize o serviço “Apache Tomcat 6″ (Ou seja lá qual versão que você instalou)
  2. Clique com o botão direito do mouse sobre o serviço e clique em “Parar”
  3. Aguarde até o serviço ser finalizado
  4. Clique com o botão direito do mouse sobre o serviço e clique em “Propriedades”
  5. Em “Tipo de inicialização”, selecione “Desativado”
  6. Clique em “OK” e pronto

Agora precisamos de um meio para que o aplicativo Tomcat seja iniciado automaticamente junto com o Windows.
O jeito mais simples é colocar um atalho para “C:\Arquivos de Programas\Apache Software Foundation\Tomcat 6.0\bin\tomcat6.exe” na pasta “Inicializar” do Menu Iniciar do Windows.

Uma vez que o Tomcat já esteja pronto, podemos instalar o Hudson nele. Para isso, baixe-o aqui.

Coloque o arquivo hudson.war que você baixou na pasta “webapps” onde está instalado o Tomcat.

Provavelmente o Tomcat não estará rodando agora. Então execute o arquivo “C:\Arquivos de Programas\Apache Software Foundation\Tomcat 6.0\bin\tomcat6.exe” para coloca-lo em execução.

Se tudo correu bem, você agora poderá acessar o Hudson por este endereço: http://localhost:8080/hudson/

Mas antes de podermos efetivamente utiliza-lo, devemos ter um projeto configurado em algum servidor de controle de versão. Por padrão, o Hudson vem com suporte para CVS e Subversion. Sei que existe um plugin que adiciona suporte a Starteam. Creio que existam plugins para diversos outros sistemas de controle de versão também.

Irei utilizar aqui o Subversion. Então devemos agora configurar um servidor Subversion, e no nosso caso será utilizado o VisualSVN que pode ser baixado aqui. A instalação dele tambem é simples, apenas mantenha os valores padrão que o instalador sugere e vá dando Next para prosseguir.

Ao final da instalação, o VisualSVN Server deve ter sido aberto. A tela dele é essa aqui:
Tela inicial do VisualSVN Server

Agora vamos criar dois usuários no servidor: Um para nós mesmos, e outro para o Hudson utilizar quando for necessario baixar os fontes do servidor. Para isso, você deve localizar a pasta “Users” que fica no painel à esquerda na tela do VisualSVN. Clique com o botão direito nela, e depois em “Create User”. Informe nome de login, senha, repita a senha, e clique em “OK”. Irei utilizar como nome de usuários “magno” e “hudson”, sendo as senhas iguais aos respectivos nomes. Após fazer isso, a tela do seu VisualSVN deve estar semelhante a esta:
Tela do VisualSVN com os usuários criados

Precisamos ainda criar um repositório, que é o local onde os fontes do nosso projeto ficam localizados. Para isso, localize o item “Repositories” na tela principal do VisualSVN, clique com o botão direito e depois em “Create New Repository”. Na tela que se abre, digite o nome para o repositorio (irei utilizar “DemoIC”) e clique check box que diz “Create default structure (trunk, branches, tags)” e depois em “OK”. Habilitar este check-box faz com que sejam criadas três pastas no nosso repositório chamadas “trunk”, “branches” e “tags”, embora elas não sejam um pre-requisito para o funcionamento nem do Subversion nem do Hudson, seu uso constitui uma boa prática e por isso incentivo seu uso aqui. O propósito de cada uma destas pastas está além do escopo deste artigo, então não vou me aprofundar aqui).

Neste momento, o VisualSVN deve estar assim:
Tela do VisualSVN com o repositório criado

Agora que já temos o servidor Subversion rodando, repositório criado e Hudson em execução, podemos começar a trabalhar nosso projeto em Delphi. Mas para sermos capazes de usar o Subversion para gerenciar os fontes deste projeto, vamos precisar de um cliente para o mesmo. Eu uso o TortoiseSVN, que é gratuito e se integra ao Windows. Então baixe-o aqui e então execute o instalador. Mantenha as opções padrão e vá avançando. Ao final, você terá que reiniciar o computador.

Muito bem, vamos começar a trabalhar no nosso projeto em Delphi (vou partir do princípio que ele já está instalado, ok?).

Então agora crie no seu computador uma pasta chamada DemoIC, pode ser em qualquer lugar. Esta será a sua pasta de trabalho, onde você poderá trabalhar no projeto.

Agora abra a pasta, clique como botão direito em qualquer área livre dela e clique em “SVN Checkout”. Em “URL of repository”, coloque “https://localhost/svn/DemoIC/trunk” (sem as aspas) e clique “OK”. Provavelmente aparecerá uma janela como esta:
Clique em “Accept permanently”.

Após isso, será aberta a seguinte tela:

Esta tela está pedindo um nome de usuário e senha para se autenticar no servidor Subversion. Informe o nome de usuário e senha que você cadastrou no VisualSVN (mas não o usuário destinado ao hudson). Se quiser, marque o checkbox “Save authentication” para que você não precise informar estes dados sempre que fizer algo no repositório.

Por hoje ficaremos por aqui. O objetivo desta parte da série era apenas mostrar a instalação e configuração de toda a infra-estrutura necessária para por em prática a integração contínua com projetos Delphi. No próximo, vamos finalmente implementar um projeto de demonstração em Delphi e ver o Hudson em funcionamento.

0