Quais são os argumentos contra a análise da maneira Cthulhu?


24

Foi-me atribuída a tarefa de implementar uma linguagem específica de domínio para uma ferramenta que pode se tornar bastante importante para a empresa. A linguagem é simples, mas não trivial, já permite loops aninhados, concatenação de strings, etc. e é praticamente certo que outras construções serão adicionadas à medida que o projeto avança.

Sei por experiência que escrever um lexer / analisador manualmente - a menos que a gramática seja trivial - é um processo demorado e propenso a erros. Então fiquei com duas opções: um gerador de analisador à la yacc ou uma biblioteca combinadora como o Parsec. O primeiro também foi bom, mas eu o escolhi por vários motivos e implementei a solução em uma linguagem funcional.

O resultado é espetacular aos meus olhos, o código é muito conciso, elegante e legível / fluente. Eu admito que pode parecer um pouco estranho se você nunca programou algo além de java / c #, mas isso seria verdade para qualquer coisa que não estivesse escrita em java / c #.

Em algum momento, no entanto, fui literalmente atacado por um colega de trabalho. Após uma rápida olhada na minha tela, ele declarou que o código é incompreensível e que eu não deveria reinventar a análise, mas apenas usar uma pilha e String.Split como todo mundo. Ele fez muito barulho, e eu não pude convencê-lo, em parte porque fui pego de surpresa e não tive uma explicação clara, em parte porque sua opinião era imutável (sem trocadilhos). Eu até me ofereci para explicar o idioma, mas sem sucesso.

Tenho certeza de que a discussão voltará à tona na frente do gerenciamento, por isso estou preparando alguns argumentos sólidos.

