Categories
Shell script

Se você não sabe Shell script, deveria saber

Ontem estava conversando com um amigo meu sobre shell script. Na verdade nós conversamos sobre esse assunto várias vezes nos últimos tempos. Sempre conto para ele como eu resolví um problema interessante com shell script e ele fica com água na boca e diz: “caramba, eu tenho que aprender esse troço”.

Shell script é pau pra toda obra. Para quem trabalha com Unix direto como eu, saber shell script pode fazer a diferença entre levar 30 minutos para resolver um problema ou levar 10 segundos para resolver o mesmo problema. Muitas vezes dá pra resolver com apenas uma linha de shell script. Pelo que eu me lembro nunca passei de umas 25 linhas. É sério.

É claro que o shell script não serve para todas as coisas. Por exemplo, ele não é feito para programar um site como este. Mas para as coisas que ele serve ele não decepciona. Vou dar exemplo de algumas coisas doidas que eu andei fazendo nas últimas semanas, todas com menos de 25 linhas de shell script:

1) Precisava visualisar simultaneamente os logs de 7 servidores de aplicação. Quando eu desse um refresh na tela (browser) precisava saber para dos servidores que o balanceador iria me jogar e visualizar o log deste servidor. E a cada refresh da tela o servidor pode mudar. Então, fiz um script para conectar simultaneamente em todas as máquinas e na mesma tela me mostrar as mensagens de log de todos os servidores, indicando no início da linha qual o servidor que havia mandado a mensagem. Tempo para fazer: 2 minutos. Linhas de script: 8 + arquivo de configuração com o endereço dos servidores.

2) Em determinadas horas o request para um mesmo Servlet feito pelo mesmo browser na mesma máquina e com os mesmos parâmetros estava gerando respostas completamente diferentes. Quem viu isso foi o técnico do suporte, que jurou de pé junto que era verdade. Olhando o código da aplicação deveria ser impossível que isso acontecesse mas como o cara jurou pela mãe dele, tinha que comprovar. Então resolví fazer um script para baixar 50.000 vezes o conteúdo deste Servlet. A cada vez que o conteúdo era baixado comparava com o anterior para ver se estava diferente. Em caso positivo ele parava a execução e me mostrava na tela a diferença entre os dois. Tempo para fazer: 10 minutos. Linhas de script: 21. (ah, todos os 50.000 arquivos baixados eram iguais um ao outro!)

3) Os DBAs aqui da empresa estavam limpando o banco de dados e selecionaram umas 30 tabelas candidatas a serem removidas do banco de dados por não estarem sendo utilizadas. Então eles me pediram para dar uma olhada nos nossos projetos e verificar se realmente elas não estavam sendo utilizadas em nenhum sistema. Fiz então um script para varrer o repositório de projetos e buscar por arquivos contendo uma ou mais das tabelas relacionadas. Tempo para fazer: 20 segundos. Linhas de script: 1 + arquivo de configuração com o nome das tabelas.

Provavelmente se eu não soubesse shell script estaria até agora escrevendo alguma coisa em Java ou qualquer outro treco para fazer tudo isso e não teria tempo de escrever no meu blog! Como eu quero ter tempo de escrever aqui preciso resolver meus problemas o mais rápido possível e por este motivo o shell script é essencial na minha vida! Hehe.

Agora é sério, já deu para ter uma idéia de como é interessante?

Para você poder começar neste minuto, selecionei alguns links legais para leitura:

– Conceitos básicos de Unix: http://sc.tamu.edu/help/general/unix/unix.html
– Wikipedia – Shell script: http://pt.wikipedia.org/wiki/Shell_script
– Prompt-doc: “Tira Dúvidas” sobre Shell: http://aurelio.net/curso/conectiva/conectiva-shell-prompt.html
– Shelldorado – Shell tips & tricks: http://www.shelldorado.com/shelltips/

Categories
Google Notícias

Podcast de desenvolvimento do Google!

O Google anunciou no seu blog de desenvolvimento Google Code Blog a criação de um Podcast sobre desenvolvimento de software!!!

Na semana passada mesmo estava falando com um amigo meu que estava com vontade de assinar um Podcast interessante sobre software e os caras me aparecem com isso! Show de bola, caiu como uma luva!

O tema do primeiro Podcast é o Google Guice, framework de injeção de dependência que eles criaram no início deste ano. Ficou muito legal, mais uma bola dentro do Google.

Categories
AOP Java Refactoring

Refatorando para aspectos

Acabo de assistir no InfoQ uma apresentação do Ramnivas Laddad sobre refatoração de aplicações para AOP.

Assistindo a explicação dele dá para entender a linha de raciocício que ele segue e a quantidade de possibilidades que a programação orientada a aspectos traz. Realmente é um saco fazer repetidamente código para controlar permissões, erros, transações, concorrência e outros mais. Com AOP a quantidade de esforço para implementar este tipo de situação é reduzida consideravelmente e este artigo trata exatamente disso: reconhecer trechos de código que são candidatos a serem reescritos para se tornarem aspectos.

