Por que eu devo ler este artigo:Este artigo será útil para desenvolvedores, líderes técnicos e gestores de equipes de criação de software interessados em tornar o processo de implementação mais eficiente e eficaz e melhorar a qualidade do código fonte de suas entregas. Para isso, o artigo sugere boas práticas de desenvolvimento e de padrões de projeto que, se bem aplicados, favorecem a elaboração de um código limpo, conciso e de fácil manutenção e alteração.

O foco deste será na linguagem de programação Java, mas muitos dos itens abordados podem ser levados em consideração na construção de qualquer software, em ambientes diversos. Contudo, não serão abordadas boas práticas de design de interface de usuário ou de geração de assets para criação de telas visto que este é um assunto que, por si só, merece um artigo à parte.

A rotina básica de desenvolvimento de software é simples: baixar o código de um repositório, codificar, validar o que foi criado e, se tudo estiver como esperado, integrar os novos artefatos ao repositório e testar as novas funcionalidades. Esta rotina, no entanto, pode ficar mais complexa ao ter novos passos adicionados, ser compartilhada através da programação em pares, não fazer uso de controle versão, entre outras possibilidades. Além disso, ela também pode ser simplificada, principalmente em cenários onde não há trabalho em equipe. Independentemente disso, isto é, em qualquer um desses casos, o que realmente irá torná-la impraticável ou ineficiente é a utilização de más práticas de desenvolvimento. Para a finalidade deste artigo, expor boas práticas nesse contexto, a rotina descrita inicialmente será considerada como o padrão adotado no dia a dia de uma empresa de TI.

A partir disso, será possível identificar, em cada uma das fases, problemas ou dificuldades causadas pelo uso de práticas indevidas, que podem levar ao atraso de entregas ou até mesmo do projeto como um todo. Descrever brevemente cada uma dessas fases e listar alguns dos problemas que ocorrem durante cada uma será o objetivo da primeira parte deste artigo. Posteriormente, serão apresentadas práticas para tornar o processo de implementação mais eficiente e eficaz, focando na melhoria da qualidade do código fonte, na metodologia e práticas de testes utilizadas.

Fase de aquisição ou atualização do código

A primeira fase da rotina básica de desenvolvimento mencionada é a de aquisição do código. Esta fase ocorre não apenas no início do desenvolvimento, mas a cada vez que é necessário atualizar o código local. Então, se a atualização toma um minuto do programador e este atualiza o código 15 vezes ao dia, somam-se 15 minutos diários. Este tempo aumenta para 1 hora e 15 minutos em uma semana e resulta em aproximadamente cinco horas em um mês de trabalho.

Se este mesmo cálculo, do tempo despendido em tarefas que podem ser evitadas, for aplicado em toda a rotina de desenvolvimento, é possível verificar quanto tempo um time desperdiça em tarefas que não agregam valor ao produto a ser entregue.

A fase de aquisição de código é prejudicada pelo uso de más práticas de várias formas. Uma das atitudes a ser evitada é a utilização de nomenclatura inadequada no repositório ou nos branches. Elas não causam grandes impactos negativos no cronograma do projeto, mas dificultam a identificação do código a ser evoluído. Outra atitude a ser evitada é a adição de arquivos desnecessários no sistema de controle de versão. Estes arquivos podem deixar o repositório grande e confuso e tornar o processo de atualização do código demorado.

A aplicação de boas práticas nesta fase resulta na diminuição do tempo gasto por cada um dos membros do time para integrar e atualizar o código com o armazenado no repositório. Atitudes simples como não adicionar arquivos gerados durante o processo de compilação e utilizar um sistema de controle de versão são suficientes para tornar a fase de aquisição de código muito mais produtiva.

Fase de codificação

A fase de codificação é aquela que oferece as melhores oportunidades para aplicação de boas práticas. Na rotina usada de exemplo neste artigo, esta etapa inclui não somente a codificação em si, mas também a modelagem e a arquitetura do aplicativo a ser construído.

A adoção de práticas ruins durante a codificação geralmente prejudica o trabalho em equipe, levando à criação de produtos de baixa qualidade ou que não atendem às necessidades dos usuários ou clientes, como aplicativos que consomem muita bateria, fazem uso do processador em excesso e/ou que apresentam muitos problemas durante a execução.

O alto consumo de processador pode ser relacionado à duplicidade de informações armazenadas, chamadas inoportunas e inserções, seleções ou atualizações desnecessárias a um banco de dados, por exemplo. O alto consumo de bateria, por sua vez, pode ser consequência do uso excessivo do processador, como também do acesso a informações remotas através de rede de dados e Wi-Fi, ou mesmo do uso abusivo do GPS e de outros sensores do aparelho.

