Reflexões sobre desenvolvimento de software (only in Portuguese)

segunda-feira, 2 de novembro de 2015

Static Factory methods em vez de construtores

A forma que a linguagem Java oferece para criar objetos é através de um construtor. Entretanto construtores possuem algumas características que podem se tornar limitações em alguns contextos.

Java considerará como construtores métodos que possuem o mesmo nome de sua classe. Isso é bom pois cria uma padronização, mas ruim quando por exemplo, você precisa de dois ou mais construtores que inicializam suas novas instâncias de maneira diferente. Nesse caso você tem três possibilidades.

1. Uma abordagem é sobrecarregar o construtor com outra assinatura. Com diferentes tipos de parâmetros, ou pior ainda, apenas mudando a ordem deles. Essa abordagem é muito ruim pois a pessoa que for ler o código que usa esses diferentes construtores não conseguirá diferencia-los e pode facilmente confundi-los. Obriga a pessoa a ver a implementação dos seus construtores. Código torna-se pouco legível. Não use.

2. Uma boa abordagem é questionar: porque preciso desse outro construtor? Minha classe não está ficando complexa? Reflita e se a resposta que você precisa, use static factory methods.

A técnica é bastante simples: é um método estático que retornará a nova instância de sua classe chamando o construtor em sua implementação. Como para o compilador esse método é um método como qualquer outro, você pode dar um nome que exprima de maneira adequada o seu uso e sua intenção.
A api de Java tem um ótimo exemplo desse uso. Esse construtor da classe BigInteger(int int, Random)retorna um BigInteger com alta probabilidade de ser um número primo. Confuso né? Por isso que na release 1.4 de Java foi incluído o static factory method BigInteger.probablePrime.Apenas dar um nome com significado em vez de usar um construtor tornou o código extremamente mais fácil de ler. E isso se converte em horas economizadas uma vez que você e toda sua equipe vai ler esse código várias vezes durante o desenvolvimento.

É por isso que static factory methods embora simples, são uma ferramenta que deve estar no cinto de utilidades de todo programador Java.

Existem outras vantagens interessantes de static factory methdos:

1. São eles que permitem os design patterns Singleton e FlyWeight.

2. Ao contrário de construtores, podem retornar subtipos  do tipo de retorno, dando grande flexibilidade.

Como desvantagens, podemos citar:

1. Caso você opte por remover o construtor tornando ele privado e usar apenar um static factory method, sua classe não será subclasseavel. Desvantagem bem específica e pouco impactante.

2. Agora uma desvantagem de fato: static factory methods não se destacam na sua api do mesmo modo que um construtor. Quando você vê um construtor, você sabe que ele forçosamente vai retornar uma nova instância da sua classe. Dependendo do nome que você der para seu static factory method, pode ficar um pouquinho difícil de ver que ali temos um método que tem o papel de um construtor. É por isso que é interessante usar algum desses padrões já bastante usados na própria api da linguagem:
  • valueOf --> Retorna instância descrito por seus parâmetros. São de fato conversores. Exemplo clássico: Integer.valueOf(3). Esse método retorná o objeto 3 a partir do primitivo 3.
  • of --> Versão concisa do valueOf popularizada pelo EnumSet.
  • getInstance -->  bastante usado em singletons.
  • newInstance --> garante que uma nova instância será retornada, contrastando com getInstance.
Resumindo: evite o reflexo imediato de prover um construtor público e reflita se um static factory method não tornará sua api mais legivel e fácil de usar

Fonte: Effective Java do Joshua Bloch. Leiam !!!

sexta-feira, 2 de outubro de 2015

Junit Category para categorizar seus testes

Essa é uma excelente solução para quando existem testes que por qualquer razão rodam muito lentamente e não queremos executar sempre (mas queremos presente pra rodar quando necessário ou/e na ferramenta de integração contínua).

No caso, utilizando Maven, Junit e Java.

1. Crie uma interface vazia com um nome que faça sentido, por exemplo, FastTest.


2. Anote as classes que se encaixam nessa categoria com o @Category que o Junit provê e passe como parâmetro a categoria que você criou.


3. Agora adicione o surefire maven plugin no seu pom na parte build e crie um profile que rodará apenas os testes da categoria que você criou.


No exemplo acima, a execução do comando "mvn test -Pprofile-de-testes" executará apenas os testes contidos em classes anotadas com @Category(FastTest.class), ignorando os demais testes. Agora você consegue anotar os testes de maneira elegante sem precisar usar aqueles padrões feios nos nomes das classes.

Fonte: https://github.com/junit-team/junit/wiki/Categories


sexta-feira, 17 de abril de 2015

O futuro dos sistemas de tipos das linguagens de programação

