LINQ + Lambda Expression + Extension Methods = Biutiful!

Publicado por Alexandre Cunha
03/3/2011
Categoria:
Tags: , , ,

De tempos em tempos surgem inovações na área de software que realmente mudam os padrões de beleza do nosso código fonte, tornando-o mais bonito, mais elegante. Quando digo mais bonito me refiro exatamente à isso: a beleza do código. O desenvolvimento de software é como uma arte, exige um trabalho criativo enorme para ser feito (ao contrário do que a maioria das pessoas – e dos próprios programadores – pensam). Portanto um código fonte pode ser apreciado, ele possui caracteristicas de beleza que são totalmente subjetivas. Da mesma forma que um Lamborghini Murcielado possui uma beleza indiscutível que transcende padrões e épocas, é simplesmente lindo! Contemplemos:

Da mesma forma como existe beleza no design de um Lamborghini podemos enxergar beleza no software, em particular no código fonte. Você olha um código e sente orgulho dele, dá vontade de pendurar na parade do quarto, contemplando todo dia, admirando aquela obra de arte. Coisa que só nós hard core developers entendemos.

Um exemplo desse tipo de inovação foi a mudança de paradigma da programação chamada Procedural/Funcional para a Programação Orientada a Objetos (OO) e posteriormente a introdução dos Design Patterns. O código antes disso era absolutamente impublicável, ninguém jamais pensaria em apontar beleza no código fonte antes do OO, na época do ANSI C, Pascal, Fortran, entre outros. Muitos irão discordar de mim mas obviamente gosto não se discute, então vamos tentar analisar um pouco melhor.

É interessante observar que a Programação OO geralmente induz códigos que possuem uma beleza tanto estrutural quanto “narrativa”. A leitura do código é agradável e traz aquele sentimento do quanto programar é realmente divertido. Design Patterns geram outro tipo de beleza, a beleza do modelo. Aquele que você escreve no quadro e fica horas analisando e enxergando quão verdadeiramente magnífico é aquele monte de quadrados e flechas com triângulos e diamantes. Tão bonito, que um dia será vendido como obra de arte, assim como os desenhos do Leonardo da Vinci (tenho certeza que na época eram considerados coisas de geek também).

Voltemos às aberrações. Uma pequena amostra de uma função simples em ANSI C, que faz uma mera comparação de arquivos, já é de chorar:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
  FILE *fp1, *fp2;
  char ch1, ch2, same;
  unsigned long l;

  if(argc!=3) {
    printf("Usage: compare <file 1> <file 2>\n");
    exit(1);
  }

  /* open first file */
  if((fp1 = fopen(argv[1], "rb"))==NULL) {
    printf("Cannot open first file.\n");
    exit(1);
  }

  /* open second file */
  if((fp2 = fopen(argv [2], "rb"))==NULL) {
    printf("Cannot open second file.\n");
    exit(1);
  }

  l = 0;
  same = 1;
  /* compare the files */
  while(!feof(fp1)) {
    ch1 = fgetc(fp1);
    if(ferror(fp1)) {
      printf("Error reading first file.\n");
      exit(1);
    }
    ch2 = fgetc(fp2);
    if(ferror(fp2)) {
      printf("Error reading second file.\n");
      exit(1);
    }
    if(ch1 != ch2) {
      printf("Files differ at byte number %lu", l);
      same = 0;
      break;
    }
    l++;
  }
  if(same)
      printf("Files are the same.\n");

  if(fclose( fp1 ) == EOF) {
    printf("Error closing first file.\n");
    exit(1);
  }

  if(fclose( fp2 ) == EOF) {
    printf("Error closing second file.\n");
    exit(1);
  }

  return 0;
}

Não me entendam mal, não estou difamando a linguagem C, ela tem utilidades fantásticas. Ainda me impressiono como aplicações extremamente complexas, com milhões de linhas de código, podem ser escritas em C puro. Eu mesmo desenvolvi em C e C++ durante muitos anos e tenho orgulho em dizer que sou (ou fui) um programador C. O que estou realmente querendo mostrar com o código acima é: what an ugly beast! Trata-se de um código meramente procedural. Get the job done! Period.