Usualmente, o processo de codificação é executado de maneira individual ou em duplas, com o objetivo de evoluir um código compartilhado por uma equipe. A falta de critérios pré-estabelecidos de formatação do código, o desleixo, a falta de atenção ou a ausência do sentido de coletividade do código e do empenho por parte de membros do time são alguns dos fatores que prejudicam o trabalho.

Portanto, saiba que aplicar boas práticas durante a codificação resulta em um código bem estruturado, limpo, conciso e de fácil compreensão, e esta melhoria da qualidade do código viabiliza o aumento da produtividade do time em todas as fases de qualquer processo de software.

Fase de validação do código desenvolvido

A terceira fase da rotina de desenvolvimento em análise é a validação do código implementado. Diferentemente dos conhecidos testes de software, esta fase não é necessariamente executada por testadores, mas pelo próprio programador com o intuito de avaliar se sua implementação está gerando os resultados esperados, antes de integrar o novo código com o gerado pelo restante da equipe.

Nesta fase, a utilização de práticas ruins normalmente não traz consequências imediatas e os problemas geralmente “aparecem” após a integração do código ou em releases subsequentes. Na verdade, as consequências são imprevisíveis: o código integrado pode cumprir com seu propósito, não causar bugs de regressão e nem novos bugs; como também pode causar problemas de compilação do código do repositório utilizado por toda a equipe.

A aplicação de boas práticas de desenvolvimento nesta etapa diminui a quantidade de problemas detectados principalmente em releases subsequentes, pois a probabilidade de adição de código que cause os chamados bugs de regressão ou novos bugs é reduzida. Dentre as boas práticas, pode-se citar a programação em pares e a consulta aos casos de uso relacionados às funcionalidades implementadas.

Fase de integração do novo código

A fase de integração de código é a quarta da rotina de desenvolvimento usada como exemplo neste artigo. Deste modo, esta etapa é diretamente impactada pela aplicação de decisões ruins nas fases anteriores. Pensando nisso, muitas das boas práticas sugeridas para a codificação, por exemplo, visam agilizar e não tornar a fase de integração do código um transtorno para os participantes do projeto.

Da mesma forma, a não utilização de boas práticas – especificamente na fase de integração – gera impactos negativos nas outras etapas da rotina. A integração de código indevido pode causar transtornos na fase de aquisição do código por outros membros da equipe, como descrito anteriormente, pode levar à evolução de código fonte sem padrão e dificultar as fases de codificação e testes, pode complicar o processo de integração do código de outros participantes do projeto, assim como pode levar ao surgimento de bugs de regressão e à perda do histórico da evolução do código, que têm outras consequências negativas para o processo de construção do software.

O uso de boas práticas neste momento é essencial para a qualidade do produto, assim como decisões equivocadas podem inutilizar boas ações aplicadas anteriormente. Dentre as tarefas a considerar, estão a revisão de código e o uso de sistemas de controle de versão.

Fase de testes

A fase de testes é a última da rotina definida como exemplo. Entretanto, é importante deixar claro que este artigo não defende a ideia de que os testes de software devem ser executados após a codificação, muito pelo contrário. Executar os testes durante a implementação das funcionalidades é uma boa prática que deve ser considerada e que será detalhada posteriormente.

Nesta fase, os casos de teste preparados são executados com o intuito de verificar se os requisitos esperados estão implementados da maneira correta, se os fluxos navegacionais estão como previstos e se o produto funciona a contento. Em outras palavras, é nesta fase onde a qualidade do produto é validada.

Assim como nas outras fases da rotina, a aplicação de más práticas durante os testes leva à diminuição da qualidade do produto criado. Por exemplo, a não automatização torna os ciclos de testes mais demorados e com cobertura menor, já que os testadores, geralmente, concentram seus esforços apenas nos casos de testes mais críticos e importantes, para que o cronograma do projeto não seja impactado.

O uso de boas práticas nesta fase é importante para agilizar o processo de desenvolvimento, melhorar a compreensão e a confiabilidade do código, entre outros. Como sugestões a considerar, estão a já citada automatização, o desenvolvimento orientado a testes e a realização das atividades de testes durante a codificação.

Utilizando boas práticas de desenvolvimento

