Você se preocupa com os IFs, suficientemente?

16/06/2010 16:47

Um dos problemas combatidos pela utilização de boas práticas de desenvolvimento, e que parece ser bastante comum, é a negligência. Aquela falta de atenção, seja por desleixo, ou por desconhecimento, que faz pequenos detalhes se tornarem desastrosos.

Quer ver um exemplo simples? O IF: essa natural instrução condicional, duas letrinhas, “I” e “F”,  simples assim (desculpem o tom sarcástico, mas assim é mais engraçado). Aparentemente, não existe tanto segredo assim num IF, certo? Analisamos uma ou algumas condições, e executamos algum código caso o IF seja satisfeito. Mas vamos analisar o código abaixo, que peguei num code review.

if ((txtWord.Text != "" || cblWords.Items.Count != 0)
     && VerifyAlias(txtAlias.Text, true)
     && (txtWord.Text == "") ? true : VerifyKeyword(txtWord.Text))
{
    // Código aqui...
}


Sim, é um IF trabalhoso de ler e entender, não o escolhi por acaso. Temos algumas condições simples a serem checadas, como o txtWord.Text != “” ou cblWords.Items.Count != 0. Mas isso, combinado com outra verificação, que utiliza uma função VerifyAlias, já complica mais um pouco. Para complicar, sensivelmente, temos um ternário no final, sendo que a condição negativa leva a uma nova verificação através da função VerifyKeyWord(txtWord.Text).

No geral, temos um IF com uma desnecessária complexidade na sua construção, a leitura não é rápida, além disso, possivelmente você teria dificuldades para escrever testes unitários que cubram todas as possibilidades.

Isso posto, vamos considerar algumas práticas úteis para codificarmos IFs.

“Escreva primeiro o caminho normal de execução, só depois escreva os casos alternativos.”

O ensinamento aqui é que devemos nos preocupar em escrever primeiros as condições normais de uso, ou os caminhos esperados de execução de um código. Após isso devemos pensar em escrever as condições de exceção. Essa prática vai muito bem, aplicada juntamente, com a seguinte:

“Ordene os testes pela frequência.”

Ou seja, procure colocar os testes que tem a maior chance de ocorrer antes dos demais. Isso é interessante inclusive para quem lê o código, posteriormente, pois terá uma ideia rápida das condições mais importantes.

“Simplifique checagens complicadas com variáveis e funções booleanas.”

Em detrimento de algumas linhas a mais, podemos melhorar a leitura do código usado anteriormente fazendo conforme vemos abaixo.

bool IsWordNotEmpty = (txtWord.Text != "");
bool IsExistsWordsList = (cblWords.Items.Count != 0);
bool IsFilterOk = IsWordNotEmpty || IsExistsWordsList;

bool IsAliasOk = VerifyAlias(txtAlias.Text, true);
bool IsKeywordOk = !IsWordNotEmpty ? true : VerifyKeyword(txtWord.Text);

if (IsFilterOk && IsAliasOk && IsKeywordOk)
{
    // Código aqui...
}

Observe que foram criadas algumas variáveis booleanas para dividirmos o resultado de cada parte da checagem em etapas. Na linha 1 eu checo se o textbox txtWord possui algum valor. Na linha seguinte verifico se existe algum item no combobox list cblWords. Na linha 3 foi criada uma variável booleana que armazena o resultado da verificação das duas primeiras “etapas”, pois no exemplo deste código, tanto a primeira ou a segunda satisfazem o esperado. Reparem que podemos utilizar as variáveis a partir daqui. Por fim, as linhas 5 e 6 fazem o mesmo processo para as demais verificações. Para melhorar a leitura do código devemos colocar nomes mais significativos. Para não ter que entrar nos detalhes de implementação deste negócio, vamos assumir apenas uma notação com “Ok” para as variáveis, como em IsFilterOk, IsAliasOK e IsKeywordOk.

