segunda-feira, 13 de dezembro de 2010

As empresas, por acaso, precisariam de um consultor da área da administração?

As empresas, por acaso, precisariam de um consultor da área da administração?

Neste post - que promete ser gigante -, vou falar um pouco sobre a história do desenvolvimento de software, alguns dos problemas atuais encontrados na nossa área e uma pequena sugestão de evolução das metodologias de desenvolvimento de software adotando um novo papel: o do consultor da área da administração.

A maior parte das empresas têm problemas para saber exatamente o que o cliente quer. Será que não seria então mais interessante para as empresas ter um serviço de consultoria para isso? Ter alguém da área de administração para fazer o MAPEAMENTO DE PROCESSOS do cliente?

Antigamente - nem tão antigamente assim - se usava o modelo cascata. Dúzias de analistas de sistemas - que não eram da área da administração mas sim da área da informática ou engenharia - se reuniam com quilos de papéis para tentar descobrir o que os clientes queriam (a famosa etapa de análise / levantamento / elicitação de requisitos) para só depois começar a implementar um sistema.

Como todos nós sabemos - ou deveríamos saber - isso não funciona. Não funciona porque o cliente pede algo e quando o sistema é entregue ele percebe que deveria ter pedido mais X, mais Y e que aquele Z que ele pediu, ele pediu errado, que ele deveria ter pedido W, na verdade.

Daí veio a famosa frase que diz que o cliente não sabe o que quer e que ele só vai saber quando o sistema for entregue. E mesmo depois que o sistema foi entregue talvez ele ainda não saiba o que quer.

Então todo mundo começou a falar: no início do projeto a incerteza é muito alta exatamente porque o cliente não sabe o que quer. Então vamos reduzir a incerteza divindindo todo o processo em ciclos pequenos e iterativos para entregar pedaços de software mais cedo.

Quanto mais cedo o cliente ver o sistema (mesmo que seja uma parte dele) mais ele vai saber o que quer e mais cedo ele vai informando o que deve mudar para a equipe.

E é muito mais fácil (e barato) alterar um sistema no início do projeto do que no final dele. Surgiu então o processo iterativo e incremental.

Com o processo iterativo e incremental - principalmente os processos iterativo e incremental utilizado pelas famosas metodologias ágeis (XP, Scrum, etc.) - você e/ou a equipe faz o levantamento geral e abrangente de tudo o que deve ser feito no programa falando diretamente com o cliente.

Depois que esse levantamento inicial e geral é feito, o cliente prioriza as tarefas levantadas dizendo quais são as mais importantes.

Então é criado um pequeno ciclo de desenvolvimento de 1 ou 2 semanas. Nestas 1 ou 2 semanas a equipe conversa com o cliente para fazer um levantamento mais detalhado destas tarefas (mesmo que de maneira meio informal) e desenvolve as tarefas que o cliente disse que são as mais importantes e que mais geram valor de negócio para o cliente.

Em cada um desses ciclos, a equipe entrega uma ou mais funcionalidades já 100%. Dessa forma, o cliente tem software rodando bem mais cedo. E se o software for entregue mais cedo, a incerteza do cliente diminui.

A cada pedaço de software entregue, mais o cliente vai descobrindo melhor o que ele quer e com isso, as chances do sistema terminar com sucesso é muito maior.

Resumindo, com o processo iterativo e incremental as chance de se entregar o que o cliente realmente quer é muito maior.

Mas e se a empresa de software deseja mais do que apenas entregar o que o cliente quer?

Mas entregar o que o cliente quer é o sonho de toda a empresa de software, certo? Certo. Mas conseguir mais do que isso é o que faz uma empresa se destacar muito mais do que as outras.

E se, ao invés de entregar o que o cliente quer, a empresa AJUDE O CLIENTE a DESCOBRIR o que ele REALMENTE PRECISA?

Isso seria possível se a empresa - além de trabalhar como uma desenvolvedora de software - também trabalhasse como uma consultoria administrativa.

Neste caso, a empresa de software disponibilizaria um profissional da área da administração para fazer todo o LEVANTAMENTO DE PROCESSOS da empresa do cliente.

Este profissional iria até a empresa do cliente, falaria com ele e iria analisar a empresa do cliente.

Analisar a empresa do cliente significa:

1 - acompanhar o dia-a-dia da empresa do cliente, para entender o que a empresa do cliente realmente faz;

2 - depois que o consultor entendeu de verdade o que a empresa faz, ele deve olhar o dia-a-dia de cada uma das pessoas que estão envolvidas no negócio do cliente.

Desde o presidente da empresa, passando pelos diretores, gerentes, técnicos, digitadores, os funcionários que controlam linhas de produção, os que atendem os usuários, etc. até o trabalho dos porteiros, office-boys, encarregados da manutenção, faxina, etc.

O seja, todos os funcionários que estão diretamente ou indiretamente ligados a empresa;

3 - fazer o mesmo, mas para todos os terceirizados e fornecedores da empresa. Como que é o relacionamento da empresa com seus fornecedores e com as empresas terceirizadas que prestam serviços pra empresa.

Acompanhar o dia-a-dia de como os funcionários da empresa se relacionam com estes terceiros;

4 - fazer o mesmo, mas agora com os clientes da empresa. Ver como que a empresa se relaciona com seus clientes. Qual é a visão que os cliente têm da empresa. Como que é a comunicação da empressa com os clientes. Quais papéis, formulários, requisições o cliente precisa preencher, etc. Quais as principais críticas e elogios que os clientes têm para com a empresa, etc.

Após analisar todos estes passos, o consultor então começa a desenhar o diagrama de processos da empresa, como se faz nas cadeiras de administração mesmo.

Tudo isso serve para poder identificar o processo que a empresa atualmente utiliza.

A maior parte das empresas pequenas ou médias não sabe atualmente como que é, 100% no papel, o processo de sua própria empresa. E este desconhecimento do seu próprio processo pode se dar pelos seguintes fatores:

1 - falta de conhecimento da área administrativa da empresa. São várias as empresas que são administradas não por pessoas da administração, mas sim por técnicos que por serem muito bons, por ter um bom relacionamento dentro da empresa e por conhecer a empresa há anos, acabam sendo elencados a uma posição administrativa, de coordenação.

Estas pessoas, mesmo sem ter uma formação administrativa, conseguem fazer a empresa funcionar muito bem e corretamente. Porém, a parte mais teórica da administração - como o mapeamento de processos - elas acabam não fazendo;

