Até onde posso empurrar a refatoração sem alterar o comportamento externo?


27

De acordo com Martin Fowler , a refatoração de código é (ênfase minha):

A refatoração é uma técnica disciplinada para reestruturar um corpo de código existente, alterando sua estrutura interna sem alterar seu comportamento externo . Seu coração é uma série de pequenos comportamentos que preservam transformações. Cada transformação (chamada de 'refatoração') faz pouco, mas uma sequência de transformações pode produzir uma reestruturação significativa. Como cada refatoração é pequena, é menos provável que dê errado. O sistema também é mantido em pleno funcionamento após cada pequena refatoração, reduzindo as chances de um sistema ser seriamente quebrado durante a reestruturação.

O que é "comportamento externo" neste contexto? Por exemplo, se eu aplicar a refatoração do método move e mover algum método para outra classe, parece que eu altero o comportamento externo, não é?

Então, estou interessado em descobrir em que ponto uma mudança deixa de ser um refator e se torna algo mais. O termo "refatoração" pode ser mal utilizado para alterações maiores: existe uma palavra diferente para isso?

Atualizar. Muitas respostas interessantes sobre a interface, mas a refatoração de métodos não mudaria a interface?


Se o comportamento existente for ruim ou incompleto, altere-o e exclua / reescreva-o. Você pode não estar re-fatorando, mas quem se importa com o nome, se o sistema (certo?) Se tornar melhor como resultado disso.
Job

2
Seu supervisor pode se importar se você recebeu permissão para refatorar e você reescreveu.
Jeffo

2
O limite da refatoração são testes de unidade. Se você tem uma especificação desenhada por eles, qualquer coisa que você altere que não interrompa os testes é refatoração?
George Silva


Se você quiser saber mais, este tópico também é um campo de pesquisa ativo. Há muitas publicações científicas sobre esse tópico, por exemplo, informatik.uni-trier.de/~ley/db/indices/a-tree/s/…
Skarab

Respostas:


25

"Externo" neste contexto significa "observável para os usuários". Os usuários podem ser humanos no caso de um aplicativo ou outros programas no caso de uma API pública.

Portanto, se você mover o método M da classe A para a classe B, e as duas classes estiverem profundamente dentro de um aplicativo, e nenhum usuário puder observar nenhuma alteração no comportamento do aplicativo devido à alteração, poderá chamá-lo de refatoração.

Se, OTOH, algum outro subsistema / componente de nível superior alterar seu comportamento ou interrupções devido à alteração, isso é realmente (geralmente) observável para os usuários (ou pelo menos para os administradores do sistema verificando logs). Ou, se suas classes fizerem parte de uma API pública, pode haver um código de terceiros, que depende de M fazer parte da classe A, e não B. Portanto, nenhum desses casos é refatorado no sentido estrito.

há uma tendência de chamar qualquer retrabalho de código como refatoração, o que eu acho incorreto.

De fato, é uma conseqüência triste, mas esperada, de a refatoração se tornar moda. Os desenvolvedores realizam o retrabalho do código de maneira ad hoc há muito tempo e é certamente mais fácil aprender uma nova palavra da moda do que analisar e mudar hábitos arraigados.

Então, qual é a palavra certa para retrabalhos que alteram o comportamento externo?

Eu chamaria isso de redesenho .

Atualizar

Muitas respostas interessantes sobre a interface, mas a refatoração de métodos não mudaria a interface?

Sobre o que? As classes específicas, sim. Mas essas classes são diretamente visíveis para o mundo exterior de alguma forma? Caso contrário - porque eles estão dentro do seu programa e não fazem parte da interface externa (API / GUI) do programa - nenhuma alteração feita lá é observável por terceiros (a menos que a alteração quebre alguma coisa, é claro).

