1 de mai. de 2011

Primitive Obsession, Value object, Uniform Access Principle, Factory Method e Open/Close Principle

Salve,

Este tópico é para abordar um Code Smell (mau cheiro no código) chamado Primitive Obsession (obsessão primitiva). Este Code Smell é encontrado regularmente nas propriedades de classes e não é difícil identificá-lo. A refatoração comum é extrair uma classe, normalmente um Value Object (objeto de valor) imutável, criando um modelo mais rico, fazendo uso de princípios adequados de projeto orientado a objetos, como o UAP, Uniform Access Principle (Princípio do Acesso Uniforme) e OCP, Open/Close Principle (Princípio Aberto/Fechado).

Primitive Obsession

Imagine a situação onde seja necessário modelo um domínio de encomendas, tendo em mente que o valor cobrado no frente é relacionado com o peso da encomenda, pode ser? Naturalmente é comum modelarmos assim:

class Encomenda {
    int peso;
}

Obs.: deixei de lado Getter's e Setter's para favorecer a legibilidade do exemplo;

Mais tarde existirá a intenção de cobrar o frete por peso, por exemplo R$ 2,15 por Kilo. Mas o peso da encomenda usa qual unidade de medida? E se for necessário usar 0,5Kg? Pois então, o modelo deve ser refinado e talvez teremos um modelo assim:

class Encomenda {
    double pesoKilos;
}

Mais tarde é confirmado que a interface gráfica mostrará uma caixa de texto para entrar com o peso em kilos ou gramas. Se o usuário entrar com, por exemplo, 700 gramas, no evento será necessário converter estes 700 gramas para kilos, dividindo por 1000, para depois atribuir a encomenda. Então, após perceber a "gambi", talvez o modelo fique assim:

class Encomenda {
    double pesoKilos;
    int unidade; 
}

Como os outros colegas não entendem o que é essa tal de unidade em inteiro você decide então colocar um (desodorante) comentário:

class Encomenda {
    double pesoKilos;
    int unidade; // use 0 para kilo e 1 para gramas
}

Enfim, é muito comum querermos representar grupos de dados com tipos primitivos e isto é muito comum para quem vem de outras linguagens, a maioria procedural, as quais não proviam uma maneira uniforme e significativa de representar grupos ou estruturas de dados.

E como é a maneira OO de fazer? Ora, criando classes.

Aos que perguntam "bah, quantas classes vou criar?", digo: quantas forem necessárias. Programar em linguagens orientadas a objetos implica em criar classes (tipos), é fato.


Value Object (VO)

Objetos de valor são importantes para representar informações que não necessitam de uma identidade (exemplo, contrastando uma classe Compromisso com uma classe Data). Normalmente são imutáveis e fornecem métodos para tratar seu estado uniformemente. O caso do peso da encomenda é um forte candidato a ser um VO. Extraindo o peso para uma classe estaremos fazendo uma importante refatoração, para "curar" a nossa obsessão primitiva.

De início, poderíamos fazer algo mais ou menos assim:

class Encomenda {
    Peso peso;
}

class Peso {
    int valor;
}

Mas e agora, como vamos tratar esta representação de Peso. Bem, podemos fazer várias melhorias, como representar e converter facilmente unidades de medida, como Kilos, gramas e outros. O Kiko por exemplo, fez uma observação importante. Ele disse que poderíamos até mais tarde representar libras ou outra unidade estrangeira.

Primeiro temos de definir a unidade básica interna de armazenamento e depois fornecer acessores a este estado. Eu prefiro usar gramas, o que seria por exemplo, uma unidade mínima. A classe, agora completa para focar a solução, seria mais ou menos assim:

class Peso {
    private int gramas;

    public Peso(int gramas) {
        this.gramas = gramas;
    }

    public Peso(double kilos) {
        this.gramas =  (int) (1000 * kilos);
    }

    public int getGramas() {
        return gramas;
    }

    public double getKilos() {
        return gramas * 1000;
    }
    
}


Uniform Access Principle

Particularmente, ainda acho que esta classe deve ser refinada, mas ela já mostra a aplicação do Princípio do Acesso Uniforme. Este princípio baseia-se na premissa de que qualquer usuário da classe não precisa saber se a propriedade que está usando é um valor armazenado ou calculado. Se o método getKilos fosse converteEmKilos() estaríamos violando este princípio.

Do modo que está, o usuário da classe pode acessar o peso em gramas e em kilos uniformemente e transparentemente.

Eu não inclui métodos setGramas e setKilos, mas se o fizesse, o método setKilos converteria para gramas, como no construtor, e o usuário da classe ainda não teria conhecimento de como o peso internamente é armazenado (e nem é interessante). Incluir métodos set também implica em tornar o objeto mutável, a não ser que a cada set seja retornado um novo objeto Peso.


Factory Methods

Comentei que era possível fazer uma melhoria, penso que poderíamos substituir os construtores por métodos de fábrica (uma implementação simplificada do padrão de projeto Factory Method). Por quê? Imagine o caso:

Em uma instanciação da classe Peso como esta, como sabemos se é em gramas ou kilos?

Peso peso = new Peso(pesagem);

Pois é, apenas com essa linha não saberemos. Teremos que seguir a variável pesagem para saber se ela é do tipo int ou double para saber se a instância será em gramas ou kilos respectivamente.

Aplicando os métodos de fábrica, torna-se os construtores e privados e adiciona-se métodos estáticos com nomes amigáveis:

class Peso {
    private int gramas;

    private Peso(int gramas) { this.gramas = gramas; }
    
    private Peso(double kilos) { this.gramas =  (int) (1000 * kilos); }

    public static Peso comGramas(int gramas) { 
        return new Peso(gramas);
    }
    
    public static Peso comKilos(double kilos) {
        return new Peso(kilos);
    }
    
    public int getGramas() {
        return gramas;
    }

    public double getKilos() {
        return gramas / 1000;
    }
    
}

Então a chamada anterior ficaria assim:

Peso peso = Peso.comGramas(pesagem);

Desta maneira não é necessário seguir variáveis ou ler comentários para entender esta linha.



Open/Close Principle

Mas e se amanhã quisermos adicionar uma nova unidade de medida? Obviamente teremos que abrir a classe e implementar, a não ser que programemos com o princípio aberto/fechado em mente. O Princípio Aberto/Fechado diz que uma classe deve estar fechada para modificação mas deve estar aberta para extensão.

Existem vários modos de "abraçar" este princípio, um deles é usando o Design Pattern Strategy (padrão de projeto estratégia). Exemplo:


class Peso {
    private int gramas;

    private Peso(int gramas) { this.gramas = gramas; }

    private Peso(double kilos) { this.gramas =  (int) (1000 * kilos); }

    public static Peso comGramas(int gramas) {
        return new Peso(gramas);
    }

