A complexidade do seu código

Se você que já deu manutenção em um site estruturado (aquele que o programador coloca tudo em um só arquivo) sabe o que é complexidade.

Muitas vezes eu acho que quem criou as funções, as criou para que o seu código ficasse menos complexo de dar manutenção e assim ele pudesse ganhar tempo.

No exemplo a seguir, vou mostra um código que é adicionado por muitos para verificar se o usuário é administrador.

O Código abaixo eu achei em um blog na internet

<?php
require 'conexao.php';
require 'funcoes.php';

$id_usuario_logado = intval($_SESSION['id_usuario_logado']);

// se na sessão não ouver o ID, não esta logado
if( $_SESSION['id_usuario_logado'] == 0 )
    header('Location login.php');

$sql = 'SELECT *
        FROM usuário
        WHERE usuario_id = '.$id_usuario_logado;
$result = mysql_query( $sql );
$usuario = mysql_fetch_row( $result );

// se o usuário não for administrador, deverá voltar para a home
if( $usuario['usuario_permissao'] != 'admin' )
    header('Location login.php');

?><!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>KrauSoft</title>
</head>
. . . 

Vocês pode não acreditar, mais existe muitos sites com um código medonho deste. Eu que trabalho muito com EAD e instalando o MoodleAdmin vejo isso quase todos os dias.

Você percebeu que existe um BUG dos grandes no código acima? Olhe de novo e tente achar!

O BUG é simples. o header() só adiciona o cabeçalho, mais não interrompe a execução e esta página será toda processada e enviada ao browser do usuário. Então um hacker poderá capturar este HTML e também poderá interagir com toda a página.

Agora, imagine que este sistema tenha 200 páginas de acesso restrito á administradores, e que o cliente pediu que os "gerentes" também tenham acesso a estas páginas. Viu o que é complexo.

Agora, além de ser ultra POG o que foi feito acima, e ultra complexo de se dar manutenção, também é esteticamente feio.

A Complexidade Ciclomática

Sendo bem simples, a complexidade ciclomática é uma métrica do número de caminhos possíveis no seu código. fonte Wikipédia.

A primeira regra de funções é que elas devem ser pequenas. A segunda regra de funções é que elas devem ser ainda menores.
— Uncle Bob 

Então, quanto menor for sua função, menor será a complexidade em dar manutenção no seu código.

Então, para melhorar nosso exemplo, vou criar uma classe Usuario e separar o máximo possível nosso código:

class Usuario{
    
    public $usuario_id;
    public $usuario_nome;
    public $usuario_permissao;

    /**
     * Variável auxiliar do método singleton
     * 
     * @var Usuario
     */
    private static $_usuarioLogado;
    /**
     * @return null|Usuario
     */
    public static function getUsuarioLogado()
    {
        if( self::$_usuarioLogado != null )
            return self::$_usuarioLogado;

        $id_usuario_logado = intval($_SESSION['id_usuario_logado']);
        Usuario::findById( $id_usuario_logado );

        return self::$_usuarioLogado;
    }

    /**
     * Retorna o usuário basead no ID
     *
     * @param int $usuario_id
     * @return null|Usuario
     */
    public static function findById($usuario_id)
    {
        $sql = 'SELECT *
                FROM usuário
                WHERE usuario_id = '.$usuario_id;
        $row = ConnectDb::returnOneRow( $sql );

        return Usuario::converteArrayToUsuario( $row );
    }

    /**
     * Valida se o usuário logado é administrador
     *
     * @return bool
     */
    public static function isLogadoAdmin()
    {
        $usuario = Usuario::getUsuarioLogado();
        if( $usuario == null )
            return false;
        
        if( $usuario->usuario_permissao == 'admin' )
            return true;
        return false;
    }

    /**
     * converte um Array vindo do banco de dados,
     * para um Objeto do tipo Usuário
     *
     * @param array $row
     * @return null|Usuario
     */
    public static function converteArrayToUsuario($row)
    {
        if( $row == null )
            return null;

        $usuario = new Usuario();
        $usuario->usuario_id        = $row['usuario_id'];
        $usuario->usuario_nome      = $row['usuario_nome'];
        $usuario->usuario_permissao = $row['usuario_permissao'];

        return $usuario;
    }
}

Então, o código na página passará a ser apenas assim:

<?php
if(Usuario::isLogadoAdmin())
    Header::location('login.php');

