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
fonte
Respostas:
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
: DSua 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.
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
fonte
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.
fonte
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
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.
fonte
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.
fonte
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.
fonte
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.
fonte