terça-feira, 13 de novembro de 2007

Validação de Parametros

Hoje deparei-me com o seguinte Código Java numa página JSP:
boolean bDisabled = (new Boolean ((String) (request.getParameter("bDisabled")))).booleanValue();
É mais um caso de conversões implícitas, usadas abusivamente. Mas desta vez o programador, gostou de complicar a coisa, para além do razoável.
Nomeadamente, é preciso que quem chame a página, saiba exactamente, qual é a conversão implícita esperada, que é "true" para true e "false" para false, e tem que respeitar o case.
Assumindo eu, que é válida a seguinte afirmação:
Se não é "true" é implicitamente "false".
Então podemos optar por algo bem mais simples.
boolean bDisabled = "true".equals( request.getParameter( "bDisabled" ) );
A vantagem é óbvia em termos de legibilidade e eficiência.
Nota: No caso em que o parâmetro não é enviado, assumimos que seja equivalente a false, o que é uma assumpção razoável, mas que validei no código existente como perfeitamente válida.
Caso não fosse, bastaria testar esse caso também, e gerar uma excepção para qualquer outro caso, por exemplo.

sexta-feira, 2 de novembro de 2007

Estruturas de dados em Javascript

Aqui temos mais um caso típico de código Javascript:
for (i=0;i < arrParameterRange.length ; i++)
{
// set the temporary variables, to improve the code readability
sParamFormat = arrParameterRange[i][0];
sParamRangeFrom = arrParameterRange[i][1];
sParamRangeTo = arrParameterRange[i][2];
...
alert( sParamFormat + " for range " + sParamRangeFrom + " : " + sParamRangeTo );
...
}
O que me levou a escrever sobre este código foi o comentário que lhe está associado.
De facto a intenção é boa, mas estão a tentar resolver o problema errado.

O problema deste código reside na forma como os dados estão a ser guardados, nomeadamente estão a usar Arrays de Arrays.
A inicialização típica destes arrays é algo como o código seguinte:
var arrParameterRange = new Array();
arrParameterRange[0] = new Array( 'DumbFormat', 'DumbFrom_0', 'DumbTo_0' );
arrParameterRange[1] = new Array( 'DumbFormat', 'DumbFrom_1', 'DumbTo_1' );
...
E isto depois implica código semelhante ao que vimos no início, com indices HardCoded por todo o lado, que não é nada fácil de ler, expecialmente quando temos sub Arrays com dimensão 10 ou mais, pois nós humanos perdemos a capacidade de associar eficazmente os indices com os dados.

Curiosidade: Está provado experimentalmente que o limite do humano médio é de 7 itens. Isto é relevante em questões relacionadas com usabilidade (número máximo de opções de um menu, por exemplo), assim como em outras aplicações.
alert( arrParameterRange[i][2] + " : " + arrParameterRange[i][3] );
Existem alguns programadores mais iluminados que tentam dar a volta ao problema, definindo "constantes" para usar para os indíces, mas novamente estamos a resolver o problema errado.
var RANGE_IDX_FORMAT = 0;
var RANGE_IDX_FROM = 1;
var RANGE_IDX_TO = 2;
...

alert( arrParameterRange[i][RANGE_IDX_FROM] + " : " + arrParameterRange[i][RANGE_IDX_TO] );
O problema que deviamos estar a resolver, era como estruturar correctamente os dados. E para isso temos objectos em Javascript, que servem a sua função, mas que também servem para dar nomes aos dados, nomeadamente às propriedades do objecto. Isto porque Objectos sem métodos são equivalentes a estruturas (dado que é tudo público, em Javascript).

Então vamos definir um objecto em Javascript para guardar os nossos dados:
var ParamRange = function( format, from, to )
{
this.format = format;
this.from = from;
this.to = to;
}
E com esta simples alteração, já conseguimos código legível e fácil de manter:
var arrParameterRange = new Array();
arrParameterRange[0] = new ParamRange( 'DumbFormat', 'DumbFrom_0', 'DumbTo_0' );
arrParameterRange[1] = new ParamRange( 'DumbFormat', 'DumbFrom_1', 'DumbTo_1' );
...

for( i=0; i < arrParameterRange.length ; i++ )
{
var paramRange = arrParameterRange[ i ];
...
alert( paramRange.format + " for range " + paramRange.from + " : " + paramRange.to );
...
}
Claro está que o melhor seria mesmo definir métodos para isolar as propriedades do objecto, e providenciar as funcionalidades necessárias, mas este tema já vai longo e ainda tinha muito que escrever se fosse por ai.

Conversões implícitas

Recentemente deparei-me com o seguinte código numa JSP (Java+Javascript):
if( <%= sTestParam.equals("true") %> )
{
...
}
À primeira vista não parece haver problema nenhum com este código, mas este código é tudo menos intuitivo por usar uma conversão implícita abusivamente e por assumir um pressuposto que não é garantido na especificação do Java.

Se olharmos com cuidado, temos um metodo que nos devolve um booleano (Java), que por sua vez devido ao método de embeber resultados numa JSP ( <%= ... %> ) é implicitamente convertido para String (Boolean.toString), que retorna "true" ou "false", e que finalmente é usado como código Javascript, que mais tarde o browser interpretará como um booleano novamente.

O problema principal é isto tudo ser muito obscuro, mas existe também um problema subjacente potencial, é que não existe garantia nenhuma que as futuras versões do Java continuem a converter um booleano implicitamente para "true" ou "false", isto pode mudar e depois este código deixa de funcionar, é pouco provável mas possível.
Como exemplo puramente ilustrativo, poderia passar a retornar "True", "_true_" ou "'True'" ou "Vero" par uma implementação italiana.
javascript:void(0)
A solução seguinte é bem mais intuitiva e explícita, e sem usar qualquer conversão implícita:
<% if( sTestParam.equals("true") ) { %>
...
<% } %>
Ter em atenção que conversões implícitas são úteis e até facilitam o trabalho, quando bem aplicadas. Mas como em tudo, quando se abusa das ferramentas e da sua função, acaba por se perder os benefícios e eventualmente criar problemas.