Teste de Software

Por que escrever testes automatizados?

Quando se fala de qualidade de software, sabemos que a etapa de testes é imprescindível. O software deve fazer o que o cliente precisa de maneira confiável, segura, eficiente e flexível. Porém, durante o desenvolvimento, é normal aparecerem alguns erros. Por isso, é de extrema importância identificar os possíveis erros antes de colocar a aplicação em produção.

“Prevenir erros é mais fácil do que corrigi-los.”

A automação de testes é de grande valia e deve ser considerado em todos os projetos de desenvolvimento de software. E, de fato, para um software ser testado corretamente, esse processo deve ser automatizado com o auxílio de ferramentas com esta finalidade.

Os testes automatizados vêm como uma forma de poupar tempo de detecção de erros e de aumento de confiabilidade com relação aos testes em si. A execução de um teste manual é eficaz, porém, conjuntos maiores de teste com muitas repetições acabam se tornando uma tarefa muito cansativa e com tendência a erros, já que, de maneira inconsciente, as pessoas que testam o software em questão começarão a o utilizar de maneira em que este não falhe. Paralelizar este processo de teste, colocando várias pessoas para executar as rotinas de teste também pode não ser a melhor solução, já que isso pode ser caro e não irá garantir a qualidade do teste. Por estes motivos, testes automatizados vêm se mostrando cada vez mais como uma excelente alternativa para que os ciclos de teste de software possam ser executados de maneira independente do tamanho e da complexidade do software em questão.

Ainda existe mais um ponto que justifica a adoção de testes automatizados. Imagine o seguinte: uma equipe está focada nos testes de forma manual. No final dos testes, um erro foi encontrado. Esse erro deve ser resolvido pelos desenvolvedores. Porém, depois de resolvido, a equipe de testes deverá começar os testes do zero para garantir que a alteração feita não ocasione nenhum erro em outra parte do sistema. Nessa situação, mais uma vez, caímos no ciclo vicioso do teste de software manual: a ocorrência de muitos processos repetitivos e cíclicos, o que vai nos levar novamente à situação onde nós, se começarmos a repetir as coisas em uma carga alta, começaremos a executar estas ações no “automático”, prejudicando o processo de teste. E ainda existe a questão da quantidade de tempo gasto nesse processo todo, quantidade esta que certamente é enorme. E já sabemos: “tempo é dinheiro”… Se gastamos tempo “à toa”, estamos gastando dinheiro à toa e produzindo um software muito menos competitivo no mercado.

Com testes automatizados, nós podemos pelo menos evitar uma boa parte desse ciclo vicioso dos testes manuais, produzindo resultados com um grau de confiabilidade bem mais alto e em um período de tempo muito mais curto!

Alguns tipos de teste…

Testes de software são de fato um assunto muito sério. Teste de software é algo tão complexo que existe até mesmo uma norma ISO para normatizar os procedimentos de teste: a ISO/IEC 9126. Em cima das métricas pregadas por esta norma ISO, alguns tipos de teste acabaram por surgir. Cada um destes grupos possui um conjunto de ferramentas de automação para atender a avaliação das métricas envolvidas.
Os tipos de teste de software mais comuns são:

Teste de Unidade

O teste de unidade verifica a menor parte testável do software – que é chamada de unidade. Neste tipo de teste, essa unidade é testada de forma isolada para garantir que tenha o comportamento esperado. Como esse teste é focado em um trecho específico do software, os erros são encontrados facilmente, diminuindo o tempo gasto com depuração. O conceito de “unidade” de software acaba sendo um pouco subjetivo, mas você pode pensar como sendo um método de uma classe sua, por exemplo. Este método, se puder ter suas entradas, comportamentos e saídas devidamente testados e validados, pode ser considerado como uma unidade de software.
Algumas ferramentas que auxiliam no processo de teste de unidade (ou teste unitário) são o jUnit, o xUnit.NET e o PHPUnit.

Teste de Integração

O teste de integração verifica se uma unidade tem o comportamento esperado quando funciona de maneira integrada a outros elementos de software, como chamada de serviços, APIs e banco de dados. Aqui, a unidade de software em si não é avaliada, mas sim a sua integração com outras unidades. Algumas ferramentas que auxiliam no processo de teste de integração são o Selenium, o Mockito e o MSTests.

Testes e2e (fim-a-fim)

Neste tipo de teste, é feita uma simulação das ações de um usuário real interagindo com o software, como por exemplo, o preenchimento de um cadastro, uma opção que foi selecionada ou um clique do mouse. Como o nome entrega, este tipo de teste visa garantir o fluxo correto dos dados entre todas as camadas que fazem parte da solução de software.
Alguns exemplos de ferramentas para testes e2e são o Protactor, o Selenium e o Cypress.

