Infra - Linux

Criando Bibliotecas de Software Reusáveis

Bibliotecas de software fornecem funcionalidade para desenvolvedores de aplicações. Elas consistem de código reusável que os desenvolvedores podem utilizar em seus projetos. Bibliotecas de software direcionadas para o Linux estão usualmente disponíveis tanto na forma binária como na de código fonte...

por Rob Tougher



1. Introdução

Bibliotecas de software fornecem funcionalidade para desenvolvedores de aplicações. Elas consistem de código reusável que os desenvolvedores podem utilizar em seus projetos. Bibliotecas de software direcionadas para o Linux estão usualmente disponíveis tanto na forma binária como na de código fonte.

Uma biblioteca de software bem escrita:

  • é fácil de usar
  • funciona sem falhas
  • fornece informação de erro detalhada

Este artigo descreve os princípios de criação de bibliotecas acima e dá exemplos em C++.

Este Artigo É Para Você?

Crie bibliotecas de software somente quando for necessário. Pergunte a si mesmo estas questões antes de prosseguir:

  • Alguém (inclusive você) vai precisar da funcionalidade X em aplicações futuras?
  • Se vai, já existe uma biblioteca que implementa a funcionalidade X?

Se ninguém vai precisar da funcionalidade que você está desenvolvendo ou já existe uma biblioteca de software que a implementa, não crie uma nova biblioteca.

2. Fazendo-a Fácil de Usar

O primeiro passo ao criar uma biblioteca de software é projetar a sua interface. Interfaces escritas em linguagens de instruções, como o C, contém funções. Interfaces escritas em linguagens orientadas a objetos, como C++ e Python, podem conter tanto funções quanto classes. Lembre-se deste lema quando projetar sua interface:

  • Quanto mais fácil de usar, melhor

Como um projetista de bibliotecas, eu sou constantemente desafiado a encontrar o equilíbrio correto entre funcionalidade e facilidade de uso. O lema acima me ajuda a resistir a adicionar funcionalidade demais aos meus projetos.

Siga as diretrizes seguintes e você estará bem.

2.1 Mantendo-a Simples

Quanto mais complexa a biblioteca, mais difícil é usá-la.

  • Mantenha-a Simples, Estúpido

Eu encontrei recentemente uma biblioteca C++ que consistia de uma classe. Esta classe continha 150 métodos. 150 métodos! O projetista era muito provavelmente um veterano de C usando C++ - a classe atuava como um módulo de C. Devido a esta classe ser tão complexa, foi muito difícil aprendê-la. Evite complexidade nos seus projetos e suas interfaces serão mais limpas e mais fáceis de entender.

2.2 Sendo Consistente

Os usuários aprendem interfaces consistentes mais facilmente. Após aprender as regras uma vez, ele se sentem confiantes ao aplicar aquelas regras para todas as classes e métodos, mesmo se eles não usaram aquelas classes e métodos antes.

Um exemplo do qual sou culpado envolve métodos de acesso públicos a variáveis privadas. Algumas vezes eu faço o seguinte:

class point
{
public:
  int get_x() { return m_x; }
  int set_x ( int x ) { m_x = x; }

  int y() { return m_y; }

private:
  int m_x, m_y;
};

Você vê o problema aqui? Para o membro m_x, o método de acesso público é "get_x()" mas, para o membro m_y, o método de acesso público é "y()". Esta inconsistência gera mais trabalho para os usuários - eles têm que ver a definição de cada método de acesso antes de usá-lo.

Aqui está outro exemplo de uma interface inconsistente:

class DataBase
{
public:

  recordset get_recordset ( const std::string sql );
  void RunSQLQuery ( std::string query, std::string connection );

  std::string connectionString() { return m_connection_string; }

  long m_sError;

private:

  std::string m_connection_string;
};

Você pode achar os seus problemas? Eu posso pensar pelo menos nestes pontos:

  • Métodos e variáveis não são nomeados de modo consistente
  • Dois termos diferentes, sql e query, são usados para representar uma seqüência SQL
  • m_sError não tem um método de acesso público
  • get_recordset() não tem uma connection na sua lista de argumentos

Aqui está uma versão revisada que resolve estes problemas:

class database
{
public:

  recordset get_recordset ( const std::string sql );
  void run_sql_query ( std::string sql );

  std::string connection_string() { return m_connection_string; }
  long error() { return m_error; }

private:

  std::string m_connection_string;
  long m_error;
};

Mantenha as suas interfaces tão consistentes quanto possível - seus usuários vão achá-las muito mais fáceis de aprender.

2.3 Mais Intuitiva

Projete uma interface para que ela trabalhe da forma que você esperaria que ela trabalhasse do ponto de vista de um usuário - não projete-a com a implementação interna em mente.

Eu acho que a maneira mais fácil de projetar uma interface intuitiva é escrever código que vai usar a biblioteca antes de realmente escrever a biblioteca. Isto me força a pensar sobre a biblioteca do ponto de vista do usuário.

Vamos ver um exemplo. Recentemente, eu estava pensando em escrever uma biblioteca de criptografia baseada em OpenSSL. Antes de pensar sobre o projeto da biblioteca, eu escrevi alguns trechos de código:

crypto::message msg ( "My data" );
crypto::key k ( "my key" );

// Algoritimo blowfish
msg.encrypt ( k, crypto::blowfish );
msg.decrypt ( k, crypto::blowfish ):

// Algoritmo rijndael
msg.encrypt ( k, crypto::rijndael );
msg.decrypt ( k, crypto::rijndael ):

Este código me ajudou a pensar sobre como eu deveria projetar a interface - ele me pôs no lugar do usuário.

Se eu decidir implementar esta biblioteca, meu projeto vai fluir a partir dessas idéias iniciais.

3. Testando Exaustivamente

Uma biblioteca de software deve funcionar sem falhas. Bem, sem falhas não, mas tão perto de sem falhas quanto possível. Os usuários de uma biblioteca precisam saber que a biblioteca está executando as suas tarefas corretamente.

  • Por que usar uma bibliteca de software se ela não funciona corretamente?

Eu testo minhas bibliotecas de software usando scripts automatizados. Para cada biblioteca, eu crio uma aplicação correspondente que exercita todos os recursos da biblioteca.

Por exemplo, digamos que eu decidi desenvolver a biblioteca de criptografia apresentada na seção anterior. Minha aplicação de teste se pareceria com a seguinte:

#include "crypto.hpp"

int main ( int argc, int argv[] )
{
  //
  // 1. Criptografa, descriptografa e verifica
  //    dados da mensagem.
  //
  crypto::message msg ( "Hello there" );
  crypto::key k ( "my key" );

  msg.encrypt ( k, crypto::blowfish );
  msg.decrypt ( k, crypto::blowfish );

  if ( msg.data() != "Hello there" )
    {
      // Erro!
    }

  //
  // 2. Cifra com um algoritmo,
  //    decifra com outro e verifica
  //    dados da mensagem.
  //

  // etc....
}

Eu executaria ocasionalmente esta aplicação para ter certeza de que a minha biblioteca de software não tem qualquer erro grosseiro.

4. Fornecendo Informação de Erro Detalhada

Os usuários precisam saber quando uma biblioteca de software não pode desempenhar as suas tarefas corretamente.

  • Alerte o usuário quando houver um problema

Bibliotecas de software escritas em C++ usam exceções para passar informação aos seus usuários.

Considere o seguinte exemplo:

#include <string>
#include <iostream>

class car
{
public:
  void accelerate() { throw error ( "Could not accelerate" ); }
};

class error
{
public:
  Error ( std::string text ) : m_text ( text ) {}
  std::string text() { return m_text; }
private:
  std::string m_text;
};


int main ( int argc, int argv[] )
{
  car my_car;

  try
    {
      my_car.accelerate();
    }
  catch ( error& e )
    {
      std::cout << e.text() << "\n";
    }
}

A classe car usa a palavra-chave throw para alertar a rotina de chamada para uma situação errônea. A rotina de chamada captura esta exceção com as palavras-chave try e catch e lida com o problema.

5. Conclusão

Neste artigo eu expliquei os princípios importantes de uma biblioteca de software bem escrita. Espero que eu tenha explicado tudo de forma clara o suficiente de forma que você possa incorporar esses princípios em suas próprias bibliotecas.

Traduzido para Português por Antonio Sergio Mello E Souza

Copyright © 2002, Rob Tougher

Rob Tougher

Rob Tougher - Rob é um engenheiro de software em C++ na área da Cidade de Nova York.