O trabalho de desenvolvimento de software deve ser visto como uma atividade coletiva e colaborativa. Com base nisso, as boas práticas sugeridas neste artigo visam, dentre outras coisas, facilitar ou viabilizar a colaboração entre os participantes de um projeto de software. Por isso, o primeiro e mais importante dos passos para aumentar a eficiência e a eficácia de um time é que os membros deste tenham consciência de que estão trabalhando em conjunto, com um objetivo em comum, e que compartilham e evoluem um recurso único, o código fonte.

O segundo passo é que o time tenha as condições básicas para aplicar as boas práticas de desenvolvimento. As boas práticas tornam as equipes mais produtivas, mas pode ser preciso uma mudança de mentalidade gerencial ou organizacional para atingir melhores resultados. Uma mudança da metodologia de desenvolvimento, por exemplo, pode ser necessária, e o apoio gerencial é fundamental para que isto ocorra.

O terceiro passo é que todos os envolvidos na criação do aplicativo estejam sempre em busca de melhorar o cenário atual, independentemente de que situação seja esta. Para que seja possível identificar pontos de melhoria e adquirir o conhecimento necessário para corrigir os defeitos, é fundamental ter acesso a treinamentos, revistas e sites especializados. Além disso, é interessante que o próprio time promova eventos com o intuito de compartilhar o conhecimento entre seus membros.

Metodologias de desenvolvimento

Existem diferentes metodologias que podem ser aplicadas em processos de desenvolvimento. Dentre elas é possível destacar as metodologias tradicionais e as ágeis. As metodologias tradicionais requerem que os custos, as atividades e as entregas sejam definidas antes do início do projeto e que estes mudem minimamente no decorrer da execução do mesmo. Já as metodologias ágeis permitem e encorajam essas mudanças, com o objetivo de estar sempre guiando o desenvolvimento de um produto que melhor atenda às necessidades do cliente e dos usuários.

Alguns dos princípios das metodologias ágeis as fazem mais interessantes, quando comparadas às metodologias tradicionais, para times que buscam melhorar a qualidade dos códigos, dos produtos e aumentar a produtividade. As metodologias ágeis incentivam a programação em pares, a realização de testes durante a codificação, a participação de todo o time na estimativa do tempo de duração do projeto, o trabalho em equipe, dentre outras atividades que levam a um ganho na qualidade do código e do produto, além de aumentar a eficiência e a eficácia do time.

A programação em pares possibilita a diminuição do tempo necessário para solucionar problemas complexos. Além disso, o código produzido pela dupla tende a ter uma qualidade próxima da ideal, pois já está sendo revisado durante a codificação. Há, também, uma redução nas exceções e erros decorrentes deste código, já que existem duas pessoas atentas aos detalhes do que está sendo produzido.

Ao envolver todo o time na estimativa do tempo de duração do projeto, as metodologias ágeis tornam as estimativas mais precisas. Como consequência, o número de horas extras, atrasos e prazos curtos, que são fatores que causam piora na qualidade do código e do produto, são minimizados.

Dentre os eventos previstos por essas metodologias durante o desenrolar do projeto estão as reuniões de retrospectiva. Nestas ocasiões o próprio time lista os problemas e propõe melhorias a serem aplicadas imediatamente. Isto proporciona uma melhoria contínua em todos os aspectos do projeto, incluindo o código fonte. Há também reuniões com todos os membros do projeto para apresentar as entregas parciais para o cliente ou seu representante. Este tipo de atividade faz com que os membros do time se sintam responsáveis pelo aplicativo e, consequentemente, pelo código fonte, pelas atividades e pelo cumprimento dos prazos.

Existem outras vantagens em se aplicar metodologias ágeis no processo de desenvolvimento, assim como há vantagens em se aplicar abordagens tradicionais. Por isso, um estudo aprofundado do tema é sugerido antes de se definir aquela que melhor se adéqua à organização, ao time ou ao produto a ser construído.

Ao implantar uma metodologia é importante que não se façam alterações significativas em seus princípios e recomendações, pois elas foram criadas a partir de estudos e observações comportamentais e foram evoluindo através da experiência de uso em situações diversas.

Ademais, como já mencionado, a mudança da forma de trabalhar durante a codificação depende de apoio gerencial, pois esta pode ir de encontro aos costumes e à cultura organizacional. Ainda assim, vale ressaltar que utilizar uma metodologia é essencial para melhorar a produtividade de um time e propiciar a organização necessária para se construir um software com sucesso. Esta é, também, a principal razão da aplicação de uma metodologia ser uma excelente prática de desenvolvimento.

Boas práticas de programação

