Sabe o programador ctrl+c/ctrl+v? Não seja ele!

Você não consegue sentir o cheiro desse code smell de longe? Acha que meta programação e abstração de lógica são coisas de outro mundo, ou que esse tipo de construção é totalmente aceitável? Nesse post vamos discutir algumas técnicas de refatoração indispensáveis para todos os desenvolvedores.

“É só copiar, colar e pronto!”

switch($url) {
	case 'home':
		include 'home.php';
		break;
	case 'parceiros':
		include 'parceiros.php';
		break;
	case 'promocoes':
		include 'promocoes.php';
		break;
	case 'quem_somos':
		include 'quem_somos.php';
		break;
	case 'servicos':
		include 'servicos.php';
		break;
	case 'produtos':
		include 'produtos.php';
		break;
	case 'sobre':
		include 'sobre.php';
		break;
	// ...
	// 10 rotas depois....
	// ...
	default:
		include 'home.php';
}

O caso acima é real, e garanto que todo mundo já viu algo assim pelo menos uma vez na vida. Evitar esse tipo de construção é fácil. Podemos assumir que a variável $url corresponde a algum tipo de rota na aplicação, onde somente algumas são válidas e todas as outras devem ser redirecionas a home. Nesse caso, é clara a relação:

 ƒ(“foo”) -> disponibilizar código de “foo.langext”

A melhor abordagem da solução pode variar dependendo da linguagem utilizada. Podemos dividir em dois paradigmas: EAFP e LBYL.

Easier to Ask for Forgiveness than Permission (é mais fácil pedir desculpas do que permissão) em programação significa assumir a validade da ação a ser executada e tratar a exceção se essa suposição se provar falsa. A maior característica desse estilo de solução é a presença de um ou mais blocos de tratamento de exceção, try/except(ou catch). Um exemplo de código escrito em Python para uma refatoração desse código seria:

from importlib import import_module

def custom_importer(url):
    try:
        module = 'validpath.' + url
        import_module(module)
    except ImportError:
        import_module('home')

Obs.: Se você não conhece o módulo importlib, vale a pena dar uma olhada aqui.


A abordagem em Look Before You Leap (olhe antes de pular) é diferente. Contamos sempre com o pior dos casos. Antes de executar uma ação que pode resultar em um erro, devemos testar se todas as condições são atendidas para o sucesso. Essa abordagem é caracterizada pela presença de condicionais, if.

Para este caso específico, uma solução genérica em PHP precisa utilizar include ou require, onde ambos, respectivamente, emitem warning e fatal error. Como não queremos nenhum dos dois, precisamos verificar primeiro se as pré-condições para inclusão do arquivo são atendidas. Ou seja, se o arquivo “.php” existe. O mesmo código, de forma mais genérica se transformaria em algo como:

function urlResolver(string $url){
    $filePath = 'pathForValidFiles/'. $url . '.php';

    if (file_exists($filePath))
        include $filePath;
    else
        include 'home.php';
}

A pessoa que não faz idéia do que é Reflection

switch ($_GET['page_to_load']) {
	case 'home':
		$controller = new HomeController();
		break;
	case 'about':
		$controller = new AboutController();
		break;
	case 'products':
		$controller = new ProductsController();
		break;
	// ...
	// ... 10 controllers depois...
	// ...
	default:
		throw new Exception('There is no controller with name ' . $_GET['page_to_load']);
		break;
	return $controller;
}

Observando o código acima, podemos tirar a conclusão de que se propõe a fazer é: Dada uma determinada string s, a função deve instanciar e retornar um novo objeto do tipo SController ou levantar uma exceção caso não exista. Ou seja:

ƒ(“foo”) -> new FooController

O caso aqui é que, precisamos criar uma instância de classe que só saberemos qual é em tempo de execução. Utilizaremos a técnica de Reflection para inspecionar e manipular o comportamento do nosso programa em tempo de execução.

def get_controller(page: str):
    cls_name = page.title() + 'Controller'
    try:
        controller = globals()[cls_name]
        # Valide seu controller
        return controller()
    except KeyError as e:
        raise InvalidControllerException('There is no controller with name ' + cls_name, e)

Na linha 4, podemos observar a função globals. Em python, essa função retorna um dicionário (hash table) correspondente a tabela de símbolos do escopo global atual. Sendo útil para o nosso caso. Assim, se FooController for um tipo válido e disponível no escopo, ele será uma chave neste dicionário e seu valor associado, uma instância de type.


Obs.1: Em python, tudo é um objeto. Módulos, classes, funções anônimas, métodos, números, strings… e o que mais você possa pensar. Ou seja, tudo é uma instância de object ou de algum tipo que herda de object.

Obs.2: Se você precisa de inicializações mais complexas e específicas para seus objetos, leia sobre Factory.

Obs.3: Reflection nem sempre é a melhor solução e, apesar de resolver um problema, pode muito raramente te trazer outros. Leia mais sobre aqui.


function getController(string $page){
    $cls_name = ucwords($page) . 'Controller';

    if (!class_exists($cls_name))
        throw new Exception('There is no controller with name ' . $cls_name);

    return new $cls_name();
}

Como já vimos antes, certas soluções em PHP dependem de uma abordagem LBYL. Esse é o nosso caso na linha 4, onde precisamos utilizar a função class_exists para que classes inexistentes não resultem em um fatal error.

“Mas o meu caso é diferente”

Neste ponto, já sabemos como abstrair a criação dinâmica de classes através de uma string. Com o mesmo conceito nós podemos ir mais longe ainda, abstraindo também a chamada de métodos.

Ainda nos tempos atuais, desenvolvedores criam seus próprios frameworks para executar tarefas que milhares outros frameworks de alto nível, bem estabelecidos e testados já fazem. Eu não recomendo isso para fins que não sejam didáticos. No entanto, casos como o do código abaixo ainda são comuns com pessoas que constroem seus próprios ORM (Object-Relational Mapping).

// ...
public function c_gravar_usuario($novo_usuario){
	$usuario = new UsuarioBean();

	$usuario->setUsu_nome($novo_usuario["usu_nome"]);
	$usuario->setUsu_email($novo_usuario["usu_email"]);
	$usuario->setUsu_numerocel($novo_usuario["usu_numerocel"]);
	$usuario->setUsu_liberado($novo_usuario["usu_liberado"]);
	$usuario->setUsu_desconto($novo_usuario["usu_desconto"]);
	$usuario->setUsu_comissao($novo_usuario["usu_comissao"]);
	$usuario->setUsu_usuario($novo_usuario["usu_usuario"]);
	$usuario->setUsu_senha($novo_usuario["usu_senha"]);
	// ...
}
// ...

Seria uma abordagem genérica aproveitar a relação da chave do array associativo com o nome do setter da classe model, e da facilidade que a linguagem tem em realizar inspeção em tempo de execução para escrever um método nessas linhas:

function gravar(array $new_object){
    // ...
    $model = new FooBean();
    foreach ($new_object as $key => $value) {
        $method_name = 'set' . ucwords($key);
        $model->$method_name($value);
    }
    // ...
}

Conclusão

Se identificou com algum caso? Não se preocupe. Todo mundo já não soube alguma dessas coisas em algum ponto de suas carreiras e, afinal de contas, todos nós sempre temos algo novo para aprender.

Tem algum desafio ou código que merece uma refatoração? E um post ou qualquer outra coisa que ache bacana de compartilhar? Entre em contato! Como sempre, espero ter ajudado. Seu retorno é de suma importância.


Anúncios

3 comentários sobre “Sabe o programador ctrl+c/ctrl+v? Não seja ele!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

w

Conectando a %s