quarta-feira, 20 de março de 2013

Tratamento de exceções

Atualmente, qualquer linguagem de programação que se preze oferece recursos para o tratamento de exceções. Nosso bom e velho PowerBuilder também oferece tais recursos (apesar de não ser da melhor forma...).

Vamos entender um pouco melhor como as exceções funcionam. Quando ocorre um erro em tempo de execução em uma aplicação PowerBuilder, é disparado um evento da aplicação chamado SystemError(). Você pode visualizar este evento abrindo um objeto "application" (figura abaixo).

Objeto "application".

Se optarmos por utilizar o SystemError() para realizar alguns tratamentos de falhas, com certeza enfrentaremos dificuldades, já que esse evento é disparado fora do local onde o código está sendo executado. Contudo, você pode querer capturar e tratar alguns erros no próprio código de uma interface ou classe. Veja o exemplo de código abaixo. Imagine que estamos tentando capturar, através de uma datawindow, o nickname de um usuário que acabou de logar em um sistema.

String ls_user

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

Como programador PowerBuilder, você deve saber que, caso esta datawindow não possua tal linha ou coluna solicitadas, a aplicação irá disparar a clássica mensagem "Error: Invalid DataWindow row/column specified at line...". Para realizar o tratamento deste erro no próprio método, precisaremos conhecer o que a linguagem oferece. Veja abaixo detalhes de cada palavra reservada:

TRY
   <codificação que pode gerar uma exceção>
CATCH
   <codificação que permite capturar e tratar uma exceção>
FINALLY
   <codificação que permite encerrar o bloco de código. Ideal para fechamento de conexões, destruição de objetos, etc. >
END TRY


Vamos agora tratar o nosso código:

String ls_user

TRY

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

CATCH (Throwable aoException)
Messagebox ("Falha", "Não foi possível identificar o  usuário: " + aoException.getMessage())
CLOSE(THIS)
END TRY


Entenda que, desta forma, ao ser disparada a falha no getItemString(), automaticamente a próxima instrução a ser executada será o CATCH, independente se existir mais codificações ao longo do TRY. 

Vamos complementar o código fonte do TRY com mais algumas instruções. Nelas você verá que também é possível lançar exceções manualmente. Desta forma, podemos centralizar o tratamento de outras situações:

String ls_user

TRY

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

   IF ISNULL(ls_user) OR Trim(ls_user) = "" THEN
  Exception loException
      loException = CREATE Exception
      loException.setMessage("O nickname do usuário está nulo ou vazio")
      THROW (loException)
  END IF

CATCH (Throwable aoException)
Messagebox ("Falha", "Não foi possível identificar o usuário: " + aoException.getMessage())
CLOSE(THIS)
END TRY


Métodos que disparam exceções

Para elaboramos códigos com qualidade e organização, é importante conhecermos as boas práticas de programação relacionadas. Quando conhecemos os recursos para tratamento de exceções, já podemos eliminar uma (má) prática que ainda é bastante utilizada. Quantas vezes você codificou um método cujo valor de retorno era um tipo int e sua codificação retornava -1 para indicar falha? Com o tratamento de exceções isto não é mais necessário!

Vamos adaptar o código que vimos anteriormente para que esteja contido em uma função chamada of_getNickname(). O retorno desta função será do tipo "string", ou seja, o nickname do usuário. Se ocorrer algum problema na recuperação desse nickname, vamos evitar retornar uma string vazia ou nula para indicar a falha. Iremos lançar uma nova exceção a fim de indicar ao código de origem (o que invocou a função) que houve uma ação inesperada.

// Função: of_getnickname() Retorno: string

String ls_user

TRY

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

   IF ISNULL(ls_user) OR Trim(ls_user) = "" THEN
  Exception loException
      loException = CREATE Exception
      loException.setMessage("O nickname do usuário está nulo ou vazio")
      THROW (loException)
  END IF

CATCH (Throwable aoException)
THROW (aoException)
END TRY

RETURN ls_user

Se você já inseriu o código acima para fazer algum teste, perceberá que o PowerBuilder acusou um erro. O motivo disto é porque devemos informar que nossa função of_getnickname() está pronta para tratar exceções. Para definir isto, você precisará preencher o campo "Throws", na especificação da função, com o valor "exception". Veja na imagem abaixo.



Pronto. Agora basta que função de origem faça um tratamento semelhante ao que você vê a seguir.



TRY

 String ls_user = of_getNickname()

CATCH (Throwable aoException)
Messagebox ("Falha", "Não foi possível identificar o usuário: " + aoException.getMessage())
CLOSE(THIS)
END TRY


Caso queira tratar suas exceções mais especificamente, listo abaixo algumas classes de exceção:
  • Throwable - Tratamento mais genérico, englobando todos os erros;
  • Exception - Exceções criadas pelo usuário e herdadas do Throwable;
  • RuntimeError - Erros de Runtime do PowerBuilder e herdado do Throwable;
  • NullObjectError - Referência nula para objetos e herdado do RuntimeError;
  • DivideByZeroError - Divisão por zero e herdado do RuntimeError;
  • DWRuntimeError - Erros em comandos da Datawindow e herdado do RuntimeError;
  • OLERuntimeError - Erros em comandos de Objetos OLE e herdado do RuntimeError;
  • CORBASystemException - Erros em comando de objetos CORBA e herdado do RuntimeError.

Espero que tenha dado uma noção de como construir aplicações com uma estrutura que utiliza tratamento de exceções. Apesar de toda a sintaxe utilizada nos exemplos ter sido em PowerScript, não descarto o aprendizado também para desenvolvedores C#, Java, Visual Basic, etc.