Discussões infindáveis sobre qual a melhor linguagem de programação sempre existiram e sempre existirão. Um dos pontos sempre muito discutidos é a questão do melhor estilo de tipagem: seria melhor uma tipagem dinâmica a la Ruby, com muita concisão, ou seria mais interessante uma linguagem estaticamente tipada a la Java, que consegue detectar vários erros em tempo de compilação, mas bastante verbosa e menos flexível?

É engraçado que quando programo em Ruby e tenho que usar um método fico muitas vezes desconfortável por não saber exatamente que tipo posso passar nos parâmetros sem olhar a documentação, ou ainda fico gastando um tempão em um jeito de fazer a assinatura dos meus métodos deixar bem claro o tipo de objeto esperado, ao passo que voltando pra Java chegava a ser ridículo o número de linhas necessários para fazer coisas simples, ou ainda acaba acontecendo muita dificuldade em refatorações que deveriam ser simples.

Sempre foi bem claro que ambas as abordagens tem vantagens e desvantagens, e sentia que a linguagem que conseguisse juntar isso estaria a frente das demais em questão de design e expressividade.

E assistindo a essa palestra mais acadêmica do Strangeloop, fica ainda mais claro que essa noção intuitiva é verdadeira. 

Nela, os palestrantes começam pelo objetivo de um bom sistema de tipos: fazer representações de estados ilegais não serem possíveis de representar pela sintaxe de um programa bem formado (ou seja, que compila sem erros). Se extrapolarmos, em um sistema de tipos perfeito (tipo um verificador formal), você tem segurança total que seu programa vai funcionar para todas as entradas antes mesmo de ele executar. 

Os sistemas de tipos atuais como os de Java e C++ estão tão longe desse ideal (nullpointer exceptions, pra citar um exemplo comum de problema de runtime), ficando muitas vezes tão no caminho do programador, que muitos designers de linguagens de programação tais como Ruby preferiram simplesmente sequer ter um sistema de tipos, e deixar que todos esses erros sejam capturados em runtime mesmo. E provavelmente esse foi um dos grandes motivadores para a comunidade Ruby ter desenvolvido tantos frameworks de teste bons, já que com testes você pode em runtime (pelo menos não em produção) se certificar que tudo funciona como esperado.

Uma frase que eu gostei da Amanda Laucher nessa mesma palestra é que não precisamos de mais linguagens dinâmicas, precisamos de linguagens com sistemas de tipos melhores. E é por esse caminho que as linguagems tem andado. Scala é um caso de linguagem estaticamente tipada que faz tantas inferências em tempo de compilação que chega a enganar os mais desavisados num primeiro momento olhando pro código de que é dinâmica (ainda que com o custo de uma compilação muito lenta). Golang também segue por ai também, com bastante inferência de tipos.

Além de estarmos presenciando essa evolução nos sistemas de tipos das linguagens mais recentes, as tradicionais dinamicamente tipadas Ruby e Python também começaram a perceber que certas verificações antes do tempo de runtime podem ser bastante benéficas. Matz, o criador de Ruby, anunciou suas propostas de verificações de tipo na Rubyconf 2014, na esperança de já ver elas em Ruby 3.0 (que eu espero que realmente aconteçam). Python também teve propostas nessa linha feitas por seu criador Guido van Rossum.

Se você pensava que já tinhamos linguagens demais, o futuro parece indicar que teremos muitas outras, além de grandes modificações nas já existentes, há muito ainda por onde evoluir.

PS: Pra quem acha o assunto fascinante, dê uma olhada em linguagens acadêmicas com dependent types como o Idrys, é meio complicado, mas as ideias pro trás parecem bem imteressantes.

Outro PS: Post inspirado por esse excelente post do Yevgeniy Brikman.

segunda-feira, 2 de fevereiro de 2015

Estimativa de histórias usando t-shirt sizes (tamanhos de camiseta)

Estimar histórias é sempre algo difícil de fazer, pois muitos dos desafios de cada história só são descobertos durante o desenvolvimento dela. Ainda que eu preferisse não precisar estimar, quando existem stakeholders e clientes investindo tempo e dinheiro, é necessária uma métrica de quanto tempo as funcionalidades levarão para ficarem prontas para ajustar expectativas.

Além desse motivo, estimativas aliadas a boas retrospectivas podem ajudar a descobrir partes de um sistema que precisam de refatorações. Por exemplo, caso histórias atrasem ou sejam sempre rotuladas como difíceis, pode-se investigar o que essas histórias tem em comum e procurar soluções que possam mitigar esses problemas.

Existem diversas maneiras de estimar histórias, dentre elas, a que mais me agrada é usando t-shirt sizes. Nesse tipo de estimativa, história podem ser pequenas, médias ou grandes (assim como tamanhos de camisetas). O que significa exatamente esses tamanhos depende do time. Além de ser uma terminologia fácil de usar mesmo por pessoas não técnicas, esse modo funciona bem em equipes com iterações bem curtas.