Estas são as primeiras razões que me vêm à cabeça para evitar uma solução baseada em String.Split:

  • você precisa de muitos ifs para lidar com casos especiais e as coisas rapidamente saem de controle
  • muitos índices de matriz codificados dificultam a manutenção
  • extremamente difícil lidar com coisas como uma chamada de função como argumento de método (por exemplo, add ((add a, b), c)
  • muito difícil fornecer mensagens de erro significativas em caso de erros de sintaxe (é muito provável que isso aconteça)
  • Eu sou a favor da simplicidade, clareza e evito coisas desnecessárias de criptografia inteligente, mas também acredito que é um erro simplificar todas as partes da base de código, para que até mesmo um barbante de hambúrguer possa entendê-lo. É o mesmo argumento que ouço por não usar interfaces, por não adotar separação de preocupações, copiar e colar códigos etc. Um mínimo de competência técnica e vontade de aprender são necessários para trabalhar em um projeto de software. (Não usarei esse argumento, pois provavelmente soará ofensivo, e iniciar uma guerra não ajudará ninguém)

Quais são seus argumentos favoritos contra a análise da maneira Cthulhu ? *

* é claro que se você puder me convencer de que ele está certo, eu também ficarei perfeitamente feliz


9
Parece-me que seu colega de trabalho está se oferecendo para fazer o projeto DSL para você!
GrandmasterB

23
"Eu não deveria reinventar a análise, mas apenas usar uma pilha e uma String. Divida como todo mundo faz" - caramba, esse cara deve estar feliz que a ignorância não machuque ...
Michael Borgwardt

4
Aconselhe seu colega de trabalho a não voltar a essa discussão, a menos que ele leia o Livro do Dragão inteiro e passe em um teste. Caso contrário, ele não tem o direito de discutir nada relacionado à análise.
SK-logic

4
desculpe, quem estava reinventando a análise?
Rwong

2
Eu acho que minha cabeça literalmente explodirá na próxima vez que eu ver alguém usando a palavra "literalmente" figurativamente.

Respostas:


33

A diferença crítica entre as duas abordagens é que a que ele considera a única maneira correta é imperativa e a sua é declarativa.

  • Sua abordagem declara explicitamente regras, ou seja, as regras da gramática são (quase) diretamente codificadas no seu código, e a biblioteca analisadora transforma automaticamente a entrada bruta em saída analisada, enquanto cuida do estado e de outras coisas difíceis de manusear. Seu código é gravado em uma única camada de abstração, que coincide com o domínio do problema: análise. É razoável supor a correção do parsec, o que significa que o único espaço para erro aqui é que sua definição gramatical está errada. Mas, novamente, você possui objetos de regra totalmente qualificados e eles são facilmente testados isoladamente. Também pode ser interessante notar que as bibliotecas analisadoras maduras são fornecidas com um recurso importante: relatório de erros. A recuperação de erro decente quando a análise deu errado não é trivial. Como prova, invoco o PHP parse error, unexpected T_PAAMAYIM_NEKUDOTAYIM: D

  • Sua abordagem manipula seqüências de caracteres, mantém explicitamente o estado e eleva a entrada bruta manualmente para a entrada analisada. Você precisa escrever tudo sozinho, incluindo o relatório de erros. E quando algo dá errado, você está totalmente perdido.

A ironia consiste em que a correção de um analisador escrito com sua abordagem é relativamente fácil de ser comprovada. No caso dele, é quase impossível.

Existem duas maneiras de construir um design de software: uma maneira é tornar tão simples que obviamente não há deficiências, e a outra maneira é torná-lo tão complicado que não há deficiências óbvias. O primeiro método é muito mais difícil.

CAR Hoare

Sua abordagem é a mais simples. Tudo o que impede é que ele amplie um pouco o seu horizonte. O resultado dessa abordagem será sempre complicado, não importa quão amplo seja o seu horizonte.
Para ser sincero, me parece que o cara é apenas um tolo ignorante, que está sofrendo da síndrome de blub , arrogante o suficiente para assumir que você está errado e gritar com você, se ele não o entender.

No final, porém, a pergunta é: quem terá que mantê-lo? Se é você, então a decisão é sua, não importa o que alguém diga. Se for ele, então existem apenas duas possibilidades: encontre uma maneira de fazê-lo entender a biblioteca do analisador ou escrever um analisador imperativo para ele. Sugiro que você o gere a partir da sua estrutura do analisador: D


Excelente explicação da diferença entre as duas abordagens.
smarmy53

6
Aparentemente, você se vinculou ao TVTropes for Programmers. Adeus tarde ...
Izkata

10

Uma gramática de expressão de análise (como a abordagem do analisador Packrat) ou combinador de analisador não está reinventando a análise. Essas são técnicas bem estabelecidas no mundo da programação funcional e, nas mãos certas, podem ser mais legíveis do que as alternativas. Eu já vi uma demonstração bastante convincente do PEG em C # há alguns anos, que na verdade tornaria minha ferramenta de primeiro recurso para gramáticas relativamente simples.

Se você possui uma solução elegante usando combinadores de analisador ou um PEG, deve ser uma venda relativamente fácil: é razoavelmente extensível, geralmente relativamente fácil de ler depois que você supera o medo da programação funcional e às vezes é mais fácil de ler do que o gerador de analisador típico As ferramentas oferecem, embora isso dependa muito da gramática e do nível de experiência que você tem com qualquer um dos conjuntos de ferramentas. Também é muito fácil escrever testes. Obviamente, existem algumas ambiguidades gramaticais que podem resultar em um desempenho de análise bastante ruim nos piores cenários (ou muito consumo de memória com o Packrat), mas a média de casos é bastante decente e, na verdade, algumas ambiguidades gramaticais são melhor tratadas com PEG do que com LALR, pois Eu lembro.

O uso de Split e uma pilha funciona com algumas gramáticas mais simples do que um PEG ou pode suportar, mas é altamente provável que, com o tempo, você esteja reinventando muito a descendência recursiva ou tenha um conjunto esquisito de comportamentos que irá auxílio à apresentação ao custo de código extremamente não estruturado. Se você tiver apenas regras simples de tokenização, provavelmente não é tão ruim, mas à medida que você adiciona complexidade, provavelmente será a solução menos sustentável. Em vez disso, procuraria um gerador de analisador.

Pessoalmente, minha primeira inclinação quando preciso criar uma DSL seria usar algo como Boo (.Net) ou Groovy (JVM), pois tenho toda a força de uma linguagem de programação existente e uma incrível capacidade de personalização criando macros e ajustes simples para o pipeline do compilador, sem ter que implementar as coisas tediosas que eu acabaria fazendo se começasse do zero (loops, variáveis, modelo de objeto etc.). Se eu estivesse em uma loja desenvolvendo Ruby ou Lisp, usaria apenas os idiomas que fazem sentido lá (metaprogramação etc.)

Mas suspeito que seu problema real seja sobre cultura ou egos. Você tem certeza de que seu colega de trabalho também não teria se assustado se tivesse usado Antlr ou Flex / Bison? Suspeito que "argumentar" por sua solução possa ser uma batalha perdida; pode ser necessário gastar mais tempo adotando uma abordagem mais suave que use técnicas de construção de consenso, em vez de apelar para a autoridade de gerenciamento local. Combine a programação e demonstre a rapidez com que você pode realizar ajustes na gramática sem sacrificar a capacidade de manutenção, e fazer uma maleta para explicar a técnica, seu histórico etc. encontro de confronto.


9

Eu não sou muito versado em algoritmos de análise e afins, mas acho que a prova do pudim está na comida. Portanto, se tudo mais falhar, você pode oferecer a ele que implemente o analisador do seu jeito. Então

  • compare o tempo investido em ambas as soluções,
  • execute as duas soluções por meio de um teste de aceitação abrangente para ver quais possuem menos erros e
  • peça a um juiz independente que compare o código resultante em tamanho e clareza com o seu.

Para que o teste seja realmente justo, convém que as duas soluções implementem a mesma API e use uma plataforma de teste comum (ou uma estrutura de teste de unidade conhecida por vocês dois). Vocês dois podem escrever qualquer número e tipo de casos de teste funcional e garantir que a própria solução seja aprovada em todos eles. E, é claro, o ideal é que nenhum de vocês tenha acesso à implementação do outro antes do prazo. O teste decisivo seria então testar as duas soluções usando o conjunto de testes desenvolvido pelo outro desenvolvedor.


Esta é uma ótima idéia! Também seria fácil usar uma estrutura de teste de unidade comum.
smarmy53

11
+1 por ter o colega de trabalho na versão dividida ... O OP foi o responsável por criá-lo, então é ele quem provavelmente terá que apoiá-lo - não o colega de trabalho. Apenas sugerir isso para ele em cima de seu outro trabalho pode ser suficiente para tirá-lo de suas costas.
Izkata 11/09/12

7

Você fez isso como se tivesse uma pergunta técnica, mas como você provavelmente já sabia, não há nenhuma pergunta técnica aqui. Sua abordagem é muito superior a hackear algo no nível do personagem.

O verdadeiro problema é que seu colega (presumivelmente mais experiente) é inseguro e se sente ameaçado pelo seu conhecimento. Você não o convencerá com argumentos técnicos ; isso apenas o deixará mais defensivo. Em vez disso, você terá que encontrar uma maneira de aliviar seus medos. Não posso oferecer muitas sugestões, mas você pode tentar demonstrar muita consideração pelo conhecimento dele sobre o código legado.

Por fim, se seu gerente concordar com seus argumentos técnicos ilusórios e descartar sua solução, acho que você precisará procurar outra posição. Claramente, você seria mais valioso e mais valorizado em uma organização mais sofisticada.


Você está certo, eu já sabia que minha abordagem é superior, mas não consegui apresentar uma explicação boa e convincente - essa é a informação técnica que estou procurando. Concordou que o lado "interação humana" do problema é tão importante quanto o lado técnico (se não mais).
smarmy53

4

Serei breve:

Analisar o caminho de Cthulhu é difícil. Esse é o argumento mais simples e convincente contra isso.

Pode fazer o truque para linguagens simples; digamos, idiomas regulares. Provavelmente não será mais fácil do que uma expressão regular.

Também pode ser útil para linguagens um pouco mais complexas.

No entanto, eu gostaria de ver um analisador Cthulhu para qualquer idioma com aninhamento, ou apenas "significativamente com estado" - expressões matemáticas ou seu exemplo (chamadas de função aninhadas).

Imagine o que aconteceria se alguém tentasse criar um analisador para essa linguagem (não trivial e livre de contexto). Desde que ele seja esperto o suficiente para escrever um analisador correto, aposto que, durante a codificação, ele "descobriria" primeiro tokenizaton e depois análise recursiva de descida - de alguma forma.

Depois disso, a coisa é simples: "Ei, olhe, você escreveu algo chamado analisador de descida recursivo! Você sabe que ele pode ser gerado automaticamente a partir de uma descrição gramatical simples, como expressões regulares?


Para encurtar a história:
A única coisa que pode impedir alguém de usar a abordagem civilizada é a sua ignorância.


1

Talvez trabalhar em uma boa semântica de DSL também seja importante (a sintaxe importa, mas também a semântica). Se você não estiver familiarizado com esses problemas, sugiro a leitura de alguns livros, como Pragmática de linguagens de programação (de M.Scott) e Christian Queinnec. Lisp Em Pedaços Pequenos . Cambridge University Press, 1996.

A leitura de artigos recentes nas conferências DSL, por exemplo, DSL2011 também deve ajudar.

Projetar e implementar um idioma específico do domínio é difícil (e a maior parte da dificuldade não é analisada!).

Eu realmente não entendo o que você quer dizer com analisar o caminho de Cthulhu ; Eu acho que você só quer analisar de uma maneira bizarra.


Bons links. Quanto a Cthulhu, desculpe, esqueci o link. É uma referência a um artigo clássico de codinghorror: codinghorror.com/blog/2009/11/parsing-html-the-cthulhu-way.html . Eu atualizei a postagem original.
smarmy53
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.