    public static Peso comKilos(double kilos) {
        return new Peso(kilos);
    }

    public static Peso comPeso(IConversorPeso conversor, double peso) {
        return new Peso(conversor.paraGramas(peso));
    }

    public int getGramas() {
        return gramas;
    }

    public double getKilos() {
        return gramas / 1000;
    }

    public double getPesoEm(IConversorPeso conversor) {
        return conversor.deGramas(gramas);
    }

    public interface IConversorPeso {

        public double deGramas(int gramas);
        public int paraGramas(double peso);

    }
    
}

Sabendo que 1 grama = 0,00220462262 libras, então poderíamos fazer isto:

class Libras implements IConversorPeso {

    public double deGramas(int gramas) { return gramas * 0.00220462262; }

    public int paraGramas(double peso) { return (int) (peso / 0.00220462262); }
}

Eu posso passar para Peso qualquer implementação de IConversorPeso e assim estender a funcionalidade da classe Peso sem alterá-la, aderindo ao Princípio Aberto/Fechado.


Caso de teste

Sabendo que 10 Kilogramas = 10000 gramas = 22,0462262 libras

Libras libras = new Libras();

System.out.println(libras.deGramas(10000));


Resultado Final

Obtendo o peso em Kilos instanciando com 500 libras:

Libras libras = new Libras();

Peso peso = Peso.comPeso(libras, 500);

System.out.println(peso.getKilos());


Conclusão

Minha implementação tem falhas de arredondamento (usa double para gramas atenuaria) mas acho que demonstra os temas. Espero que seja útil e que eu tenha me feito entender.

Abraços!

19 de abr. de 2011

Lei de Demeter, Train Wrecks e Tell, Don't Ask

Salve,

O objetivo deste tópico é abordar um princípio de OOD (Object-Oriented Design - Projeto Orientado a Objetos) chamado Lei de Demeter e junto aproveitar para falar do anti-padrão Train Wreck e sua possível cura, o princípio "Tell,Don't Ask" (Diga, não Peça).

Lei de Demeter

A Lei de Demeter é fácil de implementar e de entender, talvez o difícil de compreender seja o porquê, em outras palavras, compreender a motivação. A Lei de Demeter também é conhecida como Princípio do Menor Conhecimento.

De forma resumida, a Lei diz o seguinte: "Uma classe ou método deve ter um conhecimento limitado de outras classes e métodos, devendo comunicar-se apenas com as classes imediatamente mais próximas".

Formalmente diz que um método M de uma classe C só deve chamar métodos de:
C
Um objeto criado por M
Um objeto passado como parâmetro para M
Um objeto dentro de uma instância de C
Recentemente, em um projeto de exemplo usado em aula, dá para ilustrar bem a aplicação e a violação da Lei de Demeter. Por exemplo, temos uma classe GestorJanela, que tem internamente uma instância de JDesktopPane.

class GestorJanela {
    // ...

    public JDesktopPane getAreaTrabalho() {
        return areaTrabalho;
    }

}

A partir de uma ação ou método dentro de uma Janela eu posso trazer uma Janela para frente usando o seguinte código:

GestorJanela.getInstance().getAreaTrabalho().getDesktopManager().activateFrame(janela);

Este código viola a Lei de Demeter porque além de conhecer o GestorJanela, ele também conhece a implementação de JDesktopPane ao usar getAreaTrabalho, DesktopManager e do método activateFrame. Se qualquer parte deste código mudar todos os lugares onde é chamado também deverão mudar. Por exemplo, se o GestorJanela passar a usar outro Painel ao invés do JDesktopPane para embutir as janelas, todos os chamadores quebrarão.


Train Wreck

Este mesmo código também é um exemplo do anti-padrão Train Wreck. Ele é encontrado quando existem métodos getIsso().getAquilo().getAquiloOutro().facaAlgo()

O que fazer então? Delegar! Quem deve subir a Janela é o GestorJanela (o nome não é por acaso). A implementação ficaria assim:

class GestorJanela {
    // ...

    public void trazerJanelaParaFrente(JInternalFrame janela) {
        areaTrabalho.getDesktopManager().activateFrame(janela);

        // ainda viola o princípio aqui, mas não temos como 
        // alterar o comportamento de JDesktopPane
        // para não expor o DesktopManager, fazendo parte do Swing 
        // (em outras palavras, a culpa não é nossa)
    }

}

E os chamadores fariam assim:

GestorJanela.getInstance().trazerJanelaParaFrente(janela);

Dá para melhorar este código removendo o Train Wreck:

GestorJanela gestor = GestorJanela.getInstance();

gestor.trazerJanelaParaFrente(janela);

Um colega perguntou: "Vale a pena adicionar mais código executando a mesma funcionalidade?". A resposta é: "Vale". Para manutenções e revisões de código, a implementação deve tão simples quanto possível, permitindo entendê-la com uma passada de olhos. Se são necessárias duas ou mais passadas, então talvez esteja na hora de refatorar e dividir as funcionalidades/responsabilidades.


Tell, Don't Ask

O código de exemplo também é um bom exemplo do princípio Tell, don't Ask. Não seria Tell, don't Ask se fosse implementado assim:

GestorJanela.getInstance().setJanelaTopo(janela);

Métodos get e set são úteis para expor propriedades, não para definir comportamento. Logo, em geral, o hipotético código:

loginController.getUsuarioLogado().getPermissao().setHabilitado(false);

é melhor escrito assim:

loginController.desabilitaPermissao();

Diga, não peça.

Bibliografia:
Martin, Robert C. Código Limpo.
Wikipédia. Law of Demeter.

9 de fev. de 2011

Paradigmas, Coleções, Java, Programação Funcional, C# e Ruby

São vários os paradigmas de programação, entretanto os mais conhecidos e abordados são imperativo, estruturado, procedural e orientado a objetos (OOP). Os quais são suportados por muitas linguagens conhecidas como PHP, Java, Visual Basic, etc. Outros que tem destaque no desenvolvimento de aplicações são o funcional e orientado a aspectos (AOP).

Vou usar um estudo de caso simples nesse post para contrastar:
Imagine uma coleção de candidatos, com apenas dois atributos: nome e idade. Após obter esta lista, de um base de dados ou integração, é necessário subdividi-la em duas: candidatos maiores de idade e menores de idade. Preferencialmente deve ser uma solução reusável.

Abaixo fiz uma implementação em Java. Existem várias estratégias de implementação para obter os menores e maiores, qual seria a sua?

Arquivo Main.java
import java.util.*;

public class Main {

 public static void main(String[] args) {
  
  List<Candidato> candidatos = getCandidatosFromDataSource();
  
  //List<Candidato> menores = ??;
  //List<Candidato> maiores = ??;
  
 }
 
 
 public static List<Candidato> getCandidatosFromDataSource() {
  List<Candidato> candidatos = new ArrayList<Candidato>();
  
  candidatos.add(new Candidato("Alvaro", 23));
  candidatos.add(new Candidato("Vanessa", 13));
  candidatos.add(new Candidato("Paulo", 54));
  candidatos.add(new Candidato("André", 17));
  candidatos.add(new Candidato("Renata", 32));
  
  return candidatos;
 }
  
}