Porém eu acho que deve-se tomar muito cuidado ao sair refatorando a sua aplicação para aspectos. Imagina um sistema com centenas de classes que possuem dezenas de aspectos diferentes. A impressão que eu tenho é que se você tem aspectos demais o sistema pode ficar até mais difícil de programar. Isto porque se você tem código fora do método sendo executado “dentro” do método, a clareza em relação ao que ele faz fica reduzida. Multiplique isso por centenas de classes e métodos e você tem um monstro indomável.

Mesmo assim a abordagem dele é bem interessante pois quando utilizada corretamente estimula a produção de CÓDIGO com lógica de negócio ao invés de código que não tem nada a ver com o que o sistema realmente faz, tornando o desenvolvimento mais produtivo.

Categories
Java TDD

Test Driven Development in a nutshell

Semana passada tivemos uma reunião na empresa em que trabalho sobre os procedimentos de controle de qualidade dos softwares que produzimos. Antes de fazer o release de um software é necessário que sejam tomadas algumas precauções básicas para garantir que ele funcione satisfatoriamente em produção e estávamos apresentando para os nossos gerentes e equipe de controle de qualidade da empresa as ferramentas e procedimentos adotados pela nossa equipe para isto.

Das várias medidas que estamos tomando, talvez a principal por ser a base para todas as outras direta ou indiretamente é o “desenvolvimento guiado pelos testes” ou “test driven development” (TDD para os mais íntimos).

No TDD você desenvolve os testes do software antes mesmo de desenvolver o software. A cada peça da aplicação que é construída uma série de testes são escritos ANTES do desenvolvimento para garantir que a aplicação funciona como deveria funcionar.

O conceito de TDD é bem simples de ser aplicado mas para quem não está acostumado com testes unitários e práticas de desenvolvimento ágil pode parecer meio estranho e ser um pouco mais difícil de entender.

Inspirado nesse cenário vou tentar explicar rapidamente como funciona o TDD com um exemplo prático e bem simples. Vou explicar seguindo a linha de raciocínio que normalmente se tem em tempo de programação pois a maneira de pensar no processo de desenvolvimento também faz parte do TDD.

Imagine que você está desenvolvendo um sistema no qual um usuário deve cadastrar seu endereço. O CEP digitado precisa ser validado para que tenha o formato 00000-000 e para isso será necessário desenvolver uma classe que faça a validação dos dados. Vamos definir a interface para esta classe de validação de dados:

public interface ValidadorDeDados {
    boolean isCepValido(String cep);
}

A partir deste ponto já temos o comportamento do nosso validador claramente definido: dada uma String contendo o valor de um CEP ele retornará verdadeiro se o CEP for válido e falso caso contrário. Por exemplo, se o validador receber uma String “teste” ou “” obviamente deverá retornar falso. Por outro lado se ele receber uma String “00000-000” ou “12345-123” deverá retornar verdadeiro.

Então já podemos desenvolver uma classe de teste que faça estas verificações e mais algumas outras pertinentes. Vou utilizar para os testes o framework JUnit que é o framework de testes unitários Java mais popular do mercado.

public class ValidadorDeDadosTest extends TestCase {
    private ValidadorDeDados validador = null; // instância de validador de dados para o teste
 