2 - Outro fator pode ser o famoso "nunca fizemos isso e até agora isso não nos fez falta. Se algum dia a gente sentir necessidade de fazer isso, a gente faz". Esse fator todo mundo conhece muito bem ;) ;

3 - Outro fator é que a empresa pode achar melhor contratar uma consultoria para fazer isso. E o preço cobrado por essa consultoria pode ser bem alto, o que faria isso não entrar para a lista de prioridades da empresa devido a uma relação custo/benefício.

Geralmente as empresas só resolvem fazer um mapeamento de processos quando a empresa resolve tirar uma certificação ISO. Para se tirar uma certificação ISO, um dos passos necessários é fazer o mapeamento do processo da empresa - claro que não é só isso que é necessário. É necessário mais várias coisas como padronizar a forma de trabalhar das pessoas, etc.

Ok, fizemos o mapeamento dos processos da empresa. Agora podemos começar a desenvolver software, certo?

Hum... eu teria um pouco mais de calma.

Isto porque nós fizemos o mapeamento dos processos da empresa, certo? Ou seja, descobrimos "de cabo a rabo" como que a empresa funciona. Legal.

Geralmente, quando se faz o levantamento de requisitos ou quando se faz as conversas com o cliente tanto no modelo cascata quanto nos modelos iterativo / incremental e modelos ágeis, nós conversamos com o cliente exatamente para descobrir como que a empresa funciona. Mais ou menos como que é o mapeamento de processos da empresa.

Se toda esta conversa com o cliente for muito bem feita, você vai chegar bem perto do mapeamento de processos que eu citei aqui até agora.

Só que é aí que mora mais um dos problemas do desenvolvimento de software: chegamos o mais perto possível do mapeamento de processos perfeito da empresa, ou seja, descobrimos como a empresa trabalha. Mas e quem diz que a empresa trabalha bem? Quem diz que os processos da empresa são bons?

Se você fizer um sistema para uma empresa cujos processos são ruins, o seu software será ruim para a empresa. Isto porque o seu software será um reflexo dos processos da empresa. Um software nada mais é do que o processo de uma empresa em um formato computacional automatizado. Nada mais.

Então aí vem mais uma vantagem de se fazer o levantamento dos processos de uma empresa por alguém da área da administração: além de levantar os processos de uma empresa, sugerir mudanças para melhorar o processo da empresa.

É como se você fosse fazer um software para uma empresa que trabalha com linha de produção. Digamos que esta empresa fabrica 1000 itens por dia. Você descobre algumas falhas no processo da empresa e propõe melhorias no processo que fará a empresa produzir 1500 itens por dia ao invés dos atuais 1000.

Seria bem melhor para o seu cliente, certo? E se seria melhor para o seu cliente, seria melhor para você também.

Este é o ponto que eu queria chegar. Não basta fazer o levantamento dos processos da empresa e desenvolver um software a partir dele. De certa forma isto é o que todo mundo faz (ou tenta fazer).

Para você se diferenciar, para se destacar, para fazer mais do que os outros, você precisa fazer o levantamento dos processos da empresa, melhorar estes processos e, então sim, desenvolver um software a partir dele. Daí com certeza, sua empresa seria uma empresa diferenciada. A qualidade do seu produto seria muito melhor.

Em equipes de desenvolvimento mais tradicionais, nós temos o Analista de Requisitos ou o Analista de Negócios. A minha sugestão seria a de que, nestes casos, o Analista de Requisitos ou o Analista de Negócios não fosse alguém da área da informática, mas sim alguém da área da administração, que ele fosse o consultor da área da administração.

Ou isso, ou que os Analistas de Requisitos e/ou de Negócios fizessem uma pós-graduação em Administração para que eles mesmo possam fazer um levantamento de processos corretamente.

Em equipes ágeis, nós temos algumas figuras no desenvolvimento de software. Temos a equipe, o líder da equipe e temos o Product Owner (PO).

O PO é a pessoa que representa o cliente, a pessoa que vai dizer para a equipe o que o cliente espera do software, o que a equipe deve fazer, quais tarefas deve fazer e que também prioriza as tarefas. Este PO geralmente é o próprio cliente. Alguém que trabalhe na empresa do cliente.

A minha sugestão para equipes ágeis é que além de ter o PO como parte da equipe, também deveríamos ter o consultor da área da administração como parte da equipe. Desta forma o PO mais este consultor deveriam guiar a equipe de desenvolvimento sobre o que deve ser feito.

Mas o importante é que o consultor - nestes casos de equipes ágeis - se reúna antes com o PO para mostrar o resultado do levantamento de processos e faça as sugestões de melhorias no processo. Tudo isso antes de envolver a equipe de desenvolvimento.

Depois que tudo foi acordado e que o consultor mostrou todos os processos para o PO, daí sim deve-se dar início ao desenvolvimento com a equipe todos juntos.

Já nesta segunda etapa junto com a equipe, o consultor deve deixar o PO conduzir todo o processo e apenas deve se manifestar caso o PO tenha esquecido de mencionar alguma coisa ou se o PO falar algo que contrarie o que havia sido acordado antes entre os dois.

Afinal, se o PO fala algo que contrarie o novo processo proposto, pode ser que o PO não tenha entendido corretamente o novo processo, então o consultor deve entrar em cena para debater o assunto junto com o PO e a equipe apenas para "afinar" o desenvolvimento.

Desta forma, teríamos o desenvolvimento em equipes ágeis bem parecido com o que se faz hoje em dia, apenas adicionaríamos esta figura do consultor para ajudar.

É importante lembrar e frisar que o consultor deve deixar o PO conduzir o desenvolvimento. O PO é o dono do produto, não o consultor. O consultor é apenas uma ajuda, um reforço. O maestro deve continuar sendo o PO.

Desta forma, acho que podemos melhorar um pouco ainda mais o desenvolvimento de software para que cada vez mais projetos alcancem o sucesso com uma ótima qualidade.

Principalmente porque a quantidade de projetos que não dão certo, que não atendem às expectativas dos clientes ou que não apresentam uma boa qualidade, é muito grande. Isso sem falar dos projetos que terminam com atraso.

Tanto que na área de software, os clientes já aprenderam a se contentar com pouco, exatamente porque são poucas as empresas que conseguem fazer este pouco.