class Candidato {
 
 public String nome;
 
 public int idade; 
 
 public Candidato(String nome, int idade) {
  this.nome = nome; this.idade = idade;
 }

 @Override
 public String toString() {
  return "Candidato [nome=" + nome + ", idade=" + idade + "]";
 }
 
}



Para ilustrar vou extrair os menores de idade com algumas estratégias em Java e depois usando paradigma funcional em C#.


Java - exemplo 1 - usando lista temporária para adicionar os candidatos que aderem a regra:

public static void main(String[] args) {
 
 List<Candidato> candidatos = getCandidatosFromDataSource();
 
 List<Candidato> menores = menoresDeIdade(candidatos);  
 
 System.out.println(menores);
 
}

public static List<Candidato> menoresDeIdade(List<Candidato> candidatos) {
 List<Candidato> menores = new ArrayList<Candidato>();
 for (Candidato c : candidatos) 
  if (c.idade < 18)
   menores.add(c);
 return menores;
}

Este método funciona normalmente como uma implementação procedural. Java - exemplo 2 - usando lista temporária para remover os candidatos que não aderem a regra:

public static void main(String[] args) {
 
 List<Candidato> candidatos = getCandidatosFromDataSource();
 
 List<Candidato> menores = menoresDeIdade(candidatos);  
 
 System.out.println(menores);
 
}

public static List<Candidato> menoresDeIdade(List<Candidato> candidatos) {
 List<Candidato> menores = new ArrayList<Candidato>(candidatos);
 for (Candidato c : menores)
  if (c.idade > 18) menores.remove(c);
 return menores;
}

Esse método FALHA. Não é possível iterar e manipular uma lista simultaneamente. A seguinte exceção será lançada: Exception in thread "main" java.util.ConcurrentModificationException Na verdade, esse é um erro bem comum de programadores Java que não conhecem a API Collections. O método acima funciona se tu usares um ListIterator, que permite percorrer e manipular a lista. Exemplo corrigido:

public static List<Candidato> menoresDeIdade(List<Candidato> candidatos) {
 List<Candidato> menores = new ArrayList<Candidato>(candidatos);
 ListIterator<Candidato> iCandidato = menores.listIterator();
 while (iCandidato.hasNext()) {
  Candidato c = iCandidato.next();
  if (c.idade > 18) 
   iCandidato.remove();
 }  
 return menores;
}

Ok? Dúvidas, comentem.

 Java - exemplo 3 - especializando a coleção de Candidatos:

public static void main(String[] args) {
  
 Candidatos candidatos = new Candidatos(getCandidatosFromDataSource());
 
 System.out.println(candidatos.menoresDeIdade());
 
}


class Candidatos extends ArrayList<Candidato> {
 private static final long serialVersionUID = 0;

 public Candidatos() {
  super();
 }
 
 public Candidatos(Collection c) {
  super(c);
 }
  
 public Candidatos menoresDeIdade() {
  Candidatos menores = new Candidatos();
  for (Candidato c : this) 
   if (c.idade < 18)
    menores.add(c);
  return menores;
 }
}


Particularmente, acho essa solução mais elegante. Ela é orientada a objetos, bem diferente das outras, que chamavam um procedimento passando a lista de Candidatos (procedural). Alguns não gostam por que tem que criar classes, mas o que faz o programador de linguagens orientada a objetos senão modelar e criar classes?

 Dica: Não tenha medo de criar classes.

 O modo C# de fazer 

Assim como em outras linguagens, existem várias estratégias possíveis para fazer esta implementação. Os métodos utilizados em Java também podem ser usados em C#, mas existem duas formas particulares de fazer. Os exemplos a seguir são baseados no código abaixo: 

Arquivo Program.cs

using System;
using System.Collections.Generic;

class Program {

    static void Main(string[] args) {

        List<Candidato> candidatos = getCandidatosFromDataSource();

        // List<Candidato> menores = ??

    }

    public static List<Candidato> getCandidatosFromDataSource() {
        List<Candidato> Candidatos = new List<Candidato>();

        Candidatos.Add(new Candidato { Nome = "Alvaro", Idade = 23 });
        Candidatos.Add(new Candidato { Nome = "Vanessa", Idade = 13 });
        Candidatos.Add(new Candidato { Nome = "Paulo", Idade = 54 });
        Candidatos.Add(new Candidato { Nome = "André", Idade = 17 });
        Candidatos.Add(new Candidato { Nome = "Renata", Idade = 32 });

        return Candidatos;
    }

}

class Candidato {
    public string Nome { get; set; }
    public int Idade { get; set; }

    public override string ToString() {
        return "Candidato[Nome: " + Nome + ", Idade: " + Idade + "]";
    }
}

C# - exemplo 1 - usando yeld:

static void Main(string[] args) {

    List<Candidato> candidatos = getCandidatosFromDataSource();

    foreach(Candidato c in Menor(candidatos))
        Console.WriteLine(c);

}

public static IEnumerable<Candidato> Menor (List<Candidato> Candidatos) {
    foreach (Candidato p in Candidatos)
        if (p.Idade < 18) 
            yield return p;
}

Usando yeld return, é possível acumular os objetos em um IEnumerable, que mais tarde pode ser iterado exatamente como no exemplo.


C# - exemplo 2 - usando extension method, delegate e lambda: 

Tudo começa com esta classe de extensão:

static class Util {

    public static List<Candidato> SomenteSe(this List<Candidato> lista, Criterio crit) {
        List<Candidato> candidatos = new List<Candidato>();
        foreach (Candidato c in lista)
            if (crit(c)) 
                candidatos.Add(c);
        return candidatos;
    }

    public delegate bool Criterio(Candidato c);

}

Com ela posso retornar qualquer sublista de acordo com um Criterio, a definir. Melhor com um exemplo de uso:

static void Main(string[] args) {

    List<Candidato> candidatos = getCandidatosFromDataSource();

    foreach (Candidato c in candidatos.SomenteSe(c => c.Idade < 18))
        Console.WriteLine(c);

}

Neste exemplo o método SomenteSe é uma extensão de List<Candidato>. O parâmetro no parenteses é uma expressão lambda que representa o delegado que avalia o Criterio. É lida mais ou menos assim: para cada candidato c retorna verdadeiro se c.Idade for menor que 18. Essa é uma abordagem funcional, pois eu delego parte do processamento para uma função. 
<Candidato>

<Candidato> Na prática eu nem precisaria ter implementado por que o C# já tem uma API para lidar com coleções chamada LINQ. 
<Candidato>

