Devo refatorar funções grandes que consistem principalmente em um regex? [fechadas]

15

Acabei de escrever uma função que abrange aproximadamente 100 linhas. Ao ouvir isso, você provavelmente está tentado a me falar sobre responsabilidades únicas e me incentiva a refatorar. Esse também é meu instinto, mas aqui está o problema: a função faz uma coisa. Ele executa uma manipulação complexa de strings, e o corpo da função consiste principalmente em um regex detalhado, dividido em muitas linhas documentadas. Se eu dividir o regex em várias funções, sinto que realmente perderia a legibilidade, pois estou trocando de idioma efetivamente e não poderei tirar proveito de alguns recursos oferecidos pelo regex . Aqui está agora a minha pergunta:

Quando se trata de manipulação de strings com expressões regulares, os grandes corpos funcionais ainda são um antipadrão? Parece que os grupos de captura nomeados têm um propósito muito semelhante às funções. A propósito, eu tenho testes para cada fluxo no regex.

DudeOnRock
fonte
3
Não acho que haja algo errado com sua função, considerando que grande parte dela é documentação . No entanto, pode haver um problema de manutenção com o uso de uma expressão regular grande.
Joel Cornett
2
Você tem certeza de que um regex gigante é a melhor solução para o seu problema? Você já considerou alternativas mais simples, como uma biblioteca analisadora ou substituindo um formato de arquivo personalizado por um formato padrão (XML, JSON etc.)?
Lortabac 6/03
2
Existem outras funções, usando uma versão alterada / aprimorada / simplificada desse regex? Esse seria um indicador importante de que a refatoração deve ocorrer. Se não, eu deixaria como está também. Precisando de uma manipulação complexa de cordas como essa é uma bandeira amarela por si só (bem, eu não conheço o contexto, portanto, apenas amarelo), e refatorar a função para baixo me parece mais um ritual para resgatar da culpa que alguém sente. -lo;)
Konrad Morawski
8
Como um regexp de 100 linhas pode fazer apenas uma coisa?
27616 Pieter B #:
@lortabac: A entrada é gerado utilizador texto (prosa.)
DudeOnRock

Respostas:

36

O que você encontra é a dissonância cognitiva resultante de ouvir pessoas que favorecem a adesão servil às diretrizes, sob o pretexto de "melhores práticas", em detrimento da tomada de decisão fundamentada.

Você claramente fez sua lição de casa:

  • O objetivo da função é entendido.
  • O funcionamento de sua implementação é compreendido (isto é, legível).
  • Existem testes de cobertura total da implementação.
  • Esses testes são aprovados, o que significa que você acredita que a implementação está correta.

Se algum desses pontos não fosse verdade, eu seria o primeiro da fila a dizer que sua função precisa funcionar. Portanto, há um voto para deixar o código como está.

A segunda votação vem do exame de suas opções e o que você obtém (e perde) de cada uma:

  • Refatorar. Isso aumenta a conformidade com a ideia de alguém de quanto tempo uma função deve ser e sacrifica a legibilidade.
  • Fazer nada. Isso mantém a legibilidade existente e sacrifica a conformidade com a ideia de alguém de quanto tempo uma função deve ser.

Essa decisão se resume a que você valoriza mais: legibilidade ou duração. Eu caio no campo que acredita que a duração é boa, mas a legibilidade é importante e levará o último ao longo do primeiro em qualquer dia da semana.

Conclusão: se não estiver quebrado, não conserte.

Blrfl
fonte
10
+1 em "Se não estiver quebrado, não corrija".
Giorgio
De fato. As regras de Sandy Metz ( gist.github.com/henrik/4509394 ) são legais e tudo, mas no youtube.com/watch?v=VO-NvnZfMA4#t=1379 ela fala sobre como elas surgiram e por que as pessoas estão adotando eles muito a sério.
Amadan
@ Amdan: Com o contexto extra do vídeo, o que Metz fez faz sentido. Sua recomendação para que um cliente fosse intencionalmente extremo em uma extremidade para combater o comportamento extremo na outra extremidade, como uma maneira de arrastá-lo para o meio mais razoável. O restante dessa discussão se resume ao impulso de minha resposta: raciocínio, não fé, é o caminho para determinar o melhor curso de ação.
Blrfl
19

Honestamente, sua função pode "fazer uma coisa", mas, como você mesmo declarou

Eu poderia começar a dividir o regex em várias funções,

o que significa que o seu código regular faz muitas coisas. E eu acho que poderia ser dividido em unidades menores, individualmente testáveis. No entanto, se essa é uma boa ideia, não é fácil responder (especialmente sem ver o código real). E a resposta correta pode ser "sim" ou "não", mas "ainda não, mas da próxima vez você precisará alterar algo nesse registro".

mas sinto que realmente perderia a legibilidade dessa maneira, pois estou trocando de idioma