Concluindo…

Dessa maneira, podemos notar que todo o tempo destinado na garantia da qualidade do software é válido, pois isso irá garantir a entrega de um software mais robusto e confiável, o que aumenta até mesmo a competitividade da solução em questão no mercado.
Com essas noções básicas sobre testes automatizados, você pode se aprofundar mais no assunto em um de nossos cursos e praticar!

Até a próxima 😊

Por que desprezar o teste do seu software pode ser uma auto-sabotagem?

O teste de software é umas das áreas de TI que muitas vezes não recebe o devido valor, mas que é fundamental para a entrega de um software com qualidade.

Sempre que adquirimos um produto, esperamos que ele tenha qualidade, ainda mais se tivermos investido um valor considerável nele. Por que então não podemos esperar a qualidade de um software? Veja nesse artigo, o por que você não deve se auto sabotar pulando as etapas de teste.

Porque é importante testar?

A qualidade está diretamente ligada à satisfação do cliente. Quanto mais ele atende as expectativas, melhor é o grau de satisfação do cliente. Aliás, nenhum cliente gosta de receber um software que sempre dá erros ou não funciona.

A qualidade do software também implica, mesmo que indiretamente, na quantidade de usuários e clientes. Quando algo é muito bom, provavelmente seu cliente pode indicar para outras pessoas. Além disso, este cliente se tornará fidelizado. Por isso, o teste é super importante para o sucesso do negócio.

O que ele pode evitar?

O teste visa garantir que todos os requisitos estão sendo atendidos, que todas as funcionalidades estão sendo desempenhadas corretamente, que operações mais complexas estão sendo suportadas e que, obviamente, que não existe nenhum problema na aplicação.

Imagine que, depois da entrega do software, o cliente aponta um erro no sistema, que várias coisas não funcionaram como deveriam ou que até mesmo uma mensagem de erro apareceu. Com o teste de software adequado, você conseguiria verificar esta situação incorreta antes mesmo do cliente – o que é muito importante.

Por isso o teste deve acompanhar todo o ciclo de vida do software, desde sua concepção até sua manutenção.

Esse é um ponto importante para se levar em consideração. Quando esses bugs aparecerem, certamente será necessário reservar um tempo para a correção deste problema. Mas aqui é onde encontramos a armadilha: certamente, em um processo de desenvolvimento de software onde a etapa de teste não é algo “natural”, não existe tempo reservado para a correção de problemas.

O que acaba acontecendo, na maioria das vezes, é que o tempo que seria destinado para o desenvolvimento de funcionalidades acaba tendo que ser compartilhado com o tempo de correção do problema.

Com essa divisão, é fatal que uma situação incômoda ocorra: pelo pouco tempo, nem a correção do problema, nem o desenvolvimento das novas funcionalidades, serão desenvolvidos com a qualidade necessária, provocando uma cadeia sem fim de problemas e defeitos em geral no projeto de software em questão.

O mais interessante é que estes problemas que surgem não são simplesmente de ordem técnica: uma equipe que tem que estar sempre “correndo atrás do prejuízo” certamente será uma equipe estressada, instável e com alta rotatividade. Veja a bola de neve que pode acontecer pelo simples fato do desprezo pela etapa de teste de software.

“É melhor prevenir do que remediar”

É fato que, muitas vezes, o tempo é curto e você precisa entregar o mais rápido possível. Às vezes você até realiza a entrega consciente de que tem coisas para arrumar, mas que pelo menos cumpriu o prazo da entrega no dia certo e assim o cliente não irá reclamar. Aí, certamente você pensa: “na próxima entrega eu arrumo”. Mas isso muitas vezes acaba virando uma bola de neve, como vimos anteriormente: você tem que implementar novas funcionalidades, mas também tem que arrumar o que ficou pra trás na última entrega.

Por isso, não adianta simplesmente disponibilizar um pouquinho do tempo ou até aumentar os prazos de entrega para que você consiga verificar se o software está funcionando bem e com boa performance. É preciso que o processo de teste de software esteja integrado e fundido com o processo de desenvolvimento em geral. O teste de software precisa ser algo cultural.

Não sabe como realizar os testes no seu software? Aqui na TreinaWeb temos vários cursos desse segmento com muita prática. Então agora você não tem desculpas para não testar seu software, rs 😊

O que se pode fazer com JavaScript hoje em dia?