<Candidato> C# - exemplo 3 - usando o LINQ: 
<Candidato>

 LINQ é o acrônimo de Language Integrated Query. O LINQ é usado para manipular coleções com vários tipos de implementações como: LINQ to Objects, LINQ to XML, LINQ to DataSet, LINQ to SQL, LINQ to Entities, e por aí vai. Cada biblioteca abstrai a manipulação de objetos, elementos ou nodos.

Para usar o LINQ para objetos é necessário declarar o namespace: using System.Linq;

A implementação fica assim:

static void Main(string[] args) {
    
    List<Candidato> candidatos = getCandidatosFromDataSource();

    foreach (Candidato c in candidatos.Where(c => c.Idade < 18))
        Console.WriteLine(c);

}


Se o critério será usado mais de uma vez basta declará-lo:


static void Main(string[] args) {
    
    List<Candidato> candidatos = getCandidatosFromDataSource();
    
    Func<Candidato, bool=""> menores = c => c.Idade < 18;
    
    foreach (Candidato c in candidatos.Where(menores))
        Console.WriteLine(c);

}


Outras linguagens 

 Só para constar, existem outras linguagens que usam o paradigma funcional como Ruby por exemplo. Para ilustrar colei abaixo a mesma funcionalidade feita com Ruby:

Arquivo candidatos.rb

def main
    candidatos = candidatos_from_data_source

    menores = candidatos.select { |c| c.idade < 18 }
    
    puts menores
end

def candidatos_from_data_source
    candidatos = [
        Candidato.new("Alvaro", 23),
        Candidato.new("Vanessa", 13),
        Candidato.new("Paulo", 54),
        Candidato.new("André", 17),
        Candidato.new("Renata", 32)
    ]
    candidatos
end

class Candidato

    attr_accessor :nome, :idade
    
    def initialize nome, idade
        @nome = nome
        @idade = idade
    end
    
    def to_s
        "Candidato[nome: #{@nome}, idade: #{@idade}]"
    end

end

main


Não é a toa que há demanda por programadores multiparadigma. Ainda conversava com o Leonardo esses dias sobre uma vaga em que pediam conhecimento de Haskell, que é uma linguagem puramente funcional. Posso falar mais em outro post/tópico, inclusive sobre Programação Orientada a Aspectos.

Qualquer dúvida, comentem.

1 de fev. de 2011

Strings no Java


Aproveitando a conversa com o Bruno sobre equals e == queria expor uma situação interessante. O modo como o Java lida com Strings tem sua particularidades que estão diretamente ligadas ao gerenciamento de memória. Por exemplo, no caso abaixo, a primeira assertiva é verdadeira, já que "aaa" é igual a "aaa", e a segunda assertiva é falsa, mesmo digitando "aaa" em "Outro texto". Se o operador == for substituído pelo método equals ambas assertivas retornam verdadeiro. Por quê?

public class Main {
    public static void main(String[] args) {

        String texto = "aaa";

        System.out.println("texto == aaa: " + (texto == "aaa") );

        System.out.print("Outro texto: ");

        String outroTexto = new Scanner(System.in).nextLine();

        System.out.println("texto == outroTexto: " + (texto == outroTexto) );

    }
}


O segredo está na arquitetura da memória no Java, embora existam algumas estratégias diferentes dependendo da implementação da máquina virtual, como a Java Hotspot, Oracle JRockit e o Apache Harmony.

Basicamente temos a memória divida como ...

  • ... uma pequena área chamada Stack (pilha), onde ficam as variáveis locais e chamadas de métodos (onde são empilhados). Por isso quando fazemos um encadeamento muito longo de chamadas, ou um processamento recursivo muito profundo, recebemos o erro: StackOverflowError. O Bruno teve um erro assim no C#;
  • ... um espaço maior, que é a memória disponível para os objetos, chamada Heap. Geralmente o Heap é divido em Young e Tenured (ver imagem abaixo), ou seja, objetos jovens ficam em parte de memória e se sobrevirem ao coletor de lixo são movidos para maduros (tenured), onde o intervalo de tempo para coleta é maior.
  • ... um espaço chamado Permanent Generation (geração permanente), onde ficam as informações carregadas que serão usadas durante todo o tempo de vida da aplicação, desde aberta até fechada. Nesta área ficam as classes por exemplo e outros dados estáticos, e também, e aí está o segredo, as String literais.


Toda String literal no programa, como o "aaa" no código, são instanciadas (new) e armazenadas no PermGen (não no Heap). Assim, quando novamente usamos "aaa", literalmente no código, ele utiliza aquela instância no PermGen. Isto é uma técnica para melhorar a performance e economizar memória, e foi por esse motivo que comparando "aaa" == "aaa" devolve verdadeiro, pois ambos apontam para a mesma String na memória. Agora no caso de usar new String("aaa") (como observou o Paulo), ou ler uma String de uma base de dados, console, parâmetro de uma requisição HTTP, enfim, tudo que não for literal, nós temos uma nova instância.

O resumo da ópera é, como disse o Leonardo, devemos usar o equals, que é implementado para comparar os atributos e não a posição na memória. No C#, é possível usar o operador == sempre, por que ele é sobrescrito na classe String para executar o método equals, ou seja, quando eu faço no C# "aaa" == "aaa", ele faz "aaa".equals("aaa").

Só mais um detalhe, se alguém aqui cria aplicações Web no Java e levanta no Tomcat, Glassfish, JBoss ou Jetty, já deve ter percebido que em desenvolvimento se for fazendo vários deploys, uma hora ou outra, tem que para o servidor e iniciar outra vez por que dá OutOfMemoryError: PermGen space. É por quê cada vez que a aplicação é implantada (deployada é horrível), as classes e Strings literais são novamente colocadas no PermGen e ele acaba estourando.

Algumas leituras sugeridas são:

The Structure of the Java Virtual Machine: http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html

Java HotSpot VM Options: http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

Entendendo o NoSuchMethodError e o ClassLoader hell: http://www.arquiteturajava.com.br/livro/entendendo-nosuchmethoderror-e-classloader-hell.pdf

Começando com parâmetros e configurações da JVM: http://blog.caelum.com.br/comecando-com-parametros-e-configuracoes-da-jvm/

Mão sei se me fiz entender. Já passei por alguns problemas de performance e estabilidade com Java, por isso conheço um pouco dessa arquitetura.

Abraço a todos

20 de jan. de 2011

C# Extension Methods (e outros temas tranversais)

O problema

Quem aqui né amigo, não precisou de uma funcionalidade em um tipo básico e como não tinha acabou por criar classes com nome -tipo-Util.

Não entendeu? Bem, vou dar um exemplo. Imagine que no Java eu queira saber se uma String é um número (algo que seria útil no recebimento de parâmetros em requisições Web -que são todos string).