Sinto que há uma questão mais profunda além disso: existe uma classe específica como entidade independente por si só? Na maioria dos casos, a resposta é não : a classe existe apenas como parte de um componente maior, um ecossistema de classes e objetos, sem o qual não pode ser instanciado e / ou é inutilizável. Esse ecossistema não inclui apenas suas dependências (diretas / indiretas), mas também outras classes / objetos que dependem dele. Isso ocorre porque sem essas classes de nível superior, a responsabilidade associada à nossa classe pode ser sem sentido / inútil para os usuários do sistema.

Por exemplo, em nosso projeto que trata de aluguel de carros, existe uma Chargeclasse. Essa classe não tem utilidade para os usuários do sistema por si só, porque os agentes e clientes da estação de aluguel não podem fazer muito com uma cobrança individual: eles lidam com os contratos de contrato de locação como um todo (que incluem vários tipos diferentes de cobrança) . Os usuários estão mais interessados ​​na soma total desses encargos, que devem pagar no final; o agente está interessado nas diferentes opções de contrato, na duração do aluguel, no grupo do veículo, no pacote de seguro, nos itens extras etc. etc. selecionados, que (por meio de regras comerciais sofisticadas) controlam quais cobranças estão presentes e como o pagamento final é calculado fora destes. E os representantes dos países / analistas de negócios se preocupam com as regras comerciais específicas, suas sinergias e efeitos (sobre a receita da empresa, etc.).

Recentemente refatorei essa classe, renomeando a maioria de seus campos e métodos (para seguir a convenção de nomenclatura Java padrão, que foi totalmente negligenciada por nossos antecessores). Eu também pretendo mais refatorações para substituir Stringe charcampos com mais apropriado enume booleantipos. Tudo isso certamente mudará a interface da classe, mas (se eu fizer meu trabalho corretamente) nada disso ficará visível para os usuários do nosso aplicativo. Nenhum deles se importa com a forma como as cobranças individuais são representadas, mesmo sabendo com certeza o conceito de cobrança.. Eu poderia ter selecionado como exemplo uma centena de outras classes que não representam nenhum conceito de domínio, sendo tão conceitualmente invisível para os usuários finais, mas achei mais interessante escolher um exemplo em que haja pelo menos alguma visibilidade no nível do conceito. Isso mostra muito bem que as interfaces de classe são apenas representações de conceitos de domínio (na melhor das hipóteses), não a coisa real *. A representação pode ser alterada sem afetar o conceito. E os usuários apenas têm e entendem o conceito; é nossa tarefa fazer o mapeamento entre conceito e representação.

* E pode-se facilmente acrescentar que o modelo de domínio, que nossa classe representa, é apenas uma representação aproximada de alguma "coisa real" ...


3
nitpicking, eu diria 'mudança de design' e não redesenho. redesenhar parece muito substancial.
user606723

4
interessante - no seu exemplo classe A é 'um corpo existente de código", e se o método M é público, então o comportamento externo de A está sendo alterado Então você provavelmente poderia dizer que a classe A é redesenhado, enquanto que o sistema como um todo está sendo reformulado..
saus

Eu gosto de observável para os usuários. É por isso que eu não diria que a quebra de testes de unidade é um sinal, mas sim testes de ponta a ponta ou de integração seriam um sinal.
Andy Wiesendanger

8

Externo significa simplesmente interface em seu verdadeiro significado lingual. Considere uma vaca para este exemplo. Enquanto você alimenta alguns vegetais e obtém leite como valor de retorno, não se importa com o funcionamento de seus órgãos internos. Agora, se Deus mudar os órgãos internos das vacas, para que seu sangue fique azul, desde que o ponto de entrada e saída (boca e leite) não mude, isso pode ser considerado refatoração.


1
Não acho que seja uma boa resposta. A refatoração pode implicar alterações na API. Mas isso não deve afetar o usuário final nem a maneira como os arquivos / entrada são lidos / gerados.
rds 16/08

3