Nesse caso, acho bastante efetivo quando o tamanho médio significa um história que pode ser terminada em uma iteração. Consequentemente uma história pequena em menos que uma iteração e uma grande em mais de uma.

Funcionalidades que o cliente deseja adicionar ao produto normalmente são escritas pelo analista de negócios como épicos. Esses épicos devem ser quebrados em histórias menores que adicionem valor o mais rápido possível. Deve-se tentar quebrar épicos em várias histórias médias, mas inevitavelmente existirão histórias que seja pela arquitetura da aplicação ou mesmo por dificuldades para testá-la adequadamente, terão que ser rotuladas como uma história grande (isto é, finalizada em mais de uma iteração). Assim como às vezes existem funcionalidades pequenas demais que já agregam valor, que podem ser rapidamente finalizadas em menos de uma iteração.

Existem outras maneiras de estimar, usando por exemplo a sequência de Fibonacci, mas dado que estimativas nunca são precisas, se o time consegue fazer iterações bem curtas como de uma semana, não vejo tanto valor em dar mais precisão do que pequeno, médio ou grande na maior parte dos casos.

sexta-feira, 23 de janeiro de 2015

Atualizando-se com podcasts de tecnologia

Uma coisa que eu acho muito legal no lugar em que trabalho é que as entrevistas relacionadas a novas contratações são responsabilidade de todos na empresa independente da experiência. Uma vez com uma lista de interessados (que qualquer um pode fazer parte), o time de recrutamento marca as entrevistas de acordo com a disponibilidade dos envolvidos, de forma que sempre seja uma dupla entrevistando, com pelo menos uma pessoa com experiência. Ao fim das várias entrevistas os interessados se reúnem e compartilham suas impressões.

Além de deixar o processo de contratação bastante transparente, não sobrecarrega poucos indivíduos e faz todos se sentirem parte disso, fora que ajuda a evitar algum viés que naturalmente ocorre quando uma só pessoa fica responsável por todo um processo.

Graças a essa prática, já tive a oportunidade de entrevistar vários candidatos, especialmente em entrevistas técnicas. Uma das perguntas que muitas vezes costumo fazer é como a pessoa costuma se informar sobre novidades na área de tecnologia. Normalmente as respostas variam entre blogs e conversas com os amigos. Em menor número é citado o Twitter, livros e listas de email.

Todas essas são excelentes ferramentas para se atualizar sobre as novidades, entretanto uma forma bastante interessante de se atualizar que eu pouco ouvi ser mencionada nessas entrevistas foi podcasts. Existem atualmente uma gama de podcasts abordando o tema de tecnologia. É uma forma de se atualizar que complementa muito bem as demais e que descobri somente recentemente, pois embora soubesse da existência de muitos podcasts sobre tecnologia, não sabia que existiam específicos para desenvolvedores e analistas. Esses podcasts normalmente reúnem pessoas com bastante conhecimento no assunto, com uma abordagem mais leve do que um livro mas não menos informativa. Alguns reúnem inclusive escritores de publicações, palestrantes e pessoas bastante ativas nas comunidades de tecnologia. São excelentes oportunidades de ouvir conversas e discussões bastante enriquecedoras sobre temas que nem sempre temos um amigo ou um conhecido falando sobre.

Existem uma infinidade de podcasts, e o jeito é garimpar pela internet, mas entre os que eu tenho escutado e recomendo:

Tecnologicamente Arretado: Um podcast relativamente novo criado pelo ThoughWorker Gregório, com capítulos sobre diversos temas da atualidade como Segurança, AngularJs, Programação Funcional entre outros temas. Sempre com a presença de convidados que manjam muito do assunto.

Grok: Um dos podcasts mais antigos do Brasil com mais de 4 anos de atividade, e talvez também o mais famoso. Excelente fonte de informação, não apenas sobre tecnologias, mas também com edições sobre conferências como RubyConf e mulheres em TI.

Pra quem tem o inglês afiado, as opções são muitíssimas, mas fica a dica do Functional Geekery, que trata especificamente de programação funcional mais a fundo. No caso de podcasts em inglês, além do conteúdo propriamente dito, é uma ótima oportunidade para treinar o ouvido pra discussões que não são em Português.

sexta-feira, 2 de janeiro de 2015

Como passei a gostar de TDD

Já é lugar comum a importância de testes automatizados em desenvolvimento de software. Não somente pela confiança que eles dão ao desenvolvedor na hora de fazer refatorações e correções de bug, mas também porque para ter testes em todos os níveis, o código precisa necessariamente ser testável.

Código testável, isto é, aquele em que dependências podem ser facilmente isoláveis, não é algo sempre trivial de fazer. Normalmente requer um design mais criterioso em que os pedaços de código sejam distribuídos em classes pequenas e coesas, caso contrário,  os testes ficam muito grandes, trabalhosos e as vezes até mesmo impossíveis de fazer. 