Não existe o método isNumber, isInteger, etc, em String. Existem métodos estáticos em Character, como isDigit.

Então, lá vem os TipoUtil's. Exemplo:

public class Main {
    public static void main(String[] args) {
        
        String parametro = "15.2";
        
        System.out.println(StringUtil.isInteger(parametro));    
    }
}

class StringUtil {
    
    public static boolean isInteger(String string) {        
        for (char c : string.toCharArray())
            if ( ! Character.isDigit(c)) return false;
        
        return true;        
    }
    
}

Neste caso, mostra: false.

Obs.: o método poderia ter sido implementado com expressões regulares.
Obs2.: Existem implementações de bibliotecas utilitárias por vários frameworks, como o Apache: http://commons.apache.org/lang/api-2.5/org/apache/commons/lang/StringUtils.html

E se eu chamar:
if ("641".isInteger()) System.out.println("É um Inteiro");

Não, embora pareça legível, não dá. Eu tenho que usar o método estático:
if (StringUtil.isInteger("641")) System.out.println("É um Inteiro");


Procedural vs Orientado a Objetos

Métodos Estáticos são implementações procedurais -sim, é possível programar proceduralmente em uma linguagem orientada a objetos.

O que denuncia isto é a passagem de um objeto para outro, o qual te dá informações sobre o primeiro. Em outras palavras, não é o próprio objeto que sabe de seu estado, eu tenho que passá-lo a outro para obter informações.

Algumas linguagens, mesmo adicionando orientação a objetos, tem aspectos procedurais. Por exemplo, não sei muito de PHP, mas pelo que conheço para saber o tamanho de um array devo chamar a função count (ou seu alias sizeof- corrijam-me se eu estiver errado):



Poderia ser assim:
echo $frutas.count(); // mas não é

Bem, o C#, assim como o Java, é uma linguagem com chamadas estáticas -o próprio Main é um caso. A implementação do isInteger, como foi feito no Java, a princípio não ficaria muito diferente, veja abaixo:

using System;

class Program {
    static void Main(string[] args) {

        string parametro = "123";
        Console.WriteLine(StringUtil.IsInteger(parametro));

    }
}

class StringUtil {
    public static bool IsInteger(string str) {
        foreach (char c in str.ToCharArray())
            if ( ! Char.IsDigit(c)) return false;
            return true;
    }
}

Quase a mesma coisa não é mesmo?

Então como alterar a classe String para que eu possa fazer "123".IsInteger()? Não tem jeito, a classe String é selada (sealed), ou seja, não é possível estendê-la.



Extension Methods

Entretanto, há um subterfúgio que pode deixar o código "menos feio", os Extension Methods, ou métodos de Extensão.

Exemplo:

using System;

class Program {
    static void Main(string[] args) {

        string parametro = "123";
        Console.WriteLine(parametro.IsInteger());

    }
}

static class StringUtil {
    public static bool IsInteger(this string str) {
        foreach (char c in str.ToCharArray())
            if ( ! Char.IsDigit(c)) return false;

        return true;
    }
}

É necessário adicionar a palavra-chave static na classe (uma classe estática só pode ter métodos estáticos) e a palavra-chave this no parâmetro.

Na prática, é um açúcar sintático, se por exemplo eu chamar: "9971".IsInteger()
O Runtime traduz para StringUtil.IsInteger("9971").

Legal, não? Existem várias implementações que são feitas com Extension Methods, inclusive nos Frameworks ORM da Microsoft, o Entity Framework ou o Linq to SQL.

Obs.: Algumas linguagens permitem que eu reabra qualquer tipo, mesmo os internos, como no Ruby. No Ruby eu posso adicionar qualquer método a qualquer classe em tempo de execução, como por exemplo um método para testar se é inteiro "123123".is_integer?. Abaixo a implementação usando o IRB:

class String
     def is_integer?
         self.to_i.to_s == self
     end
end

"23123".is_integer?
# devolve true
"231.23".is_integer?
# devolve false

Obs.: Na convenção de nomes do Ruby, métodos que retornam booleano terminam com ? (interrogação)


Cultura

Quando tu conheces mais de uma linguagem isto ajuda a mudar a visão, uma mudança de cultura. Por exemplo, em C# e Java, para transformar uma string em inteiro, normalmente chamamos um método estático (procedural) da classe Int32/Integer, como no exemplo:

C#:
int valor = int.Parse("12");

Java:
int valor = Integer.parseInt("12");

No Ruby, por exemplo, o método para converter uma string em inteiro está na própria classe string, exemplo:
"12".to_i

Então, como eu gosto dessa "forma" do Ruby, eu implemento em C# assim nos meus projetos:

public static class StringUtils {

    public static int ToInteger(this string text) {
        int number;
        return int.TryParse(text, out number) ? number : 0;
    }

}

Então eu posso fazer isso em C#:
"12".ToInteger();


Design com Exceções ou Não

Optei por fazer um design sem exceções, ou seja, se eu chamar "dsd".ToInteger() retorna 0. Se na implementação eu tivesse usado int.Parse poderia lançar uma exceção se a string não for válida.

O desenvolvedor sempre pode optar pode fazer o design de funcionalidades para exceção ou não, desde que seja elegante e de acordo com o contexto.

Por exemplo, em um sistema Web, se por exemplo for passado na URL o parâmetro domino/produtos?page=12, e a listagem tiver apenas 10 páginas, o que fazer? Mostrar uma tela de página inválida? Ou mostrar a última página (no caso 10)? Particularmente eu optaria por mostrar a última, e se a página fosse inválida (algo como page=dsfsd) eu mostraria a primeira.

Outro exemplo, se recebo na URL domino/cliente/action=excluir?id=200. O ID não existe, o que fazer? Eliminar o último ID? hehehehe, penso que não, este é um bom exemplo de lançar a exceção.

Cada caso é um caso. Não sei se me fiz entender.



Programação Funcional

O C# tem suporte ao paradigma de programação funcional. Ainda não abordei detalhes a respeito, mas adianto que é muito comum usar extension methods e delegados (ou lambdas).

Por exemplo, supondo que com frequência eu tenha que executar alguma tarefas certo número (finito) de vezes. Normalmente usamos um for com um contador, tipo, i = 0 e i < 20, certo? Bem, então eu posso criar um aspecto funcional, usando Extension Methods e Delegates (delegados) para fazer esse processamento, como no código abaixo:

public static class IntegerUtils {

    public delegate void Proc(int value);

    public static void Times(this int value, Proc proc) {
        for (int i = 0; i < value; i++) proc(i + 1);
    }
}

Para usar, um exemplo:
5.Times(delegate (int i) { Console.WriteLine("Iteração " + i); });

Ou, usando uma lambda:
5.Times(i => Console.WriteLine("Iteração " + i));