Olá, Web Developers!

O JavaScript inicialmente foi criado como um complemento para o navegador da Netscape. Por muito tempo foi visto como uma linguagem ruim, bagunçada e lenta (e estavam certos).

Mas o JavaScript evoluiu muito. Hoje em dia é bem mais organizado, rápido e possui várias funcionalidades que nos facilitam criar várias coisas. Além disso, a linguagem não se limita mais apenas aos navegadores.

Vamos conhecer algumas áreas onde podemos atuar com JavaScript além do seu uso comum e até além dos navegadores. Dessa maneira você poderá usar seus conhecimentos em JavaScript e escolher entrar em alguma área além da web, como desenvolvimento de jogos, robótica, automação, etc.

Observação: esse post não incentiva o uso de JavaScript para tudo. Há de se ponderar a melhor ferramenta/tecnologia para o problema que se precisa resolver.

Node.js

Node.js

Não tem como começar essa lista sem falar do Node. Ele é basicamente uma ferramenta que executa o JavaScript fora do navegador. Quando instalado, chamamos o Node.js pelo terminal. Como não estamos executando o JavaScript em um navegador, os cuidados com segurança são diferentes.

Há várias funcionalidades disponibilizadas para o Node.js, fazendo com que possamos fazer coisas com JavaScript que não conseguimos fazer quando o nosso ambiente é o navegador.

Um exemplo é o acesso a arquivos. Um código JavaScript no navegador não consegue acessar os arquivos do usuário, pois seria uma falta de segurança você entrar em um site e ele vasculhar seu computador.Como o Node.js está instalado em sua máquina, o JavaScript tem liberdade de acessar os arquivos contidos no HD.

O Node.js possibilitou a criação de várias outras ferramentas para podermos usar JavaScript fora do navegador, e hoje em dia é um conhecimento obrigatório para quem quer trabalhar com JavaScript.

https://nodejs.org/

npm

O Node.js vem com o “npm” (node package manager). Com ele podemos gerenciar as dependências de nossos projetos.

Um exemplo: caso queira usar jQuery em seu projeto, ao invés de ter o trabalho de fazer download do jQuery, basta executar o comando:

$ npm install jquery

Caso queira atualizar a versão do jQuery em seu projeto, basta executar o comando:

$ npm update jquery

O npm também pode ser usado para instalar outros programas que podem nos auxiliar no desenvolvimento de nossas aplicações e executar comandos.

https://www.npmjs.com/

Testes

Quando estamos escrevendo código é preciso fazer muitos testes para assegurar que nossas funções estão retornando o valor esperado. É bom sempre testar a mesma função passando vários valores diferentes, inclusive valores que a função não aceita para ver se há necessidade de criar um tratamento para a aplicação não quebrar.

Acontece que qualquer alteração em uma função pode afetar outras funções. Então há ferramentas que facilitam a criação de testes.

Os mais famosos são: QUnit, Mocha e Jasmine.

Qualidade de Código

Quando estamos escrevendo nosso código, é muito importante mantê-lo com qualidade. Uma característica de um código de qualidade é mantê-lo uniforme.

