Category Archives: Blog em Português

O que faz um arquiteto de software?

Semana passada, Alberto Souza e eu fizemos um Hangout sobre arquitetura de software. E uma pergunta que nos fez pensar bastante foi “o que é um arquiteto de software?”

Não sei se há uma definição oficial por alguém chique, mas para mim, um arquiteto é aquele que:

  • Consegue comparar duas tecnologias/práticas/etc e discutir sobre suas vantagens e desvantagens.
  • Já entregou muito software, e já passou por diferentes situações. Experiência.
  • Não necessariamente sabe cada vírgula do framework que a equipe usa, mas compreende a idéia por trás dele, bem como suas desvantagens e as soluções concorrentes.
  • Apesar de conhecer bastante os conceitos, ele os experimenta no mundo real, por exemplo, por meio de POCs ou projetos menores.
  • Reflete constantemente sobre suas decisões.
  • Participa do processo de desenvolvimento com a equipe. Talvez não programando 100% do tempo, mas revisando e discutindo a implementação atual.
  • Não precisa ser necessariamente o mais experiente da equipe. É um papel dentro dela, e pode rotacionar de acordo com o problema e conhecimento de cada um.

Esqueci algo? Me ajudem a completar a lista com diferentes opiniões!

Como começar a refatorar seu legado em PHP/ASP/JSP?

TL;DR: O primeiro passo na refatoração de arquivos de script é mover todo o código de acesso a banco de dados, regras de negócio existentes e fluxo para o topo do arquivo. Para isso, crie as variáveis que serão usados na parte de baixo. Em seguida, o HTML que contém código PHP apenas para regras de visualização.

Não é difícil encontrarmos aqueles arquivos de script imensos, em PHP, ASP ou JSP, misturando um pouco de tudo: HTML, lógica, acesso a dados, mais HTML, mais lógica, etc:

A pergunta é: Como começar a refatorar esse código? Muitos anos atrás, tive uma experiência bastante interessante com um sistema enorme escrito só em JSP. O primeiro passo que costumava fazer é fazer com que todo código JSP ficasse no começo do arquivo. Assim, toda lógica de negócio, acesso a banco de dados ficaria agrupado. O resto da página com HTML conteria scripts apenas para alguma lógica de visualização.

Veja o código anterior refatorado. Tanto rs quanto rs2 foram para cima. Criei a variável total também para deixar o código um pouco melhor.

Porque fiz isso? Para começar a separar as responsabilidades desse código. Colocar todo “controle de fluxo” em um único lugar, e regras de visualização em outro. Mesmo que estando no mesmo arquivo, a separação já é visível e facilita a manutenção. Sim, no fim, estou tentando separar o M, do V, do C.

Aqui, começamos a separar o C do V. O M (modelo) não existe nesse código! O próximo passo seria começar a criar classes de domínio e lidar com elas, ao invés de manipular diretamente o array que as funções de acesso a banco geralmente devolvem. Mas isso é assunto para um próximo post!

PS: rs e rs2 são péssimos nomes. Você, claro, escolha nomes que tenham a ver com o seu domínio.

Como são as boas práticas de um médico cirurgião?

TL;DR: Boas práticas não deveriam mostrar apenas sua implementação, mas também em que contextos elas deram certo e, tão importante quanto, onde elas não são recomendadas.

É bastante interessante que todas nossas boas práticas de desenvolvimento de software sejam empíricas. Afinal, é pondo a mão na massa que você consegue experimentar diferentes abordagens e ver os prós e contras de cada uma delas. Grady Booch, no seu texto sobre RUP [1], diz que uma “boa prática é uma abordagem que se mostrou válida na indústria e que, quando combinadas, atacam as raízes de problemas em desenvolvimento de software; elas são boas práticas, não tanto porque elas conseguem quantificar seu valor, mas sim porque elas são comumente usadas na indústria em projetos de sucesso”.

Exemplo de nossas boas práticas nossas são “escreva métodos com poucas linhas”, “evite ifs aninhados”, “não tenha longas hierarquias de classes”, e assim por diante. O curioso é que, de vez em quando, queremos (ou eu quero, hehe?!) provar que uma boa prática é realmente a melhor. TDD é melhor. Agil é melhor.

Durante algumas viagens mentais, comparo um programador com um médico cirurgião. O médico sabe tudo do corpo humano, assim como o programador sabe tudo de código. Mas cada corpo é diferente do outro, assim como cada software é diferente do outro. Ele também deve ter suas boas práticas, como “segurar o aparelho X com a mão esquerda e Y com a mão direita é melhor”, ou mesmo “fazer o corte 2 cm abaixo da barriga pode dar mais espaço para manobra”.

