Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

RSS iMasters

[Resolvido] É possível melhorar seus debugs

Recommended Posts

Existem duas maneiras de construir o design de umsoftware. Uma delas é tão simples que obviamente não existem deficiências. E ooutro jeito é fazer de uma maneira tão complicada que não existam deficiênciasóbvias

 

Comovocê já sabe, o PHP é a linguagem dos apressados e espertinhos, em que atendência do desenvolvedor é sempre em direção à preguiça (eficiência).

 

Nósnem sempre testamos nosso código. Às vezes, nós pegamos atalhos de design,mesmo sabendo que eles vão nos atrapalhar mais tarde. As restrições dosnegócios atam nossas mãos, e nos impedem de fazer nosso trabalho de maneiraotimizada.

 

Se considerarmos o que FredBrooks tem a dizer, percebemos que o custo de tal miopia a longo prazo pesamuito mais que suas vantagens. Em seu trabalho seminal, The Mythical Man-Month, Brooks explica, ?mais de 90% dos custos de um sistematípico aumentam na fase de manutenção, e qualquer peça do software que funcionabem vai ser inevitavelmente mantida.?

 

 

Não preste atenção no homem atrás da cortina

Normalmente,?manutenção? significa escrever o relatório de um bug, entender como reproduzi-loe arrumar o código afetado. Ou, em casos mais sérios, refazer o design de partede sua arquitetura.

 

Felizmente, quando o assunto édebugar, a faca suíça das ferramentas de um desenvolvedor PHP é o Xdebug.Apesar de todo poder e utilidade inacreditáveis dessa extensão, o fato demuitos desenvolvedores não o terem instalado - ou pior, nunca terem ouvidofalar dele - é incrível para mim.

 

Istalaro Xdebug não poderia ser mais fácil. Se você está executando um sistemaoperacional real, sua instalação é a mesma de qualquer outra extensão PECL:execute pecl install xdebug. Se você está no Windows, você tem que achar obinário correto, o que pode ser complicado. Felizmente, o autor foi gentil osuficiente para fornecer uma ferramenta para ajudar. Uma vez instalado, vocêpode ver os benefícios do Xdebug imediatamente. Ele substitui as mensagenspuras geradas pelos erros do PHP e uncaught exceptions com erros formatados,incluindo stack traces. (Para ver mensagens de erro em sua total glória HTML,garanta que html_errors esteja ativado).

 

Issoé algo importante. A coisa mais relevante (difícil) do processo de debugar éisolar, determinar a principal causa do bug e onde ele ocorreu. Ao tentarisolar um bug, os stack traces são indispensáveis; eles permitem que vocêdescubra o caminho que seu código percorreu para chegar lá.

 

Emsoma aos traços de stack, o Xdebug tem duas configurações php.ini que são extremamente úteis. A primeira, xdebug.collect_params, especifica o nível de detalhe da função ou osparâmetros do método que você gostaria de ver nos seus stack traces. O valorpadrão, 0, não mostra nenhuma informação de parâmetro, enquantoo valor de 4 mostra detalhes completos de qualquer parâmetro. Issopode ser um pouco demais para a maioria das situações. Sugiro que você ajusteesse valor de acordo com sua necessidade.

 

Asegunda configuração, xdebug.show_local_vars, garante maior facilidade noisolamento por dumping das variáveis locais onde o erro ou a exceção ocorreram.Estabeleça esse valor a On para ver um dump formatadoem todos as variáveis do escopo.

 

 

Oque você não vê

Aousar as ferramentas apresentadas acima, você agora deve estar completamenteequipado para seguir em frente e rasgar sua lista de bugs com impunidade.Certo?

 

Errado!

 

Duas coisas que a coleção deparâmetros e variáveis do Xdebug não vão te mostrar são o estado do escopo doobjeto atual (i.e. $this), e o acesso a quaisquer dadosque existam fora do método.

 

Em vez de considerar isso comouma deficiência do Xdebug, considere as implicações de como você faz o designde seus métodos. A principal causa lógica para os bugs de softwares é um estadopobremente gerenciado; você tem mais chances, ao utilizar dados que podem mudarimprevisivelmente, de produzir um código que não pode ser testado nemsustentável.

 

Aopensar em códigos que são otimizados por previsibilidade, pensamos em coisassimples, como manipulações de funções string. Considere str_replace(), uma função altamente testável. Dados quaisquer trêsinputs, podemos esperar um output consistente, previsível. Se um call fosserepetido com os mesmos três parâmetros, nós teríamos os mesmos resultados,porque str_replace() não depende de nenhum state para fazerseu trabalho.

 

