quarta-feira, 19 de dezembro de 2007

JSPs e comentários

Comentários em JSPs pode não ser tão linear como deveria ser. Vejam o código seguinte:
<%
...
SimpleDateFormat javascriptDateFormat = new SimpleDateFormat( "yyyy, (MM-1), dd" );
// usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> )
...
%>

À partida parece estar tudo bem, mas não está, porque o pre-processador das JSPs não gosta de tags embebidas (<% ... %>). Como executa/processa antes do compilador Java, e não considera os comentários java (// ou /* ... */) caput!
A solução passa então por comentar usando comentários JSP.
<%
...
SimpleDateFormat javascriptDateFormat = new SimpleDateFormat( "yyyy, (MM-1), dd" );
<%-- // usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> ) --%>
...
%>

Mas novamente, esbarramos com a limitação das tags embebidas não serem suportadas pelo Pre-processador JSP. Vamos resolver o problema fechando e abrindo o bloco envolvente de forma a evitar que um bloco fique contido no outro:
<%
...
SimpleDateFormat javascriptDateFormat = new SimpleDateFormat( "yyyy, (MM-1), dd" );
%> <%-- // usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> ) --%> <%
...
%>

E agora já funciona e até seria um resultado expectável, não fosse haver aqui algo estranho!
A formatação da data, que está em código JSP, está dentro de um comentário JSP, ora reparem bem:
<%-- // usage: var mydate = new Date( <%= javascriptDateFormat.format( myJavaDate ) %> ) --%>

Ou seja, tags para código JSP (<% ... %>) podem estar contidas em comentários JSP (<%-- ... --%>), mas o inverso não é verdade !
Confuso no mínimo.

segunda-feira, 17 de dezembro de 2007

CSS - IE6 versus Selector de Atributos

O IE6 não suporta CSS "attribute Selectors", como no exemplo seguinte:
div.Field input[type="text"]
{
color: blue;
}

Assim, temos que circundar o problema, e a solução mais comum, é adicionar uma class específica para o caso que queremos resolver, e atribuir a class ao elemento respectivo, por exemplo:
div.Field input.Text /* IE6 Does NOT support attribute selector */
{
color: blue;
}

Passamos então a representar o conteudo da seguinte forma:
<div class="Field">
<label for="Name">Nome</label>:<input class="Text" type="text" id="Name" value="">
</div>

Mas numa perspectiva futura, podemos querer suportar ambas as regras, com algo como o seguinte:
div.Field input[type="text"],
div.Field input.Text /* IE6 Does NOT support attribute selector */
{
color: blue;
}

Perfeitamente válido ! Certo ?
Sim, é perfeitamente válido segundo o standard; Mas não, não funciona no IE6!
E de nada vale trocar a ordem das regras, dado que o comportamento é exactamente o mesmo.

O problema é devido ao IE6 rejeitar as regras que não suporta, conjuntamente com qualquer outra que lhe esteja associada.

Para resolver o problema temos que duplicar as formatações, de forma a que a rejeição da regra não suportada, não afecte as restantes, ou seja:
div.Field input[type="text"]
{
color: blue;
}

div.Field input.Text /* IE6 Does NOT support attribute selector */
{
color: blue;
}

Saber isto pode poupar-vos muito tempo :)

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.

segunda-feira, 22 de outubro de 2007

Sem comentários

Hoje deparei-me com o código Javascript seguinte:
function returnFalse()
{
return false;
}

Validação de Strings

É muito frequente encontrar código java semelhante ao seguinte:
if( myStr != null && myStr.equals( "TestString" ) )
{
...
}
Embora o código esteja correcto, é uma pattern recorrente que pode e deve ser simplificada, como na sugestão seguinte:
if( "TestString".equals( myStr ) )
{
...
}
Isto funciona, porque "TestString" existe sempre por ser uma constante (literal), e dado representar um objecto, podemos sempre chamar o metodo equals(...) que por sua vez sabe lidar com o caso de o parâmetro ser null.

Existem ainda situações semelhantes, por exemplo:
if( myStr != null && !myStr.equals( "" ) )
{
...
}
Que podem ser simplificadas de forma a serem mais eficientes, como sugerido a seguir:
if( myStr != null && myStr.length() > 0 )
{
...
}
NOTA: Comparar com a String vazia é menos eficiente, mas se preferirem podem usar a sugestão seguinte, por ser mais concisa.
if( !"".equals( myStr ) )
{
...
}

segunda-feira, 15 de outubro de 2007

Ingenuidade versus desempenho

A ingenuidade de alguns programadores, relativamente à forma como as linguagens funcionam a baixo nível, trás muitas vezes impactos inesperados no desempenho das aplicações.
Vou mostrar um exemplo típico, que se encontra frequentemente em Javascript, uma função que usa um ciclo for para gerar uma lista de valores separados por virgulas.
function getValidZoneList()
{
var zoneList = '';

for( var i=0; i < arrValidZone.length; ++i )
{
if( zoneList == '' )
zoneList = arrValidZone[ i ][ 0 ];
else
zoneList += ', ' + arrValidZone[ i ][ 0 ];
}
return zoneList;
}

