Para muitos o fato de um atributo ou método ser público ou privado pode nada ter a ver com refactoring de código. Afinal de contas isso tem mais a ver com o fato dessa classe poder ser acessada de fora ou não do objeto em questão. Então, a princípio, é melhor deixar aquele método público (padrão do php), afinal de contas deixando ele público, ele poderá ser usado por muitas outras classes, correto?
ERRADO!
Toda vez que você cria um método e declara ele como sendo público você está aumentando a interface do seu objeto. Com isso o seu sistema fica mais complexo.
E o que isso tem a ver com refactoring?
Para quem refatora código normalmente (todos fazem isso, correto? ), isso é bem fácil de entender. Se eu tenho um método público, estou dizendo que este método pode estar sendo chamado de qualquer lugar no sistema. Ou seja, se eu precisar alterar o meu método, vou precisar revisar o sistema inteiro para saber se alguém usa aquele método. E para uma linguagem dinâmica como o php, isso não é nenhum pouco fácil de fazer usando uma IDE.
Tudo que é dificil de fazer parece que atrai aquela conhecida de muitos programadores: preguiça! E a preguiça gera "ctrl+c ctrl+v". Resultado: código duplicado, dificuldade de fazer refactoring, problemas na implementação de novas regras. Mais erros, mais custo de manutenção, etc... Você deve conhecer esta novela!
Já se a pessoa que declarou o método colocou uma simples palavra antes dele (protected ou private), aí fica bem mais fácil de saber se o método está sendo usado no sistema, afinal de contas ele só poderá ser usado naquela classe ou filhas. Assim a preguiça fica no canto dela, o método é refatorado e o código fica limpo, fácil de mover, etc... espero que conheça esta história também! Se não conhece, a hora é agora. Sucesso!
sexta-feira, 18 de junho de 2010
sábado, 29 de maio de 2010
Que tal uma validação de dados "universal" para a comunidade php?
Validação de dados é um tema que sempre gera alguma controvérsia. Alguns acham que ela tem que ser feita na action, outros em uma camada de serviço ou de aplicação e outros no modelo. A maioria das pessoas também concorda que ela deva ser feita no browser. Neste texto pretendo examinar um pouco este assunto e sugerir a necessidade de se ter um componente de validação de dados para php como um projeto independente de qualquer framework ou camada.
Esses dias li um artigo interessante que diz que ao mesmo tempo que a comunidade ruby está caminhando para um único padrão forte de framework com muita convenção sobre conficuração, a comunidade php está caminhando para retirar a "mágica" dos códigos focando em padrões de projeto e OO, ter mais de um framework robusto e buscar ter interoperabilidade entre os frameworks. Achei isso muito bom. Pela minha experiência vejo que esses comportamentos "mágicos" dos frameworks costumam embaralhar muito a cabeça dos programadores mais jóvens.
Nesse rumo também vemos que algumas ferramentas estão amadurecendo muito e buscando encontrar o seu foco principal, removendo tudo aquilo que foge a esse foco. O Doctrine 2, por exemplo, que é um projeto que venho estudando, não vem com componente de validação integrado na versão 2. No início fiquei bem chateado com isso pois gostava muito de definir já algumas restrições no yml e já ter essa validação prontinha pra mim. Mais depois consegui perceber que o foco de um ORM não é esse e acho que se cada ferramente se manter no seu foco ela tem mais sucesso de prosperar.
Além disso se examinarmos um pouco mais, como já falei antes, dependendo da arquitetura uns acham melhor ter a validação no modelo, outros no JS, outros na action, etc... Não quero nesse texto ficar discutindo o que é melhor. Eu mesmo acho que isso varia de sistema pra sistema. Mas se percebemos bem essas necessidades vemos que a comunidade se beneficiará muito de ter uma ferramenta de validação de dados como sendo uma biblioteca completamente isolada. Assim ela poderá ser utilizada com qualquer framework e em qualquer camada. Claro que podem ser definidos adaptadores para integrar essa ferramenta em um lugar ou outro, mas o cerne dela deve ser "puro".
O meu pensamento é que se tivermos um arquivo de definição de dados, ele possa ser lido e usado para validar tanto um entidade na camada de modelo quanto um formulário no javascript. Com isso criamos um padrão de formato de dados que pode ser uma ótima contribuição nessa filosofia de ter ferramentas que podem ser usados nos diversos frameworks e camadas.
Algumas coisas eu acho interessante que essa ferramenta tenha:
http://groups.google.com/group/json-schema/web/json-schema-proposal-working-draft
http://tools.ietf.org/html/draft-zyp-json-schema-02
Mas com certeza existem outros. O que quero ressaltar aqui é exatamente a necessidade da existência de uma ferramenta como essa. Tendo algo assim separado de frameworks, a comunidade pode juntar esforços para contribuir melhorando cada vez mais os algorítmos de validação e ampliando cada vez mais os tipos de dados disponíveis.
Esses dias li um artigo interessante que diz que ao mesmo tempo que a comunidade ruby está caminhando para um único padrão forte de framework com muita convenção sobre conficuração, a comunidade php está caminhando para retirar a "mágica" dos códigos focando em padrões de projeto e OO, ter mais de um framework robusto e buscar ter interoperabilidade entre os frameworks. Achei isso muito bom. Pela minha experiência vejo que esses comportamentos "mágicos" dos frameworks costumam embaralhar muito a cabeça dos programadores mais jóvens.
Nesse rumo também vemos que algumas ferramentas estão amadurecendo muito e buscando encontrar o seu foco principal, removendo tudo aquilo que foge a esse foco. O Doctrine 2, por exemplo, que é um projeto que venho estudando, não vem com componente de validação integrado na versão 2. No início fiquei bem chateado com isso pois gostava muito de definir já algumas restrições no yml e já ter essa validação prontinha pra mim. Mais depois consegui perceber que o foco de um ORM não é esse e acho que se cada ferramente se manter no seu foco ela tem mais sucesso de prosperar.
Além disso se examinarmos um pouco mais, como já falei antes, dependendo da arquitetura uns acham melhor ter a validação no modelo, outros no JS, outros na action, etc... Não quero nesse texto ficar discutindo o que é melhor. Eu mesmo acho que isso varia de sistema pra sistema. Mas se percebemos bem essas necessidades vemos que a comunidade se beneficiará muito de ter uma ferramenta de validação de dados como sendo uma biblioteca completamente isolada. Assim ela poderá ser utilizada com qualquer framework e em qualquer camada. Claro que podem ser definidos adaptadores para integrar essa ferramenta em um lugar ou outro, mas o cerne dela deve ser "puro".
O meu pensamento é que se tivermos um arquivo de definição de dados, ele possa ser lido e usado para validar tanto um entidade na camada de modelo quanto um formulário no javascript. Com isso criamos um padrão de formato de dados que pode ser uma ótima contribuição nessa filosofia de ter ferramentas que podem ser usados nos diversos frameworks e camadas.
Algumas coisas eu acho interessante que essa ferramenta tenha:
- Extensibilidade. Esse é um dos pontos principais. Deve ser muito fácil acrescentar um novo tipo de dados e também a classe ou método para validar este tipo. Além disso pode ser útil ter um sistema para registrar listeners que recebem eventos durante o processo de validação extendendo ou sobreescrevendo algum comportamento.
- Facilidade de configuração. Geralmente as mensagens de erro lançados por estas ferramentas já atendem nos casos mais simples. Então é necessário poder trocar as mensagens de erro de cada validação e também poder capturar um erro e sobreescrever a mensagem para casos específicos.
- Trabalhar com objetos/arrays multidimensionais. Esse é um dos pontos onde a maioria das ferramentas do "mercado de software livre" não atende. Acho importanto poder dizer que dentro do dado que eu quero validar pode ter um array e que cada item "nome" desse array pode ter no máximo 15 caracteres.
- Ter um formato que pode ser compartilhado entre servidor e browser. É um fato que o servidor tem que ter validação para termos os dados íntegros. Também é um fato que uma validação no JS melhora a experiência do usuário e diminui o trafego entre cliente e servidor. E muitas vezes a validação nos dois pontos é a mesma. Por isso é importante que a definição da validação possa ser usada pelo browser em uma api semelhante à usada no servidor.
http://groups.google.com/group/json-schema/web/json-schema-proposal-working-draft
http://tools.ietf.org/html/draft-zyp-json-schema-02
Mas com certeza existem outros. O que quero ressaltar aqui é exatamente a necessidade da existência de uma ferramenta como essa. Tendo algo assim separado de frameworks, a comunidade pode juntar esforços para contribuir melhorando cada vez mais os algorítmos de validação e ampliando cada vez mais os tipos de dados disponíveis.
Marcadores:
dados,
json,
json schema,
php,
validação
quarta-feira, 19 de maio de 2010
Alterando uma informação automáticamente antes do update no doctrine 2. (com annotations)
1 - Definimos que a classe ouve os callbacks:
/**
* @Entity
* @HasLifecycleCallbacks
*/
2 - Definimos um método para ser atualizado antes do update.
/**
* @PrePersist @PreUpdate
*/
3 - Lembrar de mandar construir os modelos novamente.
/**
* @Entity
* @HasLifecycleCallbacks
*/
2 - Definimos um método para ser atualizado antes do update.
/**
* @PrePersist @PreUpdate
*/
function updateUpdatedAt() {
$this->updatedAt = new DateTime();
}
3 - Lembrar de mandar construir os modelos novamente.
sábado, 3 de abril de 2010
Data Repositories - low coupling with persistence layer.
Someday I used to think a method is good if it can be very flexible. But thins article tells a diferent story:
http://codebetter.com/blogs/gregyoung/archive/2009/01/16/ddd-the-generic-repository.aspx
http://codebetter.com/blogs/gregyoung/archive/2009/01/16/ddd-the-generic-repository.aspx
quarta-feira, 24 de março de 2010
Concordion - parece algo de qualidade para BDD
Tem tempo que procuro uma ferramenta útil que una testes unitários e escrita de especificação em uma coisa só. Ou seja, uma ferramenta boa de BDD. Recebi este link de um colega de trabalho e gostei. Esta pode ser uma boa ferramenta para se ter um port para php:
http://www.concordion.org/
http://www.concordion.org/
quinta-feira, 18 de março de 2010
Refactoring e Clareza de Código - Negação da Negação
Este faz parte de uma série de pequenos posts com os tags código limpo e refactoring que vou estar publicando na medida que vou limpando alguns códigos por aqui.
A negação da negação geralmente atrapalha a leitura:
Normalmente quando um if tem um else, é mais claro se não começamos negando. Porque então o else vai ser a negação da negação.
Ou então algo assim:
parece normal, mas da um trabalhinho pra ler. É bem mais fácil ler algo como:
ou no máximo:
A negação da negação geralmente atrapalha a leitura:
if(!$userAllowed) {
//x
} // aqui estamos negando a negação do if acima...
else {
//y
}
ou seja, se o sistema não tiver acesso, faça x e se ele não não tiver (que é o mesmo que ele ter, mas da um trabalho para ler) faça y. Sei que isso parece simples, principalmente em um código pequeno, mas tudo que diminui a velocidade de leitura deve ser evitado, até mesmo porque em alguns métodos fica difícil não ter um código extenso.Normalmente quando um if tem um else, é mais claro se não começamos negando. Porque então o else vai ser a negação da negação.
Ou então algo assim:
if(!$accesForbidden) {//a} else {//b}
parece normal, mas da um trabalhinho pra ler. É bem mais fácil ler algo como:
if($hasAccess) {//a} else {//b}
ou no máximo:
apesar de que este segundo caso só acho aceitável quando a variável accessForbiden já existe e tem esse nome justificado em outro contexto.
if($accesForbidden) {//b} else {//a}
quarta-feira, 17 de março de 2010
Desacoplando para Testar - Injeção de dependência, inversão de controle e outras palavras bonitas.
Objetivo: Ensinar uma maneira de se estruturar uma aplicação para ela ser mais fácil de testar. Reduzir o tempo dos testes. Simplificar os testes usando mocks de maneiras não intrusivas.
Uns dos pontos principais para o teste de software é:
Não vou entrar de jeito nenhum na discussão do que é mock e o que é um stub. Vou melhorar este termo e traduzir. Digamos que podemos usar um "orêia". Por exemplo, se temos alguma parte do software que acessa um serviço externo, provavelmente isso não vai acontecer em menos de 0,1 segundo e isso é MUITO demorado para um teste. Você não acha? Imagine que ao final de um projeto pequeno para médio você terá em torno de 1000 testes.
Bom, então como fazemos para o nosso teste não demorar tanto? Usamos um mock, quer dizer stub, ou melhor: um "orêia". O nosso orêia vai ser usado no lugar do serviço original e fingir que executa o serviço. Ele pode até retornar algo estático ou que esteja em memória para simular o componente que ele está substituindo.
Por exemplo, digamos que no método inserirTexto() usamos o serviço do yahoo de extrair palavras chaves. Para isso usamos uma classe nossa onde encapsulamos a chamada a esse serviço: a classe PalavraChaveExtractorTabajaras. então teremos algo assim:
Mas como vamos fazer no nosso método inserirTexto para poder dizer que queremos usar o ôreia (Mock)?
Temos que ter um jeito de fazer com que o sistema use o "PalavraChaveExtractorTabajaras" em um ambiente de produção e use o mock nos testes. Já sei, que tal...
Bom, uma outra alternativa é criar um builder para o extractor assim:
E então?
Então o melhor que já encontrei até agora para poder ter estes serviços variando de acordo com ambiente e não precisar saber de nada disso no código que usa o serviço é passar esta responsabilidade para uma outra classe. Esta classe vai gerenciar esse processo de instanciar e entregar os "serviços" de acordo com o ambiente que estamos. Essa classe geralmente é chamada de serviceContainer e já existem algumas implementações interessantes para ela em php, como por exemplo esta:
http://components.symfony-project.org/dependency-injection/trunk/book/03-Service-Container
Apesar de ser muito boa esta implementação, para um projeto que fizemos agora eu preferi escrever algo mais simples e que funcionou tão bem quanto, inclusive com algum ganho de performance, economizando em torno de 1m a cada requisição.
Porque o nome ServiceContainer ?
Porque a classe é um container que contém serviços. Por serviços podemos entender os objetos que implementam serviços como o de palavras chave do nosso exemplo, ou serviços de envio de email, ou até mesmo um repositório de objetos que se responsabiliza por cuidar da persistência de um determinado objeto.
Assim no nosso código podemos ter algo como:
O código fica um pouco mais extenso, mas continua claro. Estou pegando o serviço responsável por processar 'keywords'. Não sei qual serviço ele é, mas tudo bem, polimorfismo é isso aí.
Desta maneira apenas o container centraliza a responsabilidade de definir em qual ambiente estamos. Também ele vai instanciar o objeto de acordo com o ambiente. Um outro benefício que ganhamos disso é que se precisarmos trocar serviço que usamos poderemos intanciar ele e configurar também de outra maneira que não seja a que usavamos antes.
O SimpleServiceContainer
Com base na mesma interface do container do symfony, eu criei esse aí mais simplezinho:
Por padrão ele usa as definições do SimpleServiceContainer_all. Esse eu tenho que criar obrigatóriamente. Depois eu poso extender dele de acordo com o ambiente que eu quiser. Por exemplo, se eu chamo o ambiente de desenvolvimento de test eu posso criar as minhas classe da seguinte maneira:
Assim desta maneira, quando eu crio o meu serviço com:
Eu vou receber o mock para o ambiente de testes e o normal para quando eu não estiver rodando os meus testes. Pode parecer simples, mas faz toda a diferença.
Veja:
Uns dos pontos principais para o teste de software é:
- testar algo coeso, não atrapalhando um teste com funcionalidades de outras bibliotecas
- ser rápido
Não vou entrar de jeito nenhum na discussão do que é mock e o que é um stub. Vou melhorar este termo e traduzir. Digamos que podemos usar um "orêia". Por exemplo, se temos alguma parte do software que acessa um serviço externo, provavelmente isso não vai acontecer em menos de 0,1 segundo e isso é MUITO demorado para um teste. Você não acha? Imagine que ao final de um projeto pequeno para médio você terá em torno de 1000 testes.
Bom, então como fazemos para o nosso teste não demorar tanto? Usamos um mock, quer dizer stub, ou melhor: um "orêia". O nosso orêia vai ser usado no lugar do serviço original e fingir que executa o serviço. Ele pode até retornar algo estático ou que esteja em memória para simular o componente que ele está substituindo.
Por exemplo, digamos que no método inserirTexto() usamos o serviço do yahoo de extrair palavras chaves. Para isso usamos uma classe nossa onde encapsulamos a chamada a esse serviço: a classe PalavraChaveExtractorTabajaras. então teremos algo assim:
function inserirTexto($texto) {
$this->palavrasChaves = PalavraChaveExtractorTabajaras::extrair($texto);
$this->gravarNoBanco();
}
Beleza, então esse método extrai as palavras chaves e grava tudo no banco. Hum... e agora como vamos fazer para chamar esse método de um teste? Imaginem que temos o seguinte teste:function testDeveInserirTextoNoBanco() {
$texto = $this->getTextoFixture();
$conteudo = FabricaDoConteudo::criaUmNovoAiPorFavor();
$conteudo->inserirTexto($texto);
$conteudoNoBanco = RepositorioDoConteudo::lerPeloTexto($texto);
$this->assertTrue((boolean)$conteudoNoBanco,"O conteúdo deve ser gravado no banco.");
}
Mas como vamos fazer no nosso método inserirTexto para poder dizer que queremos usar o ôreia (Mock)?
Temos que ter um jeito de fazer com que o sistema use o "PalavraChaveExtractorTabajaras" em um ambiente de produção e use o mock nos testes. Já sei, que tal...
if($ambiente == 'test') {
$this->palavrasChaves = PalavraChaveExtractorTabajarasMock::extrair($texto);
}
else {
$this->palavrasChaves = PalavraChaveExtractorTabajaras::extrair($texto);
}
Funcionar funciona, mas ainda não é algo muito bonito. Se eu quiser colocar outro ambiente vou ter que adicionar mais um if. Além disso, neste meu método, o ideal é que eu escreva o código de maneira fluente sem ter que me preocupar com testes. Já pensou ter que colocar estes ifs em todo canto. Vai ficar muito sujo.Bom, uma outra alternativa é criar um builder para o extractor assim:
class PalavraChaveExtractorTabajaras {
static function build() {
// retorna de acordo com o ambiente
}
Eu teria que alterar a declaração do método "extrair", de estática para dinâmica, mas funciona melhorzinho, mas ainda assim eu acrescento código a minha classe que não tem a ver com ela. A minha classe não deve saber em que ambiente eu estou. Isso deve ficar para as classes das camadas mais altas, da aplicação ou do framework que eu estiver usando.E então?
Então o melhor que já encontrei até agora para poder ter estes serviços variando de acordo com ambiente e não precisar saber de nada disso no código que usa o serviço é passar esta responsabilidade para uma outra classe. Esta classe vai gerenciar esse processo de instanciar e entregar os "serviços" de acordo com o ambiente que estamos. Essa classe geralmente é chamada de serviceContainer e já existem algumas implementações interessantes para ela em php, como por exemplo esta:
http://components.symfony-project.org/dependency-injection/trunk/book/03-Service-Container
Apesar de ser muito boa esta implementação, para um projeto que fizemos agora eu preferi escrever algo mais simples e que funcionou tão bem quanto, inclusive com algum ganho de performance, economizando em torno de 1m a cada requisição.
Porque o nome ServiceContainer ?
Porque a classe é um container que contém serviços. Por serviços podemos entender os objetos que implementam serviços como o de palavras chave do nosso exemplo, ou serviços de envio de email, ou até mesmo um repositório de objetos que se responsabiliza por cuidar da persistência de um determinado objeto.
Assim no nosso código podemos ter algo como:
function inserirTexto($texto) {
$extrator = SimpleServiceContainer::getInstance()->getService('keywords');
$this->palavrasChaves = $extrator->extrair($texto);
$this->gravarNoBanco();
}O código fica um pouco mais extenso, mas continua claro. Estou pegando o serviço responsável por processar 'keywords'. Não sei qual serviço ele é, mas tudo bem, polimorfismo é isso aí.
Desta maneira apenas o container centraliza a responsabilidade de definir em qual ambiente estamos. Também ele vai instanciar o objeto de acordo com o ambiente. Um outro benefício que ganhamos disso é que se precisarmos trocar serviço que usamos poderemos intanciar ele e configurar também de outra maneira que não seja a que usavamos antes.
O SimpleServiceContainer
Com base na mesma interface do container do symfony, eu criei esse aí mais simplezinho:
class SimpleServiceContainer {
public $parameters;
static $containers = array();
protected $services = array();
protected $env;
/**
* @return SimpleServiceContainer
*/
static function getContainer($env) {
if(!array_key_exists($env,self::$containers)) {
$class = 'SimpleServiceContainer_'.$env;
if(!class_exists($class)) {
$class = 'SimpleServiceContainer_all';
}
self::$containers[$env] = new $class($env);
}
return self::$containers[$env];
}
static function getInstance() {
return self::getContainer(ConfigSystem::get('environment'))
}
function init() {}
protected function __construct($env) {
$this->env = $env;
$this->init();
}
/**
* @return SimpleServiceContainer
*/
function setParameter($name,$value) {
$this->parameters[$name] = $value;
return $this;
}
function getParameter($name) {
return $this->parameters[$name];
}
function getService($serviceName) {
if(!isset($this->services[$serviceName])) {
$this->createService($serviceName);
}
return $this->services[$serviceName];
}
private function createService($serviceName) {
$methodName = 'createService_'.$serviceName;
if(!method_exists($this, $methodName)) {
throw new ServiceBuilderNotConfiguredException(
'this container does not have a builder for the service called "'
.$serviceName."', the method should be called ".$methodName
);
}
$this->services[$serviceName] = $this->$methodName();
}
public function getInstantiatedServiceNames() {
return array_keys($this->services);
}
}
Por padrão ele usa as definições do SimpleServiceContainer_all. Esse eu tenho que criar obrigatóriamente. Depois eu poso extender dele de acordo com o ambiente que eu quiser. Por exemplo, se eu chamo o ambiente de desenvolvimento de test eu posso criar as minhas classe da seguinte maneira:
class SimpleServiceContainer_all extends SimpleServiceContainer {
protected function createService_keywords() {
return PalavraChaveExtractorTabajaras::build();
}
}
class SimpleServiceContainer_test extends SimpleServiceContainer_all {
protected function createService_keywords() {
return PalavraChaveExtractorTabajarasMock::build();
}
}
Assim desta maneira, quando eu crio o meu serviço com:
$extrator = SimpleServiceContainer::getInstance()->getService('keywords');
Eu vou receber o mock para o ambiente de testes e o normal para quando eu não estiver rodando os meus testes. Pode parecer simples, mas faz toda a diferença.
Veja:
- No meu código eu não digo como instanciar o serviço, pois o container funciona como uma factory. Assim qualquer outro componente que tenha a mesma interface pode ser substituido por ambiente eu até em um refactoring futuro sem impacto nas camadas que o usam.
- Isso diminui o acoplamento das classes que usam o serviço com o serviço em sí.
- Quando eu rodo meus testes o sistema automáticamente instancia o serviço de acordo com o ambiente de maneira transparente e me manda um componente que eu posso usar só sabendo a sua interface.
- Isso permite eu usar mocks transparentemente nos testes e também em outros ambientes e até substituí-los futuramente por outras classes quando estas forem desenvolvidas.
- Eu gosto desta implementação pois também instancio os objetos em puro php, sem a necessidade de um xml ou algo assim.
- Os objetos são instanciados apenas quando necessito deles.
- Os objetos dos serviços são por padrão instanciados uma vez só e usados em diferentes partes do sistema. Claro que isso pode não ser o comportamento desejado, mas pela simplicidade da implementação é fácil trocar este comportamento.
Marcadores:
arquitetura para testes,
classe,
injeção de dependência,
php,
service container,
testes
Assinar:
Postagens (Atom)