A maior parte das empresas não consegue fazer um sistema que espelhe 100% os processos atuais da empresa, quem dirá propor melhorias no processo. De fato, isto seria um "mundinho ideal". Seria muito bom fazer isso, mas é muito difícil.

Continuando no "mundinho ideal", outra coisa que é preciso prestar atenção é que mesmo sugerindo melhorias no processo da empresa, os processos têm uma característica marcante: a mutabilidade.

Ou seja, um processo não dura pra sempre estático. Os processos mudam, evoluem com o tempo. E o que acontece - pra tristeza das empresas em geral e pra alegria das empresas que desenvolvem software :) - é que sempre que processos mudam, os softwares relacionados a estes processos precisam mudar também.

Então, para completar o "mundinho perfeito", o ideal seria fazer um software que se adaptasse às mudanças nos processos sem a necessidade de precisar reescrever ou alterar o código fonte do software.

Mas para chegar neste patamar creio que ainda é preciso alguns muitos anos a mais na questão do amadurecimento da indústria de software.

Isto tudo porque a área de desenvolvimento de software é uma criança ainda, se você analisar bem. Se comparar com qualquer outra área - administração, a maior parte das engenharias, psicologia, medicina, etc. - veremos que a àrea de desenvolvimento de software ainda é muito nova e muito recente.

Na nossa área não existe ainda uma regra de como desenvolver software. Regra do tipo "faça isso, isso e aquilo e você conseguirá construir um software bom e de qualidade". Todas as outras áreas já têm isso, só nós é que ainda não temos.

Na engenharia civil, existem regras que devem ser seguidas para se construir um prédio. Estas regras até podem mudar para que sejam construídos novos prédios diferentes, com novos materiais, etc., mas se você seguir as regras normais, você conseguirá construir um prédio bom, corretamente e de qualidade. Ou seja, existem as regras básicas. E as regras básicas são boas.

Já no desenvolvimento de software nós também temos as nossas regras básicas segundo o modelo cascata (o modelo de software mais tradicional que existe). Só que daí vemos estudos que dizem que uns 70% dos projetos falham.

70% é muito. Já imaginaram se 70% dos prédios caíssem?!

Na psicologia existem várias escolas. Freudiana, Junguiana, Lacaniana. São teorias sobre a psiquê humana já consolidadas. Hoje em dia, se você encontra uma teoria psicológica nova, ela já tem uns 20 anos! Ou seja, existem as regras básicas. E as regras básicas são boas.

Tendo isto em vista, vemos como o desenvolvimento de software é uma coisa nova. De certa forma, somos todos pioneiros tentando descobrir, inventar as regras que serão as regras básicas no desenvolvimento de software daqui a 40, 50 anos.

Já tivemos a programação estruturada. Agora temos a orientada a objetos. Já tivemos o modelo cascata. Agora estão aparecendo as metodologias ágeis. E assim vai.

Do ponto de vista romântico isto é algo bom pois somos pioneiros, descobridores, desbravadores.

Já do ponto de vista do dia-a-dia, em que precisamos desenvolver software pra ganhar dinheiro, isto já não é algo tão bom assim.

Afinal quanto mais fácil, rápido e com uma qualidade mínima aceitável desenvolvermos sistemas, mais dinheiro ganharemos. Por este ponto de vista seria muito bom já tivéssemos as nossas regras básicas estabelecidas.

Mas é isso aí pessoal. "A luta continua, companheiro" :)

segunda-feira, 8 de novembro de 2010

A revolução da TIM no celular

A revolução da TIM no celular

Isso pode parecer até comercial, mas não é, hehe :)

Acontece que a realidade brasileira de desenvolvimento de software pode mudar por causa da Tim.

Isso por causa da promoção de internet no celular por R$0,50 por dia.

Já faz quase 1 mês q eu to usando internet no meu celular. Funciona.

No meu celular eu acesso o meu gmail, o email da minha empresa, acesso meu facebook, twitter e acesso as minhas notícias e blog via rsou. Tudo isso no meu celular! Por R$0,50 por dia! Osu seja, por R$15,00 reais por mês!!! E pelo q a tim fala, isso não é promoção, é o preço celes

mesmo.

Axontece q no momento em q a tim faz isso, as ourras operacoras tb vão fazer. Ou seja, não dô nem 2 anos tra q todo mundo q a gente conhece tenha internet no celular. Até a nossa avó!!! (mesmo q ela nãoruse, ela vai ter internet no xelular dela).

Só q daí a área mobile entra em uma fase diferente do q ela estava indo agora.

Digo isso pq na área de mobile agora, muito se fala em programação pra iphone e pra android, ou seja, programação pra smaetphones da nova era.

Só q atualmente no brasil, a maior parte das pessoas têm um celular basicão. A maior parte dos brasileiros nem sabe direito o q é um smartphone. Pra imaior parte das pessoas “smartphone é aquele celular bonitinho da maçãzinha, né?”. :)

E eu falo isso pq, apesar de ser um nerdzão da área da informática, eu convivo com bastantes pessoas q não são da área de informática (e eu tenho q agradcer à mnha esposa por isso, hehehe) e eu já vi pessoas falando a frase acima.

Acontece q a maior parte dos celulares basicão das pessoas rodam java ME e não iphoneOS e muito menos android!

Por isso é q eu digo q temos um novo cenário. Todo mundo querendo fazer sistema pra iphone e etc e agora quase todas as pessoas terão internet nos seus celulares basicão rodando java ME... Q coisa, hein?

Se quase todo mundo vai ter internet no celular, isso quer dizer q você pode fazer sistemas para esses MILHÕES de pessoas. E se vc fizer isso, vc e/ou sua empresa podem GANHAR DINHEIRO com isso.

E como toda empresa sabe, dinheiro é palavra mágica e ganhar dinheiro então é o sonho de toda empresa.

Então devo aprender javaME? Eu diria, sim. Acho q vai ser uma exigência básica de mercado da mesma forma q aconteceu com o firefox e com os webservices.

Hoje em dia, todo sistema web não poder rodar apenas no IE, mas tem rq odar no Firefox tb.

Hoje em dia, todo sistema grnade deve ter suporte a web service.

Rodar páginas no firefox e ter webservice já virou o básico hoje em dia. E acho q o mesmo vai acontecer com mobile.

Até pq eu já vi clientes nossos perguntando se nossos sistemas rodavam em celular. E no momento em q os clientes começam a perguntar isso... Já viu, né?