E este é o ponto principal - você tem um pedaço de código escrito na linguagem normal . Essa linguagem não fornece bons meios de abstração em si (e não considero "grupos de captura nomeados" como um substituto para funções). Portanto, a refatoração "no idioma normal" não é realmente possível, e a junção de expressões regulares menores com o idioma do host pode não melhorar a legibilidade (pelo menos, você sente isso, mas tem dúvidas, caso contrário não teria postado a pergunta) . Então aqui está o meu conselho

  • mostre seu código para outro desenvolvedor avançado (talvez em /codereview// ) para garantir que outras pessoas pensem na legibilidade da maneira que você faz. Esteja aberto à idéia de que outras pessoas podem não encontrar um registro de 100 linhas tão legível quanto você. Às vezes, a noção de "não é facilmente quebrável em pedaços menores" pode ser superada apenas por um segundo par de olhos.

  • observe a real capacidade de evolução - a sua brilhante experiência de registro ainda parece tão boa quando novos requisitos chegam e você precisa implementá-los e testá-los? Enquanto o seu reg exp funcionar, eu não o tocaria, mas sempre que algo precisa ser mudado, reconsideraria se era realmente uma boa ideia colocar tudo nesse grande bloco - e (sério!) Repensar se dividir em pedaços menores não seriam uma opção melhor.

  • observe a capacidade de manutenção - você pode depurar efetivamente o reg exp na forma atual muito bem? Especialmente depois que você precisa alterar alguma coisa e agora seus testes dizem que algo está errado, você tem um depurador reg exp ajudando você a encontrar a causa raiz? Se a depuração for difícil, isso também seria uma ocasião para reconsiderar seu design.

Doc Brown
fonte
Eu diria que os grupos de captura nomeados (grupos de captura em geral, na verdade) são mais semelhantes às variáveis ​​finais / gravação única, ou talvez macros. Eles permitem que você faça referência a partes específicas da correspondência, a partir do objeto de correspondência retornado do processador regex ou posteriormente na própria expressão regular.
JAB
4

Às vezes, uma função mais longa que faz uma coisa é a maneira mais apropriada de lidar com uma unidade de trabalho. Você pode facilmente assumir funções muito longas quando começa a lidar com a consulta de um banco de dados (usando sua linguagem de consulta favorita). Tornar uma função (ou método) mais legível e limitá-la ao seu objetivo declarado é o que eu consideraria o resultado mais desejável de uma função.

O comprimento é um "padrão" arbitrário quando se trata do tamanho do código. Onde uma função de 100 linhas em C # pode ser considerada longa, seria pequena em algumas versões de montagem. Vi algumas consultas SQL que estavam dentro do intervalo de 200 linhas de código que retornaram um conjunto muito complicado de dados para um relatório.

Código totalmente funcional , que é tão simples como você pode razoavelmente fazê-lo é o objetivo.

Não mude apenas porque é longo.

Adam Zuckerman
fonte
3

Você sempre pode dividir a regex em sub-regexes e gradualmente compor a expressão final. Isso pode ajudar na compreensão de um padrão muito grande, principalmente se o mesmo sub-padrão for repetido várias vezes. Por exemplo em Perl;

my $start_re = qr/(?:\w+\.\w+)/;
my $middle_re = qr/(?:DOG)|(?:CAT)/;
my $end_re = qr/ => \d+/;

my $final_re = $start_re . $middle_re . $end_re;
# or: 
# my $final_re = qr/${start_re}${middle_re}${end_re}/
Rory Hunter
fonte
Eu uso a bandeira detalhada, que é ainda mais conveniente do que o que você está sugerindo.
DudeOnRock
1

Eu diria que quebra se for quebrável. do ponto de vista da manutenibilidade e talvez da resuabilidade, faz sentido quebrá-la, mas é claro que você deve considerar a natureza de sua função e como obter informações e o que ela retornará.

Lembro-me de que estava trabalhando na análise de dados fragmentados de fluxo em objetos, então o que fiz basicamente foi dividi-los em duas partes principais, uma foi a construção de uma unidade completa de String com texto codificado e na segunda parte a análise dessas unidades no dicionário de dados e organização eles (pode ser uma propriedade aleatória para um objeto diferente) e depois atualizar ou criar objetos.

Também pude dividir cada parte principal em várias funções menores e mais específicas, de modo que no final eu tinha 5 funções diferentes para fazer a coisa toda e pude reutilizar algumas das funções em lugares diferentes.

arfo
fonte
1

Uma coisa que você pode ou não considerar é escrever um pequeno analisador no idioma que você está usando, em vez de usar um regex nesse idioma. Pode ser mais fácil ler, testar e manter.

Thomas Eding
fonte
Eu já pensei nisso. A questão é que a entrada é em prosa e estou pegando pistas do contexto e da formatação. Se for possível escrever um analisador para algo assim, eu adoraria aprender mais sobre isso! Eu não consegui encontrar nada sozinho.
DudeOnRock
1
Se um regex puder analisá-lo, você poderá analisá-lo. Sua resposta me parece que você pode não ser bem versado na análise. Se for esse o caso, convém manter a regex. Ou isso ou aprenda uma nova habilidade.
9788 Thomas Eding
Eu adoraria aprender uma nova habilidade. Algum bom recurso que você pode sugerir? Também estou interessado na teoria por trás disso.
DudeOnRock
1

Regexes gigantes são uma má escolha na maioria dos casos. Na minha experiência, eles são freqüentemente usados ​​porque o desenvolvedor não está familiarizado com a análise (consulte a resposta de Thomas Eding ).

De qualquer forma, vamos supor que você queira manter uma solução baseada em regex.

Como não conheço o código real, examinarei os dois cenários possíveis:

  • O regex é simples (muitas correspondências literais e poucas alternativas)

    Nesse caso, os recursos avançados oferecidos por um único regex não são indispensáveis. Isso significa que você provavelmente se beneficiará da divisão.

  • O regex é complexo (muitas alternativas)

    Nesse caso, você não pode realisticamente ter uma cobertura completa de teste, porque provavelmente possui milhões de fluxos possíveis. Então, para testá-lo, você precisa dividi-lo.

Posso não ter imaginação, mas não consigo pensar em nenhuma situação do mundo real em que um regex de 100 linhas seja uma boa solução.

Lortabac
fonte