Minha pergunta é: será que eles também ficam nessa maluquice de descobrir qual a melhor prática, ou simplesmente preferem conhecer várias delas, e usar sua experiência para escolher a melhor para aquele caso?

Talvez sempre que você bolarmos uma boa prática, devêssemos pensar não só em detalhar as técnicas para sua implementação, mas também deixar claro quais os contextos que elas deram certo, e quais os seus possíveis contras. Faça você o exercício: quando que TDD pode ser ruim? Quando que ágil pode ser?

PS: Apesar de ter alguns médicos na família, não entendo nada. Prova disso é que não soube dar exemplos de ferramentas usadas em cirurgias, e também não sei ao menos se o “fazer o corte 2cm abaixo”, faz sentido.

[1] Booch, Grady. “Software development best practices.” The Rational Unified Process: an introduction (1998): 3-16.

Qual o meu problema com tipagem dinâmica?

TL;DR: Linguagens com tipagem dinâmica não nos ajudam a pensar em tipos e/ou abstrações. Por isso, tendemos a escrever código procedural.

Esse post é polêmico. Sempre que alguém me pergunta sobre linguagens dinâmicas, eu sempre solto alguma piadinha de mau gosto. Mas, bem, tenho lá meus motivos.

O primeiro deles é o que todos já conhecem. A falta da existência do tipo em tempo de compilação “desarma” um grande aliado: o compilador. Qualquer um que já programou mais do que 2 ou 3 métodos em uma linguagem dinâmica já confundiu a ordem dos parâmetros em uma função, ou já passou um array onde deveria vir lista. Quando você percebe isso? Só em tempo de execução. A desculpa de testes automatizados também não me é justa: por que preciso gastar tempo escrevendo testes para algo que poderia ser feito automaticamente pelo compilador, sem qualquer trabalho para mim?

O segundo é que discordo da “produtividade” em que todos os programadores de linguagens dinâmica alegam, afinal você  “escreve menos”. Quando você cria uma função, você pensa no tipo de todas as coisas que estão lá (esse será o meu ponto principal, que vem a seguir). A diferença é que (novamente) ao deixar isso implícito, você novamente perde o compilador.

Por fim, o que mais me incomoda, é que, quando o programador acostuma a deixar o tipo implícito em seu código, ele passa a “pensar menos nos tipos”. E pensar em tipos/abstrações é, pra mim, o que separa um simples escrevedor de código de um desenvolvedor. Se a linguagem não te dá maneiras claras de definir contratos, tendemos a escrever código linha abaixo de linha, de maneira procedural. Raramente expressamos nossas ideias como abstrações conversando entre si.

Obviamente, a tipagem dinâmica também tem seu espaço. Concordo muito com o artigo, do Erik Meijer e Peter Drayton [1]. Use tipagem estática onde possível, e tipagem dinâmica onde necessário.

[1] Meijer, Erik, and Peter Drayton. “Static typing where possible, dynamic typing when needed: The end of the cold war between programming languages.” OOPSLA, 2004.

Classes testáveis não “buscam”, mas sim “recebem”

TL;DR: Se você quer testar sua classe por meio de teste de unidade, essa classe não deve conter código de infra estrutura (como acesso a banco de dados, e etc), e ela também deve receber toda outra informação ou dependência necessária por meio de construtores ou parâmetros de métodos — a classe nunca deve buscar a informação diretamente ou instanciar uma dependência. Dessa forma, ela será facilmente testável por meio de testes de unidade.

Regras de negócio, no fim, nada mais são do que um monte de ifs e fors juntos. Portanto, deveríamos ser capazes de sempre testá-las por meio de simples testes de unidade. No entanto, às vezes nós dificultamos isso.

Veja o código abaixo. Nele, o FiltroDeFatura pega a lista de todas as faturas que está no banco de dados, e passeia por elas, guardando as que tem valores menor que 2000.

O código é simples, mas pense que ele poderia ser um pouco mais complicado. Portanto, precisamos testá-lo. A questão é: como? Afinal, não conseguimos escrever um teste de unidade pra ela; afinal é impossível executar o método filtra() sem passar por um banco de dados.

Essa é um tipo de código bastante comum nas aplicações por aí. O desenvolvedor sabe que não pode sair misturando SQL no meio de regra de negócio, e corretamente, coloca isso dentro do DAO. No entanto, ele instancia o DAO diretamente na classe, fazendo com que não seja possível executar a regra de negócio, sem passar pelo banco de dados.