Independentemente de a organização ter definido, ou não, uma metodologia de desenvolvimento, outras boas práticas podem ser aplicadas para aumentar a produtividade da equipe e a qualidade do produto por ela criado. São ações que dependem apenas dos programadores para serem utilizadas e têm influência direta na evolução do aplicativo, pois são relacionadas ao próprio código fonte. O objetivo de se utilizar estas práticas é melhorar a legibilidade do código ou a arquitetura do mesmo.

Como já mencionado, a melhoria da compreensão do código fonte tem impacto direto nas fases de aquisição, codificação, testes e integração com o repositório. Ao adquirir um código de difícil leitura, o profissional levará um tempo muito maior para conseguir evolui-lo ou corrigir algum problema. Além disso, a codificação e a validação do que foi programado poderão ser feitos de maneira incorreta, gerando defeitos no código ou complicando ainda mais o seu entendimento. A integração com artefatos de baixa qualidade pode levar a uma concepção equivocada e provocar remoções ou alterações desnecessárias, o que, por sua vez, pode causar novos defeitos.

Por isso, uma prática importante e que deve ser mantida como prioridade não somente na fase de codificação, mas em toda rotina de desenvolvimento, é a manutenção de um código fonte legível. Esta atitude é benéfica para o projeto e para o time, pois facilita a compreensão do resultado do trabalho de cada um.

O código fonte ideal é aquele bem estruturado, limpo, conciso, de fácil entendimento e, obviamente, que não apresenta problemas de compilação e provê os resultados esperados. Este conceito, no entanto, é relativo, pois cada membro do projeto tem sua concepção sobre como deve ser um código legível. É principalmente por este motivo que se deve incentivar o sentimento de colaboração e coletividade entre os membros do time. Lembre-se que o trabalho em equipe é essencial durante todo o projeto, inclusive na definição das práticas a serem adotadas, visto que o código será compartilhado entre todos. Além disso, é importante que todos sigam o padrão estabelecido, caso contrário a situação pode ficar ainda mais complexa.

De maneira geral, um código legível é direto, simples e fácil de compreender, não possui duplicidades, é eficiente e faz apenas o que é proposto. Para auxiliar na busca por este resultado, a seguir serão descritas boas práticas que visam a criação de um código fonte de qualidade.

Para manter o código legível, uma das primeiras sugestões é adotar apenas um idioma em tudo o que for produzido. O código deve escrito em um idioma que seja compreensível por todos os envolvidos no projeto. Se este for compartilhado apenas por brasileiros, por exemplo, os comentários, os nomes das classes, das variáveis, das constantes e afins podem ser escritos em Português. Entretanto, é preciso lembrar que ao implementar um grande sistema, por exemplo, podem ser utilizadas bibliotecas e SDKs em Inglês. Nestes casos, o idioma Inglês é o recomendado. A Listagem 1 mostra um exemplo onde há mistura dos idiomas Português e Inglês.

01 class Duck {
02     boolean isAndando = false;
03 
04     void anda() {
05         isAndando = true;
06     }
07 }
Listagem 1. Código com mistura de idiomas

A indentação é um dos aspectos visuais mais importantes para facilitar ou dificultar a leitura e o entendimento do código. Primeiramente é importante decidir se serão empregados tabulações ou espaços, e depois, definir o tamanho da indentação. Normalmente, utiliza-se quatro espaços.

Ainda em relação à indentação, é importante que todo o código aninhado esteja indentado, assim como as quebras de linhas longas.

Em relação ao comprimento das linhas, é importante definir um limite. Linhas muito longas tendem a ser confusas ou, até mesmo, não caberem na tela. Os limites normalmente utilizados são 60, 80 ou 100 caracteres. A quebra da linha pode ser feita manualmente em um ponto que não prejudique muito o entendimento do conteúdo.

A Listagem 2 mostra um exemplo de um código onde o comprimento da linha ultrapassa o tamanho ideal (linha 1) e um código onde a prática da quebra manual da linha é aplicada para facilitar a compreensão (linhas 6, 7 e 8). Nesta mesma listagem a indentação correta é utilizada com o mesmo objetivo de melhorar a legibilidade, como no alinhamento dos elementos do if das linhas 6, 7 e 8.

  01 if (SuperMarket.getInstance().isOpen() && 
     !DrugStore.getInstance().isOpen() && this.isSick && this.hasMoney)
  02 {
  03     goToSuperMarket();
  04 }
  05
  06 if (SuperMarket.getInstance().isOpen() &&
  07    !DrugStore.getInstance().isOpen() &&
  08    this.isSick && this.hasMoney)
  09 {
  10     goToSuperMarket();
  11 }
