Abraçando idiomas dentro de idiomas

Martin Fowler define livremente uma interface fluente da seguinte forma: “Quanto mais o uso da API tiver essa linguagem como fluxo, mais fluente ela será.” Se o senhor detectar um pouco de ceticismo aqui, está certo: nunca vi isso funcionar. As linguagens de computador não são linguagens humanas.

Vejamos um exemplo concreto de Joshua Flanagan. Veja como definimos uma expressão regular da maneira padrão:

<divs*class="game"s*id="(?<gameID>d+)-game"(?<content>.*?)
<!--gameStatuss*=s*(?<gameState>d+)-->

Veja como definiríamos essa mesma expressão regular na interface fluente do Joshua.

Pattern findGamesPattern = Pattern.With.Literal(@"<div")
.WhiteSpace.Repeat.ZeroOrMore
.Literal(@"class=""game""").WhiteSpace.Repeat.ZeroOrMore.Literal(@"id=""")
.NamedGroup("gameId", Pattern.With.Digit.Repeat.OneOrMore)
.Literal(@"-game""")
.NamedGroup("content", Pattern.With.Anything.Repeat.Lazy.ZeroOrMore)
.Literal(@"<!--gameStatus")
.WhiteSpace.Repeat.ZeroOrMore.Literal("=").WhiteSpace.Repeat.ZeroOrMore
.NamedGroup("gameState", Pattern.With.Digit.Repeat.OneOrMore)
.Literal("-->");

Assim estamos substituindo uma expressão regular de uma linha, agradável e sucinta, por dez linhas de objetos, métodos e enumerações nomeadas. Isso é progresso?

Admito que o senhor saiba que, mesmo para os padrões dos desenvolvedores, eu tenho uma familiaridade incomum com expressões regulares. Há um motivo pelo qual elas têm a reputação de serem densas e inescrutáveis. Definitivamente, eu vi algumas expressões regulares incrivelmente ruins em minha época. Mas, na minha opinião profissional, essa regex foi bem escrita. Não tive problemas para lê-la. Adicionar uma tonelada de invólucros de objetos hiperdensos a essa regex torna-a mais difícil para eu entender o que ele faz.

A nova sintaxe que Joshua inventou é excelente, mas é específica para sua implementação. Embora possa parecer uma boa ideia usar esse tipo de treinamento para “aprender” expressões regulares, eu diria que o senhor não está aprendendo nada. E isso é uma pena, pois a sintaxe das expressões regulares é uma mini-linguagem própria. Depois de aprendê-la, o senhor pode usá-la em qualquer lugar; ela funciona (quase) o mesmo em todos os ambientes.

O Projeto Subsonic tenta fazer algo semelhante para o SQL. Considere esta consulta SQL:

SELECT * from Customers WHERE Country = "USA"
ORDER BY CompanyName

Veja como expressaríamos essa mesma consulta SQL na interface fluente da SubSonic:

CustomerCollection c = new CustomerCollection();
c.Where(Customer.Columns.Country, "USA");
c.OrderByAsc(Customer.Columns.CompanyName);
c.Load();

Já mencionei anteriormente que o não sou fã da renderização orientada a objetos quando uma simples string é suficiente. Essa é exatamente a reação que tive aqui; por que eu iria querer usar quatro linhas de código em vez de uma? Esse parece ser um exemplo particularmente notório. O SQL é mais difícil de escrever e mais difícil de entender quando está envolto em todo esse ruído de objeto proprietário do SubSonic. Além disso, se o senhor não aprender o SQL subjacente – e como os bancos de dados funcionam – terá sérios problemas como desenvolvedor de software.

Mas posso ver a lógica por trás desses tipos de ferramentas de geração de código de banco de dados:

  1. Elas “resolvem” o problema do mapeamento objeto-relacional para o senhor (e se acredita nisso, tenho uma ponte que pode lhe interessar)
  2. o senhor obtém o intellisense
  3. seu banco de dados é fortemente tipado
  4. o compilador agora “entende” o banco de dados, ou pelo menos as classes geradas que representam o banco de dados.

Definitivamente, simpatizo com o desejo de produzir menos código, e esse é o objetivo das ferramentas de geração de código de banco de dados. Pessoalmente, eu diria que a maioria desses benefícios poderia ser obtida com IDEs mais inteligentes que realmente entendessem as cadeias de caracteres SQL nativas (ou expressões regulares), em vez de depender de uma série de códigos gerados e de uma sintaxe de objetos complicada e proprietária.

Mas vamos dar um passo atrás e pensar sobre o que o realmente acontecendo aqui. Em ambos os casos, estamos incorporando uma linguagem dentro de outra. SQL é uma linguagem. Expressões regulares são uma linguagem. Envolver essas linguagens em um monte de ObjectJunk de interface fluente mega-verbosa – só para fingir que estamos escrevendo código em nossa linguagem principal – é uma uma total desonestidade. Os wrappers de objetos de interface fluente me parecem um hack desagradável.

Por que não podemos adotar o paradigma da linguagem dentro de uma linguagem, em vez de fugir e se esconder dele? Essas linguagens específicas de domínio existem porque são otimizadas para processar cadeias de caracteres e dados com eficiência. Evitá-las é contraproducente.

Talvez a solução definitiva seja redefinir a linguagem subjacente para incorporar os recursos de outra linguagem.

Considere como o Perl integra a linguagem de expressão regular:

while (my $line = <IN>) {
while ( $line =~ /(Romeo|Juliet|Mercutio|Tybalt|Friar w+)/g ) {
my $character = $1;
++$counts{ $character };
}
}

Veja como o C# 3.0, com LINQ, integra a linguagem SQL:

var c = from Customer in Customers
where Customer.Country == "USA"
orderby Customer.CompanyName
select Customer;

Observe a ausência evidente de ObjectJunk. Não há explosão na fábrica de parênteses e pontos finais. Não há MassivelyLongTextEnumerations para lidar. Não há nada além de código que se parece exatamente com o que faz. E isso é uma coisa linda.

Abrace a ideia de idiomas dentro de idiomas. No The Land of Strings, falamos expressões regulares. No país dos dados, falamos SQL. Claro, o senhor pode fingir que essas linguagens não existem e se esconder no Reino dos substantivos— mas o senhor só está se enganando para não ter uma compreensão mais profunda de como as coisas realmente funcionam nesses outros lugares. Os wrappers de objetos de interface fluente podem parecer uma conveniência útil, mas na verdade são um hack feio e um péssimo substituto para a verdadeira integração da linguagem.