O primeiro problema com que nos deparamos é o teste
( zoneList == '' )
, que é tipicamente mais lento que testar
( zoneList.length == 0 )

O segundo problema é fazer testes desnecessários dentro dos ciclos!
Neste caso concreto o teste só é necessário, para determinar se precisamos de adicionar o separador virgula no caso de já termos algum elemento na lista. O facto é que este teste só é relevante no primeiro elemento, sendo falso para todos os restantes.

Se a função fosse implementada da forma que se sugere a seguir, aumentaria significativamente o seu desempenho, pois fariamos apenas um único test, no início:
// returns a comma separated String with the list of Valid Zones
function getValidZoneList()
{
var zoneList = '';

if( arrValidZone.length )
{
var i = 0;
zoneList += arrValidZone[ i ][ 0 ];

for( ++i; i < arrValidZone.length; ++i )
zoneList += ', ' + arrValidZone[ i ][ 0 ];
}
return zoneList;
}

Debug "Artesanal"

Quantos de nos já não fomos forçados ao método de debug "Artesanal", de fazer prints pelo meio do código. À falta de melhor solução, sempre é uma solução, ainda que pobre.

Mas hoje deparei-me com o seguinte, numa tag Body (HTML):
onload="javascript:window.open('about:blank').document.write('<pre>' + document.documentElement.outerHTML.replace(/</g, '<') + '</pre>');"
A ideia desta alma é fazer debug numa janela à parte, atráves de um dump do html/código gerado.
A motivação é clara, pois trata-se de uma página gerada dinâmicamente, e que neste caso inclui geração dinâmica de Javascript, outch!
O "único" problema é que ao fazer isto, qualquer Javascript embutido na página vai deixar de funcionar, dado que todas as variáveis e funções definidas, não o foram, logo vai dar sarna para o "inventor" se coçar, com todas as mensagens de erro que irá receber.

sexta-feira, 12 de outubro de 2007

"Martelar"

O termo "martelar" pertence ao calão informático Português e refere-se ao acto de alterar uma aplicação de uma forma rápida, localizada e sem grandes cuidados estruturais.
Normalmente resulta numa solução suficiente, mas não propriamente ideal, na medida em que não tem em consideração o código circundante ou desenvolvimentos futuros.

Estou a "martelar" o código.

É uma analogia com a profissão de Carpinteiro, mais concretamente ao acto de martelar um prego, supostamente uma actividade que não requer grande destreza ou cuidado, para obter um resultado minimamente satisfatório.

Termos relacionados:
  • "martelanço"
    O acto de "martelar".

    Estou no "martelanço".

  • "martelado"
    O estado final do código depois da intervenção.

    Este código está todo "martelado".

Esta "técnica" é tipicamente usada em situações de urgência/falta de tempo, nomeadamente quando se está em cima dos prazos de término dos projectos, quando já não há tempo para estruturar correctamente o código.

Infelizmente encontram-se com alguma frequência, "Carpinteiros de Software" a tempo inteiro.

quinta-feira, 11 de outubro de 2007

IE 6 window.opener Javascript BUG

Há males que vêm por bem.

Já me aconteceu inumeras vezes ao fazer debug do IE 6 (6.0.2900.2180.xpsp_sp2_gdr.070227-2254) usando o "Microsoft Script Editor (10.0)" determinar que o object window.opener não está definido quando deveria estar, nomeadamente no documento carregado dentro de uma Iframe.
A atitude natural e expectável é culpar o browser (IE), pois já faz tanta coisa mal, que é só mais uma. Assim sendo arranja-se uma forma de circundar o problema.
Mas hoje descobri o cálice dourado :)

O culpado desta vez é o debugger "Microsoft Script Editor".

Se o debugger estiver activo durante a execução de uma instrução que envolva window.opener este objecto não está definido (undefined).

Mas se só chamarmos o debugger depois de executar essa linha, ou simplesmente não lançar o debugger, o código com o window.opener executa sem problemas, pois este encontra-se correctamente definido.

O seja, no caso seguinte window.opener não está definido e dá erro, no debugger:

function GiveZipCode( zipC, cityN, streetN )
{
debugger;
window.opener.fillBoxes( zipC, streetN, cityN );
...
}


desta forma o window.opener JÁ ESTÁ CORRECTAMENTE definido e funciona:

function GiveZipCode( zipC, cityN, streetN)
{
window.opener.fillBoxes( zipC, streetN, cityN );
debugger;
...
}

Para os mais incrédulos, basta chamar o debugger dentro da função fillBoxes() que ele para lá dentro, sem qualquer problema, ou seja conseguiu aceder ao window.opener, logo está definido.

quarta-feira, 10 de outubro de 2007

"Bombar"

O termo "bombar" pertence ao calão informático Português e refere-se ao facto de uma aplicação ou programa estar finalmente a executar sem problemas (conhecidos).

Está a "bombar"


É uma analogia ao funcionamento de uma bomba usada num poço, para extrair água.
Quando está a funcionar existe "output" (água) na saída.