Isso inclui sempre identar o código, padronizando a quantidade de espaços ou se será usado “tab”, se ao criar uma função você irá abrir chaves “{“ na mesma linha ou em uma linha nova, etc.

Há ferramentas como o JSLint e JSHint que analisam o nosso código e indicam se estamos mantendo as regras que foram definidas.

Automatização de Tarefas

Quando estamos desenvolvendo é comum precisarmos realizar certas operações para melhorar o nosso código. Um exemplo é minificar o nosso código, fazendo com que os arquivos fiquem menores, o que faz a aplicação ser carregada mais rapidamente pelo navegador.

Outras tarefas podem ser a execução de testes de qualidade, como o JSLint, ou testes de software, como o Jasmine.

Para nos auxiliar temos os automatizadores de tarefas. Os mais famosos são o Grunt e o Gulp. Podemos definir qualquer tarefa e pedir para que estas ferramentas as executem para nós.

Imagine que quando salvamos um arquivo, ele deve ser testado pelo Jasmine, analisado pelo JSLint e, se todos os testes passarem, iremos minificar o arquivo.

E mais! Podemos querer também que se tudo der certo, ele envie um E-mail para o nosso cliente dizendo que em breve iremos lançar uma nova versão da nossa aplicação!

Eles nos ajudam a automatizar o que quisermos, o limite é sua imaginação!

Servidores

Normalmente os códigos escritos no lado do servidor são feitos com linguagens como Java, PHP, Ruby, etc.

Com o Node.js foi possível começar a escrever código para o servidor com JavaScript. Já houve outras tentativas, mas o Node.js teve mais sucesso.

Os frameworks mais conhecidos para Node.js são: Express, Hapi e Koa.

Também podemos criar código Back End com o Meteor. O Meteor é uma plataforma de desenvolvimento fullstack (front e back end).

Bancos de Dados

O MongoDB é um banco de dados orientado a documentos. O console que usamos para acessar os dados executa JavaScript. Então se você sabe JavaScript, pode ter facilidade em aprender a gerenciar o MongoDB.

https://www.mongodb.com/

Aplicativos Mobile

Com JavaScript também podemos criar aplicativos mobile. A vantagem é usar uma única linguagem de programação para as diferentes plataformas. O modo mais conhecido é usando o Cordova/PhoneGap. Com eles nós criamos aplicações híbridas.

São chamadas de “híbridas” porque unem duas tecnologias diferentes. Por exemplo, o código nativo do Android é escrito em Java. Com o Cordova nós criamos aplicações web comuns, com HTML+CSS+JavaScript. Para acessar funcionalidades do dispositivo, há uma integração do código nativo em Java e nós acessamos essas funcionalidades pelo JavaScript.

Quando abrimos o aplicativo, estamos na verdade abrindo um navegador interno que irá apresentar a nossa aplicação e que pode acessar funcionalidades do dispositivo que não poderíamos acessar a partir de uma aplicação web comum.

Então a ideia de “híbrido” é porque estamos juntando tecnologia web com a tecnologia nativa.

http://facebook.github.io/react-native/

Também podemos criar aplicações nativas. As ferramentas mais conhecidas são o React Native e o NativeScript. Com eles nós criamos telas com XML ao invés de HTML, e podemos estilizar com CSS. Isso será convertido para uma tela nativa de cada plataforma, como Android e iOS. Já as ações são escritas com JavaScript mesmo.

A vantagem disso é que, por ser uma aplicação nativa ao invés de uma aplicação com um navegador, teremos melhor performance do que uma aplicação híbrida.

Outra vantagem é que as aplicações híbridas dependem do navegador padrão do sistema, então corremos o risco de criar um código que o navegador daquele dispositivo não suporte. Em uma aplicação nativa, já que não dependemos de navegadores, não precisamos nos preocupar se haverá suporte para as funcionalidades ou não.

Softwares Desktop

Com o Node.JS também é possível criar aplicações Desktop. Estas aplicações normalmente utilizam o Chromium, navegador de código aberto que está por trás do Google Chrome, e o Node.js.

As telas são feitas com HTML5 e CSS3, e o JavaScript pode se comunicar diretamente com o Node.js, que fica embutido na aplicação.

Atualmente o framework mais utilizado é o Electron, criado pela equipe do GitHub. Com ele já foram desenvolvidos famosos softwares como o Atom, Slack e Visual Studio Code.

https://electronjs.org/

SmartTVs

Também é possível criar aplicativos para SmartTVs. Você irá também usar HTML/CSS/JavaScript. As que dão maior suporte para isso são as TVs da Samsung.

3D

A partir do elemento canvas do HTML5 também temos uma API chamada WebGL. Ela nos ajuda a trabalhar com renderização de gráficos 2D e 3D.

Uma biblioteca que nos ajuda a trabalhar com 3D com JavaScript é o three.js.

https://threejs.org/

Jogos

Também é possível criar jogos com JavaScript. Isso graças ao elemento canvas do HTML5, que nos permite desenhar na tela com JavaScript.

Há várias bibliotecas que nos ajudam a criar jogos com JavaScript.
Uma das mais famosas é o Impact, que é paga. Uma outra famosa, que é gratuita, é o Phaser.

Como os jogos apenas usarão o elemento canvas do HTML5 e JavaScript, você poderá criar jogos para qualquer lugar que os suporte ou possua um navegador.

Pode-se usar ferramentas como o Cordova/Phonegap, mas também há outras ferramentas que ajudam a otimizar jogos feitos com JavaScript para dispositivos móveis.

Plugins

Há softwares que possibilitam a criação de plugins. Isso permite que as pessoas criem novas funcionalidades para eles. Normalmente esses plugins são escritos com linguagens de scripts.

Um exemplo é o PhotoShop. Ele aceita a criação de plugins escritos em JavaScript.

Sistemas Operacionais

Um sistema baseado no Node.js, escrito apenas com JavaScript, foi desenvolvido. É o NodeOS.

Qualquer pacote do npm é um pacote do NodeOS, o qual conta com mais de 475.000 pacotes. O objetivo do NodeOS é fornecer apenas o necessário e o npm cuida do resto. Já que qualquer um pode contribuir com o npm, qualquer um pode criar pacotes para o NodeOS.

Você pode ver mais em:

http://node-os.com/

Programação de Hardwares e Internet das Coisas

Com o Node.JS também é possível controlar hardwares. Podemos então usar o JavaScript para trabalhar além das telas dos computadores e celulares, como controlar drones.

Ao programar placas, podemos criar códigos para até mesmo integrar vários objetos, o famoso “Internet das Coisas” (IoT).
Imagine usar JavaScript para controlar as lâmpadas, portas e janelas de sua casa de acordo com a iluminação do ambiente. Ou que quando o GPS do seu smartphone perceber que você está chegando em casa ou no trabalho, ligue a cafeteira para que o café esteja pronto quando você chegar.

Um exemplo é o noduino, um framework para acessar os controles básicos do Arduino a partir de aplicações web usando HTML5, Socket.IO e Node.JS.

Também há o projeto Tessel. No próprio site você pode comprar as placas para montar o seu produto.

Outro famoso projeto é o Cylon.js. Ele é um framework voltado para robótica, computação física e Internet das Coisas.

Você pode ver mais em:

Hologramas

O JavaScript também é capaz de ser utilizado com hologramas.
Um exemplo é o HoloJS, da Microsoft, que é um framework para criar aplicações holográficas usando JavaScript e WebGL.

Realidade Virtual e Realidade Aumentada

Muito se fala sobre Realidade Virtual e Realidade Aumentada. Um exemplo para realidade aumentada é o JavaScript ARToolKit.

https://github.com/artoolkit/jsartoolkit5

Inteligência Artificial

Também podemos aproveitar o JavaScript na área da inteligência artificial. Há muitas bibliotecas e exemplos.

Uma biblioteca usada para isso é a “deeplearn.js”, que você pode conhecer melhor em:

https://deeplearnjs.org/ .

Concluindo

Hoje em dia várias linguagens de programação tentam estar onipresentes, como é o caso do JavaScript. Porém, nem sempre é algo bom. Não existe “bala de prata”. Há linguagens que estão mais evoluídas e adaptadas para certas áreas.

Mesmo que JavaScript seja minha linguagem principal, há momentos em que prefiro usar outras linguagens de programação, pois admito que possuem ferramentas melhores para se trabalhar e acabam entregando algo com mais rapidez e qualidade.

A vantagem que enxergo em utilizar uma única linguagem em várias áreas é possibilitar que uma pessoa com conhecimento em uma linguagem possa experimentar novas áreas, ou que uma pequena empresa possa reaproveitar os conhecimentos da equipe e códigos para trabalhar com outras tecnologias.

Complexidade ciclomática, análise estática e refatoração

Complexidade ciclomática é uma métrica do campo da engenharia de software, desenvolvida por Thomas J. McCabe em 1976, e serve para mensurar a complexidade de um determinado módulo (uma classe, um método, uma função etc), a partir da contagem do número de caminhos independentes que ele pode executar até o seu fim. Um caminho independente é aquele que apresenta pelo menos uma nova condição (possibilidade de desvio de fluxo) ou um novo conjunto de comandos a serem executados.

As métricas de software podem ser definidas como métodos de determinar quantitativamente a extensão em que o projeto, o processo e o produto de software têm certos atributos. Isto inclui a fórmula para determinar o valor da métrica como também sua forma de apresentação e as diretrizes de utilização e interpretação dos resultados obtidos no contexto do ambiente de desenvolvimento de software. FERNANDES, A. A. (1995) – Gerência de Software através de métricas, São Paulo, Editora Atlas.

O resultado da complexidade ciclomática indica quantos testes (pelo menos) precisam ser executados para que se verifique todos os fluxos possíveis que o código pode tomar, a fim de garantir uma completa cobertura de testes.

Calculando a complexidade ciclomática

Existem diferentes formas de se calcular:

  • Usando a notação de um grafo de fluxo;
  • Usando fluxograma;
  • Com análise estática do código, usando uma ferramenta que automatize essa tarefa.

Tendo um grafo de fluxo ou um fluxograma, temos três fórmulas equivalentes para se mensurar a complexidade ciclomática:

  1. V(G) = R – onde R é o número de regiões do grafo de fluxo.
  2. V(G) = E – N + 2 – onde E é o número de arestas (setas) e N é o número de nós do grafo G.
  3. V(G) = P + 1 – onde P é o número de nós-predicados contidos no grafo G (só funciona se os nós-predicado tiverem no máximo duas arestas saindo.)

Ps: Nós-predicado são àqueles que podem desviar o fluxo da execução: if, while, switch etc.

Exemplo usando a notação de grafo de fluxo:

alt

Exemplo retirado do trabalho da professora Bianca Zadrozny (do Instituto de Computação da UFF), que pode ser consultado aqui como excelente referência.

A complexidade ciclomática desse código é 6. Esse resultado pode ser obtido usando uma das três formas acima descritas:

1) V(G) = R – onde R é o número de regiões do grafo de fluxo.