    public void testIsCepValido() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido(null));
        assertFalse("retorno deve ser FALSE", validador.isCepValido(""));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("iodfjodfd"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("03490340"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("20202-020"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("00000-000"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("99999-999"));
    }
}

Para quem não conhece o JUnit, o método assertFalse checa se o retorno da execução retornou falso e o método assertTrue checa se o retorno da execução retornou verdadeiro. Em todos os casos que o assertTrue foi utilizado o validador deverá retornar verdadeiro e o oposto deverá acontecer para os assertFalse. Se isto não acontecer, significa que tem alguma coisa errada.

Além disso colocamos uma pequena mensagem explicando o que esperamos que aconteça na execução do método. Isso é util para que outros desenvolvedores da equipe possam entender com facilidade o que você programou e está esperando que aconteça nos seus testes, bem como para deixar as mensagens de erro mais explicativas.

Só para não deixar dúvida nenhuma, vamos ler uma das linhas de teste:

assertFalse("retorno deve ser FALSE", validador.isCepValido(""));

Que é: Ao executar a validação do CEP “”, certifique-se que ele retornará FALSO.

Então vamos executar o teste para ver o que vai acontecer:

TDD 1

Ele falhou com NullPointerException porque não há uma implementação de validador, desenvolvemos somente a interface. No teste unitário o validador está setado como null.

Sendo assim, vamos desenvolver a primeira implementação de validador:

public class ValidadorDeDadosImpl implements ValidadorDeDados {
    public boolean isCepValido(String cep) {
        return false;
    }
}

Além disso é necessário alterar a classe de teste para utilizar esta implementação de validador de dados que desenvolvemos.

public class ValidadorDeDadosTest extends TestCase {
    private ValidadorDeDados validador = new ValidadorDeDadosImpl(); // agora o validador não é mais null
 
    public void testIsCepValido() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido(null));
        assertFalse("retorno deve ser FALSE", validador.isCepValido(""));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("iodfjodfd"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("03490340"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("20202-020"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("00000-000"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("99999-999"));
    }
}

Agora o teste já está rodando, o resultado foi bem diferente. Antes estava sendo lançada uma exception porque a implementação do validador de dados sequer existia. Agora o que acontece é que temos um problema na implementação do validador e o teste acusou isso:

TDD 2

Veja a implementação do validador de dador e repare que independente da String passada para o método ele retorna falso, ou seja, está errado. Vamos corrigir essa implementação:

public class ValidadorDeDadosImpl implements ValidadorDeDados {
    public boolean isCepValido(String cep) {
        if ((cep == null) || (cep.length() != 9) || cep.charAt(5) != '-') {
                return false;
        }
        return true;
    }
}

E executando novamente os testes podemos verificar que agora todos estão passando:

TDD 3

Desenvolvimento finalizado? Nada disso. Navegando pelo sistema e fazendo outros testes manuais descobrimos que é possível entrar com um CEP com letras tipo ABCDE-FGH. Se analisarmos a implementação do método veremos que isso realmente é possível. Não há nenhuma verificação que impeça isto.

Mas caramba, os testes não passaram? É verdade, os testes ainda estão passando. Acabamos de descobrir um bug.

Quando um bug é descoberto ou é reportado para a equipe de desenvolvimento, a primeira coisa a se fazer é escrever um teste para comprovar a existência do bug. Vamos então complementar a nossa classe de teste:

public class ValidadorDeDadosTest extends TestCase {
    private ValidadorDeDados validador = new ValidadorDeDadosImpl();
 
    public void testIsCepValido() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido(null));
        assertFalse("retorno deve ser FALSE", validador.isCepValido(""));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("iodfjodfd"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("03490340"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("20202-020"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("00000-000"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("99999-999"));
    }
 
    public void testIsCepValidoComLetras() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido("AAAAA-AAA"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("A2AA1-333"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("x2334-567"));
    }
}

Executando os testes novamente vemos que o teste que acabamos de escrever falhou. Ao entrar com este CEP o validador deveria retornar falso pois é um CEP inválido mas ele está retornando verdadeiro. Muito bem, o bug está comprovado:

TDD 4

Repare que somente o teste novo está falhando, os testes antigos estão passando normalmente (em verde).

Com os testes prontos já podemos fazer a correção na implementação:

public class ValidadorDeDadosImpl implements ValidadorDeDados {
    public boolean isCepValido(String cep) {
        if ((cep == null) || (cep.length() != 9) || cep.charAt(5) != '-') {
            return false;
        }
        for (int i = 0; i < cep.length(); i++) {
            if (i != 5) {
                char posicao = cep.charAt(i);
                if (!Character.isDigit(posicao)) {
                    return false;
                }
            }
        }
        return true;
    }
}

E corrigida a implementação, vamos executar os testes novamente para verificar se o bug foi corrigido:

TDD 5

Agora sim tudo funcionando.

Podemos perceber como é fácil implementar funcionalidades e corrigir bugs com TDD. Alguns pontos fortes que merecem destaque:

1) Qualquer tipo de implementação por mais complexa que seja será suportada pelos testes e com isso você programa com mais confiança. Dado um comportamento do método que será definido antes do desenvolvimento você pode executar os testes inúmeras vezes até que eles passem. E quando eles passam você tem certeza absoluta de que o que você fez está efetivamente funcionando.

2) TDD facilita o refactoring: depois de cada reescrita de código ou qualquer tipo de alteração, especialmente em códigos que você não conhece bem porque foram feitos por outros membros da equipe, você pode rodar os testes da aplicação inteira afim de garantir que você não está quebrando nenhuma funcionalidade. Alguns sistemas são tão podres que dependendo do lugar que você mexe quebra tudo. Os testes te ajudam a não fazer isso.

3) Mesmo que seja um pouco mais demorado escrever testes ao desenvolver, com esta prática você praticamente não encontra bugs em produção e quando encontra eles podem ser corrigidos rapidamente e com confiança. Então no final das contas você GANHA tempo. E o melhor de tudo é que você programador não precisa ficar pisando em ovos e sem dormir porque mexeu na aplicação. Algumas aplicações são tão difĩceis de serem alteradas (porque são mal programadas) que você vai para casa e dorme com o celular do lado porque tem certeza que ele vai tocar porque deu pau no sistema!

Então, preserve seus cabelos e sua saúde: programe com qualidade! 😉

Download do código fonte.