Como resultado, podemos ver na linha 8 um IF muito simples, contendo apenas três condições, bastante auto-explicativas. Como vantagem desse método, você pode utilizar as variáveis com os resultados em outras checagens posteriores, dentro do mesmo contexto.

A quebra da checagem em variáveis e expressões booleanas podem realmente simplificar uma condicional IF complexa, tornando o código mais elegante. Mas, como nem tudo são flores, pode trazer outros problemas. Você pode, por exemplo, ter um resultado inválido se durante o código alguma condição for alterada e a variável booleana não. Outro grande problema é a realização de trabalho desnecessário.

Veja essas outras duas dicas na codificação de IFs:

“Pare o teste quando você obtiver a resposta.”


“Utilize avaliação preguiçosa.”


Caso a linguagem não suporte as chamadas “avaliações de curto-circuito” é melhor evitar uso de “AND” e “OR”. Por exemplo, no código a seguir:

if (txtWord.Text != "" && VerifyKeyword(txtWord.Text))
{
    // Código aqui...
}

Se a primeira condição for falsa, consequentemente o IF já falhou, então não é necessário checar a segunda condição VerifyKeyword(txtWord.Text). Esse é o comportamento padrão do C#, mas em algumas linguagens isso não ocorre, sendo preferível você codificar da seguinte forma:
 

if (txtWord.Text != "")
{
	if (VerifyKeyword(txtWord.Text))
         {
              // Código aqui...
         } 
}

 

Seguindo a última dica, utilizar uma avaliação preguiçosa é fazer algo semelhante às estratégias just-in-time, que executam o trabalho no momento mais próximo de quando ele é necessário. Isso significa não fazer trabalhos “caros” antes do tempo, ou da necessidade. Voltando ao código que utilizava checagens com booleanas, é o caso das seguintes linhas:
 

bool IsAliasOk = VerifyAlias(txtAlias.Text, true);
bool IsKeywordOk = !IsWordNotEmpty ? true : VerifyKeyword(txtWord.Text);


Se as funções VerifyKeyword e VerifyAlias realizam uma operação dispendiosa, como por exemplo um full scan numa matriz muito grande, não vale a pena utilizar uma variável booleana para armazenar esses resultados se temos boas chances dessas checagens nem ao menos serem necessárias.

Percebemos então, que o satisfatório é acharmos um equilíbrio entre todas essas abordagens, escolhendo o que vale mais a pena em cada caso. Ironicamente, o primeiro código apresentado não causa problemas maiores que o de legibilidade (claro, dentro do contexto em que é utilizado), muito graças ao C#, que interrompe as checagens do IF quando necessário.

O assunto poderia se estender mais, pois muitas discussões ainda podem ser adicionadas aqui. Mas o que eu queria com esse post é suscitar o interesse em boas práticas de programação. Vimos que até mesmo nas instruções mais cotidianas, podemos incorrer em erros capciosos, portanto, fique atento.

C#



Duck Typing com C# 4.0 e Dynamic Type

06/06/2010 21:22

O estilo Duck Typing é muito comum para programadores de linguagens dinâmicas, como por exemplo, Ruby. Ele permite que um objeto seja passado para um método que espera um certo tipo, mesmo que o objeto não seja deste tipo. Popularmente, o Duck Typing é definido pela frase a seguir:

If it walks like a duck and quacks like a duck, it must be a duck.

Numa tradução livre, seria algo como: “Se isso anda como um pato e fala como um pato, isso deve ser um pato”. Observe com bastante atenção o código abaixo. Repare que da linha 24 a 27 o método FazerQuack recebe um objeto do tipo dynamic, e usa a propriedade FazQuack() desse objeto. Não importa se o objeto é do tipo Pato ou Humano, desde que ele “faça quack” ele atenderá ao objetivo do método.

Experimente descomentar o código das linhas 18 e 19, e observe que ocorrerá um erro. Isso porque ao utilizarmos o tipo dynamic a checagem será realizada apenas em tempo de execução.