Listagem 2. O comprimento da linha do código e a indentação interferem na legibilidade

Outra boa prática é criar nomes consistentes para as classes, variáveis e constantes. Apenas pelo nome destes elementos deve ser possível entender a razão de existirem ou o que fazem. Não há um limite sugerido para o tamanho dos nomes.

A utilização de poucos comentários também é primordial para a manutenção de um código limpo e conciso. Pode parecer contraditório, mas comentários demais poluem o código. Além disso, se for necessário adicionar comentários para explicar o objetivo de uma classe, método, variável ou qualquer outro módulo, é porque o nome deste elemento ou o código aninhado nele pode ser melhorado.

A Listagem 3 mostra um exemplo de código onde nomes bem definidos de método e variáveis tornam desnecessário o uso de comentários, tanto para explicar a razão de existir do método quanto para compreender o seu funcionamento.

  01 // Method that makes the duck walk if it is stopped
  02 void makeDuckWalkIfStopped()
  03 {
  04     // If the duck is stopped, make it walk
  05     if (duck.isStopped()) {
  06         duck.walk();
  07     }
  08 }
Listagem 3. Nome e conteúdo do método devem tornar comentários desnecessários

Uma atitude que colabora na manutenção de um código fonte conciso é adicionar conteúdo somente quando necessário. Adicionar código para uso futuro, mesmo quando este é bem escrito, dificulta o entendimento do conteúdo por outro profissional, que irá procurar justificativas para a existência daquele código. Este conteúdo desnecessário pode, ainda, complicar a integração com o repositório.

Outra forma de manter o código fonte conciso é não repetir conteúdo. Código repetido resulta em linhas desnecessárias e a correção de um defeito nestes casos deve ser feita a cada ocorrência deste código. Nestes casos, até mesmo o autor pode ter dificuldades em recordar cada correção a ser feita.

A legibilidade do código também pode ser melhorada ao se utilizar a Orientação a Objetos com sabedoria. Classes bem escritas são concisas e representam um único objeto com suas características e comportamentos. Ademais, o relacionamento entre os objetos também deve ser respeitado, evitando-se situações como a da Listagem 4, onde um objeto Pato instancia um objeto Galinha. O correto seria que o objeto Celeiro instanciasse o objeto Galinha.

  01 class Celeiro {
  02     Celeiro()
  03     {
  04         // outras inicializações
  05         new Pato();
  06     }
  07 }
  08 
  09 class Pato {
  10     Pato()
  11     {
  12         // outras inicializações
  13         new Galinha();
  14     }
  15 }
  16
  17 class Galinha {
  18     Galinha()
  19     {
  20         // inicializar o objeto
  21     }
  22 }
Listagem 4. Relação incorreta entre objetos

Além de práticas que garantem a legibilidade do conteúdo criado, é importante evitar processamentos desnecessários para se criar produtos de qualidade. Apesar do aumento do poder de processamento e da capacidade de bateria dos computadores pessoais, dispositivos móveis e servidores, é primordial que o programador continue se preocupando com estes quesitos. A Listagem 5 mostra um exemplo de código onde há processamento desnecessário em um loop. Note que a declaração da String input e a recuperação do índice de um de seus caracteres pode ser feita apenas uma vez: fora do loop, por exemplo.

  01 char[] alphabet = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, … ‘w’, ‘x’, ‘y’, ‘z’};
  02 for(int index = 0; index < alphabet.lenght; ++index)
  03 {
  04     string input = “bus”;
  05     int indexOfU = input.indexOf(‘u’);
  06     if (input.at(indexOfU) == alphabet[index])
  07     {
  08         return true;
  09     }
  10 }
Listagem 5. Recuperação do índice do caractere na string pode ser feita apenas uma vez
Nota: Existem boas práticas que se aplicam a plataformas específicas. Na plataforma Android, por exemplo, é recomendado executar o mínimo possível de processamentos em Activities, pois estas são montadas e desfeitas a cada vez que são mostradas e escondidas, respectivamente. Deste modo, os processamentos que não precisam ser executados a cada carregamento da Activity devem ser movidos para outras classes.

Utilização de um padrão de arquitetura

Outra forma de manter o código fonte organizado e proporcionar condições favoráveis ao desenvolvimento de software de qualidade é através do emprego do padrão de arquitetura correto. O padrão recomendado varia de acordo com o projeto ou plataforma alvo. Entretanto, o padrão conhecido em Inglês como Model-View-Control, ou MVC, tem sido o mais difundido e recomendado para o desenvolvimento de sistemas Java.