Para mim, a refatoração foi mais produtiva / confortável quando os limites foram definidos por testes e / ou por especificação formal.

  • Esses limites são suficientemente rígidos para me fazer sentir seguro, sabendo que, se eu cruzar ocasionalmente, ele será detectado em breve, para que não precise reverter muitas alterações para recuperar. Por outro lado, eles oferecem margem de manobra suficiente para melhorar o código sem se preocupar em alterar o comportamento irrelevante.

  • O que eu mais gosto é que esses tipos de limites são adaptáveis, por assim dizer. Quero dizer, 1) Faço a alteração e verifico se ela está de acordo com as especificações / testes. Em seguida, 2) é passado para o controle de qualidade ou teste do usuário - observe aqui, ainda pode falhar porque algo está faltando nas especificações / testes. OK, se 3a) os testes passarem, tudo bem. Caso contrário, se 3b) o teste falhar, 4) reverter a alteração e 5) adicionar testes ou esclarecer as especificações para que da próxima vez esse erro não se repita. Observe que, não importa se o teste é aprovado ou reprovado, eu ganho algo - qualquer um dos códigos / testes / especificações é aprimorado - meus esforços não se transformam em desperdício total.

Quanto aos limites de outros tipos - até agora, não tive muita sorte.

"Observável para os usuários" é uma aposta segura se alguém tem uma disciplina para segui-la - o que, para mim, sempre envolveu muito esforço na análise de testes existentes / na criação de novos testes - talvez muito esforço. Outra coisa que eu não gosto nessa abordagem é que segui-la cegamente pode se tornar muito restritiva. - Essa alteração é proibida, pois o carregamento dos dados levará 3 segundos em vez de 2. - Uhm, que tal verificar com os usuários / especialista em UX se isso é relevante ou não? - De jeito nenhum, qualquer mudança no comportamento observável é proibida, ponto final. Seguro? pode apostar! produtivo? Na verdade não.

Outro que tentei é manter a lógica do código (da maneira que eu entendo ao ler). Exceto pelas mudanças mais elementares (e geralmente não muito frutíferas), essa sempre foi uma lata de vermes ... ou devo dizer uma lata de insetos? Quero dizer erros de regressão. É muito fácil quebrar algo importante ao trabalhar com código de espaguete.


3

O livro Refatoração é bastante forte na mensagem de que você pode executar a refatoração quando tiver cobertura de teste de unidade .

Assim, você poderia dizer que , desde que não esteja quebrando nenhum dos seus testes de unidade, estará refatorando . Quando você quebra os testes, não está mais refatorando.

Mas: que tal refatorações simples, como mudar o nome das classes ou membros? Eles não quebram os testes?

Sim, eles fazem e, em cada caso, você precisará considerar se essa interrupção é significativa. Se o seu SUT é um API / SDK público, uma renomeação é, de fato, uma mudança de última hora. Caso contrário, provavelmente está tudo bem.

No entanto, considere que, com frequência, os testes são interrompidos não porque você mudou o comportamento com o qual realmente se importa, mas porque são testes frágeis .


3

A melhor maneira de definir "comportamento externo", nesse contexto, pode ser "casos de teste".

Se você refatorar o código e ele continuar passando nos casos de teste (definidos antes da refatoração), a refatoração não alterou o comportamento externo. Se um ou mais casos de teste falhar, então você ter alterado o comportamento externo.

Pelo menos, esse é o meu entendimento dos vários livros publicados sobre o tema (por exemplo, os de Fowler).


2

O limite seria a linha que indica entre quem desenvolve, mantém, apóia o projeto e aqueles que são seus usuários, além de apoiadores, mantenedores, desenvolvedores. Assim, para o mundo externo, o comportamento parece o mesmo, enquanto as estruturas internas por trás do comportamento mudaram.

Portanto, não há problema em mover funções entre classes, desde que não sejam as que os usuários veem.

Desde que o retrabalho do código não altere comportamentos externos, adicione novas funções ou remova funções existentes, acho que não há problema em chamar o retrabalho de refatoração.