alt

Temos 6 regiões.

2) V(G) = E – N + 2 – onde E é o número de arestas (setas) e N é o número de nós do grafo G.

V(G) = 17 arestas/setas – 13 nós + 2 = 6

3) V(G) = P + 1 – onde P é o número de nós-predicados contidos no grafo G.

V(G) = 5 nós-predicados + 1 = 6

No final temos, então, 6 caminhos independentes. Com isso, sabemos que precisamos ter uma gama de pelo menos 6 testes para garantir uma boa cobertura para esse código.

Os seis caminhos independentes são:

  • 1) 1-2-10-12-13
  • 2) 1-2-10-11-13
  • 3) 1-2-3-10-11-13
  • 4) 1-2-3-4-5-8-9-2-[…]
  • 5) 1-2-3-4-5-6-8-9-2-[…]
  • 6) 1-2-3-4-5-6-7-8-9-2-[…]

Você pode traçar mentalmente um a um. Por exemplo, o primeiro caminho:

  • 1-2-10-12-13

alt

Com os caminhos definidos, você pode planejar os casos de teste para cada um deles.

Ferramentas para análise estática

Essa é, claro, a mais produtiva forma: usar um analisador estático. Dentre as várias vantagens, temos:

  • Precisão;
  • Possibilidade de incluir a análise na integração contínua;
  • Geração de relatórios da evolução das métricas;