Este padrão sugere que o código do sistema seja dividido em três camadas que interagem entre si com o objetivo de melhorar a organização, possibilitar o reuso do código de forma inteligente, facilitar a automação de testes unitários, entre outras vantagens.

A primeira camada é a Model, ou modelo. Esta define os dados a serem exibidos na interface do usuário, por exemplo, as informações armazenadas em um banco de dados ou em um arquivo. A segunda camada é a View, ou visão. Esta mostra os dados da Model para o usuário e repassa comandos vindos do usuário ou eventos do sistema para a Control (ou controlador), que, por sua vez, representa a terceira camada, sendo responsável por atualizar os dados da Model e o que deve ser mostrado na View.

Como dito, o MVC é o padrão de arquitetura mais utilizado no planejamento de sistemas Java. Entretanto, outros também podem ser utilizados, como o Model-View-Presenter, ou MVP. Neste padrão, a camada Presenter, ou apresentador, é responsável por formatar os dados da Model para serem apresentados na View.

Nota: Para o desenvolvimento de aplicativos Android, o padrão MVP é o mais indicado devido ao uso correto e esperado das Activities. Essas são as classes básicas da plataforma e são responsáveis por tratar os eventos de janela como pause e resume, além de controlar e mostrar os dados para o usuário, papel esperado pela camada View do MVP. Já a camada Presenter é representada por classes que formatam a interface na qual as informações serão mostradas, como a ListView.

Apesar das vantagens, existem situações em que não é aconselhável ou viável a separação do código nas três camadas do MVC ou do MVP. Sistemas que não possuem uma interface gráfica, por exemplo, não necessitam da View, assim como softwares estritamente visuais, como simples calendários, não precisam da Model. A adoção de um padrão de arquitetura é aconselhável quando há a possibilidade de separação de papéis entre as interfaces, classes e pacotes do software.

Utilização de padrões de projeto

Padrões de projeto são modelos para solucionar problemas comumente encontrados em sistemas de software e melhorar o design do código através da simplificação de suas estruturas básicas e do bom uso da orientação a objetos.

Os padrões de projeto aumentam a produtividade de um time por proverem modelos de programação comprovadamente funcionais e já testados em outras soluções, modelos estes que contribuem para a modularização e reutilização de estruturas de código. Esta modularização propicia um maior desacoplamento das estruturas do código e favorece possíveis mudanças no decorrer da implementação. Já a reutilização das estruturas de código proporciona o aumento da velocidade do processo de desenvolvimento.

O uso correto da orientação a objetos, uma das premissas dos padrões de projeto, permite uma melhor divisão de responsabilidades entre as classes, métodos e estruturas em geral do sistema. Além disso, os modelos aperfeiçoam a legibilidade do código, principalmente para profissionais que já conhecem e utilizam os padrões.

Entretanto, é necessário estar ciente que a aplicação de padrões de projeto pode ser onerosa tanto para o time e para o projeto, quanto para o software em criação. Isso porque mesmo times experientes e conhecedores da motivação de se aplicar cada um dos padrões podem ter dificuldades em identificar a melhor opção a ser utilizada nas diversas situações encontradas. O processo que envolve desde conhecer o objetivo de uso de cada padrão de projeto até implementá-los da forma correta é demorado e requer repetição e estudo. Em outras palavras, a curva de aprendizado dos padrões projeto é longa.

Outro fator importante a ser analisado antes de se decidir em aplicar ou não um padrão de projeto é verificar se este modelo trará mais impactos positivos do que negativos. Isto porque os padrões de projeto podem afetar negativamente o desempenho do aplicativo. Lembre-se que a modularização do código proporcionada pelos padrões requer a adição de camadas e chamadas que poderiam não existir se os padrões não fossem utilizados.

Uma boa prática relacionada ao uso de padrões de projeto na codificação de sistemas Java é seguir os padrões já estabelecidos na plataforma alvo. O BOX 1 mostra alguns exemplos de padrões já difundidos no SDK Android.

BOX 1: Padrões de projeto utilizados na plataforma Android

No código fonte da plataforma Android ou nos SDKs disponíveis para os desenvolvedores é possível encontrar diversos exemplos de uso de padrões de projeto. O padrão Builder, por exemplo, foca em tornar a inicialização de objetos complexos mais intuitiva. Basicamente, deixa-se de utilizar construtores com muitos parâmetros em favor de métodos set. Isto evita que haja a troca involuntária da posição de dois ou mais parâmetros, facilita a identificação de cada parâmetro e aumenta a legibilidade do código por tornar desnecessária a leitura da documentação do construtor para entender a inicialização do objeto em questão.