Discordo. Se você substituir todo o código de acesso a dados por nHibernate, ele não altera o comportamento externo, mas não segue as "técnicas disciplinadas" de Fowler. Isso seria reengenharia e chamá-lo de refatoração oculta o fator de risco envolvido.
Pd 14/08

1

Com todo o respeito, devemos lembrar que os usuários de uma classe não são os usuários finais dos aplicativos criados com a classe, mas as classes implementadas utilizando - chamando ou herdando - a classe que está sendo refatorada .

Quando você diz que "o comportamento externo não deve mudar", você quer dizer que, no que diz respeito aos usuários, a classe se comporta exatamente como antes. Pode ser que a implementação original (não refatorada) tenha uma única classe, e a nova implementação (refatorada) tenha uma ou mais superclasses nas quais a classe é construída, mas os usuários nunca veem o interior (a implementação) que eles apenas veja a interface.

Portanto, se uma classe possui um método chamado "doSomethingAmazing", não importa para o usuário se isso é implementado pela classe à qual se refere, ou por uma superclasse na qual essa classe é construída. O que importa para o usuário é que o novo "doSomethingAmazing" (refatorado) tem o mesmo resultado que o antigo "doSomethingAmazing" (não refatorado).

No entanto, em muitos casos, o que é chamado de refatoração não é refatoração verdadeira, mas talvez uma reimplação feita para facilitar a modificação do código e adicionar um novo recurso. Portanto, neste caso posterior de refração (pseudo), o novo código (refatorado) realmente faz algo diferente, ou talvez algo mais que o antigo.


E se um formulário do Windows aparecer para exibir uma caixa de diálogo "Tem certeza de que deseja pressionar o botão OK?" e decidi removê-lo porque ele adianta muito pouco e incomoda os usuários. Depois refiz o código, redesenhou-o, alterei-o, corrigi-o, outros?
Job

@job: você mudou o programa para atender a novas especificações.
jmoreno

IMHO você pode estar misturando diferentes critérios aqui. Reescrever o código do zero não é, de fato, refatoração, mas isso ocorre independentemente de ter ou não alterado o comportamento externo. Além disso, se a alteração de uma interface de classe não estava sendo refatorada, como é que o Move Method et al. existe no catálogo de refatorações ?
Péter Török

@ Péter Török - Depende inteiramente do que você quer dizer com "alterando uma interface de classe", porque em uma linguagem OOP que implementa herança, a interface de uma classe inclui não apenas o que é implementado pela própria classe por todos os seus ancestrais. Alterar uma interface de classe significa remover / adicionar um método à interface (ou alterar a assinatura de um método - ou seja, o número e o tipo de parâmetros que são passados). Refatorar significa quem está respondendo ao método, à classe ou a uma superclasse.
Zeke Hansell

IMHO - toda essa pergunta pode ser esotérica demais para ter algum valor útil para os programadores.
Zeke Hansell

1

Por "comportamento externo", ele está falando principalmente sobre a interface pública, mas isso também abrange produtos / artefatos do sistema. (ou seja, você possui um método que gera um arquivo, alterar o formato do arquivo alteraria o comportamento externo)

e: Eu consideraria o "método de movimentação" uma mudança no comportamento externo. Tenha em mente aqui que Fowler está falando sobre bases de código existentes que foram lançadas na natureza. Dependendo da sua situação, você poderá verificar se suas alterações não quebram clientes externos e prosseguem no seu caminho.

e2: "Então, qual é a palavra certa para retrabalhos que alteram o comportamento externo?" - Refatoração de API, Breaking Change, etc ... ainda está refatorando, simplesmente não segue as práticas recomendadas para refatorar uma interface pública que já está em estado selvagem com os clientes.


Mas se ele está falando sobre uma interface pública, o que dizer da refatoração "Move method"?
SiberianGuy