Existem várias ferramentas, você pode pesquisar sobre “análise estática” na linguagem que você utiliza em seus projetos, certamente encontrará várias opções (com focos variados).

Uma ferramenta bastante utilizada é a Sonar. Ela suporta mais de 25 linguagens. Tem a premissa de avaliar “débito de código” (o “preço” que se paga no futuro por algo que foi implementado no presente sem se preocupar tanto com a qualidade).

Se você desenvolve em PHP, escrevi um artigo sobre Ferramenta para avaliar a complexidade de código escrito em PHP.

Se você desenvolve em .NET temos:

  • ReSharper (Oferece muito mais que só análise estática)
  • ndepend (Tem um foco maior nas relações entre os objetos, acoplamento etc)

Quais os parâmetros aceitáveis para a complexidade dos meus métodos?

De acordo com o trabalho de McCabe, os valores de referência são:

Complexidade Avaliação
1-10 Método simples. Baixo risco.
11-20 Método razoavelmente complexo. Moderado risco.
21-50 Método muito complexo. Elevado risco.
51-N Método de altíssimo risco e bastante instável.

Esses são apenas valores de referência. O fato de um método ter baixa complexidade não quer dizer que ele não pode ser melhorado ou até mesmo refatorado. Essa é a parte relativa da “coisa”. Caberá a você e a sua equipe identificar esses pontos.

Como posso melhorar os meus códigos?

Um código com baixa complexidade ciclomática não necessariamente já esgotou todas as possibilidades de melhorias. Há um conjunto de fatores envolvidos. Mas, em termos gerais, refatorar é a melhor “ferramenta” para se ter um código melhor autodocumentado e com menos complexidade.

Nem sempre conseguimos de “prima” acertar em cheio naquilo que desenvolvemos. Para preencher essa lacuna, existe a refatoração: melhorar a compreensão do código existente (sem alterar o seu comportamento).

Martin Fowler criou um catálogo com algumas técnicas de refatoração. Elas são focadas em se ter um código autodocumentado (expressivo, de fácil leitura, coeso etc).

Algumas das principais técnicas descritas:

Substituir recursividade por iteração: O código recursivo é difícil de entender e na maioria dos casos pode ser substituído por iteração. A não ser que se tenha um objetivo muito bem definido para a existência da recursão (para se ter algum expressivo ganho em performance; ou se você está trabalhando com uma operação muito complexa com árvores binárias etc).

Exemplo (baseado no do catálogo):

public void countDown(int n) {
    if(n == 0) return;

    System.out.println(n + "...");
    waitASecond();
    countDown(n-1);
}