Sendo assim, a presença de testes está não somente influenciando pela sua presença per se, mas também pela consequente melhora no design que a inclusão deles forçou acontecer.

É por isso que era um pouco reticente com TDD. Se o programador não abre mão dos testes, pra mim pouco importava se os testes eram escritos antes ou depois da feature propriamente dita.

Entretanto com o tempo acabei mudando de opinião. Acredito que design (pensar em como resolver o problema) e codificação (a escrita de código) não são atividades isoladas. É necessário codar para ter insights sobre o design, assim como para codar é necessário pensar no design que se quer atingir. Design e codificação são atividades que devem andar juntas pois há uma simbiose natural nelas. 

Portanto, para mim, a forma de codar mais efetiva é aquela em que mescla design e codificação o máximo possível para um retroalimentar o outro, num algoritmo que seria mais ou menos assim:
  1. Coda um pouquinho
  2. Analisa o design da solução
  3. Volta pro passo 1 até terminar a feature
Dessa forma, o código está em constante evolução, em vez de codar toda a solução, ver que não gostou e refatorar tudo de uma vez (o que eu costumava fazer quando implementava os testes depois da feature, nada ágil).

Codando dessa maneira, TDD se encaixa como uma luva no desenvolvimento, pois cada caso de teste e sua implementação até ficar verde ficam sendo a perfeita unidade de medida e ponto para reavaliar o andamento do design.

quinta-feira, 1 de janeiro de 2015

Onboardings efetivos

Quando um integrante novo é adicionado a uma equipe de desenvolvimento, é padrão (mesmo que informalmente) fazer algum tipo de onboarding, isto é, uma introdução as regras de negócio e a arquitetura do sistema. Esse onboarding pode ocorrer de muitas maneiras, mas normalmente ocorre com integrantes mais seniors do time explicando em linhas gerais o que for julgado necessário, e em times que exploram pair programming esse processo continua naturalmente até o novo integrante se tornar tão habituado como os demais. Pair programming é extremamente efetivo nesse caso (especialmente quando é uma equipe acostumada com isso, e não apenas uma forma de onboarding).

Infelizmente, a maior parte das empresas e seus times não são habituados ao pair programming, e nesse caso, após as explicações iniciais, normalmente o novo integrante recebe uma tarefa e deve executá-la sozinho, com a premissa de que pode pedir ajuda sempre que necessário.

Entretanto, as vezes a própria disposição das mesas na empresa (por exemplo cubículos) ou mesmo uma personalidade mais introspectiva pode desestimular perguntas. Embora possa acontecer com qualquer nível de senioridade dependendo da personalidade e especialmente do grau de auto-exigência / ansiedade em colaborar logo, isso fica ainda mais crítico quando o desenvolvedor a ser agregado ao time tem pouca experiência profissional codando (por exemplo um estagiário ou desenvolvedor júnior).

Nesse caso, a preocupação compreensível de estar "atrasando o projeto" com perguntas "óbvias", ou tomando tempo dos outros desenvolvedores que certamente tem tarefas "mais essenciais ao projeto" fica ainda mais evidente. Isso constantemente leva esses desenvolvedores a perguntar menos e tentar resolver suas tarefas sozinhos com ajuda de ferramentas como o stackoverflow ou fóruns.

Isso tem seu lado positivo já que procurar essas ferramentas e tentar buscar um certo nível de autonomia são indicadores de amadurecimento. Contudo, essas tarefas iniciais caso não possam ser acompanhadas com pair programming devem ser atribuídas de forma mais criteriosa.

Caso não seja possível dar uma tarefa fácil por falta delas no backlog, ou ainda exista o interesse proposital de atribuir uma tarefa um pouco mais difícil para de forma saudável desafiar o novo integrante, deixe isso bem claro de forma transparente. Muitas vezes essa pequena informação pode aliviar sentimentos que podem surgir na cabeça do novo desenvolvedor em que ele acha que está performando mal ou tendo dificuldades demais, o que desmotivam e tornam a curva de aprendizado menos efetiva.

Ou como Katherine Wu disse no excelente post how to be a better junior developer (infelizmente apenas em inglês, trecho traduzido livremente):
".. se como mentor você quer intencionalmente deixar um desenvolvedor junior sofrer um pouquinho para aprender alguma coisa, deixe-o saber que você está fazendo isso. Dessa forma elimina-se sentimentos como o da síndrome do impostor, no qual a pessoa perde sua auto-confiança porque acredita erroneamente que a tarefa deveria ser mais fácil mesmo que de fato seja difícil.  Eu mesma fiz esse erro muitas vezes. É preciso ter cuidado quando atribuímos desafios para pessoas e deixá-las saber o quão difícil é esperado que um problema seja."