Meu codigo tem muito if, meu chefe disse que ta errado, é verdade?

[quote=andredecotia]

Talvez eu não tenha sido claro, vou recolocar de outra forma: Vc poderia com base no q ela nos enviou, analisar, pensar e então, enviar uma opinião factível, visto q ela é estagiária, qlq. coisa muito abstrata é possível q não assimile…

Opinei sendo objetivo com base no q ela enviou. Se ficasse falando de TDD, livros Refactoring, apenas, acho q não ajudaria ela, tanto q, ela enviou o código pra esta finalidade, avaliarmos e com base nele podermos ser mais diretos. Desculpe, mas não foi o seu caso. :wink: [/quote]
Desculpa, André. Realmente não estou querendo ser chato, mas você deu sua opinião.

Você mesmo disse que uma quantidade grande de ifs pode não ser má prática, porém não disse e não exemplificou nada que justificasse sua resposta. Outros responderam com ideias e exemplos de código tornando essa discussão bastante saudável, mas eu também fiquei curioso quanto ao seu ponto de vista.

Você realmente consegue exemplificar uma situação onde a melhor solução é um encadeamento enorme de ifs?

Não alimente spammers

Só pode ser brincadeira:

if (linha.contains("Revifilhaa Mãe") && linha.contains("Início da Execução")) { mae.setDataFinal(linha); mae.setDataInicial(linha); mae.setDescricao(linha); mae.setHoraFinal(linha); mae.setHoraInicio(linha); mae.setId(linha); if (linha.contains("erro")) { neta.setfilhaatus(Enumfilhaatus.ERRO); } else if (linha.contains("atual")) { neta.setfilhaatus(Enumfilhaatus.ATUAL); } else neta.setfilhaatus(Enumfilhaatus.U2013); }

Mas reutilize essa lógica com um método com passagem de parâmetros, os ifs tem códigos repetidos, isso é extremamente ruim pensando de forma orientada a objetos…

Oi,

Falar que algo é errado por utilizar 17 IFs é muita ignorância. O máximo é dizer que o código não está bom e que pode ser melhorado. O errado é algo que não funciona, ou algo que não condiz com o especificado.

É obvio e muito claro que um código de 17 IFs pode ser melhorado. Nenhum código é perfeito. A ideia já foi feita, agora basta melhorar a lógica.

Acho que o pessoal exagerou na codificação. E acho que muitos usuários responderam coisas absurdas para uma simples pergunta.

Quer ajuda? Pergunte. Nota-se que você é esforçado(a).

Tchauzin!

17 ifs está errado!

Assim como: get_nome() também está errado.

Pra mim, não é só código que retorna o esperado que está certo. Se retorna o resultado, mas fazendo gambiarra, pra mim está errado.
E tenho certeza que isso é errado para outros programadores experientes também.

Você liga um arame na sua fiação elétrica e a luz funciona. Tá certo? Não!

[quote=rogelgarcia]17 ifs está errado!

Assim como: get_nome() também está errado.

Pra mim, não é só código que retorna o esperado que está certo. Se retorna o resultado, mas fazendo gambiarra, pra mim está errado.
E tenho certeza que isso é errado para outros programadores experientes também.

Você liga um arame na sua fiação elétrica e a luz funciona. Tá certo? Não![/quote]

“Quem” diz o que está errado ou não é o compilador.
O máximo que você pode dizer é sobre o nível de abstração OO, uso de padrões e convenções.

Porém, é obvio que o código dela pode e deve ser melhorado.

uol poxa vida hein ♫♫

17 ifs,pode ser varias condições, se eu faço assim ta errado hahaha (logico que não!).Posso ter ate 30 ifs.

