terça-feira, 30 de janeiro de 2007

Dica C++: Onde constará o const ?

Olhe para este código:

 

char nome[] = "Mickey Mouse";

const char *pc = nome;

char *const cp = nome;

const char *const cpc = nome;

 

Sabe dizer ao certo o que as três últimas constantes declaradas significam, na prática ?

Uma dica: tenta lê-las da direita para esquerda.

Nos casos exemplificados, fica:

  • const char *pc pode ser lido como "pc é um ponteiro para um caractere constante".
  • char* const cp pode ser lido como "cp é um ponteiro constante para um caractere".
  • const char* const cpc pode ser lido como "cpc é um ponteiro constante para um caractere constante".

Vamos a explicação sintática, pra clarear mais o significado:

  • Colocando const antes de um ponteiro (*), você torna o objeto constante - e não o ponteiro. (Ex: const char *pc faz o valor do caracterer ser constante, não o ponteiro.)
  • Para o ponteiro ser constante, use *const.
  • Não existe const*.

Mas ora bolas - você pode estar perguntando - para que serve um objeto constante ? E para que um ponteiro constante ?

Sua utilidade principal é em parâmetros de funções. Quando você declara um objeto constante, ele não pode ser modificado pela função. Logo, como a função fica proibida de modificar o objeto, você tem a garantia que o objeto passado por parâmetro ficou intacto após ser manipulado pela mesma. Ex.:

void Imprimir(const char *linha);

(No exemplo, linha não poderá ser modificada pela função pois ela é uma constante.)

Já um ponteiro constante garante que o ponteiro não apontará para mais nenhum objeto além do que foi inicializado. Ex.:

 

int a = 10, b = 20, c = 30;
int *pa = &a;
int *const pb = &b;

*pa = 50; // OK - pode mudar o valor
*pb = 90; // OK - pode mudar o valor

pa = &c; // OK - pa pode apontar para outro
pb = &a; // ERRO - pb só pode apontar para b

 

Bem aplicado, isto pode auxiliar a evitar erros no código.

Veja o caso de acima introduzirmos um ponteiro constante para uma constante:

 

const int *const pc = &c;

*pc = 70; // ERRO - nao pode mudar o valor
pc = &a; // ERRO - nao pode apontar para outro

 

(Nem o valor do objeto apontado nem o objeto apontado podem mudar.)

Coisas assim, são úteis para tornar nosso código menos propenso a erros (limitando possibilidades de atribuições incorretas), evitando maiores dores de cabeça na hora da depuração.

domingo, 28 de janeiro de 2007

Truques com Baralho

Dez truques de mágica com baralho revelados. Nos vídeos (em inglês) é ensinado como fazer os truques:

Top 10 Magic Tricks Tutorial Videos

Aplicando Design by Contract

No último post sobre DbC, aprofundei seus conceitos e levantei algumas considerações necessárias para sua aplicação. Agora, introduzirei alguns exemplos práticos que permitirão materializar os conceitos vistos e dar início à prática de DbC.

 

DbC em linguagens que suportam nativamente

Em linguagens como EiffelD, existem estruturas na linguagem que permitem especificar formalmente um contrato.

Por exemplo, em D:

 

class Hora
{
    invariant
    {
        assert( _hora >= 0 && _hora <= 23 );
        assert( _minuto >= 0 && _minuto <= 59 );
        assert( _segundo >= 0 && _minuto <= 59 );
    };
 
    this(
      const int hora,
      const int minuto,
      const int segundo
      )
    {
        _hora = hora;
        _minuto = hora;
        _segundo = segundo;
    }
 
    this()
    {
        this( 0, 0, 0 );
    }
 
    ~this()
    {
    }
 
    int hora() { return ( _hora ); }
    void hora(int novaHora) { _hora = novaHora; }
 
    int minuto() { return ( _minuto ); }
    void minuto(int novoMinuto) { _minuto =         novoMinuto; }
 
    int segundo() { return ( _segundo ); }
    void segundo(int novoSegundo) { _segundo =     novoSegundo; } 
 
private:
    int _hora;
    int _minuto;
    int _segundo;
};

 

Aqui, a estrutura invariant definiu que os atributos privados _hora, _minuto e _segundo deverão sempre estar dentro de uma faixa de valores determinada, para todas as instâncias que a classe venha a ter. Enquanto algum método é executado, a verificação do contrato na invariant é desativado. Depois da execução, essas condições serão novamente verificadas.

Estas estruturas de invariáveis somente podem testar o valor de atributos e métodos que não sejam públicos. Para os métodos, caso haja a chamada de um método público dentro de um método privado, a chamada do último se tornará inválida para a invariável, gerando um erro.

Quanto a definição de contratos dentro de métodos, geralmente há uma estrutura como a exemplificada na linguagem D:

 

string Hora::FormatarComoString(
  const string &formato)
{
    in
    {
        // PRE-CONDICOES
        bool formatoValido =
          Formatador::FormatoHoraValido( formato );
  
        assert( formatoValido );
    }
    out
    {
        // POS-CONDICOES
    }
    body
    {
         // IMPLEMENTACAO DO METODO
  
        return ( Formatador::FormatarHora(
          *this, formato ) );
    }
}