Recentemente, desenvolvendo em C#, comecei a utilizar com mais regularidade LINQ Lambda Expressions e Extension Methods, que surgiram já faz algum tempo no .NET 3.5 (C# 3.0).

Code please!

public void DoStuff(IList<Account> list)
{
    Query query = new Query();
    List<QuerySpec> queryList = new List<QuerySpec>();
    foreach (Account account in list)
    {
        if (account.Country == "Brazil")
        {
            QuerySpec spec = new QuerySpec();
            spec.Name = account.Name;
            spec.Type = account.Type.Id;
        }
    }
    query.QueryList = queryList.ToArray();
    service.Execute(query);
}

Como vemos é um código simples que faz uma query qualquer, eu sei, totalmente fora de contexto, eu sei, mas abstraiam comigo. Get the job done, right? É um código relativamente interessante, utiliza OO, fácil de ser lido. Mas ao mesmo tempo é muito massante para uma coisa tão simples.

Vamos aplicar um pouco de LINQ, Extension Methods e Lambda Expressions:

public static void DoStuff2(IList<Account> list)
{
    Query query = new Query();
    query.QueryList = list.
        Where((a) => a.Country == "Brazil").
        Select((a) => new QuerySpec() { Name = a.Name, Type = a.Type.Id }).ToArray();
    service.Execute(query);
}

Agora sim, sinto orgulho desse código! O uso combinado de Lambda Expressions com Extension Methods deixa o código Biutiful! Vou ler ele pra vocês verem: cria-se um objeto Query. A propriedade QueryList recebe o conteúdo de list onde Country é igual a “Brazil” e para cada item deste resultado cria-se um objeto QuerySpec e no final converte-se tudo para um Array. Se continuarmos nesse ritmo, em pouco tempo estaremos programando em pseudo-linguagem. Imagine um dia escrever um código assim:

DoStuff3(list of accounts)
{
    service.Query(where account is from "Brazil");
}

De fato não estamos muito longe desta realidade. O mesmo código acima pode ser escrito diretamente em LINQ da seguinte forma:

public static void DoSuff4(IList<Account> list)
{
    Query query = new Query();
    query.QueryList =
        (from a in list where a.Country == "Brazil"
            select new QuerySpec() { Name = a.Name, Type = a.Type.Id }
            ).ToArray();
    service.Execute(query);
}

Outro exemplo, e tenho certeza que todos já escreveram algo parecido, é o código abaixo:

public static void ProcessAccounts(IList<Account> accounts)
{
    Dictionary<string, IList<Account>> map = new Dictionary<string, IList<Account>>();
    foreach (Account account in accounts)
    {
        IList<Account> list;
        if (!map.TryGetValue(account.Country, out list))
        {
            list = new List<Account>();
            map.Add(account.Country, list);
        }
        list.Add(account);
    }
    service.CreateReport(map["Brazil"]);
    service.CreateReport(map["Canada"]);
}

Trivial mas extremamente entediante. Esse é o tipo de código que tende a se repetir, pois é um cenário muito comum. Alguns (eu inclusive) se dão ao trabalho de criar uma classe que abstraia esse comportamento (como um MultiMap por exemplo). Mesmo assim, não é uma solução elegante. Em C# podemos escrever o seguinte código equivalente:

public static void ProcessAccounts2(IList<Account> accounts)
{
    ILookup<string, Account> map = accounts.ToLookup((a) => a.Country);
    service.CreateReport(map["Brazil"]);
    service.CreateReport(map["Canada"]);
}

I rest my case!

A leitura e clareza do exemplo acima, feito em tão poucas linhas, é absolutamente fantástica. Isso representa um ganho enorme para os desenvolvedores, pois permite que eles pensem em níveis de abstrações muito mais altos. Ao invés de pensar no fgetc, if (argc != 3), exit(1); podemos agora pensar em algo muito mais próximo da linguagem natural.

