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. oheader()
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ática | bad 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…
Muito bom! Todo bom programador deveria entender isso.
E, entendendo, como diminui os Bugs.
Muy buen artículo...
Saludos desde Cuba
Abel Valdivia
Gracias