segunda-feira, 7 de dezembro de 2009

Convention over Configuration no dia-a-dia

Recentemente fiz um curso de Ruby on Rails e estou brincando um pouco com esse Framework. Uma das características mais importantes do Rails é o extensivo uso de Convention over configuration.

Para quem não está familiarizado com o conceito, Convention over configuration em português seria algo como “convenção sobre configuração” no sentido de dar preferência a convenções ao invés de configurar tudo.

Grande parte da produtividade do Rails se dá devido as convenções. São coisas simples, mas que ajudam bastante na produtividade durante o desenvolvimento.
ConventionOverConfiguration Imagem retirada do site Geek & Poke.

Uso de convenções em .NET

Desenvolvedores .NET não estão muito acostumados ao uso de convenções. O mais comum é configurar explicitamente tudo, seja via código ou em arquivos XML. Porém, aos poucos estamos sendo influenciados por outras linguagens, adotando as boas práticas já adotadas por outros desenvolvedores a anos.

A própria Microsoft já está adotando convenções nos produtos dela, como por exemplo no ASP.NET MVC. Por exemplo, ao acessar a url http://site/usuario/cadastrar o framework entenderá que deve utilizar o controller UsuarioController e chamar a ação Cadastrar. Nenhuma configuração é necessária.

Outro exemplo do uso de convenções é o NHibernate. Ao mapear o atributo Nome da classe Pessoa, por padrão o NHibernate utilizará a tabela Pessoa e o campo Nome do tipo varchar. Ao mapear o atributo o framework irá verificar que o tipo dele é string e utilizará a convenção de nome de campo e tabela.

Criando convenções personalizadas

Alguns produtos definem uma convenção e temos que nos ater a ela. Porém, o ideal é que possamos criar nossas próprias convenções.

Um ótimo exemplo disso é o Fluent NHibernate. Para quem não conhece, o Fluent NHibernate permite criar o mapeamento do NHibernate diretamente via código, sem utilizar arquivos XML.

No Fluent NHibernate podemos mapear as classes de forma automatizada. Ao invés de definir manualmente cada tabela e campo, fazemos isso por meio de convenções. Podemos utilizar as convenções padrão ou criar nossas próprias. Por exemplo, se queremos que todas as tabelas sejam igual ao nome da classe com o prefixo “tbl”, simplesmente criamos uma nova convenção que define isso e pronto, todas as tabelas serão mapeadas dessa maneira.

Resolvendo problemas

O uso de convenções certamente nos auxiliam bastante, porém, em alguns momentos elas parecem “caixas pretas” ou simplesmente “mágica”. Há casos onde queremos saber exatamente o que está sendo feito por baixo dos panos.

Nesses casos, é essencial que o framework disponha de recursos para que possamos identificar qual a convenção utilizada e qual o resultado obtido. Por exemplo, no ASP.NET MVC, se criamos um Controller e esquecemos de criar a View recebemos um aviso conforme imagem abaixo. Repare que o framework detalha todos os possíveis locais onde a view poderia estar. Com essa informação fica claro qual o problema e sua solução.
erro_mvc

Em resumo

As convenções quando bem empregadas facilitam bastante o desenvolvimento e aumentam nossa produtividade. Só precisamos tomar cuidado para que essa “mágica” possa ser facilmente entendida quando precisamos resolver um problema.

A alguns meses, Jeremy Miller fez uma apresentação sobre o assunto. Recomendo o vídeo, muito interessante ouvir as experiências dele na área.

segunda-feira, 16 de novembro de 2009

Refatorar ou reescrever?

Uma situação que já presenciei algumas vezes é alguém dizendo: “devemos reescrever essa aplicação do zero”. Mas, realmente vale a pena? E quais as situações que levam a isso?

Nas situações que presenciei, normalmente haviam dois possíveis motivos: o código era muito ruim ou houve uma grande evolução tecnológica desde que a aplicação foi escrita.

Repare que ambos os motivos são de ordem tecnológica, não tendo nada a ver com o negócio em si.

Vamos reescrever!!!

Todo o desenvolvedor gosta de criar algo novo, dificilmente alguém gosta de manter um código existente e, principalmente, escrito por outros. Quando vemos um código ruim, sempre pensamos “eu poderia fazer muito melhor” e “é melhor jogar todo esse código fora e começar do zero”.

O mesmo ocorre quando uma tecnologia antiga é utilizada em um software.

Sempre temos algumas desculpas para reescrever:
  • A tecnologia atual é muito melhor;
  • Dessa vez vamos fazer bem feito, deixaremos o código correto;
  • Entendemos melhor o produto, não vamos cometer os mesmos erros;
  • Reescrever o produto vai ser rápido, afinal, todos os requisitos já foram levantados.

Mas, nem sempre é como esperamos

Certo, decidimos reescrever toda a aplicação. Vamos deixa o antigo código de lado. Utilizamos a antiga aplicação para olhar os requisitos e copiamos o funcionamento dela em uma aplicação novinha em folha.