BroadcastReceivers são exemplos de uso do padrão Observer. Este padrão considera que um objeto é responsável por notificar seus dependentes quando há mudanças de estados. No caso dos BroadcastReceivers, as mudanças de estados são os eventos do sistema ou da aplicação recebidos.

É possível, também, encontrar exemplos de uso dos padrões Adapter, Memento, Chain of Responsibility, ViewHolder, Proxy, Composite, Facade, entre outros. Um estudo aprofundado destes padrões e seus objetivos é indicado para programadores interessados em melhorar a qualidade do código fonte de seus aplicativos.

Outras boas práticas de desenvolvimento

As boas práticas citadas previamente sugerem, basicamente, melhorias na forma de se escrever o código fonte e no uso de metodologias de desenvolvimento. Entretanto, existem outras boas práticas a serem utilizadas que podem melhorar o rendimento do time na rotina básica de criação de software.

Como descrito anteriormente, mesmo em um processo simples é possível enumerar um alto número de problemas causados pelo uso de práticas inadequadas. Porém, as atividades listadas a seguir, se bem aplicadas pelo time de desenvolvimento, evitam o surgimento desses problemas ou os corrigem.

A utilização de uma ferramenta para controle de versão é uma atividade essencial para qualquer time de desenvolvimento ou projeto. Além da segurança de haver uma ou mais cópias do código fonte, o controle de versão possibilita a geração de releases, o acompanhamento do histórico da evolução do código e facilita o compartilhamento do código entre várias pessoas e a localização de bugs. O uso do Git para controle de versão é uma excelente prática a ser considerada.

Ao decidir adotar uma ferramenta para controle de versão, é preciso definir quais os arquivos, ou tipos de arquivos devem ser adicionados ao repositório. Arquivos binários, por exemplo, não têm necessidade de estarem sob versionamento, pois são gerados durante a compilação.

Outros arquivos que não devem estar sob controle de versão são aqueles utilizados para armazenar informações que variam de um programador para outro, como os caminhos de diretórios onde estão os arquivos do aplicativo, acessados durante a compilação. Quando atualizados com os dados do ambiente de outro programador, existe uma grande chance de ocorrerem erros de compilação, mesmo o código fonte estando perfeito.

Outra vantagem do uso de ferramentas de controle de versão é a possibilidade de se criar branches do código fonte. Os branches podem ser utilizados para separar códigos estáveis, já aprovados e testados daqueles recém-implementados, por exemplo. Esta é, inclusive, uma boa prática de desenvolvimento, pois facilita a identificação e correção de bugs, já que existe uma chance maior de os bugs terem sido adicionados em códigos não estáveis.

Entretanto, é importante evitar a criação e uso de branches em excesso. Muitos branches causam confusão aos membros da equipe que não os criaram e tornam o processo de atualizar o código mais lento.

Uma forma simples e ainda não descrita para otimizar a codificação é a utilização de ferramentas que visam automatizar tarefas para manter o código fonte limpo e organizado; prática esta associada ao uso de recursos presentes em algumas IDEs, como plug-ins para formatação e ordenação do código. Estes recursos podem ser configurados com as regras acordadas entre os membros do time e, ao serem executados, o código é organizado de acordo com o combinado.

Outra boa prática ainda não descrita e que pode tornar a fase de validação melhor aproveitada é a execução dos casos de testes relacionados ao que foi implementado pelo próprio desenvolvedor. Desta forma, reduz-se consideravelmente a probabilidade de que novos bugs sejam acrescentados através do código recém-implementado.

Com o intuito de diminuir as chances de adição ou reaparecimento de bugs, adotar um processo de revisão na fase de integração do código é bastante aconselhado. Neste processo a implementação é verificada e validada por outros membros do time, que devem sugerir mudanças em relação à legibilidade, ao uso da Orientação a Objetos, aos possíveis defeitos ou qualquer situação que vá de encontro ao que foi combinado para alcançar a meta de qualidade.

Outra excelente prática de desenvolvimento é a automatização dos processos de compilação, execução de testes e geração de releases. Nestes casos, a utilização de ferramentas como o Jenkins possibilita que essas atividades sejam realizadas de forma mais rápida e segura quando comparadas com a execução de forma manual.