using System;

public class Program
{
    static void Main()
    {
        Executar();
    }

    private static void Executar()
    {
        var pato = new Pato();
        var homem = new Humano();

        FazerQuack(pato);
        FazerQuack(homem);

        //var cao = new Cachorro();
        //FazerQuack(cao);  // Essa linha gera um erro.

        Console.ReadKey();
    }

    private static void FazerQuack(dynamic ser)
    {
        ser.FazQuack();
    }
}

internal class Pato
{
    public void FazQuack()
    {
        Console.WriteLine("Quack!");
    }
}

internal class Humano
{
    public void FazQuack()
    {
        Console.WriteLine("Eu sei falar 'Quack!'");
    }
}

internal class Cachorro
{
    public void FazAu()
    {
        Console.WriteLine("Au!");
    }
}


Esse tipo de abordagem nos faz refletir sobre o equilíbrio entre o valor da tipagem forte e a produtividade de trabalharmos em um contexto dinâmico. Com o advento do C# 4.0 e suas bibliotecas dinâmicas, ficou bem mais fácil adotar esse estilo de programação.

Para exemplificar um dos possíveis problemas do Duck Typing, imagine termos métodos com o mesmo nome mas que não desempenham a mesma atividade. Claro que isso dependerá de quem estiver programando, mas o fato é que quando trabalhamos com dinamismo temos muito poder nas mãos, e erros serão mais desastrosos ao código do que dentro de um contexto estático e fortemente tipado.

O código-fonte de exemplo demonstrado pode ser baixado aqui:

C# ,



Fluent Interfaces e Method Chaining

01/06/2010 11:33

Conversando com um amigo que não conhecia Fluent Interfaces, acabei criando um exemplo simples para explicar o conceito, e achei bacana postar aqui a solução. Fluent Interfaces (ou interfaces fluentes) é uma técnica para escrita de interfaces que descrevam suas classes, métodos e propriedades. Elas recebem este nome porque a sua semântica é clara, seja individualmente, seja seguindo a sequência lógica da execução.

Em C#, a base para construção de Fluent Interfaces é o suporte oferecido pela linguagem ao chamado Method Chaining (ou “encadeamento de métodos”, termo cunhado por Martin Fowler, por volta de 2006).

Abaixo vemos um exemplo de utilização de código com Fluent Interfaces. Observe que da linha 10 a 13 a leitura do código fica muito simplificada, pois codificamos da mesma forma que encadeamos as ações a serem feitas. Ou seja, o código além de semântico é fluente.

using System;

namespace Estudo.FluentInterface.App
{
    class Program
    {
        static void Main()
        {
            // Escrita de código fluente.
            var vendedora = new Vendedora()
                .Oferecer(Produto.Fogao)
                .ComDescontoDe(15)
                .ValendoAte(new DateTime(2010, 7, 1));

            vendedora.Escrever();
            vendedora.Falar();

            Console.ReadKey();
        }
    }
}

 
O que vemos no código acima é um encadeamento de métodos da classe Vendedora. Mas como isso é possível?

O código abaixo mostra como foi criada a interface utilizada pela classe Vendedora. Note que ao criarmos os métodos “Oferecer”, “ComDescontoDe” e “ValendoAte” como sendo do tipo IFluentConversa estamos criando o encadeamento dos métodos, por inferência de tipos.
 

using System;

namespace Estudo.FluentInterface
{
    public interface IFluentConversa
    {
        IFluentConversa Oferecer(Produto produto);
        IFluentConversa ComDescontoDe(decimal desconto);
        IFluentConversa ValendoAte(DateTime data);

        void Falar();
        void Escrever();
    }
}


A seguir, temos a enumeração de produtos existentes.