É, este é o maravilhoso mundo da informática. Um mundo onde uma tecnologia q aparentava estar ficando esquecida pode acabar voltando com força total :)

terça-feira, 10 de agosto de 2010

[java, javautils] gerarHashMD5() - como gerar um hash MD5 em java

Método java para gerar um hash MD5. Veja como gerar um hash MD5 em java:


   /**
    * Gera um hash MD5 a partir da senha passada por parâmetro
    *
    * @param senha
    * @return String - o hash gerado
    * @throws NoSuchAlgorithmException
    */
   public static String gerarHashMD5(String senha) throws NoSuchAlgorithmException {
      MessageDigest digest = MessageDigest.getInstance("MD5");
      digest.reset();
      digest.update(senha.getBytes());
      byte[] byteDigest = digest.digest();
      String res = "";
      for (int i = 0; i < byteDigest.length; ++i) {
         String aux = Integer.toHexString((byteDigest[i] & 0xFF));
         if (aux.length() == 1) {
            aux = '0' + aux;
         }
         res += aux;
      }
      return res;
   }

segunda-feira, 9 de agosto de 2010

Be-a-Byte - Aulas de informática gratuitas - Inclusão Digital

bem interessante, estou repassando




Repasso esse artigo do Sérgio Carvalho sobre o site BE-A-BYTE construído pelo Professor João Antônio. O projeto, que aborda a inclusão digital para todos, é muito interessante e importante para o nosso país. 

Leiam e acessem o site (www.beabyte.com.br). No vídeo inicial ele explica como tudo começou. Se acharem interessante repassem! 

------------------------------------------------


Um Presente Para João! 

06/08/2010

Queridos Amigos,
Espero que estejam todos bem!
O assunto que lhes trago hoje é da maior importância e de uma grandeza ímpar!

Imaginem vocês, só por hipótese, se o melhor professor de informática do Brasil olhasse em volta, e reconhecesse que vive num país de excluídos de todas as espécies e de todas as idades!

Suponham que este professor, impulsionado pelo mais sincero propósito de fazer algo para mudar esta tão excruciante situação, tenha pensado consigo: "O que está a meu alcance fazer? Eu sou apenas um professor... O que poderia doar de mim mesmo?"

E eis que surgiu a abençoada ideia: "Vou ensinar tudo o que sei. Vou repassar todo o CONHECIMENTO que aprendi. É tudo o que tenho para DOAR!"

Este professor existe, meus amigos! Seu nome é João Antônio.

Essa historinha é a mais perfeita expressão da verdade!

Muito provavelmente vocês ainda não se tenham dado conta da dimensão que representa o projeto BE-A-BYTE (www.beabyte.com.br) criado por João.

Em poucas palavras: João Antônio está ensinando informática para crianças, para jovens, para adultos e para idosos! GRATUITAMENTE!

"Por que ele está fazendo isso, professor? O que há por trás disso?"

Ele está fazendo isso porque se importa - e muito! - com o país que vai deixar para seus filhos e netos. João se importa com as pessoas. João sabe que a realidade não se modifica por si, e que nada será diferente um dia se todos ficarmos de braços cruzados, esperando, esperando, esperando...

O Be-a-Byte está aí! Já começou! Em uma semana, já são quatro mil inscritos!

É muito? Não acho! É muito pouco! Pouquíssimo!

João completa amanhã 34 anos.

Então o presente que eu proponho "darmos" a ele é o seguinte: que tal tentarmos conseguir, até o fim do dia de amanhã, mais TRINTA MIL inscritos no Be-a-Byte?

Já pensaram vocês se, no domingo de manhã, João abrisse o site e descobrisse trinta e quatro mil inscritos? Que presente maravilhoso e MERECIDO!

A inscrição no Be-a-Byte, bem como tudo mais o que há neste site, é inteiramente grátis!

Se você que está lendo agora fizer seu cadastro no site, estou certo que conseguiremos construir este presente! 

E se você tem criança em casa (filho, filha, sobrinho, sobrinha), se você tem pessoas idosas em seu convívio (pai, mãe, avô, avó), se você tem adolescentes que lhe são próximos, então, meu amigo, minha amiga, o BE-A-BYTE é do seu interesse!

Já pensou que presente magnífico você pode dar para seu pai, apresentando para ele o Be-a-Byte e o inscrevendo no site?

Então é isso, meus amigos! Desde já agradeço a todos pelo que irão fazer, disseminando pelos quatro cantos do Brasil esse projeto tão belo e singular!

Mãos à obra!

Um forte abraço a todos!

Sérgio Carvalho

Sérgio de Carvalho Filho é Auditor Fiscal da Receita Federal do Brasil, tendo sido aprovado para este cargo no primeiro concurso do ano de 2002. Atualmente leciona Matemática Financeira, Estatística e Raciocínio Lógico em diversas cidades, de norte a sul do Brasil.

[humor] java vs .net! Muito cômico

muito cômico!!!

sexta-feira, 30 de julho de 2010

[javautils] moveFile

   /**
    * Move um arquivo
    *
    * @param filenameSource
    * @param filenameDestiny
    */
   public static void moveFile(String filenameSource, String filenameDestiny) {
      try {
         JavaUtils.copyFile(filenameSource, filenameDestiny);
         JavaUtils.deleteFile(filenameSource, false);
      } catch (Throwable e) {
         try {
            JavaUtils.deleteFile(filenameDestiny, true);
         } catch (Throwable e1) {
         }
         throw new RuntimeException(
                  "JavaUtils.moveFile() - Erro desconhecido ao mover o arquivo de \"" +
                           filenameSource + "\" para \"" + filenameDestiny + "\". Error: " + e);
      }
   }