E passar pelo banco de dados, na maioria das vezes, não é boa ideia. Escrever o teste é mais difícil (afinal, precisamos fazer INSERT dos dados no começo, DELETE depois, garantir o schema do banco, e etc), e mais demorado. E, aliás, não deveríamos precisar do banco de dados, para testar a simples regra de filtro de fatura.

A solução pra isso é mais fácil do que parece. Se você tem uma classe que contém regras de negócio, essa classe deve apenas conter regras de negócio. Ou seja, ifs e fors. Se sua regra de negócio precisar de alguma informação que venha de uma outra classe qualquer (seja um DAO, seja outra coisa), ela nunca deve “buscar” essa informação, mas sim “recebê-la”.

Ou seja, nesse código em particular, ou passamos a List<Fatura> para o método filtra(), ou passamos o FaturaDao pelo construtor. Veja:

Dessa forma, com o DAO sendo recebido pelo construtor da classe, conseguimos simular seu comportamento durante o teste, por meio de mock objects. E agora sim, nada de depender do banco para fazer um teste tão simples.

Portanto, guarde essa regra: se sua classe é uma classe que contém regras de negócio, ela nunca pode buscar as informações ou dependências que precisa por conta própria; ela deve sempre recebê-las.

Ah, essa ideia tem um nome bonito, inclusive: chama-se inversão de controle. Ou seja, “invertemos” a maneira tradicional de programar, que é sempre buscar pela dependência. Agora, alguém nos dá a dependência. Você pode ler mais sobre isso no meu livro de orientação a objetos e SOLID.

Quais as características de um bom engenheiro de software?

O que faz um bom engenheiro de software? Essa é uma pergunta que a Universidade de Washington tentou responder junto com a Microsoft. Eles entrevistaram 59 desenvolvedores experientes de 13 divisões diferentes da gigante, e chegaram a um total de 53 atributos que um engenheiro de software deve ter.

Lá vão eles:

  • Tenta melhorar o tempo todo.
  • É apaixonado pela área.
  • Cabeça aberta.
  • Avalia o resultado das suas ações.
  • Sistemático.
  • Produtivo.
  • Perseverante.
  • Trabalho duro.
  • Curioso.
  • Aceita riscos.
  • Adaptável.
  • Auto organizado.
  • Toma ações de melhoria quando preciso.
  • Alinhado com o objetivo da empresa.
  • Executa ações.
  • Tem orgulho da equipe e do produto que trabalha.
  • Consegue transformar ideias em realidade.
  • Focado.
  • Conhece as pessoas e a organização.
  • Analisa uma situação nas suas diferentes perspectivas.
  • Atualiza seus modelos mentais.
  • Lida bem com complexidade.
  • Conhece o domínio que trabalha.
  • Conhece o negócio e os usuários.
  • Conhece as ferramentas de trabalho.
  • Conhece processos de engenharia de software.
  • Cria modelos mentais para resolver problemas.
  • Sempre coloca contexto quando fala com outros engenheiros.
  • Compartilha seu sucesso com a equipe.
  • Cria um ambiente seguro para trabalhar.
  • Honesto.
  • Consegue integrar diferentes contextos.
  • Trabalha os outros com respeitos.
  • Busca por contexto quando não entende a situação.
  • Não torna uma briga pessoal.
  • Faz mentoring.
  • Traz desafios à equipe.
  • É um desenvolvedor exemplar.
  • Gerencia expectativas.
  • Tem uma boa reputação.
  • É firme em relação à pressão e prazos.
  • Troca favores com a equipe.
  • É uma pessoa legal.
  • Pede ajuda.
  • Cria soluções simples e elegantes.
  • É criativo.
  • Antecipa necessidades.
  • Faz tradeoffs.
  • Atento à detalhes.
  • Conhece bem o código.
  • Cria software fácil de ser evoluído.
  • Pensa não só no curto prazo, mas também no longo prazo.
  • Constrói software com cuidado, por exemplo, usando testes automatizados.

O artigo, claro, entra nos detalhes de cada um desses atributos.

Quais os impactos que essa lista traz para nós? E você, tem todos eles?

Li, Paul; Ko, Andrew; Zhu, Jiamin. What Makes a Good Software Engineer? 37th International Conference on Software Engineering, 2015.

Montando cenários em testes de sistema

TL;DR: Crie serviços web para montar os cenários que você precisa em seus testes de aceitação.