Aos poucos alguns problemas vão surgindo. Alguns percebemos de imediato, outros só aparecem quando já é muito tarde:
  • Os requisitos estão embutidos no código fonte: mesmo que a aplicação tenha uma ótima documentação, não podemos confiar totalmente nela. Sempre há detalhes que só estão presentes na aplicação em si. Pequenas correções, algumas features e outros pequenos detalhes. Se olhamos apenas para a documentação, deixamos passar esses detalhes, e mesmo ao se basear no código fonte, certamente muitos detalhes passarão despercebidos;
  • O mundo não para de girar: enquanto a nova aplicação é escrita, a antiga continua sendo utilizada. Sempre será necessário incluir um novo recurso ou corrigir um bug. Nesse meio tempo, enquanto reescrevemos a aplicação, toda o desenvolvimento tem que ser duplicado, é feito na versão antiga e na nova. Além disso, evitamos adicionar qualquer recurso na versão antiga, afinal, o cliente que espere a versão nova e nela fazemos isso. Infelizmente, não é bem assim que funciona na prática;
  • Vai demorar tanto quanto a primeira versão ou ainda mais: a ilusão de que o desenvolvimento será rápido logo cai por terra. Criar uma aplicação baseando-se em outra é uma tarefa difícil, além disso, temos que manter as duas versões até que a nova esteja pronta;
  • A nova versão só poderá ser utilizada quando estiver pronta: para tirar proveito da nova aplicação precisamos esperar que ela esteja 100% concluída. Nesse meio tempo, ela não adiciona nenhum valor aos desenvolvedores e nem ao cliente. Imagine se, por algum motivo, o projeto é cancelado. Todo o código desenvolvido é perdido sem nunca ter tido nenhum valor.

Então, como fazer?

A melhor tática é refatorar a aplicação existente, fazendo ajustes de forma gradual até que todo o sistema esteja da forma que desejamos. Já escrevi a respeito de como utilizar a orientação a objetos para nos ajudar nessa refatoração (veja Parte 1, Parte 2 e Final).

Podemos alterar pequenas partes do sistema ou até mesmo jogar toda uma parte fora, porém, não o sistema inteiro.

Ao fazer ajustes graduais na aplicação evitamos toda a dor de cabeça gerada pela reescrita da aplicação inteira. É claro que normalmente é mais difícil ajustar uma aplicação do que reescrevê-la, mas no final das contas, vale a pena.

Quando reescrever?

Em algumas situações realmente vale a pena reescrever totalmente a aplicação. Uma aplicação desktop que precisa ser reescrita para web por exemplo, envolve uma mudança grande de tecnologia, nesse caso vale a pena reescreve-la totalmente.

Mesmo quando decidimos reescrever uma aplicação é recomendável ter uma estratégia de como fazer isso de forma gradual. Implementando as partes mais importantes na nova versão e deixando as duas versões viverem em conjunto enquanto essa migração é feita.

Um artigo bastante interessante escrito pelo Joel Spolsky também fala a respeito disso. Recomendo a leitura: Things You Should Never Do

terça-feira, 3 de novembro de 2009

Criando um protótipo rapidamente com Balsamiq Mockups

Frequentemente precisamos criar um protótipo de tela, seja simplesmente para mostrar uma idéia ou para mostrar para o cliente e verificar se é isso mesmo que ele deseja.

Nesse momento temos duas opções:
  • Criar o protótipo real, exatamente da maneira como será o resultado final;
  • Criar um esboço, destacando apenas aquilo que nos interessa;
login
Eu evito ao máximo ter que criar um protótipo fiel a realidade, primeiramente pelo trabalho, mas principalmente porquê isso acaba virando um compromisso. Na hora de implementar somos obrigados a seguir a risca aquele protótipo, e como bem sabemos, na hora de implementar nem sempre podemos seguir a risca o que desenhamos.

Minha primeira opção ao criar um esboço é o bom e velho papel e caneta, ou um quadro branco se disponível. Porém, na maioria das vezes isso não é suficiente, precisamos enviar a imagem por e-mail a outras pessoas, anexá-la a documentos, etc. Nesse caso, como não tenho habilidades artísticas e tampouco sei trabalhar muito bem com ferramentas de edição gráfica acabo optando pelo Paint. Bem, o Paint até que é rápido pra se desenhar, fácil, mas deus queira que eu não tenha que refazer algo!!

Descobri agora um software muito bom para isso, é o Balsamiq Mockups. Simplesmente ótimo! É exatamente aquilo que eu procuro ao criar um esboço. As principais características que posso destacar são:
  • Bastante intuitivo – em poucos minutos usando a ferramenta já sabemos como tudo funciona;
  • Rápido de criar;
  • Facilmente alterável – cada elemento na tela é um “componente", podemos modificá-los a qualquer momento;
  • Não requer habilidades artísticas;
  • E principalmente, tem cara de esboço. Não precisamos nos ater a detalhes. Nos focamos no fundamental apenas.
tela
O mais legal é que já criaram até um site com vários componentes e layouts comuns, como por exemplo a figura abaixo:
mytube

A única coisa que talvez esteja faltando é a possibilidade de criar nossas próprias bibliotecas de componentes. Porém, pelo visto, isso já está sendo desenvolvido.