Obs.: O Ruby também tem suporte a programação funcional através de blocos e procs. Esta implementação que fiz no C# existe no Ruby e poderia ser feito assim:
5.times { |n| puts "Iteração #{n}" }


Acho que é isso. Se alguém testar e tiver algum erro poste aí, fiz os exemplo na correria.

Abraço a todos

24 de dez. de 2010

Programador e Desenvolvedor: enfim, qual a diferença?

Este assunto é bem polêmico, para uns, programar e desenvolver significam a mesma coisa, para outros (como eu), são atividades distintas.

Então, um conceito de programador:

Um programador é um profissional preocupado com a qualidade do código - por isto também é chamado de coder. Normalmente, conhece muito uma determinada linguagem e tem sólidos conhecimentos de lógica e matemática. A sua atividade geralmente começa quando lhe é entregue uma especificação e, a partir desta, escreve o código necessário para cumpri-la. O programador geralmente atua em partes específicas da aplicação.

E o conceito de um desenvolvedor:

Um profissional preocupado com a qualidade do software. Normalmente tem um conhecimento mais interdisciplinar, não só da linguagem, mas de plataforma, tecnologia, negócio, administração. Com um olhar mais crítico, consegue avaliar soluções diferentes para o mesmo problema e selecionar aquela que tem o melhor custo benefício para o domínio do problema. O desenvolvedor geralmente está preocupado com a big picture e esforça-se para pensar fora da caixa.

Pré-conceitos, conceitos e pós-conceitos, por aí ...

Neste blog, é argumentado que o trabalho realizado por programadores e desenvolvedores é fruto das mudanças de mercado. Compara o desenvolvimento de software a uma linha de montagem de uma fábrica e o programador àquele funcionário que executa uma determinada pequena tarefa.

Neste outro, achei interessante a abordagem sobre o aspecto social de programadores e desenvolvedores; embora não concorde em totalidade.

Alguns são mais contundentes, ao ponto de dizer "seja um desenvolvedor, não um programador". Também não acho que é por aí, penso que se alguém é feliz codificando, sem as dores de cabeça do desenho e arquitetura, que mal há?

Para descontrair: vida de programador/desenvolvedor ...




Tem certeza que deseja seguir nesta carreira?

27 de nov. de 2010

Palestra sobre TDD no Tchê Linux Rio Grande

Hoje, 27 de novembro, é dia de Tchê Linux em Rio Grande. Eu estarei por lá com a apresentação sobre Metodologia Ágil: TDD com Java. Fico feliz de participar deste evento, já participei no de Pelotas, que tem muito valor para os profissionais e "aspiras" de TI Infraestrutura e Desenvolvimento. Agradeço a todos os organizadores do evento e patrocinadores.

Ainda lançarei mais posts sobre TDD. Os slides e código da palestra estão abaixo:
Para abrir o código-fonte sugiro que importe os projetos para um workspace do Eclipse.

Para quem está lendo este post durante ou após a apresentação, obrigado pela presença.

Quem não compareceu mas deseja saber do que se trata, não hesite em perguntar.

26 de set. de 2010

Documentar faz bem

Um recurso que às vezes não é aproveitado pelos desenvolvedores é a documentação.

Bem, já disse em outros posts que o código "fala", em outras palavras, um código bem escrito faz os outros programadores entenderem a intenção.

Também sou adepto do ditado que diz: "Sempre que for adicionar um comentário, pense: 'Como eu posso escrever este código para que este comentário não seja necessário?'".

Mas o que estou falando neste tópico é mais do que apenas inserir comentários, digo usar o método de documentação de código disponível na linguagem na hora de criar uma API.

Aqui Josh Bloch fala como desenhar uma boa API e em um trecho da apresentação diz:

Documente todas classes, interfaces, métodos ...

Também diz:

Reuso é algo mais fácil de falar do que fazer. Fazer requer bom design e muito boa documentação.

Cada linguagem fornece um meio de documentar as classes, interfaces e métodos, no Java tem o JavaDoc no .NET tem o XML Documentation e por aí vai.

No caso do Java, os profissionais que desenvolvem na linguagem já devem ter percebido quando usam um IDE com code completion/assist, como o Eclipse ou NetBens, que aparece a descrição dos métodos, exceções e parâmetros na janela de contexto, sempre que uma classe ou método é autocompletado ou o codeassist é invocado, como na ilustração:



Mãos a obra no Javadoc então

Vou ilustrar com uma aplicação baseada no modelo Transaction Script, onde disponibilizo uma classe de serviço com uma interface para operações de Crédito e classes de dados que representam o retorno desse serviço, ok?

Então eu tenho uma classe de dados que representa uma Analise de Crédito, como abaixo:



public class AnaliseCredito {

    // .. dados sobre a análise de crédito
}


A primeira documentação é sobre a classe, que irá expor detalhes importantes acerca do que as instâncias desta classe irão representar.

Todo início de documentação no padrão Javadoc inicia com /**, seguido de um * por linha e termina com */, como abaixo:



/**
 * 
 * @author Márcio Torres
 */
public class AnaliseCredito {

}


Outras informações pertinentes podem ser a versão e a qual a versão mínima, como segue:



/**
 * 
 * @author Márcio Torres
 * @version 1.0, 09/23/10
 * @since 1.5
 */


De acordo com a Oracle a versão deve usar o formato acima, mas obviamente cada equipe pode chegar em um consenso para isto, eu por exemplo gosto do formato timestamp (2010-09-23 13:55:52).

Ainda falta explicar a classe, como segue:



/**
 * Representa o resultado de uma Análise de Crédito com detalhes importantes
 * acerca de um determinado cliente e o risco a ele associado no que diz
 * respeito a empréstimos e financiamentos.
 * 
 * @author Márcio Torres
 * @version 1.0, 09/23/10
 * @since 1.5
 */
public class AnaliseCredito { ...


Tente sempre respeitar as 80 colunas para escrever esta descrição. Ainda é possível destacar certos trechos, como a tag tt que representa um trecho de código, i, strong, e outras tags HTML. Também é possível indicar a consulta para classes relacionadas através do @see, como abaixo:



/**
 * Representa o resultado de uma Análise de Crédito com detalhes importantes
 * acerca de um determinado cliente e o risco a ele associado no que diz
 * respeito a empréstimos e financiamentos, representados pelas
 * classes Emprestimo e Financiamento
 * 
 * Para analisar o risco use o método {@link #getRisco()}
 * 
 * @author Márcio Torres
 * @version 2010-09-23 13:40:25
 * @see Financiamento
 * @see Emprestimo
 * @since 1.5
 */
public class AnaliseCredito { ...



Como no código acima, ainda é possível estabelecer links para métodos importantes da classe. Embora existam outros detalhes, os que exemplifiquei acima já cobrem o básico, mais detalhes aqui: http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html

Documentar as interfaces e classes é só o começo, é necessário comentar os métodos. Neste caso, é comum nas interfaces esclarecer detalhes do contrato, sendo normalmente mais detalhado, e que também de algum modo dê informações a um programador que venha a implementá-la, como segue o exemplo abaixo de uma interface e uma implementação simples:



public interface CreditoService {