@Idsa Editou minha resposta para responder à sua pergunta.

@kekela, mas ainda não está claro para mim onde essa coisa de "refatoração" termina #
SiberianGuy

@idsa De acordo com a definição que você postou, deixa de ser uma refatoração no minuto em que você altera uma interface pública. (movendo um método público de uma classe para outra seria um exemplo disso)

0

"Método de movimentação" é uma técnica de refatoração, não uma refatoração por si só. Uma refatoração é o processo de aplicação de várias técnicas de refatoração nas classes. Lá, quando você diz, apliquei "método de movimentação" a uma classe, na verdade você não quer dizer "refatoração (a classe)", na verdade você quer dizer "apliquei uma técnica de refatoração nessa classe". A refatoração, no sentido mais puro, é aplicada ao design, ou mais específico, a alguma parte do design do aplicativo que pode ser vista como uma caixa preta.

Você poderia dizer que "refatoração" usada no contexto de classes significa "técnica de refatoração", portanto "método de movimentação" não quebra a definição de refatoração do processo. Na mesma página, "refatorar" no contexto do design, não quebra os recursos existentes no código, apenas "quebra" o design (que é seu objetivo de qualquer maneira).

Em conclusão, "o limite", mencionado na pergunta, será ultrapassado se você confundir (mix: D) refatorar a técnica com refatorar o processo.

PS: Boa pergunta, aliás.


Lê-lo várias vezes, mas ainda não entendo
SiberianGuy

que parte você não entendeu? (há refatoração do processo ao qual sua definição se aplica ou refatoração da técnica, no seu exemplo, método de movimentação, ao qual a definição não se aplica; portanto, o método de movimentação não quebra a definição de refatoração da técnica processo ou não cruza seus limites, sejam eles quais forem). Estou dizendo que a preocupação que você tem não deve existir no seu exemplo. o limite da refatoração não é algo confuso. você está apenas aplicando uma definição de algo a outra coisa.
Belun

0

se você fala sobre números de fatoração, está descrevendo o grupo de números inteiros que, quando multiplicados juntos, igual ao número inicial. Se tomarmos essa definição para fatorar e aplicá-la ao termo refatorador de programação, a refatoração estaria dividindo um programa nas menores unidades lógicas, de modo que, quando executadas como um programa, produzam a mesma saída (com a mesma entrada) ) como o programa original.


0

Os limites de uma refatoração são específicos para uma determinada refatoração. Simplesmente não pode haver uma resposta e abrange tudo. Uma razão é o termo refatoração ser inespecífico.

Você pode refatorar:

  • Código fonte
  • Arquitetura do Programa
  • Design da interface do usuário / interação do usuário

e tenho certeza de várias outras coisas.

Talvez refator possa ser definido como

"uma ação ou conjunto de ações que diminuem a entropia em um sistema específico"


0

Definitivamente, existem alguns limites de quão longe você pode refatorar. Veja: Quando dois algoritmos são iguais?

As pessoas geralmente consideram os algoritmos mais abstratos do que os programas que os implementam. A maneira natural de formalizar essa idéia é que algoritmos são classes de equivalência de programas com relação a uma relação de equivalência adequada. Argumentamos que não existe essa relação de equivalência.

Portanto, não vá muito longe, ou você não terá nenhuma confiança no resultado. Por outro lado, a experiência determina que você pode substituir um algoritmo por outro e obter as mesmas respostas, às vezes mais rapidamente. Essa é a beleza disso, não é?


0

Você pode pensar no significado "externo"

Externo ao stand-up diário.

Portanto, qualquer alteração no sistema que não afeta nenhum porco pode ser considerada como refatoração.

Alterar uma interface de classe não é um problema, se a classe for usada apenas por um único sistema criado e mantido por sua equipe. No entanto, se a classe é uma classe pública na estrutura .net que é usada por todos os programadores .net, é uma questão muito diferente.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.