Enfim, muitas são as práticas que podem ser adotadas na implementação de sistemas Java. Por isso, é importante que o processo de definição destas, leve em consideração as que mais se adequam aos objetivos do projeto, do produto e do time envolvido.

Boas práticas de testes de software

O processo de preparação e execução de testes de software é complexo e, assim como a codificação, sempre passível de melhorias. Não somente por isso, existem boas práticas a serem seguidas por todos os participantes do projeto para que as atividades de testes sejam realizadas de forma a criar sistemas com sucesso.

Uma opção a ser avaliada é considerar a participação dos profissionais de testes em todas as fases do projeto. Na primeira fase, quando o projeto é analisado e estimado, o testador colabora na identificação de riscos, exceções, casos de uso e cenários esquecidos. Além disso, por participar da avaliação do produto a ser criado juntamente com o restante do time desde o início, o testador não necessita de um período de adaptação ao projeto no momento de executar os ciclos de testes. Neste período de adaptação, normalmente, é preciso que outro participante pare suas atividades constantemente para explicar ao testador os requisitos, fluxos e funcionalidades planejados para aquele ciclo.

Outra prática recomendada é a realização de testes de software durante a codificação. Esta sugestão é um importante complemento às metodologias ágeis, pois estas sugerem que o projeto seja dividido em partes menores, chamadas sprints. Usualmente, estes têm duração de duas ou três semanas, período no qual ocorrem a codificação, os testes e a correção dos defeitos de partes do produto em criação. Isto evita o acúmulo de problemas, pois estes são corrigidos em poucos dias ou horas após serem adicionados ao código.

Como comparação, na metodologia tradicional a fase de testes e correção de problemas ocorre somente após a implementação de todos os requisitos. Isto faz com que um problema adicionado no código no início do projeto persista por toda a fase de codificação, podendo causar outros defeitos. Além disso, os programadores, provavelmente, estarão fora do contexto deste problema quando ele for encontrado, levando a um aumento do tempo para a correção do mesmo.

Outra forma de melhorar a eficiência na identificação de defeitos é através da automatização dos testes. Ao tornar o processo de execução de casos de testes automático, espera-se reduzir a interação humana em atividades repetitivas e que não requerem validações subjetivas. Assim, o testador poderá se concentrar na execução manual de casos de testes mais complexos e na expansão da cobertura dos testes ou em testes exploratórios.

A automatização também facilita e complementa a validação do código desenvolvido. Isto porque logo após implementar uma nova funcionalidade ou corrigir um problema, o próprio desenvolvedor tem condições de executar uma bateria de casos de testes com o intuito de verificar se há o surgimento de um novo defeito após a adição do novo código. Esta forma de trabalho facilita a correção de defeitos, já que o programador está trabalhando no contexto onde o problema ocorre.

Como descrito anteriormente, ferramentas como o Jenkins podem ser utilizadas para executar os testes a cada vez que há integração de código no repositório. Esta prática é útil para complementar a validação do código feita pelo desenvolvedor durante a codificação.

Outra boa prática é desenvolver o produto direcionando o código para a automatização de testes, o chamado Test Driven Development, ou TDD. Neste processo, o programador primeiramente desenvolve um caso de teste e em um segundo momento cria o mínimo de código necessário para cumprir este teste. A vantagem de uso do TDD é que a automatização dos testes ocorre naturalmente e há uma maior garantia de que o produto atenda aos requisitos e tenha um padrão de qualidade elevado.

Além disso, o código produzido em um processo TDD tende a ser mais conciso, modularizado, com menos dependências e, por consequência, mais fácil de interpretar. Isto porque apenas o essencial para cumprir o caso de teste é adicionado ao repositório, eliminando complexidades desnecessárias.

O uso de boas práticas é essencial para um ganho de produtividade de qualquer time de desenvolvimento. Estas práticas devem ser estudadas e avaliações constantes devem ser feitas com o objetivo de identificar as mudanças ou adaptações necessárias na organização ou no time para que elas sejam aplicadas sem causar transtornos e gerem os resultados esperados. Por isso, recomenda-se fortemente a continuidade nos estudos nesta área, o que pode ser feito através da leitura de livros, participação em treinamentos sobre padrões de projeto e desenvolvimento ágil, por exemplo, entre outras opções.

Links:
  • Livro recomendado: Clean Code: A Handbook of Agile Software Craftsmanship
  • Livro recomendado: Head First Design Patterns
  • Livro recomendado: Essential Scrum: A Practical Guide to the Most Popular Agile Process