[javautils] deleteFile

   /**
    * Exclui um arquivo (e seus subdiretorios e sub arquivos, se ele for um diretorio)
    *
    * @param filename
    * @param silent - se true, não gera nenhuma exceção mesmo que não consiga excluir o arquivo
    */
   public static void deleteFile(String filename, boolean silent) {

      File arquivo = new File(filename);

      /*
       * Se o arquivo não for um diretório, é deletado. Caso seja um diretório, é deletado com
       * seus subdiretórios e arquivos.
       */
      if (!arquivo.isDirectory()) {
         JavaUtils.subDeleteFile(filename, silent, arquivo);
      } else {
         File[] listFiles = arquivo.listFiles();

         for (int i = 0; i < listFiles.length; i++) {
            File arquivoFilho = listFiles[i];
            JavaUtils.deleteFile(arquivoFilho.getAbsolutePath(), silent);
         }
         JavaUtils.subDeleteFile(filename, silent, arquivo);
    }
   }  
  

   /**
    * Usado pelo deleteFile. Tenta apagar o arquivo e faz o tratamento de exceção.
    *
    * @param filename
    * @param silent
    * @param arquivo
    */
   protected static void subDeleteFile(String filename, boolean silent, File arquivo) {
      boolean excluiu = false;
      try {
         excluiu = arquivo.delete();
      } catch (Throwable e) {

         if (!silent) {
            // Se silent for false, gera exceção.
            throw new RuntimeException(
                     "JavaUtils.deleteFile() - Erro desconhecido ao excluir o arquivo \"" +
                              filename + "\". Error: " + e);
         }
      }

      if (!excluiu && !silent) {
         //Se não conseguir excluir o arquivo, gera exceção.
         throw new RuntimeException(
                  "JavaUtils.deleteFile() - Erro desconhecido. Não conseguiu excluir o arquivo com o nome \"" +
                           filename +
                           "\". Pode ser que o arquivo não exista, por isso o erro.");
      }
   }  

[javautils] createFile

   /**
    * Cria um novo arquivo vazio
    *
    * @param filename
    * @param overwrite - se true, recria o arquivo mesmo que ele já exista
    */
   public static void createFile(String filename, boolean overwrite) {
      try {
         boolean criou = false;
         File file = new File(filename);
         criou = file.createNewFile();
         if (!criou && overwrite) {
            JavaUtils.writeFile(filename, new StringBuffer());
         } else if (!criou) {
            throw new RuntimeException(
                     "JavaUtils.createFile() - Erro desconhecido. Não conseguiu criar o arquivo com o nome \"" +
                              filename +
                              "\". Pode ser que o arquivo já esteja criado, por isso o erro.");
         }
      } catch (Throwable e) {
         throw new RuntimeException(
                  "JavaUtils.createFile() - Erro desconhecido ao criar o arquivo \"" +
                           filename + "\". Error: " + e);
      }
   }

[javautils] copyFile


   /**
    * Faz a cópia de um arquivo
    * 
    * @param filenameSource
    * @param filenameDestiny
    */
   public static void copyFile(String filenameSource, String filenameDestiny) {
      try {
         // Cria a stream para ler o arquivo original
         FileInputStream fin = new FileInputStream(filenameSource);

         // Cria a stream para gravar o arquivo de cópia
         FileOutputStream fout = new FileOutputStream(filenameDestiny);

         // Usa as streams para criar os canais correspondentes
         FileChannel in = fin.getChannel();
         FileChannel out = fout.getChannel();

         // Número de bytes do arquivo original
         long numbytes = in.size();

         // Transfere todo o volume para o arquivo de cópia.
         in.transferTo(0, numbytes, out);

         out.close();
         in.close();
         fout.close();
         fin.close();
      } catch (Throwable e) {
         throw new RuntimeException(
                  "JavaUtils.copyFile() - Erro desconhecido ao copiar o arquivo \"" +
                           filenameSource + "\" para \"" + filenameDestiny + "\". Error: " + e);
      }
   }

[javautils] readFileAsStringBuffer

   /**
    * Lê o texto de um arquivo
    *
    * @param filename
    * @return StringBuffer - Um StringBuffer com todo o texto do arquivo
    */
   public static StringBuffer readFileAsStringBuffer(String filename) {
      StringBuffer texto = new StringBuffer();
      try {
         FileReader reader = new FileReader(filename);
         BufferedReader in = new BufferedReader(reader);
         String linha = null;
         long count = 0;
         while ((linha = in.readLine()) != null) {
            if (count == 0) {
               texto.append(linha);
               count++;
            } else {
               texto.append("\n");
               texto.append(linha);
            }
         }
         in.close();
         reader.close();
      } catch (Throwable e) {
         throw new RuntimeException(
                  "JavaUtils.readFileAsStringBuffer() - Erro desconhecido ao ler o arquivo \"" +
                           filename + "\". Error: " + e);
      }
      return texto;
   }

quarta-feira, 28 de julho de 2010

[javautils] writeFile

   /**
    * Escreve um texto no arquivo (sobrescrevendo todo o texto que já existia nele)
    *
    * @param filename
    * @param texto
    */
   public static void writeFile(String filename, String texto) {
      try {
         FileWriter writer = new FileWriter(filename);
         BufferedWriter out = new BufferedWriter(writer);
         out.write(texto);
         out.close();
         writer.close();
      } catch (Throwable e) {
         throw new RuntimeException(
                  "JavaUtils.writeFile(String, String) - Erro desconhecido ao escrever um texto no arquivo \"" +
                           filename + "\". Error: " + e);
      }
   }  

[javautils] appendFile

   /**
    * Escreve um texto no final do arquivo (append)
    *
    * @param filename
    * @param texto
    */
   public static void appendFile(String filename, String texto) {
      try {
         FileWriter writer = new FileWriter(filename, true);
         BufferedWriter out = new BufferedWriter(writer);
         out.write(texto);
         out.close();
         writer.close();
      } catch (Throwable e) {
         throw new RuntimeException(
                  "JavaUtils.appendFile(String, String) - Erro desconhecido ao escrever um texto no final do arquivo \"" +
                           filename + "\". Error: " + e);
      }
   }

[javautils] convertCollectionToSqlINClause

   /**
    * Converte uma coleção qualquer em um formato que possa ser colocado em uma cláusula SQL
    * "IN".
    *
    * Ex: dada uma coleção da seguinte forma:
    *
    * Collection textoColecao = new ArrayList();
    * textoColecao.add("primeiro");
    * textoColecao.add("segundo");
    * textoColecao.add("terceiro");
    *
    *
    * A chamada em uma clásula IN ficaria assim:
    *
    *
    * StringBuffer sql = new StringBuffer();
    * sql.append("select * from algo where algo.id in ( ");
    * sql.append(JavaUtils.convertCollectionToSqlINClause(textoColecao);
    * sql.append(" ) ");
    *
    *
    * O resultado da sql montada será:
    *
    *
    * select * from algo where algo.id in ( primeiro, segundo, terceiro )
    *
    * @param colecao
    * @return String
    */
   public static String convertCollectionToSqlINClause(Collection colecao) {
      try {
         StringBuffer buffer = new StringBuffer();
         Iterator iterator = colecao.iterator();
         Object primeiroItem = iterator.next();
         buffer.append(primeiroItem.toString());
         while (iterator.hasNext()) {
            Object item = iterator.next();
            buffer.append("," + item);
         }
         return buffer.toString();
      } catch (Throwable e) {
         throw new RuntimeException(
                  "JavaUtils.convertCollectionToSqlINClause() - Error on convert the collection \"" +
                           colecao +
                           "\" with size \"" +
                           ((colecao == null) ? null : colecao.size()) +
                           "\" in a sql \"IN\" clause. Error details: " + e);
      }
   }