Montar cenários é sempre a parte mais chata em qualquer teste, seja ele manual, de unidade, de integração ou de sistema. O pior é que, quanto mais o teste é parecido com o do mundo real, mais difícil é. No teste de unidade, você tem uma classe, geralmente pequena, e que lida com poucas outras classes. Montar o cenário é razoavelmente fácil; você instancia duas ou três classes e pronto. Mas e quando o teste é de sistema? Como fazer para testar a tela de Nota Fiscal, se para isso, precisamos de um Cliente, Produto e Orçamento cadastrados?

A primeira opção é fazer com que o próprio teste seja responsável por navegar pelas telas anteriores e montar os cenários. Ou seja, antes de ir pra tela de nota fiscal, o teste passeia pela teste de clientes, e cadastra um por lá. Depois vai pra tela de Produto e cadastra um por lá. A mesma coisa com um Orçamento, para aí sim conseguir testar a Nota Fiscal. O problema disso é que absurdamente demorado passear por todas essas telas. E a chance de você repetir código entre as várias baterias de teste é ainda maior.

Nesse momento, meu coração diz que a melhor solução é fazer com que a aplicação Web contenha um conjunto de serviços web, prontos para montar o cenário que a aplicação quiser.

  • Precisa de um Cliente? O teste faz um post para /ws-testes/clientes, com o Cliente desejado.
  • Precisa de um Orçamento? O teste faz um post para /ws-testes/orcamentos.
  • Precisa de um Estado (e não existe tela de estado?) Não tem problema, tem o /ws-testes/estados.

Dessa forma, fica fácil. No código do teste, você faz uso de Test Data Builders para rapidamente montar esses objetos, e simplesmente os envia para a aplicação. Fazer serviços web simples, que recebem basicamente entidades, também é fácil, e seu framework MVC deve fazer isso quase que automático.

Além disso, porquê não ter também o /ws-testes/limpa ? Precisamos limpar nosso banco de dados antes de cada teste, para que um teste não influencie o outro.

Apesar de parecer pouco produtivo, na prática, os Test Data Builders usados são os mesmos que você já usa nos seus testes de unidade. E se você tem uma infraestrutura razoável, fazer serviços web é geralmente mais simples do que escrever a navegação nas funcionalidades ao redor, que você precisaria escrever de qualquer jeito.

Fuja dos XMLs malucos do DBUnit ou mesmo de testes de sistema que precisam navegar por 30 telas antes da sua. Dá trabalho? Claro que dá… Mas testar software é trabalho, não é?!

Variáveis de explicação melhoram o código?

É impressionante nossa capacidade de escrever “ifs” complicados ou com condições malucas. Veja, por exemplo, o if que escrevi no fim de semana. Consegue me dizer o que ele faz em 5 segundos?


if(!m.wasDeleted() && m.getFileName().toLowerCase().endsWith(".java") && m.hasDiff()) {
// ...
}

Uma simples maneira de refatorá-lo é extraindo partes da condição para variáveis com nomes significativos, que explicam melhor o que aquela condição significa. Por exemplo:


boolean naoDeletado = !m.wasDeleted();
boolean ehJava = m.getFileName().toLowerCase().endsWith(".java");
boolean temDiff = m.hasDiff();

if(naoDeletado && ehJava && temDiff) {
// ...
}

Perceba como é muito fácil agora ler a condição. Afinal, “as variáveis explicam”. Essa refatoração é conhecida por “Introducing Explaining Variable”, e você pode vê-la no famoso livro de Refatoração do Martin Fowler, ou mesmo nos atalhos da sua IDE favorita de desenvolvimento.

Li um artigo sobre esse tipo de refatoração há pouco tempo [1]. Nele, os autores mostram que desenvolvedores costumam fazer essa refatoração justamente em classes que já apresentaram muitos defeitos no passado. Para tal, ele observou refatorações feitas em 5 diferentes releases do projeto Eclipse.

O artigo tem lá seus viéses. O autor separa classes que receberam essa refatoração, e classes que não receberam essa refatoração, e mostra a média e a mediana da propensão das classes terem defeitos. Apesar da média ser realmente diferente, a mediana é igual em alguns casos. E, claro, em distribuições como essas, a mediana faz muito mais sentido. Ou seja, os resultados dele parecem ser mais fracos do que o que eles argumentam. Um ponto positivo é que o autor preocupou-se em mostrar que as classes tinham os mesmos tamanhos (afinal, classes maiores são mais propensas a terem bugs).