No site do fabricante você encontra maiores informações do produto: Balsamiq.

quarta-feira, 28 de outubro de 2009

A teoria e prática caminham juntas

Frequentemente ouço alguém pedir: "devo aprender primeiro a tecnologia X ou Y?" ou, "o que é melhor eu conhecer, Java ou C# ?". Normalmente minha resposta vai ser: não importa qual você aprenda, desde que aprenda primeiro os conceitos por trás delas.

A importância da teoria

As formas de ensino as quais estamos acostumados normalmente separam a teoria da prática, nunca mostrando como aplicar toda a teoria no mundo real. A teoria e prática são separadas de tal modo que somos levados a crer que uma não tem relação com a outra.

Certamente você já ouvir frases do tipo "Isso é coisa de acadêmico", "No mundo real uma coisa dessas não se aplica" ou então "Eu sou uma pessoa pragmática não ligo pra essas teorias".

Essas frases são exemplos claros de como a teoria e prática são vistas como duas coisas distintas. Uma pessoa se diz pragmática e se afasta de qualquer teoria. A verdade é que a teoria está pros traz de tudo que fazemos, querendo ou não, ela é o fundamento.

O foco excessivo na tecnologia

Uma tendência que vemos atualmente é um foco excessivo na tecnologia em si e não nas motivações por traz dela. Todos querem estudar a nova ferramenta da moda.  As soluções são baseadas apenas em ferramentas.

O que adianta conhecer o framework de desenvolvimento web XYZ se a pessoa não sabe os conceitos básicos de desenvolvimento web? Ou aprender o lindo e maravilhoso framework de persistência recém lançado, sem saber quais seus objetivos e razões de existir.

Esse foco excessivo leva as pessoas a serem especialistas em uma determinada ferramenta, sendo incapazes de utilizar outras ferramentas de mesmo propósito ou até mesmo de fazer qualquer julgamento a respeito delas.

A teoria faz parte da prática

Sempre que preciso utilizar uma ferramenta procuro entender os problemas que motivaram o desenvolvimento dela e a forma que ela visa resolver esses problemas. Com isso em mente, fica fácil comparar diversas ferramentas e adotar outra se necessário.

Um exemplo prático é o desenvolvimento Web. Comecei a desenvolver sistemas web utilizando ASP.Net, e como todos devem saber, o conceito de WebForms adotado pelo ASP.Net tenta abstrair a complexidade da web e simular o desenvolvimento desktop. Nunca gostei muito dessa idéia de abstrair a web, por isso acabava estudando o funcionamento por traz daquela abstração. É importante saber como mostrar os dados em um Grid ou como utilizar um Repeater com dados aninhados, mas tem coisas muito mais importantes que isso.

Com o conhecimento de desenvolvimento web em geral que adquiri, e não apenas o conhecimento da abstração criada pelo ASP.Net, fui capaz de utilizar outros frameworks sem problemas. É só uma questão de traduzir a teoria para os comandos do novo framework.

Orientação a objetos na teoria e na prática

A orientação a objetos não é nova, porém somente na última década ela passou a ser amplamente utilizada.

Em qualquer projeto desenvolvido hoje, se alguém cogitar utilizar o desenvolvimento estruturado, certamente será motivo de piadas. Afinal, programação orientada a objetos (POO) é um paradigma muito superior a programação estruturada. Há apenas um problema: grande parte dos projetos que atualmente são desenvolvidos utilizando POO não utilizam os conceitos de POO, são apenas programas estruturados utilizando classes.

POO não se resume a conhecer o que é classe, objeto, método e herança. Há muitos outros princípios que se aplicados ajudam no bom desenvolvimento. Os princípios mais difundidos são os que conhecemos pelo acrônimo SOLID. São eles:
  • SRP - Single Responsibility Principle;
  • OCP - Open Closed Principle;
  • LSP - Liskov Substitution Principle;
  • ISP - Interface Segregation Principle;
  • DIP - Dependency Inversion Principle.
Apenas com estes princípios é que podemos realmente alcançar todo o potencial da POO e não apenas desenvolver programas estruturados utilizando classes. Em futuros posts devo falar mais sobre eles.
Mais uma vez, é um questão de mudar o foco da tecnologia para a teoria.

Em resumo

Para ter sucesso no desenvolvimento de software devemos tirar um pouco o foco da tecnologia e aprender os conceitos por traz dela. É uma pena que a cada dia as instituições de ensino estão focando menos nessa teoria e mesmo quando o fazem, é de forma distante da realidade, ficando difícil fazer uma ligação entre teoria e prática.

quinta-feira, 22 de outubro de 2009

Valores nulos em chave única no SQL Server

O Microsoft SQL Server tem uma característica que eu não gosto nem um pouco: em uma chave única dois valores nulos são considerados iguais.

Imagine uma tabela com os campos abaixo, tendo uma chave única com o campo CPF.
ID Nome CPF
1 João 123.456.789-00
2 Pedro NULL
3 Bino NULL

No SQL Server, ao contrário de outros bancos como o PostgreSQL, esses dados não seriam válidos. Ao tentar inserir o terceiro registro seria gerado um erro de chave duplicada.