 AnaliseCredito analisaCredito(String cpf);
 
}

public class CreditoServiceImpl implements CreditoService {

 @Override
 public AnaliseCredito analisaCredito(String cpf) {
  if (cpf == null) {
   throw new NullPointerException("O CPF não foi informado (nulo)");
  }
  if (cpf.length() < 11) {
   throw new IllegalArgumentException("O CPF não está completo, use apenas números e sem formato, ex: 11122233344");
  }
  if (cpf.length() > 11) {
   throw new IllegalArgumentException("O CPF tem caracteres a mais, use apenas números e sem formato, ex: 11122233344");
  }
  
  // Verifica o Crédito através de uma integração
  // pode lançar WebServiceException
  // webService.checkCredit(cpf) ... 
  
  AnaliseCredito analise = new AnaliseCredito();
  
  return analise;
 }

}


Documentando a interface:



/**
 * Contrato para as operações relacionadas a <strong>Crédito</strong> e
 * operações financeiras.
 * 
 * @author Márcio Torres
 * @version 2010-09-23 14:13:10
 * @see AnaliseCredito
 * @since 1.5
 */
public interface CreditoService {

 /**
  * Dado um CPF reúne informações pertinentes e traz a resposta na forma
  * de uma instância de {@link AnaliseCredito}.
         * 
         * Para obter uma análise com mais dados use
  * {@link #analiseCompletaCredito(String)}
         *
  * @param cpf
  *   O número do CPF que será utilizado para recolher as informações
  *   sendo apenas número e sem formato, por exemplo: <tt>11122233344</tt>
  * @return
  *   Uma análise de crédito representada pela classe
  *   {@link AnaliseCredito}.
         * @throws NullPointerException 
  *   Se for passado um parâmetro nulo
  * @throws IllegalArgumentException
  *   Se o CPF não tiver 11 números ou for inválido
  */
 AnaliseCredito analisaCredito(String cpf);
 
}



No exemplo foi documentada a interface e o método analisaCredito. Na documentação do método é explicada a pré-condição, pós-condição e efeitos colaterais, bem como o formato do parâmetro esperado e as exceções que podem ser lançadas. A classe de implementação pode ser mais espartana, mas ainda tem que documentar detalhes da implementação, como exceções novas. Segue exemplo:



/**
 * Executa operações relacionadas a <strong>Crédito</strong> e financeiras
 * 
 * @author Márcio Torres
 * @version 2010-09-23 14:24:20
 * @see AnaliseCredito
 * @since 1.5
 */
public class CreditoServiceImpl implements CreditoService {

 /**
  * Dado um CPF reúne informações pertinentes devolvendo
  * uma Análise de Crédito {@link AnaliseCredito}.
         * 
  * @param cpf
  *   O número do CPF que será utilizado para recolher as informações,
  *   por exemplo: <tt>11122233344</tt>
  * @return
  *   Uma análise de crédito
  * @throws NullPointerException 
  *   Se for passado um parâmetro nulo
  * @throws IllegalArgumentException
  *   Se o CPF for inválido
  * @throws WebServiceException
  *   Se houver um problema de comunicação com o subsitema de integração
  */
 @Override
 public AnaliseCredito analisaCredito(String cpf) {
 
     // implementação
     
 }
 
}



Resultados

O resultado da documentação, no IDE Eclipse por exemplo, é o que pode ser visto abaixo:



Ao completar o método fica assim:



E pelo Eclipse é possível exportar como HTML através de File -> Export -> Javadoc. Após concluir, se todas as opções ficarem em default, aparecerá um diretório doc no projeto e os HTML's. Basta usar um Open With -> Web Browser, e mais tarde disponibilizar a documentação em um lugar comum para a equipe.

Veja abaixo como ficou:



Outras linguagens

Outras linguagens disponibilizam o seu meio de documentação, PHP, ActionScript, C#, esta última posso dar um exemplo pois tenho mais contato:

C# com XML Documentation:



namespace Business.Services {

    /// <summary>
    ///     Executa as operações relacionadas a crédito e finanças.
    /// <summary>
    /// <remarks>
    ///     A partir desta classe são disponibilizados ...
    /// </remarks>
    public class CreditoServiceImpl : CreditoService {