A pergunta que o artigo nos levanta é: Por que os desenvolvedores resolveram aplicar essa refatoração, justamente em classes problemáticas? Será que é por que essa refatoração realmente deixa o código mais claro de ler e, por consequência, menos suscetível a defeitos? Meu coração diz que sim.

Eu, particularmente, costumo sempre aplicar esse tipo de refatoração em ifs complicados como esse. Aliás, sempre que extraio variáveis de explicação, penso se esse código não deveria estar dentro da classe de origem.

A condição da variável ehJava, por exemplo, poderia estar dentro da classe Modification (que é o tipo da variável m). Dessa forma, fica fácil reusar a condição, e fácil de ler:


boolean ehJava = m.isJava();

[1] S. Counsell, X. Liu, S. Swift, J. Buckley, M. English, S. Herold, S. Eldh, and A. Ermedahl. 2015. An exploration of the ‘introduce explaining variable’ refactoring. In Scientific Workshop Proceedings of the XP2015 (XP ’15 workshops). ACM, New York, NY, USA, , Article 9 , 5 pages. DOI=10.1145/2764979.2764988 http://doi.acm.org/10.1145/2764979.2764988

Para melhorar meu design, preciso mesmo fazer o teste antes?

Já comentei bastante sobre os efeitos que a prática de TDD tem no projeto de classes. Meu livro, aliás, é totalmente focado nisso. No entanto, algo que não comentei nele, porquê provavelmente só soube transformar isso em palavras há pouco tempo, é que não me importo muito se o teste é escrito antes ou depois. Ué, como assim!?

Deixe-me explicar. Não é a “prática de TDD” que faz seu projeto de classes melhorar, mas sim, o que você consegue ver de problemas ao olhar para o código do seu teste. Ou seja, é o teste que te mostra o problema. Se ele é escrito antes ou depois, tanto faz.

Obviamente, se você deixar para escrever o teste 1 semana depois de escrever a classe, na hora em que ler o feedback do teste, talvez seja tarde demais pra refatorar — tarde, no sentido, de caro, afinal você provavelmente precisará mudar em muitos pontos diferentes.

No entanto, se você gastar 15 minutos no código, 15 minutos no teste, 15 minutos no código, 15 minutos no teste…. o feedback será o mesmo! Afinal, para pensar no design, a implementação depende do teste e o teste depende da implementação; a ordem em que você faz não ter dará mais ou menos retorno.

O teste é só um rascunho do seu projeto de classes. E o importante não é ter um rascunho, ou mesmo começar por ele, mas sim rascunhar com frequência.

 

ICSE2015 – When and Why Your Code Starts to Smell Bad

When and Why Your Code Starts to Smell Bad
Michele Tufano, Fabio Palomba, Gabriele Bavota, Rocco Oliveto, Massimiliano Di Penta, Andrea De Lucia, Denys Poshyvanyk

TL;DR. Na maioria dos casos, os artefatos já são criados com maus cheiros. Novas funcionalidades ou melhorias nas existentes é o que mais fazem artefatos ficaram mau cheirosos também. Novatos não são os maiores responsáveis, mas sim pressão pra release e quantidade de trabalho que ele fez (medido pelo número de commits).

Maus cheiros de código (code smells) é o nome que damos para quando o projeto ou a implementação são mais pobres do que deveriam. A indústria gosta do termo, e muitos maus cheiros tem nomes populares, como “spaghetti code”, “feature envy”, e assim por diante. A pergunta é: quando e por quê esses maus cheiros são criados?

Para tal, os autores analisaram meio milhão de commits, bem como o issue tracking em 200 projetos de código aberto diferentes. Para dizer se um determinado arquivo contém ou não um mau cheiro, eles fizeram uso do DECOR, uma ferramenta que busca por alguns dos mais conhecimentos maus cheiros.

Interessantemente, eles perceberam que a maioria dos maus cheiros foram introduzidos no momento em que os arquivos foram criados. Como esperado, maus cheiros são inseridos geralmente perto do deadline final de entrega — eles também notaram um número considerado de maus cheiros inseridos no primeiro ano do projeto. E, contrariando a lógica, novatos não são os maiores responsáveis pelos maus cheiros, mas sim, os próprios “donos” das classes ou aqueles que tem um fluxo grande de trabalho (no trabalho, calculado como a quantidade de commits feitos durante um período de tempo).

No entanto, todo o trabalho confia nos resultados da DECOR. E, claro, como todo trabalho que avalia uma grande quantidade de projetos, ele não leva em consideração o contexto de cada um deles (apesar dos autores terem inspecionado por volta de 9k classes).