Recomendo muito a leitura de um excelente artigo sobre C#/.NET Little Wonders. Este artigo fornece vários exemplos sobre micro-sized tips do C# que são extremamente úteis mas que muitas vezes são desconhecidos. A sua utilização pode aumentar significativamente a clareza, facilidade de manutenção e desempenho do código. Quanto menos tempo for gasto com essas tarefas, mais tempo podemos gastar fazendo boas abstrações, que é o real problema que um desenvolvedor tem que saber resolver.

O abismo que existe entre um requisito (alto nível) e o código fonte que deve ser escrito (baixo nível), diminuiu tremendamente nos últimos tempos. Imagine um programador recebendo a tarefa de adicionar um campo novo em um determinado formulário. O trabalho necessário para realizar esta tarefa em um projeto em C, desde a modelagem de banco de dados até a exibição do novo campo, está longe de ser trivial. Dependendo do sistema podemos estar falando em ter que atualizar meia dúzia de selects, inserts e updates, reimplementar validações e assim por diante. Hoje, na plataforma .NET com Hibernate e Spring, essa tarefa é totalmente trivial. Basta editar 2 arquivos XML e inserir o campo em um arquivo aspx.

Todas essas inovações que vem sendo introduzidas na linguagem C# e no .NET em geral são contribuições extremamente importantes. Existe uma ligação muito forte entre o código fonte, a ferramenta de desenvolvimento e a abstração do que tudo aquilo representa. No passado esse processo de mapeamento entre os requisitos do cliente e o código fonte era tão complexo que muitas vezes sem o criador original do código e sem uma boa documentação, era quase impossível dar continuidade no trabalho feito por outro programador. Hoje essa situação melhorou bastante, chegamos a um ponto onde o código fonte é a documentação do próprio código fonte.

Infelizmente existe também um revés. Temos hoje pessoas que não entedem absolutamente nada sobre o que é construir um software mas que, pela facilidade da utilização das ferramentas, trabalham como programadores. Este não é entretanto um problema da evolução das linguagens e das ferramentas mas sim da organização e regulamentação da área de desenvolvimento de software. De forma geral, considero extremamente saudável essa luta entre Java e C# onde a inovação é a única saída. Mal posso esperar pelo próximo round.

3 Comentários

  • Reply

    Por Ronaldo em 24 de March de 2011 às 1:45

    murciélago…é mesmo lindo….
    Gosto dos boms e velhos códigos quase ilegíveis escritos em C ou Perl… Melhor ainda em shell(bash).
    Mas eu acho que esses códigos apresentados nāo sāo lá tāo bonitos…. Os modelos sim…São lindos, um alto nível como esses me parece coisa pra quem não quer aprender a fazer algo….”deixa que o compilador resolve….” eu gosto mais da coisa em baixo nível….me intriga e etc….mas concordo que a evolução é por aí, deixar o código mais simples para que as pessoas possam pensar no que interessa…. O modelo….a abstração e etc…
    Bom tema para se abordar….um pouco de sardinha para o .net….mas tudo bem… Nos vemos no próximo round…. ;)

  • Reply

    Por José Luz em 11 de April de 2011 às 11:46

    Realmente a “briga” entre C# e Java é uma batalha na qual os vencedores somos nós, desenvolvedores.
    Quantos mais pensam, mais e melhores idéias surgem, se esses pensadores estiverem sendo pressionados pela concorrência, haverá sempre uma meta a bater…

    Agora a beleza de um código mais objetivo, um semi-pseudo código, faz realmente um programador se preocupar com o real problema que é abstração, e não em decorar “trocentos” comandos, acessos a ponteiros, clean memory, etc…

  • Reply

    Por Tadeu em 9 de April de 2012 às 8:27

    Bom dia Alexandre,
    Como ficaria se fosse necessário aplicar mais de uma condição ao LINQ de forma dinâmica? Digo, imagine um BI onde o usuário poderia submeter uma consulta com 1,2,3 ou N condições.

    Vamos pegar o seu primeiro ex.
    Condição
    Where((a) => a.Country == “Brazil”

    Teríamos que verificar se o usuário submeteu o filtro de Pais, caso positivo aplicaríamos o mesmo se não executaríamos sem a condição.

    abs





Desenvolvido por hacklab/ com WordPress