terça-feira, 27 de julho de 2010

[java] convertStringDecimalToBigDecimal

   /**
    * Converte uma String no formato decimal (valor de moeda, p. ex.) para um BigDecimal.
    * Se a String for vazia ou se ocorrer uma exceção, retorna null
    *
    * @param valor
    * @param locale - pegue o Locale desejado pelo método "getLocale..." do JavaUtils
    * @return String
    */
   public static BigDecimal convertStringDecimalToBigDecimal(String valor, Locale locale,
            int minimoCasasDecimais, int maximoCasasDecimais) {
      BigDecimal retorno = null;
      if (JavaUtils.isStringNaoVazia(valor)) {
         try {
            NumberFormat formato = DecimalFormat.getNumberInstance(locale);
            formato.setMinimumFractionDigits(minimoCasasDecimais);
            formato.setMaximumFractionDigits(maximoCasasDecimais);
            retorno = new BigDecimal(formato.parse(valor).doubleValue());
         } catch (Throwable e) {
            log.warn("JavaUtils.convertStringDecimalToBigDecimal() - Erro de " +
                     "conversao. Vai retornar null. Error: " + e);
         }
      }
      return retorno;
   }

[java] isBigDecimalVazio


/**
* Verifica se o BigDecimal está vazio (se é null ou igual a 0)
*
* @param valor
* @return boolean
*/
public static boolean isBigDecimalVazio(BigDecimal valor) {
 try {
  BigDecimal zero = new BigDecimal(0);
  if (valor.compareTo(zero) == 0) {
   // se o numero eh igual a 0 (zero) retorna true
   return true;
  }
 } catch (NullPointerException e) {
  // se o numero eh null, retorna true
  return true;
 }
 return false;
}

[java] convertBigDecimalToStringDecimal

   /**
    * Converte um BigDecimal para uma String no formato
    * decimal (valor de moeda, p. ex.). Se o
    * BigDecimal for vazio ou se ocorrer uma exceção,
    * retorna uma String vazia ("")
    *
    * @param valor
    * @param locale - pegue o Locale desejado pelo
    *       método "getLocale..." do JavaUtils
    * @param minimoCasasDecimais
    * @param maximoCasasDecimais
    * @return String
    */
   public static String convertBigDecimalToStringDecimal(BigDecimal valor, Locale locale,
            int minimoCasasDecimais, int maximoCasasDecimais) {
      String retorno = "";
      if (JavaUtils.isBigDecimalNaoVazio(valor)) {
         try {
            NumberFormat formato = DecimalFormat.getNumberInstance(locale);
            formato.setMinimumFractionDigits(minimoCasasDecimais);
            formato.setMaximumFractionDigits(maximoCasasDecimais);
            retorno = formato.format(valor);
         } catch (Throwable e) {
            log.warn("JavaUtils.convertBigDecimalToStringDecimal() - Erro de " +
                     "conversao. Vai retornar \"\". Error: " + e);
         }
      }
      return retorno;
   }

[java] getLocaleEUA

   /**
    * Retorna o Locale dos Estados Unidos
    *
    * @return Locale
    */
   public static Locale getLocaleEUA() {
      return Locale.ENGLISH;
   }

[java] getLocaleBrasil

   /**
    * Retorna o Locale do Brasil
    *
    * @return Locale
    */
   public static Locale getLocaleBrasil() {
      return new Locale("pt", "BR");
   }

[java] isBigDecimalNaoVazio

   /**
    * Verifica se o BigDecimal não está vazio (se não é null nem igual a 0)
    *
    * @param valor
    * @return boolean
    */
   public static boolean isBigDecimalNaoVazio(BigDecimal valor) {
      return !JavaUtils.isBigDecimalVazio(valor);
   }

domingo, 25 de julho de 2010

[java] convertStringArrayToIntArray

convertendo um array de strings de em um array de int:




/**
* The function stringArrayToIntArray takes as input a String array which is assumed to
* contain Strings that represent ints. It returns an int array containing the ints
* represented by each String from the String array.
*
* @param s
* @return int[]
*/
public static int[] convertStringArrayToIntArray(String[] s) {
try {
int[] intArray;
try {
intArray = new int[s.length];
} catch (NullPointerException e2) {
return null;
}
for (int i = 0; i < s.length; i++) { try {
intArray[i] = Integer.parseInt(s[i]);
} catch (Throwable e) {
try {
if (s[i].equals("")) {
intArray[i] = 0;
continue;
}
} catch (NullPointerException e1) {
intArray[i] = 0;
continue;
}
throw e;
}
}
return intArray;
} catch (Throwable e) {
throw new RuntimeException(
"JavaUtils.convertStringArrayToIntArray() - Error on convert the String array \"" +
s + "\" to an int array. Error details: " + e);
}
}

domingo, 2 de maio de 2010

Java - Convertendo um String pra Date

Método Java para converter um String para Date.