?><!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>KrauSoft</title>
</head>
. . .

E a chamada do header, eu passei a usar a classe Header. Implementei o método notFound() também:

class Header
{
    public static function location($url)
    {
        ob_clean();
        header('Location: ' . $url);
        die();
    }

    public static function notFound()
    {
        header('HTTP/1.0 404 Not Found');
    }
}

Agora não há como se esquecer de colocar o die() e o ob_clean() nas chamadas do header('Location: …'). Ou seja, menor a possíbilidade de ter Bugs.

Mais, apesar de escrever muitas linhas a mais, o nosso código esta muito mais simples, pois sempre que recusar saber quem é o usuário logrado é só chamar Usuario::getUsuarioLogado(), ou se tiver o ID do usuário e precisar saber do nome é só chamar Usuario::findById($id).

Veja que nossa classe Usuario tem vários métodos pequenos, e que podem ser usados em muitos casos. E, cada um destes métodos com baixa complexidade de manutenção.

Como calcular a complexidade ciclomática?

Veja abaixo a função PHP qe criei:

function umaFuncaoQualquer($umValor)
{
    $resultado = 0;
    if ($umValor > 9)
        $resultado += 1;
    return $resultado;
}

Este código pode percorrer dois caminhos. Se o $umValor possuir o valor maior que 9, ele entará no IF, caso contrário passará direto. Vamos então ver esta segunda função:

function outraFuncaoQualquer($umValor)
{
    $resultado = 0;
    if ($umValor > 9)
        $resultado += 1;
    if ($umValor < 15)
        $resultado += 2;
    return $resultado;
}

Já a execução desta função poderá percorrer 4 caminhos diferentes. Só com a adição de um novo IF, a complexidade Ciclomática dobra. Se eu apenas adicionar mais um IF, a complexidade passa a se 8.

Entenderam como é "complexo" evitar Bugs?

Bad Fix

Em tese, recomenda que a complexidade ciclomática nunca ultrapasse 10 pontos. Sendo que 10 já é um um valor muito alto. Veja este PDF em inglês.

Segundo a pesquisa que a aivosto.com fez, uma correção aplicada em um método com complexidade ciclomática 25 tem 20% de chances de introduzir um novo bug na sua aplicação. E acredito, pois já aconteceu comigo… 

Então Bad Fix é a probabilidade que seu código terá de produzir outro BUG, ao ser corrigido um BUG.

Eles então montaram a seguinte tabela:

Complexidade Ciclomáticabad fix
1 - 10 5%
10 - 20 20%
maior que 50 40%
perto de 100 60%

Entendeu?

Eu fico pensando, que bagunça deve ser o ruWindows para todo dia vir um monte de correções de BUG!!!!!

Pra que você precisa saber disso?

Saber, eu acho que todo mundo sabe, mais agora é pensar nisso e tomar mais cuidado para que da próxima vês programar cada vês, métodos menores e menores.

E você Kraus, faz o que escreve?

Antes de alguém comentar, hehehehe, vou aqui descrever meu caso. No projeto wowzaadmin.com eu, antes de começar coloquei no papel todo o sistema e todas as funcionalidades e como cada uma destas funcionalidades iria trabalhar. 

O sistema é complexo e tem milhares de formas diferentes de resposta e de analise de dados e toda a comunicação com o servidor Wowza é feita em XML que retorna muitos dados. Então analisar estas centenas de dados que retornam iria, em tese, gerar um monte de Bugs.

Essa era a maior preocupação. Os Bugs. Meu medo seria que o sistema chegasse a um ponto que os Pogs gerados para corrigir os Bugs fossem tantos que o sistema poderia ser abandonado.

Imagine este sistema, aonde a mesma página que analisa o trafego de transmissões ao-vivo, também analisa o trafego de dados de vídeos gravados, câmeras IP, e re-transmissão de rádios ShoutCast. Será que eu estava certo em ficar preocupado?

Então comecei, antes de escrever uma sequer linha de código, a separar o projeto em grandes partes e cada uma destas partes separar o máximo possível até chegar que cada parte fosse pequena e simples de dar manutenção. Assim, por exemplo, só na parte de controle e analise dos dados do Wowza, o sistema possui 107 classes com 930 métodos.

E, para minha surpresa, muito poucos Bugs…

Fique por dentro de nossas novidades, ideias e atualizações