Alémdisso, códigos previsíveis minimizam ou eliminam efeitos colaterais, tais comoecos no output, modificações em uma variável que é passada como referência, oudesignações de cabeçalhos. Efeitos colaterais se tornam problemáticos, porque ?além de isolar um método em particular ? você também deve isolar o que aquelemétodo mudou em outro lugar do aplicativo.

 

Produzirefeitos colaterais e depender do estado externo em um método não é sempre tãoóbvio quanto uma referência ao $GLOBALS (ou superglobais). Nóstambém temos os singletons, a memorização das variáveis globais em designs orientadosa objetos.

 

Existemtambém incursões sutis de estado, como usar informações de tempo e de data. Mesmonão sendo tipicamente considerado um estado em um aplicativo, é uma peça deinformação que está constantemente mudando fora do escopo do seu código. Issopara não dizer que é totalmente perigoso; apenas seja prudente ao usá-lo.

 

Paracódigos orientados a objetos, você não precisa de nada além de $this. Gerenciar o estado efetivamente pode ser difícil emOOP, porque o OOP (diferentemente da programação funcional) não força você apensar sobre o estado. Em vesdisso, ele disponibiliza estados mutantesimediatamente acessíveis, promovendo demora em volta de um dos mais críticospreceitos do design do software.

 

Nãoentenda errado: esta não é uma condenação universal de programação orientada aobjetos. É simplesmente uma sugestão para pensar antes de fazer seu código. Sevocê somente aprendeu sobre design de software através de um ponto de vistaorientado pelo objeto, eu o aconselho a estudar a linguagem sob um outroparadigma. Você abrirá seus horizontes e ganhará um melhor entendimento do seupapel como desenvolvedor.

 

 

Finalizando

Uma questão final quando oassunto é debugar com o Xdebug se refere ao novo recurso do PHP 5.3, closures.Quando você grava um stack trace que contém closure, em vez de informaçõesúteis, o Xdebug apenas grava {closure}. Enquanto nós ainda vemos onúmero do arquivo e a linha onde a closure foi invocada pelo stack, isso não ésuficiente para que você seja capaz de determinar rapidamente onde ele foidefinido.

 

Em vez disso, nós podemos criarum pequeno script que irá converter um stack PHP normal para um que fornece umpouco mais de informação sobre onde a closure foi definida. Esse código usacomponentes do Lithium framework com o objetivo de simplicar a introspecção de classes e métodos, mas a premissabásica é simples, e nenhum framework é necessário. Você provavelmente optará porusar o Reflection API - nativo do PHP - diretamente.

<?php

use lithiumanalysisDebugger;
use lithiumanalysisInspector;

class ClosureLocator {

 public static function trace($stack = null) {
   $stack = $stack ?: Debugger::trace(array('start' => 1, 'format' => 'array'));

   foreach ($stack as $i => $frame) {
     $prev = isset($stack[$i - 1]) ? $stack[$i - 1] : null;
     unset($stack[$i]['args'], $stack[$i]['object']);

     if (strpos($frame['function'], '{closure}') === false || !$prev) {
       $stack[$i]['function'] = $frame['functionRef'];
       continue;
     }
     if ($class = Inspector::classes(array('file' => $prev['file']))) {
       foreach (Inspector::methods(key($class), 'extents') as $method => $extents) {
         if (!($extents[0] <= $prev['line'] && $prev['line'] <= $extents[1])) {
           continue;
         }
         $class = key($class);
         $reference = "{$class}::{$method}";
         $stack[$i]['function'] = "{$reference}()::{closure}";
         break;
       }
     } else {
       $reference = $prev['file'];
       $stack[$i]['function'] = "{$reference}::{closure}";
     }
   }
   return $stack;
 }
}

?>

Primeiramente,este código gera um stack trace especialmente formatado, que é essencialmente debug_backtrace() com algumas checagens e formatações.Em seguida, ele repetesobre o stack, procurando por frames contendo calls feitos por closures. Umavez que encontra um (e garante que exista um chamado anterior no stack para servirde referência para o indicador de informação), ele tenta determinar se aclosure é definida em um método de classe ou em um arquivo processual PHP. Estaparte do código está fazendo uma suposição, especificamente de que seu códigousa uma convenção nominal class-to-file (classe-para-arquivo).

 