       /// <summary>
       ///     Checa o crédito dado um CPF ...
       /// </summary>
       /// <param name="cpf">
       ///     CPF para consultar ....
       /// </param>
       /// <seealso cref="AnaliseCompletaCredito">
       ///     Veja o método AnaliseCompletaCredito
       /// </seealso>
       /// <exception cref="System.ArgumentException">
       ///     Lançada quando o CPF não é válido
       /// </exception>
       /// <exception cref="System.ArgumentNullException">
       ///     Lançada quando o CPF não é nulo
       /// </exception>
       public AnaliseCredito AnaliseCredito(String cpf) {
         ...


Acho que é isso. A implementação no exemplo não foi perfeita, mas penso ter coberto o objetivo de apresentar a documentação e espero ter ficado entendido a necessidade da mesma.

Abraço, até a próxima.

31 de ago. de 2010

Tchê Linux: Boas práticas e Refatorações

Olá,

A apresentação no último sábado (28/ago) foi muito legal. Encontrei vários amigos, conversei com pessoas do ramo, etc. Esses encontros sempre são bons.

Na apresentação falei sobre Boas práticas e refatorações com Java, e fiz mais slides e código do que tinha de tempo disponível, para variar. Bem, o fato é que deixei disponível tanto a apresentação quanto o código que podem ser baixados nestes links abaixo:

Apresentação:
https://docs.google.com/leaf?id=0B3CElJmzM2Y6YjlmYjY2NWMtYTQ1Yi00YjIxLWExNGQtMGY1NzBkYTZiODk0&sort=name&layout=list&num=50

Código fonte:
https://docs.google.com/leaf?id=0B3CElJmzM2Y6MzlmZDNkYjQtOWQ2Mi00NDM1LWFjMjYtNTFmMTAwOGNhYWE3&sort=name&layout=list&num=50

Desculpem o atraso para disponibilizar, pois resolvi deixar uns comentários no código para facilitar o entendimento e editei o texto e Layout dos slides também.

Breve postarei mais coisas neste blog. Um abraço a todos e até a próxima, quem sabe estarei no Tchê Linux Rio Grande na FURG? Vou tentar. Até!

28 de jun. de 2010

Strings no Java


Intro

Salve,

Após conversar com colegas e tal, resolvi compartilhar o conhecimento sobre Strings e sua implementação e características na linguagem Java. Alguns detalhes inclusive fazem parte do exame para Programador Java Certificado (SCJP) e a utilização em outras linguagens é semelhante, como no C#, ActionScript, etc.

Espero que seja de bom proveito.


Strings

As Strings diferentemente dos tipos primitivos (int, long, float, double, char, double) são reprentadas por uma classe. Logo, toda declaração de String é uma instância. 

Ver exemplo:

import static java.lang.System.out;

public class Teste {
public static void main(String[] args) {
out.println("Teste");
}
}

No exemplo, a simples declaração de "Teste" gera uma instância (automaticamente) da classe String. Isto permite que eu faça código desta maneira:

String texto = "Teste";
out.println(texto.toUpperCase());
out.println("Teste".toUpperCase());

Ambas instruções de saída geram: TESTE

Como qualquer instância de uma classe, sendo um objeto, as String tem atributos e métodos. Mas existem duas características que fazem o tratamento de String um pouco diferente:

- Strings são imutáveis
- Strings literais são armazenadas no pool no espaço de geração permanente (PermGen Space).




Strings são imutáveis

Cada operação chamada em uma instância de String cria outra String (nova instância). Isto garante a integridade das referências. Ver exemplo:

String texto = "teste";
texto.toUpperCase()
out.println(texto);

Produz a saída: "teste".

Ao usar o método toUpperCase na variável texto, foi criado uma nova String em maiúscula. A String referenciada pela variável texto ainda é "teste" e, enquanto a variável texto não for reatribuida, sempre será. Se quiser que a variável texto aponte para o novo objeto String em maiúsculo faça o seguinte:

String texto = "teste";
texto = texto.toUpperCase()
out.println(texto);

É importante entender que cada alteração na String gera uma nova instância, e isto implica em questões de performance e confiabilidade. Exemplo:

String frase = "texto";
frase = frase + " de ";
frase = frase + "exemplo";

Neste caso, eu criei pelo menos 5 instâncias: "texto", " de ", "texto de", "exemplo", "texto de exemplo".

Este é um dos motivos por que é preferível usar StringBuilder quando é necessário concatenar muitas Strings. Exemplo:

StringBuilder frase = new StringBuilder();
frase.append("texto");
frase.append(" de ");
frase.append("exemplo");

Note que ainda assim, cada declaração literal gera uma instância, mas o StringBuilder (StringBuffer se for necessário sincronização para Threads) pode receber variáveis por exemplo.

[ :( ] Por exemplo, nunca faça isso:

String nomes = "";
for (String nome : dataSource.getNomes()) {
nomes = nomes + "; " + nome;
}

Supondo que seja uma lista de 100 nomes. A cada iteração é gerado uma nova String com o nomes + ";", e depois outra com nomes (já com o ;) mais o nome.

[ :) ] Faça assim:

StringBuilder nomes = new StringBuilder();
for (String nome : dataSource.getNomes()) {
nomes.append(nome);
}

Para pegar a String gerada faça: nomes.toString();


[ :( ] Como não há garantia de estar referenciando a mesma instância nunca faça isto:

if (texto == "sim") { ...

[ :| ] Talvez faça isto:

if (texto.equals("sim")) { ...

[ :) ] Mas, prefira fazer assim:

if ("sim".equals(texto)) { ...

Por que? Por que se texto vier nulo, no caso de texto.equals("sim") receberemos um belo NullPointerException. No caso de "sim".equals(texto), se a variável for nulo simplesmente avaliará falso. Estude o contrato do equals: http://java.sun.com/javase/6/docs/api/java/lang/Object.html#equals(java.lang.Object)


Entendido?




Strings são armazenadas no espaço de geração permanentente

O Java reutiliza Strings utilizando o pool de String, que é guardado no espaço de geração permanente, o PermGen Space.

Em outro artigo já falei sobre como a memória é gerenciada no Java, mas para clarear, existem 3 espaços básicos:
- A pilha (Stack): onde ficam as variáveis e os métodos;
- O Heap: onde ficam as instâncias de objetos;
- O espaço de geração permanente (PermGen): onde ficam armazenadas informações até o fim da aplicação, como as Classes e as Strings.

Ou seja, se eu declarar uma String "teste", assim, literalmente na aplicação, ela irá para o PermGen.

Acredito que os leitores saibam que o operador == compara igualdade de referências, não de conteúdos, certo? Bem, vamos a um exemplo:

String a = "teste";
String b = "teste";
out.println(a == b);

Qual é a saída?

true

Embora pareça óbvio, nem sempre é. Neste caso, acusou verdadeiro por que ambas variáveis apontam para a mesma instância, já que o primeiro "teste" foi colocado no pool e reutilizado para a variável b, então NESTE CASO 'a' e 'b' referenciam o mesmo objeto. 

O fato de as String serem imutáveis favorecem esta técnica, já que a instância "teste" no PermGen nunca será alterada.


Bem, eu posso forçar a criação de uma nova instância, em outras palavras, não aproveitar do pool, fazendo assim:

String a = "teste";
String b = new String("teste");
out.println(a == b); 

Ambas valem "teste", mas a saída da operação é false. Sempre que eu utilizo a palavra-chave new com Strings, eu estou ignorando o pool.

[ :( ] Evite usar o new para Strings, por exemplo, a instrução new String("teste") gera duas instâncias, a literal "teste" vai para o PermGen, e outra instância é gerada no Heap devido a palavra-chave new. 

[ :) ] Aproveite o pool e só instancie diretamente quando tiver uma razão clara para isto.



Operações com Strings

A classe String já tem inumeras operações, desde conversão de caixa até extrações (substrings). 

Contudo, algumas operações podem lhe fazer falta de alguma maneira.

Inicialmente, é normal pensarmos em estender a classe String, só que isto não é possível.

Devido ao contrato de imutabilidade, a classe String é final (sealed), e não pode ser estendida.

Por isso é comum criarmos métodos auxiliares em outra classe, tipo, uma StringUtil (no java temos muito destas xxxUtil).

Por exemplo, se preciso de um método que me diga se a String é um número posso fazer um método nesta classe utilitária. Fiz um exemplo tosco abaixo:


import static java.lang.System.out;
import java.util.regex.*;

public class Teste {
public static void main(String[] args) {
String b = new String("2132");
out.println(StringUtil.isNumber(b));
}

}

class StringUtil {
public static boolean isNumber(String s) {
if (s == null) return false;
Pattern p = Pattern.compile("\\d*");
Matcher m = p.matcher(s);
return m.matches();
}
}


Do meu ponto de vista este é um código procedural, não que seja ruim antes que me atirem pedras, mas que não é bem a proposta do paradigma OO. 

[ ! ] Não necessariamente por utilizar uma linguagem orientada a objetos estamos desenvolvendo um programa orientado a objetos, entende?

Bem, isto fica para outro tópico.


Dúvidas, comentários, críticas, sugestões, etc, fiquem a vontade.


Um abraço

Márcio Torres