namespace Estudo.FluentInterface
{
    public enum Produto
    {
        Cafeteira,
        Liquidificador,
        Batedeira,
        Fogao
    }
}

 
Abaixo vemos como fica a implementação da classe Vendedora. Apenas para tornar o exemplo um pouquinho mais interessante, o método Falar utiliza o a classe SpeechSynthesizer (disponível no Windows 7) que permite informarmos um texto para o sintetizador de voz do Windows para que o mesmo seja narrado por uma mulher (“Microsoft Anna”).

using System;
using System.Text;
using System.Speech.Synthesis;

namespace Estudo.FluentInterface
{
    public class Vendedora : IFluentConversa
    {
        Produto produto;
        decimal desconto;
        DateTime data;

        public IFluentConversa Oferecer(Produto produto)
        {
            this.produto = produto;
            return this;
        }

        public IFluentConversa ComDescontoDe(decimal desconto)
        {
            this.desconto = desconto;
            return this;
        }

        public IFluentConversa ValendoAte(DateTime data)
        {
            this.data = data;
            return this;
        }

        public void Falar()
        {
            var speech = new SpeechSynthesizer();
            speech.SelectVoice("Microsoft Anna");
            speech.Speak(MontarTexto());
        }

        public void Escrever()
        {
            Console.Write(MontarTexto());
        }

        private string MontarTexto()
        {
            var texto = new StringBuilder();
            texto.Append("Você gostaria de comprar ");

            switch (produto)
            {
                case Produto.Batedeira: texto.Append("uma batedeira "); break;
                case Produto.Cafeteira: texto.Append("uma cafeteira "); break;
                case Produto.Fogao: texto.Append("um fogão "); break;
                case Produto.Liquidificador: texto.Append("um liquidificador "); break;
            }

            texto.Append("com incríveis " + desconto.ToString() + "% de desconto? ");
            texto.Append("Essa promoção é valida até " + data.ToShortDateString() +  ".");

            return texto.ToString();
        }
    }
}


Apesar de simples, implementar Fluent Interfaces traz suas particularidades e questões que precisam ser avaliadas. Elas podem ser uma boa opção para criação de DSL’s ou para descrever abstrações de uso de uma API. Em contrapartida, pode ser trabalhoso manter todas as chamadas de métodos encadeadas. Outro problema comum é que podemos não entender quando e qual método finaliza a cadeia de ações. Por conta disso, muitos criticam o uso de Fluent Interfaces por elas dificultarem a compreensão de conceitos de design e até mesmo de orientação a objetos.

Abaixo, você pode fazer o download da solução com este exemplo.

C#



Encontrando números em uma string com Regex

09/09/2009 22:18

Como regex são úteis! Quer ver um exemplo? Hoje eu precisava encontrar números inteiros dentro de uma string, queria fazer de um jeito rápido e fácil e sugeriram-me usar a classe Regex do System.Text.RegularExpressions.

O código ficou muito simples, abaixo coloco um exemplo parecido com o que utilizei:

string texto = "Quero achar os números 5 e 255!";

Regex regex = new Regex(@"\d+");
Match match = regex.Match(texto);

Console.WriteLine(match.Value);
Console.WriteLine(match.NextMatch().Value);

Neste caso era de conhecimento prévio que haviam apenas dois números na string por isso foi possível utilizar NextMatch() para retornar a próxima ocorrência encontrada. Uma maneira mais elegante seria utilizar a classe MatchCollection, conforme demonstrado a seguir:

string texto = "Quero achar os números 5, 78, 67 e 255!";

Regex regex = new Regex(@"\d+");
MatchCollection match = regex.Matches(texto);

if (match.Count > 0)
{
    for (int c = 0; c < match.Count; c++)
    {
        Console.WriteLine(match[c].Value); 
    }
}

Dessa forma não importa o número de ocorrências encontradas. A classe Match também é capaz de retornar a posição onde o número foi encontrado.

Quer encontrar uma data ao invés de um número? Monte uma expressão regular pra isso. ;)

C#