Exercícios baseados em casos reais para iniciantes

Usarei este tópico para postar alguns exercícios baseados em casos reais.

Os que pensei hoje são baseados em dois pequenos detalhes que já deixaram desenvolvedores com a pulga atrás da orelha por não saberem o que estava acontecendo, passando um bom tempo deles debugando o código para encontrar a solução.

Estes exercícios não acabam quando uma solução é encontrada, procure também refatorar o métodos e as classes envolvidas para que erros humanos como esse sejam minimizados.

Expliquem também o por quê destes erros ocorrerem.

  1. Nos dias das mães, uma loja resolveu coletar o nome das mães de seus clientes e seus respectivos e-mails para mandar cupons de presentes da loja. Parte do código era assim:
Mãe mãeDoCliente = new Mãe();
List listaDasMães = new ArrayList();

for (Cliente c : clientes)
{
  mãeDoCliente.setNome(c.getNomeDaMãe());
  mãeDoCliente.setEmail(c.getEmailDaMãe());

  listaDasMães.add(mãeDoCliente);
}

Segunda-feira depois dos dias das mães, uma senhora chegou com mais de mil cupons para trocar, e para surpresa da loja, todos eram válidos, com o nome dela. Como era de se esperar, entrou Procon, a justiça e advogados no meio, e a loja saiu perdendo dezenas de milhares de reais. Estranhamente, nenhuma outra pessoa apareceu na loja com cupons. O que aconteceu?

  1. Em um shopping center, havia uma promoção que a cada 100 reais em compras nas lojas usando um cartão de crédito de certa bandeira, o consumidor ganharia 1 cupom para concorrer à a um carro de luxo ao final do mês. Ao final de cada dia os responsáveis colhiam os cupons, e digitavam somente os números dos cartões das pessoas num arquivo texto, cada um separado por linha, e os carregavam num servidor. Numa dessas semanas, o computador com Windows que usavam pegou um vírus, mas logo arranjaram outro, um notebook com Linux, para terminarem o trabalho.

No dia do sorteio foram relacionar os números com a base de dados da operadora de cartões, para eliminar casos suspeitos. E deu um erro:

java.lang.NumberFormatException: For input string: "1111222233334444" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48) at java.lang.Long.parseLong(Long.java:403)

E o código:

final String REGEX_SEPARAÇÃO_POR_LINHAS = "\n";

List cartõesVálidos = new ArrayList();

String textoDoArquivo = arquivo.getConteúdo();
String[] cartões = textoDoArquivo.split(REGEX_SEPARAÇÃO_POR_LINHAS);

for (String cartão : cartões)
{
  long numero = Long.parseLong(cartão);

  if (okNaOperadora(numero))
    cartõesValidos.add(cartão);
}

Note que o número 1111222233334444 é um long válido.

EDIT (Moderador) - alguém verificou que o código certo é “String[] cartões”, não “String cartões”, mas isso não é o ponto; só um erro de transcrição. Corrigi para poderem perceber exatamente qual é o problema.

EDIT (Bruno) - Obrigado pela correção!

Bom, em relação ao primeiro exemplo, eu pensei o seguinte:
Dentro do for está sendo adicionado a lista os dados referentes de um mesmo objeto. Eu mudaria o código com a seguinte linha:

 Mãe mãeDoCliente = new Mãe();  
 List listaDasMães = new ArrayList();  
   
 for (Cliente c : clientes)  
 {  
   mãeDoCliente = new Mãe();
   mãeDoCliente.setNome(c.getNomeDaMãe());  
   mãeDoCliente.setEmail(c.getEmailDaMãe());  
   
   listaDasMães.add(mãeDoCliente);  
   mãeDoCliente = null;
 }

Em relação ao segundo exemplo a linha onde é feito:

String cartões = textoDoArquivo.split(REGEX_SEPARAÇÃO_POR_LINHAS);

Não teria de retornar um array de cartões? String[] cartões = ...

Para o caso 2, vou dar uma pequena dica. O que ocorre quando você roda o seguinte programa?

class TesteLong {
    public static void main(String[] args) {
        long l = Long.parseLong ("1111222233334444\r");
    }
}

Ele dá a seguinte mensagem de erro no Windows: (note a quebra de linha bizarra. Rodei o programa em um Prompt de Comando com 80 colunas):

C:\>java -cp . TesteLong
Exception in thread "main" java.lang.NumberFormatException: For input string: "1
"11222233334444
        at java.lang.NumberFormatException.forInputString(Unknown Source)
        at java.lang.Long.parseLong(Unknown Source)
        at java.lang.Long.parseLong(Unknown Source)
        at TesteLong.main(TesteLong.java:3)

Para deixá-los com mais pulgas atrás da orelha, vou alterar o Prompt de Comando para mostrar 132 colunas (o comando é "mode con cols=132").
Note a quebra de linha bizarra.
A mensagem aparece como:

C:\>java -cp . TesteLong
"xception in thread "main" java.lang.NumberFormatException: For input string: "1111222233334444
        at java.lang.NumberFormatException.forInputString(Unknown Source)
        at java.lang.Long.parseLong(Unknown Source)
        at java.lang.Long.parseLong(Unknown Source)
        at TesteLong.main(TesteLong.java:3)

E para deixar na porta do gol (basta você acabar de bater o pênalti), qual é a diferença entre arquivos-texto gravados no Windows e no Linux?

Vixi ai complicou, de Linux não manjo nada…rsrsrs :lol:

No caso 1 minha solução está correta?

Vou dizer o que deveria ter sido feito no caso 2, mas ainda quero que alguém explique direitinho por quê.

final String REGEX_SEPARAÇÃO_POR_LINHAS = "\r?\n";

[quote=moacirjava]
No caso 1 minha solução está correta?[/quote]

Ainda não. É new ArrayList ou new Mãe?

E além disso, você não precisa setar = null.

Opa…rsrsr, já “consertei” minha resposta da questão 1, e sete o objeto com null para não ficar recriando ele toda hora e ocupando o espaço na memória, agora essa formatação de printf ainda não tenho muito conhecimento.

Uma dica que dou sempre e repito todos os dias: para ler um arquivo-texto, normalmente você nunca deve lê-lo por completo dentro de uma variável String; usualmente é melhor lê-lo linha por linha, já que a estrutura de um arquivo-texto é por linhas.

Use sempre new BufferedReader (new FileReader()) se você não tem problemas de encoding, ou new BufferedReader (new InputStreamReader (new FileInputStream(), “encoding”))) se você os tiver.

A vantagem de usar BufferedReader é que ele sabe que arquivos-texto podem ter linhas terminadas por “\r\n” (Windows) ou “\n” (Linux) e pode até ler arquivos com essas combinações misturadas, e você nem precisa ficar sabendo que houve alguma linha que terminava por “\r\n” ou “\n”, já que isso é desprezado. De qualquer maneira, é sempre bom usar “trim” antes de efetuar o parse de um dado numérico, para eliminar eventuais sujeiras.

E o 1?

Opps, Botão errado.

Bom, como vc disse, a mesma mãe 1000 cupons válidos todos no nome dela.
Então não poderia ser guardado o nome da mesma mãe repetidamente.

Ao invés de List quem sabe implementar um HashMap que não permite repetições?! Essa é minha resposta agora.

Moacir, não fique chutando para todo lado. O que você tinha respondido antes estava certo, mas só queria que o Bruno confirmasse.

[quote=moacirjava]Bom, como vc disse, a mesma mãe 1000 cupons válidos todos no nome dela.
Então não poderia ser guardado o nome da mesma mãe repetidamente.

Ao invés de List quem sabe implementar um HashMap que não permite repetições?! Essa é minha resposta agora.[/quote]

Não, esta não é a mensagem que queria passar, por mim tudo bem que ela tenha dois ou mais filhos que sejam clientes. Mas ela não tem 1000 filhos, nem é a única mãe do mundo.

A tua primeira resposta está certa.

Agora, os exercícios não param depois de solucionados, gostaria que sugerissem uma refatoração daquele método e das classes envolvidas para que erros humanos como aqueles não aconteçam (dica: sets e gets são perigosos). O thingol já sugeriu os Readers para usar no segundo exercício.

Foi mal… inexperiência é foda… :lol:

Foi mal… inexperiência é f*… :lol:[/quote]

Não é inexperiência, é ansiedade :slight_smile:

De fato, em alguns casos (mas não nesse) um Set (HashSet, TreeSet) poderia até ser usado, mas não nesse aqui.

1° Voce adiciona a mesma referencia de mae 1000 vezes. Então apenas os ultimos valores setados para essa referencia vai mudar de todas.

2° No windows quebras de linhas são representadas por \r\n no linux \n.
O split separa as marcações \n deixando \r no resultado. \r não é um numero então por causa dele o parser lança uma Exception. Mas como não é um caractere visivel não vemos ele no stack trace da exception.

Agora que vi que a proposta é evitar erros.

No primeiro a cada loop criar uma nova referencia de Mae para a variavel já seria o suficiente.

Para o segundo o thingol já falou.

Para o primeiro eu sugeriria que usassem mais construtores e menos métodos acessores.

Um new Mãe(c.getNomeDaMãe(), c.getEmailDaMãe());já diminuiria bastante os erros humanos.

Uma segunda sugestão seria usar objetos mais compostos. Cliente poderia ter um atributo do tipo Mãe já com os dados dentro dele. Bastaria usar um getMãe() para obter o dado.
Além de melhorar o programa, melhora o uso de memória, e outras coisas, basta prestar atenção na imutabilidade do objeto.

E falando de imutabilidade, esse vai ser o tema do próximo exercício que estou produzindo.