A vantagem deste método é que se o padrão for dd/MM/yyyy e se o usuário digitar 17/10/10, o método converte o "10" para "2010" automaticamente.


   public Date stringToDate(String string, String pattern)
            throws Throwable {
      Date data = null;
      if (JavaUtils.isStringNaoVazia(string)) {
         // Caso o padrão seja dia/mês/ano e o usuário tenha digitado o ano
         // com dois dígitos. (Ex.: 07)
         // o ano deve ser 2007
         if (JavaUtils.isObjectsIguais("dd/MM/yyyy", pattern) && string.length() == 8) {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(string.substring(0, 6));
            stringBuffer.append("20");
            stringBuffer.append(string.substring(6));
            string = stringBuffer.toString();
         }
         // Caso o padrão seja mês/ano e o usuário tenha digitado o ano com dois
         // dígitos. (Ex.: 07)
         // o ano deve ser 2007
         if (JavaUtils.isObjectsIguais("MM/yyyy", pattern) && string.length() == 5) {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(string.substring(0, 3));
            stringBuffer.append("20");
            stringBuffer.append(string.substring(3));
            string = stringBuffer.toString();
         }
         // Caso o padrão seja dia/mês/ano HH:mm e o usuário tenha digitado o ano
         // com dois dígitos. (Ex.: 07)
         // o ano deve ser 2007
         if (JavaUtils.isObjectsIguais("dd/MM/yyyy HH:mm", pattern) && string.length() == 14) {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(string.substring(0, 6));
            stringBuffer.append("20");
            stringBuffer.append(string.substring(6));
            string = stringBuffer.toString();
         }
         // Caso o padrão seja mês/ano HH:mm e o usuário tenha digitado o ano
         // com dois dígitos. (Ex.: 07)
         // o ano deve ser 2007
         if (JavaUtils.isObjectsIguais("MM/yyyy HH:mm", pattern) && string.length() == 11) {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(string.substring(0, 3));
            stringBuffer.append("20");
            stringBuffer.append(string.substring(3));
            string = stringBuffer.toString();
         }
         SimpleDateFormat sdf = new SimpleDateFormat(pattern);
         sdf.setLenient(false);
         data = sdf.parse(string);
      }
      return data;
   }

Java - Métodos para verificar se dois objetos são diferentes e se são iguais

Dois métodos: um para verificar se dois objetos são diferentes ou não e outro para verificar se dois objetos são iguais:

 
public boolean isObjectsDiferentes(Object objeto1, Object objeto2) {
      try {
         if (objeto1.equals(objeto2)) {
            return false;
         }
      } catch (NullPointerException e) {
         try {
            objeto2.toString();
         } catch (NullPointerException e1) {
            return false;
         }
      }
      return true;
   }


   public boolean isObjectsIguais(Object objeto1, Object objeto2) {
      return ! isObjectsDiferentes;
   }



Java - Método que verifica se uma String está vazia

Método que verifica se uma String está vazia ou se é null:


   public boolean stringVazia(String texto) {
      try {
         // pega a primeira letra da palavra. se conseguir, eh porque a string não tah vazia.
         // nesse caso retorna false.
         texto.charAt(0);
         return false;
      } catch (NullPointerException e) {
         // se o texto eh null, retorna true
         return true;
      } catch (StringIndexOutOfBoundsException e) {
         // se o texto eh "", retorna true
         return true;
      }
   }

Java - Convertendo um Date para String

Método para converter um java.util.Date do Java para String:


   public String dateToString(Date date, String pattern) {
      String formatado;
      try {
         SimpleDateFormat sdf = new SimpleDateFormat(pattern);
         formatado = sdf.format(date);

      } catch (NullPointerException e) {          
         return "";

      } catch (Throwable e) {        
         throw e;
      }
   }

Sendo que o parâmetro "pattern" deve ser de acordo com o que está escrito em http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html

Até mais

sábado, 1 de maio de 2010

Chrome Extensions - Web Developer

Web Developer

Extensão pro Chrome que fornece várias informações para programadores web. Muito bom. É a versão do Web Developer do Firefox, só que agora pra Chrome. Muito bom:

https://chrome.google.com/extensions/detail/bfbameneiokkgbdmiekhjnmfkcnldhhm

Chrome Extensions - Flash Video Download

Flash Video Download

Extensão do Chrome para baixar vídeos do youtube, dailymotion, etc.

http://bitbucket.org/m.23/flv_download/wiki/Home

Chrome Extensions - Smooth Gesture


Smooth Gesture


Extensão do Chrome que permitem que você controle e navegue entre as abas do seu navegador através de movimentos do mouse. Muito bom e prático :)

Chrome Extensions - goo.gl URL Shortener

goo.gl URL Shortener
Extensão do Chrome para "encurtar" URLs. Muito útil, rápido e prático. Principalmente pra quem usa twitter.

https://chrome.google.com/extensions/detail/iblijlcdoidgdpfknkckljiocdbnlagk

Java - Convertendo um array de String para um array de Int

Digamos que você tem um array de Strings, só que este array de String na verdade só tem números no formato String dentro dele. Você pode converter este array de Strings em um array de int de forma fácil. É só usar este método abaixo:


   public static int[] convertStringArrayToIntArray(String[] s) {
      try {
         int[] intArray;
         try {
            intArray = new int[s.length];
         } catch (NullPointerException e2) {
            return null;
         }
         for (int i = 0; i < s.length; i++) {
            try {
               intArray[i] = Integer.parseInt(s[i]);
            } catch (Throwable e) {
               try {
                  if (s[i].equals("")) {
                     intArray[i] = 0;
                     continue;
                  }
               } catch (NullPointerException e1) {
                  intArray[i] = 0;
                  continue;
               }
               throw e;
            }
         }
         return intArray;
      } catch (Throwable e) {
         throw new RuntimeException(
                  "JavaUtils.convertStringArrayToIntArray() - Error on convert the String array \"" +
                           s + "\" to an int array. Error details: " + e);
      }
   }

quinta-feira, 29 de abril de 2010

XMarks - Sincronizador de bookmarks (favoritos)

XMarks - ótima ferramenta que serve para sincronizar bookmarks (favoritos).

O XMarks é uma ferramenta que pode ser instalada no firefox, internet explorer ou chrome.

Com ela, todos os seus bookmarks são sincronizados de um navegador pra outro, ou seja, você tem todos os seus favoritos à sua disposição independente do navegador que estiver usando!!! muito bom :)

E o melhor de tudo, você também pode acessar seus favoritos a partir da internet, a partir do site do XMarks, ou seja, você pode acessar seus favoritos onde quer que você esteja! muito bom mesmo.

Este é o site do XMarks:

http://www.xmarks.com

Java - Pegar uma conexão Oracle (OracleConnection) real do Tomcat

Geralmente, precisamos pegar a Connection do banco de dados em que estamos trabalhando para uma porção de coisas: para pegar alguma informação do banco de dados, para chamar alguma stored procedure, para executar algum código específico do banco de dados, etc.