Poderia ser escrito assim:

public void countDown(int n) {
    while(n > 0) {
        System.out.println(n + "...");
        waitASecond();
        n -= 1;
    }
}

Decompor condicional: Quando se têm uma estrutura condicional muito complicada com diversos if-elseif-else, pode-se extrair métodos dos resultados dessas condições e invocá-los.

Exemplo (baseado no do catálogo):

if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
    charge = quantity * _winterRate + _winterServiceCharge;
} else {
    charge = quantity * _summerRate;
}

Poderia ser decomposto para:

if (notSummer(date)) {
  charge = winterCharge(quantity);
} else {
  charge = summerCharge(quantity);
}

Substituir ninhos de condicionais por cláusulas de proteção: Para tirar a complexidade do entendimento do caminho de execução.

Exemplo (baseado no do catálogo):

double getPayAmount() {
  double result;
  if (_isDead) result = deadAmount();
  else {
    if (_isSeparated) result = separatedAmount();
    else {
      if (_isRetired) result = retiredAmount();
      else result = normalPayAmount();
    };
  }
  return result;
};

Poderia ser escrito assim:

double getPayAmount() {
  if (_isDead) return deadAmount();
  if (_isSeparated) return separatedAmount();
  if (_isRetired) return retiredAmount();
  return normalPayAmount();
};

Substituir condicional por polimorfismo: Tendo um comando condicional que a partir do tipo de objeto escolhe diferentes comportamentos, você pode tornar o método original abstrato e mover essas condicionais para suas próprias subclasses.

Exemplo retirado do catálogo:

class Bird {
  //...
  double getSpeed() {
    switch (type) {
      case EUROPEAN:
        return getBaseSpeed();
      case AFRICAN:
        return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return (isNailed) ? 0 : getBaseSpeed(voltage);
    }
    throw new RuntimeException("Should be unreachable");
  }
}

Poderia ser descomposto para:

abstract class Bird {
  //...
  abstract double getSpeed();
}

class European extends Bird {
  double getSpeed() {
    return getBaseSpeed();
  }
}

class African extends Bird {
  double getSpeed() {
    return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
  }
}