A linguagem apresenta três blocos nos quais separa as pré-condições (in), pós-condições (out) e a implementação do método em si (body). Ambos os blocos de pré e pós-condições são opcionais, podendo ser declarados ou não. E ao compilar o programa em modo de liberação (release mode), o código destes dois blocos (ou seja, os contratos estabelecidos para o método) são automaticamente ignorados.

 

DbC em linguagens que não suportam nativamente

Nas demais linguagens, infelizmente estas estruturas não existem e, para implementarmos contratos, temos que usar de recursos específicos de cada linguagem e tentar simular o mesmo funcionamento.

Sobre pré e pós-condições, se a linguagem que estivermos usando possuir uma rotina como assert, podemos simulá-las:

// C++
string Hora::FormatarComoString(
  const string &formato)
{
    // PRE-CONDICOES ..............................
    bool formatoValido =
      Formatador::FormatoHoraValido( formato );
    assert( formatoValido );

    // IMPLEMENTACAO DO METODO ....................
    string resultado = Formatador::FormatarHora(
      *this, formato );

    // POS-CONDICOES ..............................
    // (validaria o resultado, caso preciso)
    // ............................................

    return ( resultado );

}

Repare que, diferente do exemplificado na linguagem D, em C++ tivemos de ter declarar as pós-condições após a implementação do método. O retorno do método só pode ser feito após a validação das pós-condições, caso hajam. Logo, o código deve ser implementado de forma a sempre poder fazer a validação das pós-condições - o que pode se tornar mais difícil quando o método precisa retornar após a validação de condições que não estão presentes no contrato. Neste caso, é preciso desviar a execução do código para o ponto onde as pós-condições são verificadas.

Com as invariáveis, é recomendado colocar as verificações dentro de um método virtual protegido (para poder ser redefinido por classes filhas) e chamar este método após as pós-condições:

// C++
string Hora::FormatarComoString(
  const string &formato)
{
    // PRE-CONDICOES ............................
    bool formatoValido =
      Formatador::FormatoHoraValido( formato );
    assert( formatoValido );

    // IMPLEMENTACAO DO METODO ..................
    string resultado = Formatador::FormatarHora(
      *this, formato );

    // POS-CONDICOES ............................
    // (validaria o resultado, caso preciso)    
       

    // INVARIAVEIS .............................        VerificarInvariaveis();
    // .........................................

    return ( resultado );   

}


// Metodo virtual protegido
void Hora::VerificarInvariaveis() const
{
    assert( _hora >= 0 && _hora <= 23 );
    assert( _minuto >= 0 && _minuto <= 59 );
    assert( _segundo >= 0 && _minuto <= 59 );
}

 

Há o incômodo de ter que acrescentar o método VerificarInvariaveis em cada método da classe. Porém esta é uma forma fácil de acrescentar invariáveis ao código de sua linguagem.

 

Considerações sobre outras possibilidades

Em algumas linguagens é ainda possível usar de outros artifícios para simular o mesmo comportamento de DbC em linguagens como D ou Eiffel. Em C++, por exemplo, há algumas bibliotecas que tentam simular este comportamento através de macros:

Veja #1#2 e #3.

Em Java, há um framework que através de classes de proxy dinâmico, captura definições à la DbC que são declaradas em comentários do código - no formato JavaDoc - e assim possibilitam introduzir fácilmente contratos ao código.

Há diversas opções, dependendo da linguagem escolhida, que possibilitam a introdução de contratos. Vale a pena procurar por formas simples e que contenham boa portabilidade.

Além de bibliotecas e frameworks que simulem DbC, há algumas que simplesmente focam no uso de assertivas. Um bom exemplo é a biblioteca SmartAssert (C++). Ela acrescenta diversas opções em relação ao assert usual e assim facilita muito as coisas para o programador.

Qualquer que seja a linguagem e suas possibilidades, há sempre uma forma de introduzir o conceito de contratos e assim melhorar a qualidade do código escrito.

 

 

sexta-feira, 26 de janeiro de 2007

Interfaces Alternativas

Alguns vídeos interessantes sobre outras formas de interação homem-pc:

Grid Interface
Multi-Touch Interface
Mixed Reality Interface
XGL Desktop for Linux
Spectasia 3D

Facilitando o uso do computador

Recentemente conheci uma linha de software chamada Enso, da GetHumanized, que me chamou um pouco a atenção por se tratar de softwares voltados para melhorar a relação usuário-computador. Realmente, se pararmos pra pensar o quanto é complicado realizar algumas tarefas simples, corriqueiras, sem ter uma (boa) noção de informática básica, veremos o quanto isso torna difícil a adoção do uso do computador pelas pessoas.
É comum vermos pessoas mais velhas - ou não tão velhas assim - com dificuldade de compreender o funcionamento de um software aplicativo ou mesmo do sistema operacional. Isso não só esbarra na abertura de novos conceitos e no esforço pra compreender um mundo virtual cheio de metáforas e caminhos não triviais, mas principalmente na carga de memorização das tarefas e em sua burocracia.

Pra tentar solucionar esses problemas, há por aí algumas ferramentas que se integram ao sistema operacional para facilitar a realização das tarefas e tentar tornar a coisa mais fácil pro usuário. E não só pro usuário leigo ou mesmo básico. Pelo contrário, essas ferramentas podem ser ainda mais úteis para usuários avançados, que primam pela agilidade, já que estão cansados de ter sempre ter que seguir uma série de passos pra conseguir realizar seu trabalho.