[code]if(this.nomeSegurado == null) {
msg.append("
- Nome não preenchido");
atributos.add(“nomeSegurado”);
invalido = true;
}

	if(this.dataNascimento == null) {
		msg.append("<br>- Data de nascimento não preenchida");
		atributos.add("dataNascimento");
		invalido = true;
	}

	if(this.sexo == null) {
			msg.append("<br>- Sexo não selecionado");
			atributos.add("sexo");
			invalido = true;
		} ........	
	[/code]

Sinceramente, eu acho que o pessoal tá muito preocupado no sintoma do que no problema em si. Na minha opinião, trocar 17 IF’s por 17 classes ou 17 métodos só quebraria a coesão do método e tornaria a leitura do código muito mais sofrida. Na prática, somente serviria para mudar a duplicação de lugar.

É impressão minha ou estão discutindo a definição de erro?

Essa definição varia de acordo com o que você queira definir. A ISO, por exemplo, chamaria uma falha de padrão de “não conformidade”.
Agora, não se enganem, não conformidades podem ser tão ou mais problemáticas do que o erro em si. Não é porque você deixou de chamar de erro, que o problema ficou menos problemático.

public Object lerArquivoTXTPrepararObjetosParaVirarXML_arvore() {  
	mae_VO mae = new mae_VO();
	filha_VO filha = new filha_VO();
	neta_VO neta = new neta_VO();
	binesta_VO binesta = new binesta_VO();

	try {  
		Scanner arquivo = new Scanner(new File("C:\\Projeto Revistas\\catalogacao_2013.log")); 

		while (arquivo.hasNext()) {
			String linha = arquivo.nextLine();

			if(linha.contains("Início da Execução")){ // coisas que estao no começo
				if (linha.contains("Revifilhaa Mãe")) {
					mae.setDataFinal(linha);
					mae.setDataInicial(linha);
					mae.setDescricao(linha);
					mae.setHoraFinal(linha);
					mae.setHoraInicio(linha);
					mae.setId(linha);	
				}else if (linha.contains("Revifilhaa Filha")) {
					filha.setDataFinal(linha);
					filha.setDataInicial(linha);
					filha.setDescricao(linha);
					filha.setHoraFinal(linha);
					filha.setHoraInicio(linha);
					filha.setId(linha);
				}else if (linha.contains("Revifilhaa Neta")) {
					neta.setDataFinal(linha);
					neta.setDataInicial(linha);
					neta.setDescricao(linha);
					neta.setHoraFinal(linha);
					neta.setHoraInicio(linha);
					neta.setId(linha);
				}else if (linha.contains("Revifilhaa Bisneta")) {
					binesta.setDataFinal(linha);
					binesta.setDataInicial(linha);
					binesta.setDescricao(linha);
					binesta.setHoraFinal(linha);
					binesta.setHoraInicio(linha);
					binesta.setId(linha);
				}

				if (linha.contains("erro")) {
					neta.setfilhaatus(Enumfilhaatus.ERRO);
				}	else if (linha.contains("atual"))	{
					neta.setfilhaatus(Enumfilhaatus.ATUAL);
				} else{
					neta.setfilhaatus(Enumfilhaatus.U2013);
				}
			}

			if(linha.contains("Executado com")){// coisas que estao no final
				if (linha.contains("Revista Mãe")) { 
					mae.getLifilhaaSuites().add(filha);
					filha = new filha_VO();
				}else if (linha.contains("Revista Filha") && linha.contains("2013")) {
					filha.getLifilhaanetas().add(neta);
					neta = new neta_VO();
				}else if (linha.contains("Revista Bisneta")) {
					neta.getLifilhaabinestas().add(binesta);
					binesta = new binesta_VO();
				}
			}				
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
	
	return mae;
}

Bom, eu tentei remover as duplicações do código … ao invés de trocar o if por classes ou métodos eu procurei remover somente os trechos duplicados. O que eu percebi é que duplicação está nas classes VO. Reparem que elas possuem exatamente a mesma interface, caberia ai declarar uma interface comum a todas ou ainda, criar uma classe só com um campo de tipo.

Pessoal me pediu que colocasse o código comentado sobre a resolução para evitar IFS.

Abaixo do código colocarei uma explicação:

[code]
/*

  • Esse sistema tem dois conceitos.
  • Etapa da vida, associado a idade da pessoa.
  • Classificacoes, associadas a qualquer informação da pessoa.
  • A idéia é resolver essa problemática sugerida pelo colega Luiz Augusto Prado sem o uso de IFS:

if( dadosDoUsuario.idade < 18 )
{
os dados vão para o objeto a
}
else if( dadosDoUsuario.idade >= 18 && dadosDoUsuario.idade<55 )
{
os dados vão para o objeto b
}
else if( dadosDoUsuario.idade >= 55 && dadosDoUsuario.idade<120 || dadosDoUsuario.numeroFilhos>2 )
{
os dados vão para o objeto c
}
else
{
os dados vão pro limbo
}

*/

/**

  • Esse enum representa as 4 etapas possíveis da vida para esse sistema.
    */
    enum EtapaDaVida {

    JOVEM(18),
    ADULTO(55),
    IDOSO(120),
    HIGHLANDER(Integer.MAX_VALUE);

    //um ENUM pode ter atributos
    int idadeMaxima;

    //que sao atribuidos através do construtor
    //após atribuido, o valor é imutável (lembre-se ENUMs são imutáveis)
    private EtapaDaVida(int idade){
    this.idadeMaxima = idade;
    }

    /**

    • Método que retorna a Etapa da vida para determinada idade
      */
      public static EtapaDaVida get(int idade){
      for (EtapaDaVida etapa : values()) { //values() = [JOVEM, ADULTO, IDOSO, HIGHLANDER]
      if(idade <= etapa.idadeMaxima){
      // se a idade for menor que a idade máxima da etapa significa que
      // é essa a etapa relacionada a idade
      return etapa;
      }
      }
      //se não encontrar etapa relacionada com a idade retorna um erro
      throw new RuntimeException(“resultado inesperado”);
      }

    /**

    • Método que retorna a Etapa da vida para determinada data.
    • Irá calcular quantos anos a pessoa tem através de Utils.getIdade
    • @param data
    • @return
      */
      public static EtapaDaVida get(Date data){
      return get(Utils.getIdade(data));
      }
      }

class Usuario {
Date nascimento;
int numeroFilhos;
}

/**

  • Esse enum representa as 4 classificações do sistema.
    */
    enum Classificacao {

    //cada enum será construido com um classificador
    A(new ClassificadorA()),
    B(new ClassificadorB()),
    C(new ClassificadorC()),
    LIMBO(new ClassificadorLimbo());

    Classificador classificador;

    Classificacao(Classificador classificador){
    this.classificador = classificador;
    }

    public boolean isClassificacao(Usuario u){
    //a classificacao utiliza o classificador para determinar se o usuario faz parte dela
    return classificador.isClassificacao(u); //o classificador é uma das classes implementadas abaixo
    }

    public static Classificacao get(Usuario usuario) {
    for (Classificacao c : values()) {//values() = [A, B, C, LIMBO]
    //para cada Classificacao (enum), verificamos se o usuario faz parte dela
    if(c.isClassificacao(usuario)){
    return c; //retorna a classificacao do usuario
    }
    }
    throw new RuntimeException(“resultado inesperado”);
    }
    }

/**

  • Interface que representa um classificador
    /
    interface Classificador {
    /
    • Retorna true se esse classificador considera que o usuário faz parte dele
      */
      public boolean isClassificacao(Usuario usuario);
      }

class ClassificadorA implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.JOVEM;
}
}
class ClassificadorB implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.ADULTO;
}
}
class ClassificadorC implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.IDOSO || usuario.numeroFilhos > 2;
}
}
class ClassificadorLimbo implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return true;
}
}

public class Example2 {

public static void main(String[] args) {
	//monta a estrutura de dados que vai receber a distribuicao
	Map<Classificacao, List<Usuario>> grupos = new HashMap<Classificacao, List<Usuario>>();
	for(Classificacao etapa: Classificacao.values()){
		//para cada chave (Classificacao), criamos uma nova lista
		grupos.put(etapa, new ArrayList<Usuario>());
	}
	
	//aqui é onde viria o ninho de ifs
	for (Usuario usuario : getListaUsuarios()) {
		//retornamos a classificacao do usuario
		Classificacao classificacao = Classificacao.get(usuario);
		//adicionamos o ususario a classificacao correta
		grupos.get(classificacao).add(usuario);
	}
	
}

public static ArrayList<Usuario> getListaUsuarios() {
	return new ArrayList<Usuario>(); //aqui seria criada a lista de usuarios a serem organizados
}

}

class Utils {

//método utilitário para retornar a idade de uma data
public static int getIdade(Date data){
	Calendar hoje = Calendar.getInstance();
	Calendar nascimento = Calendar.getInstance();
	nascimento.setTime(data);
	int idade = hoje.get(Calendar.YEAR) - nascimento.get(Calendar.YEAR);
	nascimento.set(Calendar.YEAR, hoje.get(Calendar.YEAR));
	if(nascimento.compareTo(hoje) > 0){
		//nao fez aniversario esse ano
		idade--;
	}
	return idade;
}

}[/code]

Não se atente ao número de linhas do código. O importante aqui é a análise da arquitetura criada. Qual a diferença entre esse código e um conjunto de ifs do ponto de vista de organização do sistema e não de esforço para criar tal solução.

Esse sistema possui dois conceitos: Etapa da vida e Classificação.
Os objetos do tipo Usuario, serão organizados em uma lista de acordo com sua classificação. Essa classificação envolve a descoberta da etapa da vida de um usuário.

Etapa da Vida

Para descobrir a etapa da vida, podemos fazer o seguinte:

EtapaDaVida ev = EtapaDaVida.get(usuario.nascimento);

A primeira questão a se notar nesse código é que existe uma estrutura que representa a etapa. É bastante diferente, de termos uma String ou int para identificar essa etapa. Veja que se não tivessemos essa estrutura, é com tipos básicos que teriamos que representar essa informação no sistema. Porém, uma String não define bem o que está sendo representado. Uma String pode conter qualquer coisa, um EtapaDaVida só pode conter etapas de vida.

Nessa solução, pegamos um conceito presente no sistema e criamos uma entidade para representar esse conceito.

Veja que se quiséssemos separar os usuários por etapa de vida, e não tivéssemos essa estrutura. Teriamos um código semelhante a esse:

List<Usuario> listaJovens = new ArrayList<Usuario>();
List<Usuario> listaAdultos = new ArrayList<Usuario>();
List<Usuario> listaIdosos = new ArrayList<Usuario>();
List<Usuario> limbo = new ArrayList<Usuario>();
for (Usuario usuario : getListaUsuarios()) {
	if (Utils.getIdade(usuario.nascimento) < 18) {
		listaJovens.add(usuario);
	} else if (Utils.getIdade(usuario.nascimento) < 55) {
		listaAdultos.add(usuario);
	} else if (Utils.getIdade(usuario.nascimento) < 120) {
		listaIdosos.add(usuario);
	} else {
		limbo.add(usuario);
	} 
}

A estrutura que temos aqui são quatro listas de usuários.

Com o Enum:

Map<EtapaDaVida, List<Usuario>> map = new HashMap<EtapaDaVida, List<Usuario>>();
for (Usuario usuario : getListaUsuarios()) {
	EtapaDaVida etapa = EtapaDaVida.get(usuario.nascimento);
	List<Usuario> list = map.get(etapa);
	if(list == null){
		map.put(etapa, list = new ArrayList<Usuario>());
	}
	list.add(usuario);
}

A estrutura que temos aqui é um mapa cuja chave representa um conceito no sistema.

Podemos ainda dizer, que o segundo algoritmo nunca precisará ser modificado caso novas classificações de idade sejam adicionadas ao sistema. Basta adicionar uma nova entrada ao Enum. Diferente do primeiro caso onde o algoritmo de organização será alterado.

O Enum também facilita encontrar referencias no sistema que utilizam esse conceito.

O segundo algoritmo não possui uma sequencia de IFs para testar diversas condições. Ele possui um algoritmo que recebe uma entrada, e processa. Sem variações.

Sabe aquele erro que só acontece em determinadas condições no sistema? Então, geralmente essas condições são uma sequencia de IFs onde um caso não foi previsto. Na arquitetura proposta, não existem casos não previstos.

Classificacao

A solução dada à Classificação, é semelhante a solução da EtapaDeVida. Porém, a classificação pode conter qualquer condição. Por isso, foi criada uma interface Classificador. Cada enum Classificacao, possui um objeto dessa interface. Esse objeto, especifico do enum é capaz de dizer se determinado dado é da Classificacao ou não. No final das contas, pode-se utilizar o enum para dizer se o dado faz parte da classificacao. Por trás dos panos ele usa o classificador.

As vantagens apresentadas na EtapaDeVida também acontecem nessa situação. E ainda, se determinada condição de uma classificação mudar, é necessário mudar apenas um único ponto no sistema. Um ponto muito bem definido.


Pegando o gancho do colega Luiz Augusto Prado, um algoritmo deve ser uma fórmula matemática. Deve-se dar um input, que retorna um output, sem determinadas condições. É como uma máquina, que devido as suas engrenagens sempre funciona, não importando a situação. Imagine a marcha de um carro, não existem ifs perguntando a posicao da alavanca para mudar a engrenagem da marcha. A alavanca está relacionada ao cambio e a modificacao da posição implica a mudança da engrenagem (sem ifs).

Com construções avançadas, onde existe uma engrenagem no seu sistema, é possível ter muito mais segurança, e afirmar com mais precisão que um sistema é robusto. Não tem ifs… pode bater com pau que ele vai funcionar.

No dia a dia do desenvolvimento vão existir situações onde outros fatores vão importar no resultado da sua programação, principalmente o tempo. É perfeitamente normal que faça alguma solução que não seja ideal. Mas é importante saber, quando uma arquitetura pode ou precisa ser melhorada e fazer isso. Caso contrário, quando o sistema tiver já com seus anos de desenvolvimento, a zona vai ser tão grande que a manutenção fica impraticável (quem aqui conhece um sistemas desse?? rsrsrs)

Não necessariamente a solução dos 17 ifs é a utilização de uma interface Classificação. É necessário analisar os requisitos e pensar em uma solução que seja adequada e tenha bom custo x beneficio.

[quote=rogelgarcia]Pessoal me pediu que colocasse o código comentado sobre a resolução para evitar IFS.

Abaixo do código colocarei uma explicação:

[code]
/*

  • Esse sistema tem dois conceitos.
  • Etapa da vida, associado a idade da pessoa.
  • Classificacoes, associadas a qualquer informação da pessoa.
  • A idéia é resolver essa problemática sugerida pelo colega Luiz Augusto Prado sem o uso de IFS:

if( dadosDoUsuario.idade < 18 )
{
os dados vão para o objeto a
}
else if( dadosDoUsuario.idade >= 18 && dadosDoUsuario.idade<55 )
{
os dados vão para o objeto b
}
else if( dadosDoUsuario.idade >= 55 && dadosDoUsuario.idade<120 || dadosDoUsuario.numeroFilhos>2 )
{
os dados vão para o objeto c
}
else
{
os dados vão pro limbo
}

*/

/**

  • Esse enum representa as 4 etapas possíveis da vida para esse sistema.
    */
    enum EtapaDaVida {

    JOVEM(18),
    ADULTO(55),
    IDOSO(120),
    HIGHLANDER(Integer.MAX_VALUE);

    //um ENUM pode ter atributos
    int idadeMaxima;

    //que sao atribuidos através do construtor
    //após atribuido, o valor é imutável (lembre-se ENUMs são imutáveis)
    private EtapaDaVida(int idade){
    this.idadeMaxima = idade;
    }

    /**

    • Método que retorna a Etapa da vida para determinada idade
      */
      public static EtapaDaVida get(int idade){
      for (EtapaDaVida etapa : values()) { //values() = [JOVEM, ADULTO, IDOSO, HIGHLANDER]
      if(idade <= etapa.idadeMaxima){
      // se a idade for menor que a idade máxima da etapa significa que
      // é essa a etapa relacionada a idade
      return etapa;
      }
      }
      //se não encontrar etapa relacionada com a idade retorna um erro
      throw new RuntimeException(“resultado inesperado”);
      }

    /**

    • Método que retorna a Etapa da vida para determinada data.
    • Irá calcular quantos anos a pessoa tem através de Utils.getIdade
    • @param data
    • @return
      */
      public static EtapaDaVida get(Date data){
      return get(Utils.getIdade(data));
      }
      }

class Usuario {
Date nascimento;
int numeroFilhos;
}

/**

  • Esse enum representa as 4 classificações do sistema.
    */
    enum Classificacao {

    //cada enum será construido com um classificador
    A(new ClassificadorA()),
    B(new ClassificadorB()),
    C(new ClassificadorC()),
    LIMBO(new ClassificadorLimbo());

    Classificador classificador;

    Classificacao(Classificador classificador){
    this.classificador = classificador;
    }

    public boolean isClassificacao(Usuario u){
    //a classificacao utiliza o classificador para determinar se o usuario faz parte dela
    return classificador.isClassificacao(u); //o classificador é uma das classes implementadas abaixo
    }

    public static Classificacao get(Usuario usuario) {
    for (Classificacao c : values()) {//values() = [A, B, C, LIMBO]
    //para cada Classificacao (enum), verificamos se o usuario faz parte dela
    if(c.isClassificacao(usuario)){
    return c; //retorna a classificacao do usuario
    }
    }
    throw new RuntimeException(“resultado inesperado”);
    }
    }

/**

  • Interface que representa um classificador
    /
    interface Classificador {
    /
    • Retorna true se esse classificador considera que o usuário faz parte dele
      */
      public boolean isClassificacao(Usuario usuario);
      }

class ClassificadorA implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.JOVEM;
}
}
class ClassificadorB implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.ADULTO;
}
}
class ClassificadorC implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.IDOSO || usuario.numeroFilhos > 2;
}
}
class ClassificadorLimbo implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return true;
}
}

public class Example2 {

public static void main(String[] args) {
	//monta a estrutura de dados que vai receber a distribuicao
	Map<Classificacao, List<Usuario>> grupos = new HashMap<Classificacao, List<Usuario>>();
	for(Classificacao etapa: Classificacao.values()){
		//para cada chave (Classificacao), criamos uma nova lista
		grupos.put(etapa, new ArrayList<Usuario>());
	}
	
	//aqui é onde viria o ninho de ifs
	for (Usuario usuario : getListaUsuarios()) {
		//retornamos a classificacao do usuario
		Classificacao classificacao = Classificacao.get(usuario);
		//adicionamos o ususario a classificacao correta
		grupos.get(classificacao).add(usuario);
	}
	
}

public static ArrayList<Usuario> getListaUsuarios() {
	return new ArrayList<Usuario>(); //aqui seria criada a lista de usuarios a serem organizados
}

}

class Utils {

//método utilitário para retornar a idade de uma data
public static int getIdade(Date data){
	Calendar hoje = Calendar.getInstance();
	Calendar nascimento = Calendar.getInstance();
	nascimento.setTime(data);
	int idade = hoje.get(Calendar.YEAR) - nascimento.get(Calendar.YEAR);
	nascimento.set(Calendar.YEAR, hoje.get(Calendar.YEAR));
	if(nascimento.compareTo(hoje) > 0){
		//nao fez aniversario esse ano
		idade--;
	}
	return idade;
}

}[/code]

Não se atente ao número de linhas do código. O importante aqui é a análise da arquitetura criada. Qual a diferença entre esse código e um conjunto de ifs do ponto de vista de organização do sistema e não de esforço para criar tal solução.

Esse sistema possui dois conceitos: Etapa da vida e Classificação.
Os objetos do tipo Usuario, serão organizados em uma lista de acordo com sua classificação. Essa classificação envolve a descoberta da etapa da vida de um usuário.

Etapa da Vida

Para descobrir a etapa da vida, podemos fazer o seguinte:

EtapaDaVida ev = EtapaDaVida.get(usuario.nascimento);

A primeira questão a se notar nesse código é que existe uma estrutura que representa a etapa. É bastante diferente, de termos uma String ou int para identificar essa etapa. Veja que se não tivessemos essa estrutura, é com tipos básicos que teriamos que representar essa informação no sistema. Porém, uma String não define bem o que está sendo representado. Uma String pode conter qualquer coisa, um EtapaDaVida só pode conter etapas de vida.

Nessa solução, pegamos um conceito presente no sistema e criamos uma entidade para representar esse conceito.

Veja que se quiséssemos separar os usuários por etapa de vida, e não tivéssemos essa estrutura. Teriamos um código semelhante a esse:

List<Usuario> listaJovens = new ArrayList<Usuario>();
List<Usuario> listaAdultos = new ArrayList<Usuario>();
List<Usuario> listaIdosos = new ArrayList<Usuario>();
List<Usuario> limbo = new ArrayList<Usuario>();
for (Usuario usuario : getListaUsuarios()) {
	if (Utils.getIdade(usuario.nascimento) < 18) {
		listaJovens.add(usuario);
	} else if (Utils.getIdade(usuario.nascimento) < 55) {
		listaAdultos.add(usuario);
	} else if (Utils.getIdade(usuario.nascimento) < 120) {
		listaIdosos.add(usuario);
	} else {
		limbo.add(usuario);
	} 
}

A estrutura que temos aqui são quatro listas de usuários.

Com o Enum:

Map<EtapaDaVida, List<Usuario>> map = new HashMap<EtapaDaVida, List<Usuario>>();
for (Usuario usuario : getListaUsuarios()) {
	EtapaDaVida etapa = EtapaDaVida.get(usuario.nascimento);
	List<Usuario> list = map.get(etapa);
	if(list == null){
		map.put(etapa, list = new ArrayList<Usuario>());
	}
	list.add(usuario);
}

A estrutura que temos aqui é um mapa cuja chave representa um conceito no sistema.

Podemos ainda dizer, que o segundo algoritmo nunca precisará sem modificado caso novas classificações de idade sejam adicionadas ao sistema. Basta adicionar uma nova entrada ao Enum. Diferente do primeiro caso onde o algoritmo de organização será alterado.

O Enum também facilita encontrar referencias no sistema que utilizam esse conceito.

O segundo algoritmo não possui uma sequencia de IFs para testar diversas condições. Ele possui um algoritmo que recebe uma entrada, e processa. Sem variações.

Sabe aquele erro que só acontece em determinadas condições no sistema? Então, geralmente essas condições são uma sequencia de IFs onde um caso não foi previsto. Na arquitetura proposta, não existem casos não previstos.

Classificacao

A solução dada a classificação é semelhante a solução da EtapaDeVida. Porém, a classificação pode conter qualquer condição. Por isso, foi criada uma interface Classificador. Cada enum Classificacao, possui um objeto dessa interface. Esse objeto, especifico do enum é capaz de dizer se determinado dado é da Classificacao ou não. No final das contas, pode-se utilizar o enum para dizer se o dado faz parte da classificacao. Por trás dos panos ele usa o classificador.

As vantagens apresentadas na EtapaDeVida também acontecem nessa situação. E ainda, se determinada condição de uma classificação mudar, é necessário mudar apenas um único ponto no sistema. Um ponto muito bem definido.


Pegando o gancho do colega Luiz Augusto Prado, um algoritmo deve ser uma fórmula matemática. Deve-se dar um input, que retorna um output, sem determinadas condições. É como uma máquina, que devido as suas engrenagens sempre funciona, não importando a situação. Imagine a marcha de um carro, não existem ifs perguntando a posicao da alavanca para mudar a engrenagem da marcha. A alavanca está relacionada ao cambio e a modificacao da posição implica a mudança da engrenagem (sem ifs).

Com construções avançadas, onde existe uma engrenagem no seu sistema, é possível ter muito mais segurança, e afirmar com mais precisão que um sistema é robusto. Não tem ifs… pode bater com pau que ele vai funcionar.

No dia a dia do desenvolvimento vão existir situações onde outros fatores vão importar no resultado da sua programação, principalmente o tempo. É perfeitamente normal que faça alguma solução que não seja ideal. Mas é importante saber, quando uma arquitetura pode ou precisa ser melhorada e fazer isso. Caso contrário, quando o sistema tiver já com seus anos de desenvolvimento, a zona vai ser tão grande que a manutenção fica impraticável (quem aqui conhece um sistemas desse?? rsrsrs)

Não necessariamente a solução dos 17 ifs é a utilização de uma interface Classificação. É necessário analisar os requisitos e pensar em uma solução que seja adequada e tenha bom custo x beneficio.
[/quote]

“Poxa vida ehm uôooou”. rs. :smiley:

Apenas para completar o tópico… O resultado do algoritmo que tem que ler o seguinte arquivo:

[18/01/2013 15:26:19] Revista Mãe => Início da Execução
[18/01/2013 15:26:19] 		Revista Filha => Início da Execução
[18/01/2013 15:26:19] 				Revista Neta  => Início da Execução
[18/01/2013 15:26:19] 				Revista   => Executando
[18/01/2013 15:26:19] 				Revista   => Início execução 
[18/01/2013 15:26:19] 				Revista   => Valores 
                      					@.codigo = 4
                      					@.nome = Cupom Brinde
                      					@.tipoDeCupom = Vale 
[18/01/2013 15:26:19] 						Revista Bisneta  => Início da Execução
[18/01/2013 15:26:27] 								SUCESSO 
[18/01/2013 15:26:28] 								SUCESSO 
[18/01/2013 15:26:31] 								SUCESSO 
[18/01/2013 15:26:31] 								SUCESSO 
[18/01/2013 15:26:31] 								SUCESSO 
[18/01/2013 15:26:32] 								SUCESSO 
[18/01/2013 15:26:33] 								SUCESSO 
[18/01/2013 15:26:33] 						Revista Bisneta => Executado com SUCESSO
                      						Duração Total: 13s 948ms
[18/01/2013 15:26:33] 						Revista Bisneta => Início da Execução
[18/01/2013 15:26:33] 								SUCESSO 
[18/01/2013 15:26:33] 								SUCESSO 
[18/01/2013 15:26:36] 								SUCESSO 
[18/01/2013 15:26:37] 								SUCESSO 
[18/01/2013 15:26:38] 								ERRO 
[18/01/2013 15:26:38] 								SUCESSO 
[18/01/2013 15:26:38] 								SUCESSO 
[18/01/2013 15:26:38] 								SUCESSO 
[18/01/2013 15:26:38] 						Revista Bisneta => Executado com INSTABILDADE
                      						Duração Total: 5s 469ms
[18/01/2013 15:27:48] 				Revista Neta => Executado com FALHA 
                      				Duração Total: 1m 28s 954ms
[18/01/2013 15:27:48] 		Revista Filha => Executado com FALHA
                      		Duração Total: 1m 28s 955ms
[18/01/2013 15:27:48] Revista Mãe => Executado com FALHA
                      Duração Total: 1m 28s 963ms

De acordo com o que eu entendi que deve ser feito… O programa deve extrair informações de execuções de revista.
Buscando conjuntos assim:

[18/01/2013 15:26:19] Revista Mãe => Início da Execução
[18/01/2013 15:27:48] Revista Mãe => Executado com FALHA
                      Duração Total: 1m 28s 963ms
[18/01/2013 15:26:19] 						Revista Bisneta  => Início da Execução
[18/01/2013 15:26:33] 						Revista Bisneta => Executado com SUCESSO
                      						Duração Total: 13s 948ms

O que tem que ser extraído é o timestamp de início e fim. O tempo de duração e o status da execução. É necessario criar uma estrutura de árvore e armazenar as filhas dentro da mae. As netas dentro das filhas, etc.

As estruturas de dados de Revista e Status são:

enum Status {
	SUCESSO,
	INSTABILDADE,
	FALHA,
	ERRO
}

class Revista {
	String inicio;
	String fim;
	Status status;
	String tipo;
	List<Revista> filhas = new ArrayList<Revista>();
	Revista parent = null;
	String duracao;
	
	@Override
	public String toString() {
		return toString("");
	}

	private String toString(String padding) {
		String value = "";
		if(this.parent == null){
			value = padding + "root\n";
		} else {
			value += String.format("%s + %s %s %s %s %s%n", padding, tipo.toUpperCase(), status, duracao, inicio, fim);
		}
		for (Revista filha : filhas) {
			value += filha.toString(padding+"   ");
		}
		return value;
	}
}

O main é bem pequeno:

public static void main(String[] args) throws IOException {
	Reader log = new FileReader("revista.txt");
	List<String> lines = readLines(log);
	Revista root = new Revista();
	readLevel(root, lines);
	System.out.println(root);
}

O que ele faz é…

  1. Cria um reader para o arquivo
  2. readLines: Lê as linhas do arquivo e salva em uma lista
  3. Cria uma revista para ser o root (é uma revista que ficará no topo, para ser o node principal da árvore)
  4. readLevel: Método recursivo que dada uma lista de linhas e uma revista. Le o conteúdo e preeche os dados (método principal do algoritmo)
  5. Imprime o resultado

Método: readLevel (contém a lógica principal para resolver o problema)

private static void readLevel(Revista parent, List<String> lines) {
	if(lines.size() == 0){//acabou o arquivo
		return;
	}
	String firstLine = lines.get(0); //[18/01/2013 15:26:19] (...) => Início da Execução
	int level = getLevel(firstLine);//level basicamente é a identação da linha
	int lastLineIndex = getCloseNode(lines, level);//[18/01/2013 15:27:48] (...) => Executado com FALHA
	String lastLine = lines.get(lastLineIndex); 
	Status status = getStatus(lastLine); // FALHA, SUCESSO, etc
	String duracao = lines.get(lastLineIndex+1).substring(level); //Duração Total: 1m 28s 963ms
	String tipo = getTipo(firstLine); // Mãe, Filha, Neta Bisneta
	
	Revista revista = new Revista(); //configura a revista com os dados extraidos
	revista.parent = parent;
	revista.status = status;
	revista.duracao = duracao;
	revista.tipo = tipo;
	revista.inicio = firstLine.substring(0, 21);//[18/01/2013 15:27:48]
	revista.fim = lastLine.substring(0, 21);//[18/01/2013 15:27:48]
	
	parent.filhas.add(revista); //adiciona essa revista na revista parent
	
	readLevel(revista, lines.subList(1, lastLineIndex)); //le o interno dessa revista
	
	readLevel(parent, lines.subList(lastLineIndex+2, lines.size())); //le as revistas subsequentes (irmãs da variavel revista)
}

Outros métodos auxiliares que extraem as informações:

[code]
private static String getTipo(String line) {
//[18/01/2013 15:26:19] Revista Mãe => Início da Execução
// ^^^
int close = line.indexOf(" =>");
line = line.substring(0, close).trim();
int begin = line.lastIndexOf(" ");
return line.substring(begin);
}

private static Status getStatus(String statusLine) {
//[18/01/2013 15:27:48] Revista Filha => Executado com FALHA
return Status.valueOf(statusLine.substring(statusLine.trim().lastIndexOf(’ ') + 1).trim());
}

/**

  • Retorna a próxima linha que fecha com o nível
  • Ex.:
  • [18/01/2013 15:26:19] Revista Mãe => Início da Execução
  • Para esse nível ^
  • Retorna essa linha
  • [18/01/2013 15:27:48] Revista Mãe => Executado com FALHA
  •                   ^
    

*/
private static int getCloseNode(List lines, int level) {
for (int i = 1; i < lines.size(); i++) {
if(getLevel(lines.get(i)) == level){
return i;
}
}
throw new RuntimeException(“file inconsistent”);
}

private static int getLevel(String line) {
//[18/01/2013 15:26:19] Revista Filha => Início da Execução
//retorna a posição do caracter ‘R’
int lineStart = line.indexOf(’]’) + 2;
while(line.charAt(lineStart) == ‘\t’){
lineStart++;
}
return lineStart;
}

static String[] ignore = {
“(.?)Revista =>(.?)”, //[18/01/2013 15:26:19] Revista => Início execução
“(.?)@\.(.?)”, // @.codigo = 4
“\[(.*?)\]\s+[A-Z]+\s?”, //[18/01/2013 15:26:36] SUCESSO
};

/**

  • Lê as linhas do arquivo ignorando as não desejadas
    */
    public static List readLines(Reader log) throws IOException {
    List lines = new ArrayList();
    //TODO CLOSE
    BufferedReader in = new BufferedReader(log);
    String linha = null;
    OUTER:
    while((linha = in.readLine()) != null){
    for (String pattern : ignore) {//ignora as linhas não desejadas
    if(linha.matches(pattern)){
    continue OUTER;
    }
    }
    lines.add(linha);
    }
    return lines;
    }[/code]

O código é recursivo e genérico. Suporta quantos níveis forem de filhos e não importa o nome da Revista.

Saída do programa (no meu arquivo coloquei duas maes para testar melhor o algoritmo)

root
    +  MÃE FALHA Duração Total: 1m 28s 963ms [18/01/2013 15:26:19] [18/01/2013 15:27:48]
       +  FILHA FALHA Duração Total: 1m 28s 955ms [18/01/2013 15:26:19] [18/01/2013 15:27:48]
          +  NETA FALHA Duração Total: 1m 28s 954ms [18/01/2013 15:26:19] [18/01/2013 15:27:48]
             +  BISNETA SUCESSO Duração Total: 13s 948ms [18/01/2013 15:26:19] [18/01/2013 15:26:33]
             +  BISNETA INSTABILDADE Duração Total: 5s 469ms [18/01/2013 15:26:33] [18/01/2013 15:26:38]
    +  MÃE SUCESSO Duração Total: 1m 28s 963ms                       [19/01/2013 15:26:19] [19/01/2013 15:27:48]
       +  FILHA FALHA Duração Total: 1m 28s 955ms [19/01/2013 15:26:19] [19/01/2013 15:27:48]
          +  NETA FALHA Duração Total: 1m 28s 954ms [19/01/2013 15:26:19] [19/01/2013 15:27:48]
             +  BISNETA SUCESSO Duração Total: 13s 948ms [19/01/2013 15:26:19] [19/01/2013 15:26:33]
             +  BISNETA INSTABILDADE Duração Total: 5s 469ms [19/01/2013 15:26:33] [19/01/2013 15:26:38]

Última situação… agora que o cérebro está fervendo com tantas informações… um exemplo mais simples:

Como calcular a idade de uma data (quantos anos se passaram)

Para resolver essa questão propus o seguinte método (atualizado)

	public static int getIdade(Date data){
		//burocracia
		Calendar hoje = Calendar.getInstance();
		Calendar nascimento = Calendar.getInstance();
		nascimento.setTime(data);

		//diferenca de anos
		int idade = hoje.get(Calendar.YEAR) - nascimento.get(Calendar.YEAR);
		//verificar aniversario
		nascimento.set(Calendar.YEAR, hoje.get(Calendar.YEAR));
		if(nascimento.get(Calendar.DAY_OF_YEAR) > hoje.get(Calendar.DAY_OF_YEAR)){
			//nao fez aniversario esse ano
			idade--;
		}
		return idade;
	}

Basicamente, o que esse algoritmo faz é verificar a diferença em anos de hoje até a data passada como parametro.
Depois ele pega a data passada como parametro, e passa para o ano atual. Teremos duas datas no mesmo ano.
Se não tiver passado o aniversário, temos que remover 1 da idade.
Matemática pura.

Agora, veja essa outra solução de outro site:

Calendar dob = Calendar.getInstance(); dob.setTime(dateOfBirth); Calendar today = Calendar.getInstance(); int age = today.get(Calendar.YEAR) - dob.get(Calendar.YEAR); if (today.get(Calendar.MONTH) < dob.get(Calendar.MONTH)) { age--; } else if (today.get(Calendar.MONTH) == dob.get(Calendar.MONTH) && today.get(Calendar.DAY_OF_MONTH) < dob.get(Calendar.DAY_OF_MONTH)) { age--; }
Fonte: http://stackoverflow.com/questions/1116123/how-do-i-calculate-someones-age-in-java

É desnecessário fazer tantos testes sendo que a matemática resolve…

EDITED.

[quote=rogelgarcia]Apenas para completar o tópico… O resultado do algoritmo que tem que ler o seguinte arquivo: …

[code]
[18/01/2013 15:26:19] Revista Mãe => Início da Execução
[18/01/2013 15:26:19] Revista Filha => Início da Execução
[18/01/2013 15:26:19] Revista Neta => Início da Execução
[18/01/2013 15:26:19] Revista => Executando
[18/01/2013 15:26:19] Revista => Início execução …

[/code][/quote]

Liked.

Olá pessoa! Desculpem-me pela demora.

Vou tentar mostar alguns erros que já passei aqui, mas antes vou postar um código:

Classe ICondicao:

package testeiforstrategy;


interface ICondicao<T>
{
    public boolean execute(T p); 
} 


class Parametros
{  
	String nome;
	int idade;
	int QtFilhos;
}  


class CondicaoA implements ICondicao<Parametros>
{  
    public boolean execute(Parametros p) 
	{
		if(p.idade<18)
		{
			System.out.println("O usuário \""+ p.nome +"\" é JOVEM");
			return true;
		}
        return false;
    }
}

class CondicaoB implements ICondicao<Parametros>
{  
    public boolean execute(Parametros p) 
	{  
		if(p.idade<55)
		{
			System.out.println("O usuário \""+ p.nome +"\" é ADULTO");
			return true;
		}
        return false;
    }  
}  

class CondicaoC implements ICondicao<Parametros>
{  
    public boolean execute(Parametros p) 
	{  
		if( p.idade<120 || p.QtFilhos>0)
		{
			System.out.println("O usuário \""+ p.nome +"\" é IDOSO ou já possui Filhos");
			return true;
		}
        return false;
    }  
}  

class CondicaoD implements ICondicao<Parametros>
{  
    public boolean execute(Parametros p) 
	{  
		System.out.println("O usuário \""+ p.nome +"\" é Muito Idoso ou já possui Filhos");
		return true; 
    }  
} 

As classes TesteIfOrStrategy1 e TesteIfOrStrategy2 são a mesma coisa, mas

Classe de teste TesteIfOrStrategy1 aproxima-se mais do feito pelo rogelgarcia

package testeiforstrategy;

enum EnumCondicoes 
{
    A(new CondicaoA()), 
	B(new CondicaoB()), 
	C(new CondicaoC()), 
	D(new CondicaoD());
    ICondicao condicao;  
    EnumCondicoes(ICondicao condicao)
	{  
        this.condicao = condicao;  
    }
    public static <T> boolean isCondicao(T p) 
	{  
        for (EnumCondicoes c : values()) 
		{  
            if(c.condicao.execute(p))
			{  
                return true;  
            }  
        }   
		throw new RuntimeException("resultado inesperado");
    }  
}  

/**
 * @author Luiz A. Prado
 * sobre assunto discutino no link:
 * http://www.guj.com.br/java/291925-meu-codigo-tem-muito-if-meu-chefe-disse-que-ta-errado-e-verdade
 * 
 */
public class TesteIfOrStrategy1
{	
	/**
	 * @param args the command line arguments
	 */
	public static void main(String[] args)
	{
		Parametros p = new Parametros();
		p.nome = "Luiz";
		p.idade = 66;
		p.QtFilhos = 3;
		
		EnumCondicoes.isCondicao(p); 
	}
}

classe de teste TesteIfOrStrategy2 é o que prefiro utilizar

package testeiforstrategy;

import java.util.ArrayList;
import java.util.List;

class Seletor
{
	List condicoes; 	
	Seletor()
	{
		condicoes = new ArrayList<ICondicao>(); 
	}
	public void add(ICondicao c)
	{
		condicoes.add(c);
	}
    public <T> boolean isCondicao(T p) 
	{  
        for (Object c : condicoes) 
		{  
            if(((ICondicao)c).execute(p))
			{  
                return true;  
            }  
        }   
		throw new RuntimeException("resultado inesperado");
    } 
}  

/**
 * @author Luiz A. Prado
 * sobre assunto discutino no link:
 * http://www.guj.com.br/java/291925-meu-codigo-tem-muito-if-meu-chefe-disse-que-ta-errado-e-verdade
 * 
 */
public class TesteIfOrStrategy2
{
	/**
	 * @param args the command line arguments
	 */
	public static void main(String[] args)
	{
		Parametros p = new Parametros();
		p.nome = "Luiz";
		p.idade = 66;
		p.QtFilhos = 3;
		
		Seletor s = new Seletor();
		s.add(new CondicaoA());
		s.add(new CondicaoB());
		s.add(new CondicaoC());
		s.add(new CondicaoD());
		s.isCondicao(p); 
	}
}

Eu acredito que o uso de IFs ou Strategy não estão errados. É mais uma questão de gosto.
Mas se for para falar de erros, vou mostrar 2 exemplos que costumam me dar trabalho no Strategy:

  1. ORDEM: No arquivo TesteIfOrStrategy1 entre as linhas 4 e 9 e na TesteIfOrStrategy2 entre as linhas 47 e 53 são inseridos as condicionais. Se vc errar esta ordem ou esquecer de inserir uma das condições, o programa roda, mas com o funcionamento incorreto. E neste caso seria melhor que o programa parasse de funcionar quando dessemos pauladas.

  2. FRAGMENTACAO DA LOGICA: No arquivo ICondição eu reuní todas as classes (metodos) das condicionais, para facilitar a comparação das condicionais. Em uma empresa onde trabalhei isso não acontecia. Cada classe ficava em um arquivo separado. Isso não tem como acontecer quando utilizamos IFs seguidos. A análise era muito chata porque a condicional de uma dependia da outra. Olhe o codigo exemplo abaixo para entender o que quero dizer.
    Isso é só um exemplo. O mesmo problema poderia ocorrer com outros tipos de objetos ou combinando operadores:

if(i<10)
{
...
}
else if(i=10)
{
...
}
else if(i<10)
{
...
}

Se quisermos mudar de 10 para 20, teremos que ficar atentos em 3 condicionais. Quando as condicionais estão espalhadas em vários arquivos, estes erros ficam chatos de se controlar. Nesse ultimo caso, eu acharia mais fácil de analisar as condicionais em uma sequencia de ifs.

[quote=Hebert Coelho][quote=Luiz Augusto Prado]Quero saber como vcs evitam os tipos de erros e gambiarras que eu vou mostrar.
Exemplo do que ocorreu em um momento com o rogelgarcia :

if(usuario.numeroFilhos <= 2) { iterator.remove(); } [/quote]TDD
[=[/quote]

Se eu escrevesse um codigo de 300 ifs seguidos sem erros, o TDD acusaria a má esconha do pattern?

[quote=Luiz Augusto Prado][quote=Hebert Coelho][quote=Luiz Augusto Prado]Quero saber como vcs evitam os tipos de erros e gambiarras que eu vou mostrar.
Exemplo do que ocorreu em um momento com o rogelgarcia :

if(usuario.numeroFilhos <= 2) { iterator.remove(); } [/quote]TDD
[=[/quote]

Se eu escrevesse um codigo de 300 ifs seguidos sem erros, o TDD acusaria a má esconha do pattern?

[/quote]Erro o JUnit apontaria, má pratica existem plugins para isso.

Quais os plugins que vc usa para evitar as más práticas?

[quote=Luiz Augusto Prado][quote=Hebert Coelho]
Erro o JUnit apontaria, má pratica existem plugins para isso.
[/quote]

Quais os plugins que vc usa para evitar as más práticas?[/quote]Atualmente uso o Intellij e ele faz essa análise.
Com o eclipse utilizava o findbug e o pmd.