class NorwegianBlue extends Bird {
  double getSpeed() {
    return (isNailed) ? 0 : getBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.getSpeed();

Outras técnicas com exemplos aplicados, você pode consultar aqui.

Focar na qualidade do meu software ou na entrega?

Ideologicamente falando, o processo de refatorar é contínuo. O software vai crescer, precisará ser “tocado” e a refatoração andará lado a lado a tudo isso. Não existe código perfeito, não existe código “à prova de bala”, mas é possível ter código coeso, fácil de ser lido e o principal: fácil de ser testado.

Aliar entrega (o resultado final, o que interessa para a empresa) com qualidade de software é uma questão ampla e complexa. Sabemos que, na maioria das vezes, a realidade é essa: pressão para que as entregas aconteçam o mais rápido possível. Quase nunca entra na análise de tempo e custo o que será gasto para cuidar da qualidade/análise do código. O foco é quase sempre nas entregas e, pensando no lado “corporativo”, elas são extremamente importantes para manter a competitividade da empresa e isso não pode ser ignorado, mas também tem o lado negativo, que é o débito de código (o custo e o tempo que será gasto no futuro para implementar algo novo ou corrigir uma implementação).

Não tem “receita de bolo”, não tem manual, a equipe precisa aprender a mensurar todas essas nuances e achar um “meio termo”. Não dá pra julgar essa guerra de interesses, todos têm seus motivos e suas justificativas.

Outro ponto interessante de reflexão: Não, ninguém usa todas as “melhores técnicas” de codificação (e isso é muito relativo). “Código impecável” e que usa as “melhores técnicas” etc, quase sempre é apenas case para palestrantes, professores e artigos (como este aqui). No entanto, isso não tira e nem invalida o fato de que, como desenvolvedores, podemos evoluir, podemos encontrar novas formas e novos conceitos de pensar no “todo” que envolve o desenvolvimento de software. E essa responsabilidade é nossa, não sendo possível delegá-la.

Concluindo

Os temas aqui introduzidos são vastos. A ideia não foi a de esgotar os assuntos (e isso nem seria possível), o objetivo principal foi o de acender àquela “chama” que carregamos em nós: àquela que alimenta o nosso amor e interesse pelo desenvolvimento de software. Temos de mantê-la acesa, estudando coisas novas e buscando um conhecimento multimodal.

Até a próxima!

Ferramenta para avaliar a complexidade de código escrito em PHP

Recentemente o Taylor Otwell (criador do Laravel Framework) publicou um artigo avaliando a complexidade de código dos principais frameworks utilizados em PHP. Ele decidiu simplificar e colocar na análise apenas as seguintes informações/métricas:

  • Quantidade de linhas de código.
  • Método mais longo (em número de linhas).
  • Média da complexidade dos métodos.
  • Máxima complexidade de um método.
  • Porcentagem de métodos não estáticos.

Essa última métrica é importante (especialmente) para Frameworks, pois há uma relação direta entre baixa testabilidade e métodos estáticos (quanto ao uso indiscriminado). Portanto, em teoria, quanto menos métodos estáticos, mais facilmente “testável” é o “todo”.

A ferramenta utilizada por ele para gerar o relatório foi a phploc do Sebastian Bergmann (criador do PHPUnit).

phploc

A instalação da phploc se dá pelo Composer, numa instalação global, de tal forma que o binário da ferramenta fique disponível no caminho de busca do SO.

Como pré-requisitos, você precisa ter o PHP e o Composer instalados (e no caminho de busca global). Se você tiver alguma dúvida sobre como proceder com a instalação do Composer, verifique aqui.

Instalando a ferramenta

No terminal (ou CMD, se você estiver no Windows), execute:

composer global require 'phploc/phploc=*'

Finalizado, tudo o que você precisa fazer é navegar até a pasta do projeto que você quer gerar o relatório e executar:

phploc ./folder

Para que possamos aqui visualizar a saída da execução da ferramenta, vamos clonar o core do Laravel e executá-la na pasta do componente Database.

Para isso, no diretório padrão onde você tem os seus projetos PHP (ou em outro, se preferir), execute:

composer require laravel/framework

Isso vai criar uma pasta chamada framework. Para executar a análise somente no componente Database, primeiro navegue até essa pasta e em seguida execute:

phploc src/Illuminate/Database/

Veja:

alt

Esse foi o relatório gerado:

Directories                                         16
Files                                              107

Size
  Lines of Code (LOC)                            26752
  Comment Lines of Code (CLOC)                   12193 (45.58%)
  Non-Comment Lines of Code (NCLOC)              14559 (54.42%)
  Logical Lines of Code (LLOC)                    3884 (14.52%)
    Classes                                       3450 (88.83%)
      Average Class Length                          32
        Minimum Class Length                         0
        Maximum Class Length                       345
      Average Method Length                          1
        Minimum Method Length                        0
        Maximum Method Length                       12
    Functions                                        0 (0.00%)
      Average Function Length                        0
    Not in classes or functions                    434 (11.17%)

Cyclomatic Complexity
  Average Complexity per LLOC                     0.24
  Average Complexity per Class                    9.88
    Minimum Class Complexity                      1.00
    Maximum Class Complexity                    104.00
  Average Complexity per Method                   1.61
    Minimum Method Complexity                     1.00
    Maximum Method Complexity                    17.00

Dependencies
  Global Accesses                                   10
    Global Constants                                10 (100.00%)
    Global Variables                                 0 (0.00%)
    Super-Global Variables                           0 (0.00%)
  Attribute Accesses                              1374
    Non-Static                                    1317 (95.85%)
    Static                                          57 (4.15%)
  Method Calls                                    2606
    Non-Static                                    2407 (92.36%)
    Static                                         199 (7.64%)

Structure
  Namespaces                                        17
  Interfaces                                         5
  Traits                                            13
  Classes                                           89
    Abstract Classes                                 9 (10.11%)
    Concrete Classes                                80 (89.89%)
  Methods                                         1566
    Scope
      Non-Static Methods                          1496 (95.53%)
      Static Methods                                70 (4.47%)
    Visibility
      Public Methods                              1037 (66.22%)
      Non-Public Methods                           529 (33.78%)
  Functions                                        119
    Named Functions                                  0 (0.00%)
    Anonymous Functions                            119 (100.00%)
  Constants                                          5
    Global Constants                                 3 (60.00%)
    Class Constants                                  2 (40.00%)

Como você pôde observar, avalia-se muito mais do que só o que introduzimos no começo desse post, como a quantidade de:

  • Linhas de código;
  • Diretórios;
  • Classes concretas;
  • Classes abstratas;
  • Interfaces;
  • Traits;
  • Métodos estáticos;
  • Funções;
  • Etc.

A ferramenta também a avalia a complexidade ciclomática média das classes e métodos.

Em futuros artigos veremos sobre teorias de teste de software e métricas para medição da qualidade de código.

Até a próxima!

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES