sexta-feira, 16 de julho de 2010

Diretivas de Compilação - WARNINGS, HINTS e MESSAGES

$WARNINGS

Warnings são alertas emitidos durante a compilação que apontam para um potencial ou real problema no código fonte.
Algumas vezes, por exemplo, os warnings se referem a variáveis que foram declaradas mas que não estão sendo usadas. Esse é um caso onde não há um problema, mas mesmo assim vale a pena dar atenção para esse warning porque ele é um indício de que há espaço para tornar seu código fonte mais limpo/claro.
O ideal é nunca ter qualquer warning emitido durante a compilação.

Em certos casos o compilador emite alertas para um trecho de código do qual temos a certeza de estar correto, e que esse alerta poderia ser desconsiderado sem qualquer receio.
Para essa situação nós podemos usar a diretiva de compilação {$WARNINGS} para desabilitar a emisão dos warnings em um certo techo de código.

{$WARNINGS OFF}
// Seu código fonte
{$WARNINGS ON}

Uma outra abordagem mais recomendada para essa situação é desabilitar somente o warning específico a qual se tem ciência de não representar problema. Isso evita que outros warnings sejam desconsiderados não intencionalmente em decorrência de uma modificação futura nesse trecho de código, principalmente se o trecho de código for relativamente grande.

A sitaxe é:
{$WARN identifier OFF}
// Seu código fonte
{$WARN identifier ON}
onde "identifier" é o símbolo correspondente do warning.

Considere o exemplo abaixo:

unit uTest;

interface
  procedure MyTest; experimental;

implementation

procedure MyTest;
begin
  // Código fonte
end;

end.

Para quem não sabe "experimental" é uma palavra reservada que pode ser usada para métodos, funções, units e outros com a intenção clara de sinalizar que tal implementação está, como o próprio nome sugere, em face experimental e que deve ser cauteloso no seu uso.
Ao compilar esse exemplo será apresentado uma mensagem semelhante a essa:

[DCC Warning] Project2.dpr(13): W1003 Symbol 'MyTest' is experimental

O código de identificação desse warning, como mostra a mensagem, é 1003 e o correspondente símbolo é  SYMBOL_EXPERIMENTAL. Dessa forma, caso queira anular tal alerta em um trecho de código, basta usar:

{$WARN SYMBOL_EXPERIMENTAL OFF}
// trecho de código
{$WARN SYMBOL_EXPERIMENTAL ON}

Não tenho intenção de listar todos os símbolos pré-definidos que o Delphi possui, mas vou detalhar alguns poucos:
  • HIDING_MEMBER: Warning produzido quando uma descendente declara uma nova propriedade com o mesmo nome que uma propriedade do ancestral.
  • HIDDEN_VIRTUAL: Warning produzido quando um descendente declara um método, não especificado como override, e com o mesmo nome de um método virtual do ancestral.
  • HRESULT_COMPAT: Warning sobre o uso do tipo "Integer" no lugar de um HRESULT.
  • STRING_CONST_TRUNCED: Todos os warnings sobre instâncias na qual o compilador atribui um string literal ou um valor constante para um short string cujo tamanho é menor do que o suficiente.
  • SYMBOL_DEPRECATED: Habilita/desabilita todos os warnings sobre símbolos definidos como deprecated na unit corrente.
  • SYMBOL_EXPERIMENTAL: Habilita/desabilita todos os warnings sobre símbolos definidos como experimental na unit corrente.
  • SYMBOL_PLATFORM: Habilita/desabilita todos os warnings sobre símbolos definidos como platform na unit corrente.
  • SYMBOL_LIBRARY: Habilita/desabilita todos os warnings sobre símbolos definidos library na unit corrente.
  • UNIT_DEPRECATED: Habilita/desabilita todos os warnings relacionadas a units definidas como deprecated.
  • UNIT_EXPERIMENTAL: Habilita/desabilita todos os warnings relacionadas a units definidas como experimental.
  • UNIT_LIBRARY: Habilita/desabilita todos os warnings relacionadas a units definidas como library.
  • UNIT_PLATFORM: Habilita/desabilita todos os warnings relacionadas a units definidas como platform.
  • ZERO_NIL_COMPAT: Warnings sobre instâncias na qual o compilador converte o valor 0 como sendo um nil.
  • GARBAGE: Warnings produzidos quando há caracteres diferentes de espaços em branco após o marcador 'end.'.
Esses e todos os demais símbolos de alertas pode ser conferido na unit DCCStrs.pas, que faz parte da biblioteca do Delphi (provavelmente em %ProgramFiles%\...\sources\toolsapi\DCCStrs.pas).

Pontos importantes
Quando uma diretiva {$WARNINGS} ou {$WARN} é utilizada para alterar o estado padrão de um determinado tipo de alerta, esse estado permanece até que seja encontrada outra diretiva de mesmo tipo modificando seu estado ou até que seja encontrado o final da unit.
Uma diretiva de compilação só tem efeito dentro do contexto da unit a qual é utilizada e a partir do ponto onde estão definidas dentro do arquivo. Ao ser passado o contexto para outra unit, o estado da diretiva é revertido para sua condição padrão.

Mas se a intenção é habilitar/desabilitar um determinado warning para um projeto inteiro, como fazer?
Vejo duas formas de fazer isso. Uma usando um arquivo auxiliar e outra usando o IDE o Delphi para selecionar quais warnings devem ou não ser considerados.
A primeira solução consiste em você utilizar um arquivo contendo as opções desejadas e acrescentar uma diretiva {$INCLUDE filename} ou {$I filename} em todas as unit do projeto.
A diretiva {$INCLUDE} tem o objetivo de copiar o conteúdo de um determinado arquivo para o ponto onde ela está definida.
No entanto, essa solução não faz muito sentido em ser utilizada para um caso isolado. Faz mais sentido quando se tem um arquivo central com várias defines, constantes, ... onde este é incluído (com {$I}) em todos os arquivos do projeto.
Imagino que já devem ter visto em algum projeto, por exemplo, o uso de um arquivo Types.inc com todas as definições usadas. Esse é um bom ponto para aplicar esse caso.)

A segunda forma, mais recomendada e também mais fácil é marcar/desmarcar quais warnings deverão ou não ser emitidos no próprio IDE do Delphi.
Para isso acesse a opção do menu Project -> Options e vá até a aba "Hints and Warnings".
Bom, a partir daqui não há mais razão de detalhar os passos. O processo é bem intuitivo.


Caso queira conhecer um pouco mais sobre os tipos de mensagens de erros e warnings tratados pelo Delphi, acesse o link http://docwiki.embarcadero.com/RADStudio/en/Error_and_Warning_Messages_(Delphi)_Index.



Estados dos Alertas
Aqui está outro ponto muito importante e que nem sempre é mencionado.
O warning não possui somente dois estados (ON e OFF), mas sim três. Exatamente!
O terceiro estado é ERROR que instrui o compilador a tratar o warning como sendo um erro, ou seja, aumenta o nível de atenção daquele tipo de alerta.

{$WARN SYMBOL_EXPERIMENTAL OFF}
{$WARN SYMBOL_EXPERIMENTAL ON}
{$WARN SYMBOL_EXPERIMENTAL ERROR}

Essa opção pode ser bastante útil quando, por exemplo, há muitas ocorrências de warning no projeto e você quer dar atenção para um tipo específico.


$MESSAGES

O Delphi possui a diretiva de compilação $Message que nos permite instrumentar o código fonte de forma a salientar pontos importantes que devem ser observados quando o código for compilado.
Essa diretiva trabalha diretamente com o compilador o qual irá emitir o texto definido no corpo da definição tratando-a conforme seu nível atenção.

A sintaxe é a seguinte:

{$MESSAGE HINT|WARN|ERROR|FATAL 'Textstring'}

onde HINT|WARN|ERROR|FATAL é o nível de atenção/importância da mensagem e 'Textstring' é a mensagem que deve ser emitida pelo compilador.

O level da mensagem é opcional e, se não especificado, será assumido o valor padrão, ou seja, HINT. Já a sequência do testo é obrigatória e deve utilizar a sintaxe da string, ou seja, delimitada por aspas simples.
Destaco a importância de conhecer o significado que cada tipo de mensagem possui porque o compilador irá tratar essas mensagens da mesma forma como trata os alertas nativos.

HINT: Notificações quase sempre informativas e não incorrem risco

{$Message 'Essa mensagem é do tipo HINT'}
{$Message Hint 'Essa mensagem também é do tipo HINT'}

WARN: Notificação de alerta para um real ou potencial risco

{$Message Warn 'Essa mensagem é do tipo WARNING'}

Error: Falha de sintaxe ou semântica

{$Message Error 'Essa mensagem é do tipo ERROR'}

Fatal: Interrompe a compilação imediatamente

{$Message Fatal 'Essa mensagem é do tipo FATAL'}
 

$HINTS

Esse artigo tem explorado mais o lado técnico dos Warnings e Hints no Delphi do que propriamente sua utilização.
Esse é uma outra abordagem com ampla gama para exploração. Penso que poderia ser redigido um artigo enorme falando só das diversas formas de empregar, e de forma adequada, esse recurso que não é uma exclusividade do Delphi, muito pelo contrário.
Mas por enquanto iremos concluir esse texto com mais algumas notas sobre a diretiva {$HINT}.

O Delphi permite que a emissão de mensagens que são do tipo HINT possa ser habilita ou desabilitada através de:

{$HINTS ON}
// Trecho de código
{$HINTS OFF}

Vamos utilizar um exemplo simples.

procedure Teste;
var
  a, b: Integer;
begin
  {$HINTS ON}

  a := 1;
end;

Ao compilar, será apresentado duas mensagens do tipo hint.

[DCC Hint] uTest.pas(13): H2077 Value assigned to 'a' never used
[DCC Hint] uTest.pas(11): H2164 Variable 'b' is declared but never used in 'Teste'

Agora experimente substituir a linha onde aparece {$HINTS ON} por {$HINTS OFF} e recompile o código. Irá observar que ambas as mensagens não serão mais apresentadas.
Aqui a regra é a mesma. A diretiva é relevante apenas no contexto da unit e para as linhas abaixo.

Até a próxima.

segunda-feira, 12 de julho de 2010

Ranking de popularidade das linguagens de programação

Um assunto que frenquentemente faz parte das discussões dos grupos de desenvolvedores é o quanto uma determinada linguagem de programação está diceminada no mercado em um dado momento.
Muitas vezes embasadas em opiniões próprias ou simplesmente por questão de afinidade, a conversa tende a abordar o universo restrito ao grupo.
Mas você sabe realmente qual a porcentagem de adoção da linguaguem de programação que você utiliza?
Se você fosse investir seu tempo para aprender uma linguagem, mas gostaria que a escolha fosse consciente, por onde começar?

A tabela abaixo foi extraída na data de hoje (12-Julho-2010) do site TIOBE, que eu considero um ótimo referencial no assunto.



Position
Jul 2010
Position
Jul 2009
Delta in Position Programming
Language
Ratings
Jul 2010
DeltaJul 2009
11 Java 18.673%-1.78%
22 C 18.480%+1.16%
33 C++ 10.469%+0.05%
44 PHP 8.566%-0.70%
56 C# 5.730%+1.19%
65 (Visual) Basic 5.516%-2.27%
77 Python 4.217%-0.22%
88 Perl 3.099%-1.10%
921 Objective-C 2.498%+1.99%
109 JavaScript 2.432%-1.08%
1111 Delphi 2.323%+0.33%
1210 Ruby 1.982%-0.59%
1312 PL/SQL 0.772%-0.12%
1413 SAS 0.701%-0.09%
1515 Pascal 0.639%-0.07%
1617 Lisp/Scheme/Clojure 0.622%+0.01%
1720 MATLAB 0.581%+0.07%
1816 ABAP 0.548%-0.15%
1919 Lua 0.535%+0.00%
2028 PowerShell 0.493%+0.17%

Essa tabela é apenas uma das várias tabelas e gráficos estatísticos que são disponibilizados no site TIOBE.
Além do ranking atual é possível obter um histórico de longo prazo de cada uma das linguagens de programações apresentada na tabela das 20 primeiras posições, bastando clicar sobre o link em seus nomes (coluna "Programming Language").

Vamos aproveitar e ver o histórico dos últimos 8 anos com relação ao Delphi.

Para quem é desenvolvedor Delphi a alguns anos certamente irá saber qual a versão que vez tanto sucesso para proporcionar aquele pico, em 2004. Se não sabe, acessa a página da wikipedia.

Além do TIOBE, http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html, há outros sites onde também é possível conferir quais as linguagens de programação mais cultuadas.
Aqui destaco o LangPop.com (http://www.langpop.com/) que utiliza as informações de pesquisa realizadas em sites como Google, Yahoo, Google Code e vários outros, para montar os quadros de ranking das linguagens de programação mais procuradas.
Dependendo do local onde as informações foram coletadas, o ranking fica sensivelmente diferente. Nesse site você encontra também uma tabela que é o resultado normalizado de todas as outras (com valores bem menos tendenciosos).

Até a próxima.

domingo, 11 de julho de 2010

Segredos do ForEach

Você sabe como funciona e como utilizar o ForEach no Delphi?
O suporte ao ForEach foi adicionado no Delphi 2005 e tem como finalidade oferecer uma interface de acesso aos itens das coleções (listas, filhas, filhas, array, conjuntos, vetores, ...) de forma simplificada e sem se preocupar com contadores ou com índices.

A sintaxe do ForEach é:
for item in collection do
begin
  //
end;

Note que na sitaxe do comando aparece a palavara reservada "in", o que reforça a idéia de um laço de repetição destinado a trabalhar com conjuntos/coleções, e é exatamente essa idéia que se deve manter em mente ao utilizar esse recurso em seu código fonte.
Dependendo do tipo de coleção que se está trabalhando, o comando terá uma característica específica, mas sempre mantendo a finalidade original.

Vamos ver alguns exemplos de uso.

1. Uso em conjuntos
Esse é um dos usos mais freqüentemente encontrado nos códigos fontes e que particularmente encorajo sua adoção e emprego.
Para quem não está muito familiarizado com o uso de conjuntos, o exemplo abaixo pode parecer estranho, num primeiro momento, mas numa segunda olhada verá que é muito intuitivo.

type
  TWeekDay = (wdDomingo, wdSegunda, wdTerca, wdQuarta, wdQuinta, wdSexta, wdSabado);
  TWeek = set of TWeekDay;
const
  STRWEEKDAY: array[TWeekDay] of string =
  ('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab');
var
  lDay: TWeekDay;
  lWorkDays: TWeek;
begin
  lWorkDays := [wdSegunda, wdQuarta, wdSexta];

  for lDay in lWorkDays do
  begin
    ShowMessage(STRWEEKDAY[lDay]);
  end;
end;

O exemplo acima define o conjunto TWeek baseado nos dias da semana (TWeekDay) e usa essa definição para declarar a variável local lWorkDays.
Na primeira linha do bloco, nosso conjunto lWorkDays recebe a atribuição de três elementos, ou melhor, de três dias da semana e é utilizada no ForEach abaixo.
Como a coleção aqui é um conjunto, o laço de repetição do comando for ir percorrer todas as possíveis entradas do conjunto, mas somente executará as instruções do bloco quando a respectiva entrada realmente está presente no conjunto. Assim, no nosso exemplo, a função ShowMessage será chamada três vezes.
Reforço a vantagem do uso do ForEach com conjuntos porque além de intuitivo o código binário gerado é limpo, rápido e não consome recursos como o quando é utilizado com objetos.

2. Uso com strings
A utilização do ForEach com strings é um exemplo clássico do passado, mas que ao meu ver tem mais pontos negativos do que positivos.

const
  S: string = 'Using ForEach with strings';
var
  lChar: Char;
begin
  for lChar in S do
  begin
    if lChar in ['a','e','i','o','u'] then
    begin
      // ...
    end;
  end;
end;

Como dito anteriormente, o comando terá uma particularidade que depende do tipo de coleção utilizada, mas sempre centrado na idéia de acessar os elementos da coleção.
No exemplo acima nossa coleção abrange todo o espectro do tipo Char, nativo do Delphi, e o laço de repetição executará tantas vezes quanto for o número de caracteres da string.
Em outras palavras o comando for irá percorrer toda a string, atribuindo para a variável local lChar o valor de cada caracter.
Eu desencorajo a adoção dessa prática como alternativa para percorrer strings porque o código binário gerado é, do ponto de vista de micro-otimizações, bastante oneroso por requerer a manipulação do tipo string.

3. Uso com arrays estáticos
Também podemos utilizar o laço de repetição para percorrermos arrays de forma muito semelhante ao exemplo acima.

type
  TMonthDays = array[1..31] of Boolean;
var
  lWorkedDays: TMonthDays;
  lDay: Boolean;
  lCounter: Integer;
begin
  FillMemory(@lWorkedDays, SizeOf(lWorkedDays), 0);

  lWorkedDays[1] := True;
  lWorkedDays[2] := True;
  lWorkedDays[5] := True;

  lCounter := 0;
  for lDay in lWorkedDays do
  begin
    if lDay then
      Inc(lCounter);
  end;
  ShowMessage('Sum: ' + IntToStr(lCounter));
end;

Observe que nesse nosso exemplo o índice do array não é utilizado, mas apenas o valor de cada entrada.
Aqui temos uma situação que merece um certo destaque por permitir ao código fonte um incremento na clareza e legibilidade, evitando a declaração de variáveis locais e especificando explicitamente os limites do array.
Se considerarmos uma abordagem mais clássica usando o laço for comum como, por exemplo:
var
  i: Integer;
begin
  // ...
  for i = 1 to 31 do
    // ...
corremos o risco de introduzir um bug no sistema caso os limites do array seja moificado.
Obviamente podemos evitar esse tipo de bug removendo os hard codes,
  for i = Low(TMonthDays) to High(TMonthDays) do
mas ainda assim dependeríamos do uso de uma variável como contador do laço de repetição.

4. Uso com arrays dinâmicos
Essa é uma situação levemente diferente do exemplo acima onde, em vez de termos um array com limites bem definidos, temos um array dinâmico cujo tamanho pode variar inclusive durante a execução do ForEach.
Aqui novamente estamos considerando que saber o índice atual do laço de repetição não é requerido, mas somente o valor de cada entrada do array.

var
  lWorkedDays: array of Boolean;
  lDay: Boolean;
  lCounter: Integer;
begin
  SetLength(lWorkedDays, 31);

  lWorkedDays[0] := True;
  lWorkedDays[1] := True;
  lWorkedDays[4] := True;

  lCounter := 0;
  for lDay in lWorkedDays do
  begin
    if lDay then
      Inc(lCounter);
  end;
  ShowMessage('Sum: ' + IntToStr(lCounter));
end;

Uma particularidade do array dinâmico é que a primeira entrada tem índice zero (0).
Utilizando o ForEach para percorrer o array, seja estático ou dinâmico, o limite inicial e final é tratado de forma transparente pelo compilador.

5. Usando com objetos
Esse é o último caso de uso do ForEach apresentado nesse artigo e o que merece mais atenção.
Como os tipos nativos (conjuntos, strings e arrays) o Delphi tem total conhecimento de como devem ser manipulados. Já quando a coleção é uma objeto, essa premissa não é verdadeira e, nesse caso, é você quem deve instruir o Delphi a acessar os itens do objeto de forma adequada.

Observe o exemplo abaixo.
var
  lFiles: TStrings;
  lFilename: string;
begin
  // ...
  for lFilename in lFiles do
  begin
    // ...
  end;
end;

No código acima a variável lFiles está sendo usada para armazenar uma lista de nomes de arquivos que serão tratados, e o laço de repetição irá acessar cada item dessa lista e repassar à variável local lFilename o valor do item (nesse caso, a string com o nome do arquivo).
O mesmo caso é válido para outras classes nativas do Delphi como por exemplo TTreeNode, TActionList,  ...
Bom aí vem a pergunta. Como o Delphi sabe qual é a lista de elementos da minha coleção quando se trata de um objeto?
Se você for na definição da classe TStrings encontrará um método com o nome "GetEnumerator" cujo retorno é um objeto do tipo TStringsEnumerator. E é exatamente esse método que será usado pelo ForEach para acessar os elementos da lista.

Para ser mais específico, um pré-requisito para usar o ForEach com objetos (instâncias de classes) é justamente a existência e implementação do método GetEnumerator pela classe do objeto.
Se você tentar usar um objeto qualquer o próprio compilador irá notificar sobre esse requisito, apresentando uma mensagem semelhante:

[DCC Error] Unit1.pas(145): E2431 for-in statement cannot operate on collection type 'TMyList' because 'TMyList' does not contain a member for 'GetEnumerator', or it is inaccessible

Além disso, o método GetEnumerator não pode retornar qualquer valor, ele deverá retornar uma instância cuja classe possua dois métodos específicos (GetCurrent e MoveNext) que serão utilizados para instruir o ForEach a percorrer os itens do objeto.
O método GetCurrent será utilizado para retornar o valor do atual elemento da lista e o método MoveNext irá posicionar o cursor no próximo item ou retornando False quando não há mais elementos na lista.

Vamos criar um exemplo simples para ilustrar como isso é feito.

TMyList = class;

TMyListEnumerator = class
private
  FIndex: Integer;
  FMyItems: TMyList;
public
  constructor Create(ARef: TMyList);
  function GetCurrent: Integer; inline;
  function MoveNext: Boolean;
  property Current: Integer read GetCurrent;
end;

A classe TMyListEnumerator será a responsável por acessar a lista de valores de TMyList.
O layout acima é exatamente o que você precisa implementar para todas as classes a qual deseja dar suporte ao ForEach, modificando apenas o tipo de retorno de acordo com o tipo de item das classes. Por exemplo, se coleção mantém uma lista de valores de ponto flutuante, substitua a o tipo "Integer" (no layout acima) por "float".

TMyList = class
  protected
    FName: string;
    FItems: array of Integer;

  public
    destructor Destroy; override;
    function Add(const Value: Integer): Integer;
    function GetEnumerator: TMyListEnumerator;
end;

A classe TMyList é nossa coleção a qual mantém um array de valores inteiros (propriedade FItems).
Agora observe a implementação dos métodos da coleção. Nada de diferente a não ser pelo método GetEnumerator.

destructor TMyList.Destroy;
begin
  SetLength(FItems, 0);
  inherited;
end;

function TMyList.Add(const Value: Integer): Integer;
begin
  Result := Length(FItems);
  SetLength(FItems, Result + 1);

  FItems[Result] := Value;
end;

function TMyList.GetEnumerator: TMyListEnumerator;
begin
  Result := TMyListEnumerator.Create(Self);
end;

A implementação da nosso classe de enumeração não tem nada de complexo ou diferente, e o próprio código é auto explicativo.

constructor TMyListEnumerator.Create(ARef: TMyList);
begin
  inherited Create;
  FIndex := -1;
  FMyItems := ARef;
end;

function TMyListEnumerator.GetCurrent: Integer;
begin
  Result := FMyItems.FItems[FIndex];
end;

function TMyListEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < (Length(FMyItems.FItems) - 1);
  if Result then
    Inc(FIndex);
end;

Caso tenha tido algum tipo de dificuldade, faça o download do projeto de exemplo (link http://rapidshare.com/files/406448596/ForEach.7z) que contém todo o código fonte utilizado nesse artigo.