Seo closure é definido em uma classe, cada um dos métodos de classe é repetido einspecionado para ver se o chamado do stack subsequente ao número da linhaestá entre o começo e o fim das linhas da definição do método. Quando ocorrespondente é encontrado, nós temos nossa classe e métodos de origem.

 

Esse é um bom começo. Agora nóssomos capazes de ver o nome do arquivo ou o método de referência onde o closureestá definido em nosso stack trace. No entanto, para uma simples identificação,nós também queremos ser capazes de ver em qual número de linha a closure foidefinida.

 

Para tanto, podemos adicionar osseguintes métodos às nossas classes ClosureLocator :

public static function definition($reference, $callLine) {
 if (file_exists($reference)) {
   foreach (array_reverse(token_get_all(file_get_contents($reference))) as $token) {
     if (!is_array($token) || $token[2] > $callLine) {
       continue;
     }
     if ($token[0] === T_FUNCTION) {
       return $token[2];
     }
   }
 } else {
   list($class, $method) = explode('::', $reference);
   $classRef = new ReflectionClass($class);
   $methodInfo = Inspector::info($reference);
   $methodDef = join("n", Inspector::lines($classRef->getFileName(), range(
     $methodInfo['start'] + 1, $methodInfo['end'] - 1
   )));



   foreach (array_reverse(token_get_all("<?php {$methodDef} ?>")) as $token) {
     if (!is_array($token) || $token[2] > $callLine) {
       continue;
     }
     if ($token[0] === T_FUNCTION) {
       return $token[2] + $methodInfo['start'];
     }
   }
 }
}

Este método pega um nome dearquivo, ou o nome de uma classe e método, separados por dois pontos. Se $reference é um nome de arquivo, o arquivo estáanexando tokens em ordem contrária, seguindo em frente até que encontramosonde o próximo chamado (vindo do closure) aconteceu.

 

Umavez que achamos a linha onde o chamado aconteceu, continuamos repetindo aocontrário os tokens até que encontramos o T_FUNCTION, indicando o início dadefinição do closure. O número da linha do token é então devolvido.

 

Asegunda parte do método funciona praticamente da mesma maneira. Ao utilizaralgumas das técnicas acima, nós podemos determinar os números das linhas noarquivo onde o método é definido. Então, extraímos apenas o código para adefinição do método, o anexamos e começamos a repetir os tokens aocontrário. Após passar por todos os tokens que aparecem depois do método, anexamos, e começamos a repetir a primeira instância do f T_FUNCTION. Quando encontrado, nós voltamos o número da linharelativo ao trecho extraído mais o do início da definição do método (de ondeextraímos o código).

 

Finalmente,adicionaremos as seguintes linhas ao método trace() , pouco antes da chave de fechamento do loop foreach anterior:

$line = static::definition($reference, $prev['line']) ?: '?';
$stack[$i]['function'] .= " @ {$line}";

Isso invoca nosso novo método e acrescenta o número da linha no fim da definição dereferência do closure.

 

Agora podemos ?chamar? nosso método de qualquer lugar onde precisemos de uma stacktrace, e deveríamos poder ver algo um pouco mais informativo. Reduzido somente àinformação que geramos, nosso stack trace deve estar mais ou menos assim:

Array
(
   [0] =>
appcontrollersHelloWorldController::test()::{closure} @ 24
   [1] =>
lithiumnethttpRouter::parse()
   [2] =>
lithiumnethttpRouter::process()
   [3] =>
lithiumaction{closure}
   [4] =>
lithiumactionDispatcher::run()::{closure} @ 108
   [5] =>
/path/to/app/config/bootstrap/action.php::{closure} @ 42
   [6] =>
lithiumcoreStaticobject::_filter()
   [7] =>
lithiumactionDispatcher::run()
   [8]
=> [main]
)

Conclusão

Aíestá. Com o bom uso das ferramentas e das práticas de design de software, você podese salvar de um mundo de dor e salvar sua empresa de um colapso sobre projetosdifíceis de manter. O que poderia ser maior motivo para celebração nessa temporadade comemorações?

 

*

 

Textooriginal de Nate Abele, disponível emhttp://phpadvent.org/2010/debugging-by-nate-abele

 

hospedagem2-300x250.jpg

 

http://imasters.com.br/artigo/20113/php/e-possivel-melhorar-seus-debugs

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.