Para contornar esse problema existe um truque denominado Nullbuster. Descobri essa técnica por acaso ao ler um post do Karl Seguin. Pesquisando na internet descobri que os créditos são dados ao Steve Kass, porém não encontrei nenhum link onde pudesse confirmar isso.

O truque é bastante simples, veja o SQL abaixo:
CREATE TABLE dupNulls (
  pk int identity(1,1) primary key,
  X  int NULL,
  nullbuster as (case when X is null then pk else 0 end),
  CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
Repare que foi criado um campo adicional denominado “nullbuster” onde o valor será igual a PK se o valor for NULO ou 0 se o valor estiver preenchido. A chave única então é composta dos dois campos.

A tabela exibida acima ficaria com os valores:
ID Nome CPF Nullbuster
1 João 123.456.789-00 0
2 Pedro NULL 2
3 Bino NULL 3

Um truque bastante simples que resolve alguns problemas.

segunda-feira, 19 de outubro de 2009

Influências de Domain-Driven Design

Nos últimos anos a prática de Domain-Driven Design vêm ganhando popularidade. Mesmo não sendo amplamente aplicada, muitas de suas abordagens influenciam o desenvolvimento de softwares como um todo.

Para quem não está familiarizado com o tema, DDD é uma abordagem para desenvolvimento de software. Esse termo for criado por Eric Evans no livro Domain-driven design: tackling complexity in the heart of software.

De uma maneira super resumida, DDD visa eliminar as diferenças entre negócio e software, o software deve espelhar exatamente o domínio sendo tratado. Para tanto, são adotadas diversas práticas no desenvolvimento, como por exemplo: colaboração mais próxima do especialista no negócio, uso de uma linguagem única pelos desenvolvedores e especialistas, e deixar os conceitos envolvidos na aplicação explícitos.

Como o próprio Eric Evans deixa claro, DDD não é uma abordagem a ser adotada no desenvolvimento de qualquer software, ela deve ser adotada apenas em sistemas complexos.

Mesmo não adotando a abordagem como um todo, podemos tirar proveito de diversas práticas no desenvolvimento de qualquer software.

Deixando os conceitos explícitos

Uma das práticas de DDD é deixar os conceitos explícitos. Ou seja, deixar clara a intenção deles. Isso deve ser feito tanto no nível de código como na própria interface.

Um exemplo que me deparei recentemente onde os conceitos não estão muito claros é no Blogger. Ao editar uma postagem vemos uma tela semelhante a figura abaixo:
blogger
A questão nessa tela são os botões “Publicar postagem” e “Salvar”. As ações deles não ficam muito claras ao usuário. Enquanto criamos uma postagem eles tem as funções de:
  • Publicar postagem – tira a mensagem dos rascunhos e publica oficialmente a mensagem;
  • Salvar – simplesmente atualiza os dados da postagem;
Se a mensagem já foi publicada, as funções são:
  • Publicar postagem – simplesmente atualiza os dados da postagem;
  • Salvar – move para os rascunhos a mensagem já publicada;
Ficaria muito mais claro para o usuário se as funções de Publicar, Mover para rascunhos e Salvar fossem distintas. Na minha opinião, ficaria muito mais claro com as seguintes opções:
  • Salvar – simplesmente atualiza os dados. Não move para rascunhos e não publica, atualiza os dados independente do estado da postagem;
  • Publicar postagem – aparece apenas se a postagem ainda não foi publicada;
  • Mover para rascunhos – cancela a publicação e coloca novamente a mensagem nos rascunhos.
É uma alteração simples e que envolve basicamente renomear os botões. Pode ser que eu seja chato, mas acho que dessa forma fica muito mais claro para o usuário qual o efeito de cada comando.

Utilizando técnicas de DDD no dia-a-dia

Como já mencionei antes, DDD não é uma abordagem a ser adotada em todo e qualquer software, porém podemos tirar lições dela e adotar algumas partes no desenvolvimento do dia-a-dia. Sempre que possível podemos deixar os conceitos mais explícitos e usar nomenclaturas padronizadas. Ou seja, deixar claro tanto para o desenvolvedor como para o usuário o efeito de cada ação e sua relação com o negócio.

segunda-feira, 12 de outubro de 2009

Plugin do Visual Studio - RockScroll

Para quem trabalha com o Visual Studio 2005 ou 2008, um plugin muito útil é o RockScroll. Ele destaca o código fonte na barra de rolagem. Veja a imagem abaixo pra entender melhor:
RockScroll
Esse plugin tem alguns recursos interessantes:
  • Exibe o código fonte na barra de rolagem, destacando os comentários em verde;
  • Com um duplo-clique em qualquer texto ele deixa o texto destacado em todos os locais onde aparece, no arquivo e na barra de rolagem. Bom para ver onde uma variável ou método são utilizados;
  • Destaca os breakpoints;
Como esse plugin foi desenvolvido para uso interno na Microsoft, eles liberaram sem nenhum suporte ou garantia. Ou seja, pode não funcionar na sua máquina. Eu pessoalmente nunca tive problemas, o único problema que conheço é que em arquivos que utilizam “region” a posição da barra de rolagem não fica correta.

Para fazer o download visite o blog do Scott Hanselman(excelente blog) ou diretamente nesse link.

quinta-feira, 8 de outubro de 2009

O programador fita adesiva

Recentemente Joel Spolsky publicou um artigo falando a respeito do “programador fita adesiva”. Esse artigo gerou muita discussão pela internet. Algumas pessoas apoiando e muitas totalmente contra.

Nesse artigo Joel apresenta um tipo de programador, o “duct tape programmer”, que só se preocupa em terminar o produto. Esse programador não inventa moda, não enche o código de frescuras ou fica limpando o código para ficar melhor. Ele simplesmente fará da forma mais simples possível, e se necessário utilizará uma fita adesiva só pra deixar as diversas partes juntas.
duct tape programmer
Jeffrey Pallermo, assim como muitos outros na internet, publicou uma resposta ao artigo. Concordo com a resposta dele, e compartilho da mesma opinião. Recomendo a leitura dessa resposta.

Objetivo correto, atitude errada

Primeiramete tenho que concordar plenamente com o objetivo, que é simplesmente liberar o produto. Nós desenvolvedores estamos muito ligados ao software em si e nem sempre temos uma visão clara do negócio e das necessidades do cliente.

Deixar de liberar uma funcionalidade apenas porquê o código está ruim não é aceitável. Em alguns casos a necessidade do cliente nos obrigada a fazer o sistema funcionar, de um jeito ou de outro, mesmo que utilizando técnicas “não recomendáveis”. Ou seja, é o velho conhecido: “dá um jeito de liberar isso, seja como for”.

Entretando, esse caso deve ser a excessão e não a regra. Não podemos construir um sistema inteiro dessa forma. O padrão deve ser construir um sistema com qualidade e, se por algum motivo precisamos utilizar técnicas obscuras para liberar logo a funcionalidade, OK, desde que seja uma solução temporária e logo seja substituída pela solução correta.

A fita adesiva serve muito bem para tampar um burraco, mas não adianta querer montar uma estrutura inteira com ela que não dará certo. Mesmo esse burraco tampado precisa ser logo consertado.

Qualidade não é opcional

Softwares em geral não são bem vistos, encontrar erros se tornou normal. Anormal é um software com qualidade. Devemos mudar isso, e logo!!

Qualidade não deve ser algo a mais, deve ser o padrão. Um produto com qualidade não é mérito, é obrigação. Devemos investir em processos e práticas que possibilitem aumento na qualidade e produtividade.

Com melhores práticas quem sabe podemos evitar o uso de fita adesiva e mudar um pouco a forma como os softwares são vistos atualmente.

segunda-feira, 5 de outubro de 2009

Teoria das janelas partidas

Uma teoria bastante interessante é a Teoria das Janelas partidas.  Ela diz:
"Considere-se um edifício com algumas janelas quebradas. Se as janelas não são reparadas, a tendência é para que vândalos partam mais janelas. Eventualmente, poderão entrar no edifício, e se este estiver desocupado, tornam-se "ocupas" ou incendeiam o edifício.  
Ou considere-se um passeio. Algum lixo acumula-se. Depois, mais lixo acumula. Eventualmente, as pessoas começam a deixar sacos de lixo.”
Basicamente, enquanto as janelas estão intactas, as chances de um vidro ser quebrado são pequenas. Assim que um janela é quebrada e permanece assim, logo mais e mais janelas são quebradas.

Enquanto que o prédio está intacto, significa que tem alguém cuidando dele. Assim que uma janela é quebrada e ninguém conserta, significa que não tem ninguém ligando pra ele. Se ninguém liga, por quê não quebrar mais uma janela?

Agora imagine essa situação no desenvolvimento de software (o site Coding Horror tem um artigo falando justamente disso, recomendo a leitura). No desenvolvimento de software ocorre exatamente a mesma coisa. Não, não estou dizendo que temos desenvolvedores vândalos, o que quero dizer é que, assim que um problema é negligenciado, logo o sistema todo estará ruim.

Um desenvolvedor olha para um determinado código ruim e pensa “isso está ruim, mas o sistema já é ruim mesmo. um problema a mais não faz diferença”. Tenho certeza que você já presenciou isso.
Como prevenir isso? Da mesma forma demonstrada na Teoria das Janelas Partidas. Não deixar os problemas se acumularem. A cada problema encontrado, resolvê-lo imediatamente. Ou seja, mostrar que tem gente que dá valor ao código, que cuida dele.

É uma teoria muito simples e fácil de ser aplicada. Evita que em pouco tempo tenhamos um código muito ruim. Na próxima vez que você ver um problema e deixar de corrigí-lo, lembre-se dela.

quarta-feira, 30 de setembro de 2009

Como refatorar um sistema para melhor aproveitar recursos de POO – Final

Nos dois últimos posts mostrei o código original do programa, e os passos para refatorá-lo. Leia a Parte 1 e Parte 2 se você ainda não leu. Para finalizar, quero mostrar como podemos extendê-lo de forma simples.

Imagine que agora, ao invés de ler do console, queremos ler as notas de um arquivo e salvar a média em outro. Para isso, não vamos alterar o código existente, vamos apenas criar duas classes. Uma implementando a interface ILeitorNotas e outra a interface IMostradorMedia.

Veja o código abaixo:
public class LeitorNotasEmArquivo : ILeitorNotas
{
  private string filename;

  public LeitorNotasEmArquivo(string filename)
  {
    this.filename = filename;
  }

  public List<decimal> LeNotas()
  {
    string[] valoresNoArquivo = File.ReadAllLines(filename);

    List<decimal> notas = new List<decimal>();
    foreach (string valor in valoresNoArquivo)
    {
      decimal nota = decimal.Parse(valor);
      notas.Add(nota);
    }

    return notas;
  }
}

public class MostradorMediaEmArquivo : IMostradorMedia
{
  private string filename;

  public MostradorMediaEmArquivo(string filename)
  {
    this.filename = filename;
  }

  public void ExibeMedia(decimal media)
  {
    File.WriteAllText(filename, media.ToString());
  }
}

A chamada do ProcessaMedia ficaria assim:
static void Main(string[] args)
{
  ProcessaMedia processaMedia = new ProcessaMedia(
    new LeitorNotasEmArquivo("D:\\notas.txt"), 
    new MostradorMediaEmArquivo("D:\\media.txt")
  );
  processaMedia.Executa();

  Console.ReadKey();
}

Apenas lembrando que esse código é apenas para fins de exemplo, ele não tem nenhum tratamento adicional para deixá-lo simples.

terça-feira, 29 de setembro de 2009

Como refatorar um sistema para melhor aproveitar recursos de POO – Parte 2

Na primeira parte mostrei o código original da aplicação e os seus problemas, agora vamos botar a mão na massa. Vamos refatorar esse programa, passo-a-passo, identificando os pontos onde podemos melhorá-lo.

Separar as responsabilidades

Como vimos anteriormente, nossa classe tem três responsabilidades distintas: ler as notas, calcular a média e exibir o resultado.

O primeiro passo é separar cada responsabilidade em uma classe distinta. Vamos apenas recortar o trecho de código correspondente e colocá-lo em uma nova classe.

Começamos separando a leitura de notas:
public class LeitorNotas
{
  public List<decimal> LeNotas()
  {
    List<decimal> notas = new List<decimal>();
    bool continuaLeitura = true;
    while (continuaLeitura)
    {
      Console.Write("Nota (Digite 'S' para sair): ");
      string valorDigitado = Console.ReadLine();
      if (valorDigitado.Equals("S", StringComparison.CurrentCultureIgnoreCase))
      {
        continuaLeitura = false;
      }
      else
      {
        decimal nota = decimal.Parse(valorDigitado);
        notas.Add(nota);
      }
    }

    return notas;
  }
}

E depois a exibição da média:
public class MostradorMedia
{
  public void ExibeMedia(decimal media)
  {
    Console.WriteLine("Média: "+ media);
  }
}

Nesse momento, a classe ProcessaMedia deve estar assim:
public class ProcessaMedia
{
  public void Executa()
  {
    LeitorNotas leitorNotas = new LeitorNotas();
    List<decimal> notas = leitorNotas.LeNotas();

    //RESTO DO CÓDIGO AQUI

    MostradorMedia mostradorMedia = new MostradorMedia();
    mostradorMedia.ExibeMedia(media);
  }
}

Dependendo de uma abstração

O próximo passo é alterar o código para depender de uma abstração e não de uma implementação.
Nesse caso, vamos criar a interface ILeituraNotas e a IMostradorMedia, que serão implementadas respectivamente pela classe LeituraNotas e MostradorMedia.

public interface ILeitorNotas
{
  List<decimal> LeNotas();
}

public class LeitorNotas : ILeitorNotas
{
  public List<decimal> LeNotas()
  {
    //RESTO DO CÓDIGO AQUI
  }
}

public interface IMostradorMedia
{
  void ExibeMedia(decimal media);
}

public class MostradorMedia : IMostradorMedia
{
  public void ExibeMedia(decimal media)
  {
    Console.WriteLine("Média: "+ media);
  }
}

A classe ProcessaMedia criava o LeitorNotas e MostradorMedia, agora ela não fará mais isso. Ela irá receber no construtor um objeto que implemente a interface ILeitorNotas e um objeto que implemente IMostradorMedia.

public class ProcessaMedia
{
  private ILeitorNotas leitorNotas;
  private IMostradorMedia mostradorMedia;

  public ProcessaMedia(ILeitorNotas leitorNotas, IMostradorMedia mostradorMedia)
  {
    this.leitorNotas = leitorNotas;
    this.mostradorMedia = mostradorMedia;
  }

  public void Executa()
  {
    List<decimal> notas = leitorNotas.LeNotas();

    //RESTO DO CÓDIGO AQUI

    mostradorMedia.ExibeMedia(media);
  }
}

Ao criar o objeto ProcessaMedia passamos para o construtor os objetos LeitorNotas e MostradorMedia.

Também poderíamos utilizar um framework de injeção de dependência, onde ele seria responsável por criar esses objetos auxiliares. Em um futuro post entro em mais detalhes a respeito disso.

Extendendo a funcionalidade

À partir desse momento o processamento da média não depende mais diretamente da leitura no console. Se necessário, podemos criar uma classe que implemente ILeitorNotas lendo os dados de um arquivo, por exemplo.

Um dos princípios da orientação a objetos diz que “uma classe deve estar aberta para extensão e fechada para modificação”. No exemplo, a classe ProcessaMedia está “aberta para extensões” (podemos extendê-la por meio das interfaces criadas) e “fechada para modificações” (a única razão dela ser alterada é se a regra de negócio for alterada).

Finalizando

Obviamente, esse exemplo é bastante simples e temos a sensação de “muita complexidade para pouca necessidade”. Mas repare bem, o que aumentou foi o número de classes, a complexidade foi reduzida. Cada classe tem sua responsabilidade bem definida e clara.

Espero que eu tenha conseguido mostrar o objetivo dessa refatoração. Como você pode ver, é bastante simples e provê várias vantagens.

Como refatorar um sistema para melhor aproveitar recursos de POO – Parte 1

Atualmente a programação orientada a objetos (POO) é o paradigma predominante no desenvolvimento de sistemas. Porém, apesar de todos utilizarem e saberem como funciona, nem sempre o código desenvolvido realmente utiliza o potencial da POO.

É bastante comum encontrarmos sistemas desenvolvidos com orientação a objetos mas que não passam de programas estruturados utilizando classes.

Tentarei mostrar aqui um exemplo simples de como refatorar um código utilizando alguns princípios da orientação a objetos.

O código original

O exemplo que utilizarei aqui é bastante simples, não quero me ater no programa em si, mas nos conceitos utilizados. Então, não leve muito em consideração o programa, veja o conceito para poder utilizá-lo em qualquer outro caso.

Segue abaixo o código original do programa. Como você pode notar, ele simplesmente lê várias notas, calcula a média e a exibe.

class Program
{
  static void Main(string[] args)
  {
    ProcessaMedia processaMedia = new ProcessaMedia();
    processaMedia.Executa();

    Console.ReadKey();
  }
}


public class ProcessaMedia
{
  public void Executa()
  {
    List<decimal> notas = new List<decimal>();
    bool continuaLeitura = true;
    while (continuaLeitura)
    {
      Console.Write("Nota(Digite 'S' para sair):");
      string valorDigitado = Console.ReadLine();
      if (valorDigitado.Equals("S", StringComparison.CurrentCultureIgnoreCase))
      {
        continuaLeitura = false;
      }
      else
      {
        decimal nota = decimal.Parse(valorDigitado);
        notas.Add(nota);
      }
    }

    decimal totalNotas = 0;
    int qtdNotas = notas.Count;
    foreach (decimal nota in notas)
    {
      totalNotas += nota;
    }

    decimal media = 0;
    if (qtdNotas > 0)
    {
        media = totalNotas/qtdNotas;
    }

    Console.WriteLine("Média: "+ media);
  }
}

Quais os problemas nesse código?

Quais os problemas nesse código e o que podemos fazer para melhorá-lo? Vejamos:
  1. A classe ProcessaMedia tem três responsabilidades. Ela é responsável por lêr a nota, calcular a média e exibir o resultado. O ideal é que uma classe tenha apenas uma responsabilidade;
  2. Ela está diretamente acoplada ao Console. Isso pode gerar alguns problemas, por exemplo, como você faria um teste unitário dessa função? Simplesmente não é possível.
  3. Ela não é extensível. Se, por exemplo, surgir a necessidade de dar a opção de ler as notas de um arquivo, como você faria? No código seriam feitos uma série de condições IF?
Para resolver esses problemas vamos adotar algumas medidas:
  1. Separar cada responsabilidade em uma classe;
  2. A classe CalculaMedia deverá depender de uma abstração, e não do Console;
  3. Com a implementação dos itens 1 e 2 automaticamente ganhamos a extensibilidade, nenhuma medida adicional será necessária.

Mãos na massa

No próximo post mostrarei os passos para refatorar esse código.

Apesar deste ser um exemplo simples, podemos aplicar os mesmos conceitos em muitas outras situações. Pense um pouco e você logo vai lembrar de situações semelhantes a esse exemplo.

quinta-feira, 24 de setembro de 2009

Introdução ao TDD

A algum tempo comecei a adotar a prática de TDD (Test-Driven Development). No início pensava "não tem como desenvolver desse jeito", em pouco tempo passei a pensar "não tem como desenvolver sem isso".

Existe muita informação na Internet a respeito de TDD, por isso não quero falar de como fazer, mas sim, dos conceitos e práticas importantes para quem está começando agora. Para quem está começando, parece que TDD é relacionado apenas a "testes automatizados", mas é muito mais que isso.

Definição de TDD

A prática de TDD se resume a três passos simples, mas fundamentais:
  • Escrever um teste que falhe - o primeiro passo é pensar o que deseja-se fazer. Com isso, criar um teste definindo o contexto, a execução e as expectativas;
  • Fazer o teste passar - deve-se escrever o mínimo de código necessário para fazer o teste passar, nada além disso;
  • Refatorar - deve-se rever o código e verificar se há melhorias a serem feitas. Essas melhorias não podem alterar o comportamento apenas a estrutura do código.
Apesar dos passos serem simples, no início é difícil segui-los. Nossa tendência é escrever imediatamente todo o código e no final adicionar os testes. É justamente esse o ponto mais crítico e mais importante na adoção de TDD, se acostumar a escrever o teste primeiro. Toda a vantagem do desenvolvimento dirigido por testes vêm disso.

Podemos utilizar as três regras definidas por Robert Martin:
  • Você não pode escrever nenhum código a não ser que ele seja necessário para fazer um teste que esteja falhando passar;
  • Você não pode escrever nenhum teste além do necessário para falhar. E, falhas de compilação também são falhas;
  • Você não pode escrever nenhum código além do necessário para fazer o teste passar;
Em resumo, só escrever qualquer código necessário para atender um teste que esteja falhando, nada além disso.

Benefícios

A grande pergunta é: quais os benefícios de TDD?

Para quem ainda não conhece a prática, a única vantagem vista são os testes unitários. Ninguém vê logo de cara os demais benefícios que essa prática traz. Alguns deles que posso destacar:
  • Requisitos melhor detalhados - os testes ajudam a entender melhor os requisitos;
  • Codificar apenas o necessário - como apenas é codificado o mínimo necessário para passar um teste evitamos introduzir códigos que não temos certeza se serão utilizados;
  • Reduz o acoplamento das classes - para que os testes sejam fáceis de serem criados, as classes não podem ter um forte acoplamento;
  • Deixa claro o comportamento do sistema - para um desenvolvedor que entra no projeto, ou quando vamos alterar um código existente, sempre podemos consultar os testes para saber qual o comportamento esperado;
  • Incentiva a refatoração - é bastante comum ouvirmos a frase "é melhor não mexer ali pois não sabemos quais os problemas que pode gerar". Com um sistema com boa cobertura de testes, o desenvolvedor tem muito mais confiança para fazer qualquer alteração;
  • Menor utilização do depurador - com os testes é muito mais fácil verificar o comportamento. Evitamos ter que abrir o sistema, acessar meia dúzia de telas e só depois testar a funcionalidade;
Um fato interessante da prática de TDD é que ele mostra claramente os pontos do sistema onde o design está ruim. Basicamente, se está difícil ou trabalhoso testar determinada parte do sistema, isso é um grande sinal de um problema no design.

O que testar?

O ideal é que 100% do sistema esteja coberto por testes, porém isso dificilmente é possível. Há determinados pontos do sistema onde não é possível criar testes unitários ou o trabalho não compensa.

Normalmente o código que acessa recursos externos (banco de dados, sistema de arquivos, etc) não pode ser testado com testes unitários. Para estes casos é necessário criar testes integrados que, normalmente são mais difíceis de serem criados/mantidos e mais lentos. Deve-se tentar desacoplar ao máximo o sistema destes recursos externos.

Vale a pena?

No início é difícil se acostumar a essa nova forma de trabalho, porém em pouco tempo percebemos os benefícios e não queremos voltar atrás. No momento que percebi que estava gastando muito menos tempo depurando os programas e o código estava melhor, vi que esse era um caminho sem volta.

Primeiro post

Finalmente decidi criar um blog. Após algum tempo pensando em criar um, resolvi tentar e ver como será.

Criei esse blog com alguns objetivos:
  • Melhorar o meu conhecimento nas áreas que atuo - sempre que preciso explicar algo acabo aprendendo mais sobre o assunto;
  • Passar o conhecimento adiante - gosto de compartilhar tudo que aprendo. Ao invés de compartilhar esse conhecimento por e-mail ou de alguma forma fechada, um blog vai permitir tornar isso público. Quem sabe mais pessoas podem se aproveitar disso;
  • Aprender a redigir melhor - algumas vezes colocar em palavras os pensamentos é um tanto complicado, esse blog pode me ajudar bastante;
  • Ter um banco de lições aprendidas - muito do que aprendi hoje vou precisar amanhã e provavelmente já terei esquecido. Com um blog, posso deixar isso salvo para minha própria pesquisa futura;

Como desenvolvo principalmente em .Net, mais especificamente com a linguagem C#, o conteúdo do blog provavelmente será voltado a essa área. Porém, não quero me ater a parte técnica, pretendo postar a respeito de práticas para melhorar a qualidade do software como um todo.

Atualmente estou procurando melhorar a qualidade dos softwares que desenvolvo e ter uma maior efetividade no desenvolvimento. Para isso, estou adotando práticas do desenvolvimento ágil, e aperfeiçoando meu entendimento de programação orientada a objetos. Essa é uma área muito interessante e que pretendo escrever a respeito.

Então, é isso. Vamos ver como será daqui por diante. Certamente ganharei muita experiência e espero poder contribuir um pouco com os outros também.

Caso você tenha algum comentário, sugestão ou crítica, por favor deixe um comentário. Toda opinião é bem vinda.