Em alguns casos, quando se está trabalhando com Java e Oracle, se formos utilizar alguns recursos avançados exclusivos do Oracle, veremos que algumas bibliotecas exigem que seja utilizado uma instância de OracleConnection (que seria o resultado normal quando você pega uma conexão com o Oracle via java).

No entanto, quando estamos utilizando o Tomcat e pegamos uma conexão do Oracle, percebemos que não pegamos uma instância de OracleConnection, mas sim uma instância de org.apache.tomcat.dbcp.dbcp.PoolableConnection.

Para conseguirmos pegar o OracleConnection a partir deste org.apache.tomcat.dbcp.dbcp.PoolableConnection retornado, devemos fazer dois passos:

1º) Primeiro devemos alterar o arquivo XML de conexão com o banco de dados do Tomcat. Este arquivo pode ser o context.xml ou o server.xml (ou ainda alguma outra configuração), depende de como você usa o Tomcat para se conectar ao banco de dados.

Este é um exemplo de arquivo de conexão ao banco de dados do Tomcat:


<Context docBase="c:\workspace\MinhaApp\WebContent" path="/minhaapp" reloadable="true">
   <Resource auth="Container"
      driverClassName="oracle.jdbc.driver.OracleDriver"
      maxActive="-1" maxIdle="30" maxWait="10000"
      name="jdbc/minhaapp" type="javax.sql.DataSource"
      username="minhaapp" password="minhaapp"
      url="jdbc:oracle:thin:@localhost:1521:xe"
   />
</Context>


Neste caso, estou usando o Tomcat para me conectar a um banco de dados Oracle XE.

O que devemos fazer aqui é - na tag Resource - que devemos adicionar o atributo "accessToUnderlyingConnectionAllowed" como true.

Dessa forma, o arquivo ficaria assim:

<Context docBase="c:\workspace\MinhaApp\WebContent" path="/minhaapp" reloadable="true">
   <Resource auth="Container"
      driverClassName="oracle.jdbc.driver.OracleDriver"
      maxActive="-1" maxIdle="30" maxWait="10000"
      name="jdbc/minhaapp" type="javax.sql.DataSource"
      username="minhaapp" password="minhaapp"
      url="jdbc:oracle:thin:@localhost:1521:xe"
      accessToUnderlyingConnectionAllowed="true"
   />
</Context>


Pronto, esta é a primeira parte da alteração que precisamos fazer.



2º) A segunda parte da alteração é no próprio código que pega a conexão com o banco de dados.

Digamos que este é o código que você usa para pegar uma conexão com o banco de dados:

InitialContext ic = new InitialContext();
DataSource dataSource = (DataSource) ic.lookup("java:comp/env/XXX");
Connection con = dataSource.getConnection();

Pronto, pegamos a conexão com o banco de dados. Só que, na verdade, pegamos uma instância de org.apache.tomcat.dbcp.dbcp.PoolableConnection.

Para pegar o OracleConnection, devemos utilizar o método "getInnermostDelegate()" da conexão recebida.

Desta forma, o nosso código completo para pegar o OracleConnection seria este:

InitialContext ic = new InitialContext();
DataSource dataSource = (DataSource) ic.lookup("java:comp/env/XXX");
Connection con = dataSource.getConnection();

OracleConnection oracleConnection;
if (connection instanceof org.apache.tomcat.dbcp.dbcp.DelegatingConnection) {
   oracleConnection = (OracleConnection) ((org.apache.tomcat.dbcp.dbcp.DelegatingConnection) connection).getInnermostDelegate();
} else {
   oracleConnection = (OracleConnection)connection;
}


Pronto, desta forma conseguimos pegar o OracleConnection a partir do PoolableConnection.


Atenção: a classe DelegatingConnection é de um .jar do Tomcat. Esta classe está no .jar chamado "naming-factory-dbcp.jar".

Como este .jar está dentro do Tomcat, você não precisa colocar ela dentro do .war do seu projeto. Mas você precisa colocar ela no classpath do seu projeto (build path do projeto no Eclipse, classpath da aplicação, etc.) senão você não irá conseguir compilar este código, ok?


Bem, é isso. Até mais, pessoal.

Sem doutorado? Então fora!


Sem doutorado? Então fora!


Texto interessante que fala que as universidades brasileiras não aceitam profissionais super conceituados como professores universitários enquanto que no exterior este problema não existe.

Segundo o autor: "no Brasil, vigora o 'quem sabe faz e quem não sabe ensina'".

Texto interessante... Questionável? ... deixo as conclusões para cada um. Particularmente não tenho uma opinião 100% formada sobre o assunto. Mas é um assunto interessante.



Usabilidade na Web - Jakob Nielsen

Jakob Nielsen é considerado o pai da usabilidade na web.

Ele tem vários livros sobre usabilidade na web. Vale a pena conhecer o trabalho dele e os seus livros. Procurem no google pelos livros dele.

Além disso, ele mantém um site que fala sobre usabilidade na web. O site é o http://www.useit.com/ (particularmente achei o site dele bem feinho... acho que é o tipo "faça o que digo, mas não faça o que eu faço". mas as idéias dele são muito boas) :)

Abraços

6 Teorias que todo Marketeiro Digital deveria saber

6 Teorias que todo Marketeiro Digital deveria saber

Texto bem interessante sobre teorias sobre a internet, seus usuários e marketing.

muitolegal blog :: sua overdose diária de inspiração: 6 Teorias que todo Marketeiro Digital deveria saber

domingo, 7 de fevereiro de 2010

O homem que Se Endereçou - Ignácio de Loyola Brandão

"O homem que Se Endereçou.

Apanhou o envelope e na sua letra cuidadosa subscritou a si mesmo:

Narciso, rua Treze, nº 21.

Passou cola nas bordas do papel, mergulhou no envelope e fechou-se. Horas mais tarde a empregada colocou-o no correio. Um dia depois sentiu-se na mala do carteiro. Diante de uma casa, percebeu que o funcionário tinha parado indeciso, consultara o envelope e prosseguira. Voltou ao DCT, foi colocado numa prateleira.Dias depois, um novo carteiro procurou seu endereço. Não achou, devia ter saído algo errado. A carta voltou à prateleira, no meio de muitas outras, amareladas, empoeiradas. Sentiu, então, com terror, que a carta se extraviara. E Narciso nunca mais encontrou a si mesmo." Em O Homem do furo na mão e outras histórias de Ignácio de Loyola Brandão.