Validação de CPF

3 respostas Resolvido
Allan2M

Olá! Eu não encontrei as normais do fórum, então me desculpem se o tópico não estiver de acordo.

Eu trabalhei com Java em 2010, como estagiário, e desde então me distanciei da linguagem e de programação no geral. Agora gostaria de retornar, voltar a programar e rumar profissionalmente para este caminho.

Bem, direto ao ponto, a algumas semanas estou estudando Java e fiz um código simples de validação de CPF. Como estou aprendendo sem um orientador, não tenho esse feedback se o código está bom, apenas sei que está funcionando. Então gostaria de saber se podem dar uma olhada e comentar se estou seguindo as boas práticas do Java ou não?

Obrigado!

Código:

public class CPF {

	private String cpf;
	private int[] cpfDesmembrado = new int[11];

	public CPF(String cpf) {
		this.cpf = cpf;
		desmembrarCpf();
	}

	private void desmembrarCpf() {
		for (int i = 0; i <= 10; i++) {
			cpfDesmembrado[i] = Character.getNumericValue(cpf.charAt(i));
		}
	}

	public int getDigit(int pos) {
		return this.cpfDesmembrado[pos];
	}

	public boolean isValid() {
		if (new ValidaCPF().validar(this))
			return true;
		return false;
	}
}

public class ValidaCPF {

	private CPF cpf;

	public ValidaCPF() {

	}

	public boolean validar(CPF cpf) {
		this.cpf = cpf;
		if(validarEtapa1() && validarEtapa2() &&validarEtapa3()) {
			return true;
		}
		return false;
	}

	private boolean validarEtapa1() {
		float result = 0;
		int indice = 0;
		for (int i = 10; i >= 2; i--) {
			result += this.cpf.getDigit(indice) * i;
			indice++;
		}
		result = (result * 10) % 11;
		if (result == this.cpf.getDigit(9)) {
			return true;
		}
		return false;
	}

	private boolean validarEtapa2() {
		float result = 0;
		int indice = 0;
		for (int i = 11; i >= 2; i--) {
			result += this.cpf.getDigit(indice) * i;
			indice++;
		}
		result = (result * 10) % 11;
		if (result == this.cpf.getDigit(10)) {
			return true;
		}
		return false;
	}
	
	private boolean validarEtapa3() {
		for(int i = 1; i <= 10; i++) {
			int antigo = this.cpf.getDigit(i - 1);
			int atual = this.cpf.getDigit(i);
			if(antigo != atual)
				return true;
		}
		return false;
	}

}

3 Respostas

A
Solucao aceita

Oi @Allan2M,

Vou fazer uma série de comentários mas muito desses são opiniões baseadas em minha experiência, não são verdades absolutas:

  • Eu prefiro diminuir ao máximo a interface de uma classe. Por interface eu me refiro a parte pública dela. Qualquer pessoa usando sua classe CPF do jeito que está, encontrará os métodos: getDigit, isValid, sendo que provavelmente só estarão interessados no valor do CPF em si, que nem está disponível.

  • O método isValid por si só, já não considero uma boa prática. Imagino seu objeto CPF fazendo parte de uma classe Pessoa, por exemplo e esse objeto pessoa será passado entre várias classes. O fato de existir um método isValid pode levar a duplicação de código em várias camadas, perguntando se o cpf de uma pessoa é válido ou não.
    Uma alternativa para isso é você garantir que seu objeto CPF é sempre criado de forma válida. Se passar uma string com um cpf inválido no construtor, você pode lançar uma exceção. Dessa forma, quem tiver um objeto CPF pode considerar que ele já é válido.

  • A classe ValidaCPF pode ser uma causa de expor métodos como getDigit na classe CPF. Acredito que faça mais sentido você mover essa lógica para a classe CPF diretamente, ou pelo menos criar uma classe interna. A classe ValidaCPF será usada apenas dentro da classe CPF, então não há motivos para existir fora dela.
    Repare que sugiro mover a validação pra dentro da classe CPF, pois não há dependência em nenhum serviço externo para realizar a validação (checar num banco de dados, num web service da receita federal, ect).

  • Evite manter cópia redundantes do mesmo dado, ah menos que tenha confirmado que isso causaria um grande impacto na performance. No seu caso a duplicação está em String cpf e int[] cpfDesmembrado.
    Você pode mudar o método desmembrarCpf para retornar um array de inteiros ao invés de manter esse array como atributo.

Essas são minhas sugestões. Eu não acho que seu código esteja ruim e na verdade, é muito comum ver código similar nos sistemas em produção por aí. Mas seguindo essas dicas eu acredito que você simplifique o código em longo prazo, sendo mais simples para mantê-lo, que é sempre um dos principais objetivos.

Allan2M

Muito obrigado pelas dicas! Foram extremamente valiosas.

Eu adicionei uma Exception para indicar que o CPF é invalida e fiz algumas modificações usando as dicas que você me forneceu, realmente ficou bem melhor!

public class CPF {

	private String cpf;

	public CPF(String cpf)  {
		this.cpf = cpf;
		validaCpf();
	}

	public String getCpf() {
		return this.cpf;
	}

	private int getDigit(int pos) {
		return Character.getNumericValue(this.cpf.charAt(pos));
	}

	private void  validaCpf() throws IllegalArgumentException {
		if (!validarPrimeiro() || !validarSegundo() || !validarRepetidos())
			throw new IllegalArgumentException("CPF Invalido");
	}

	// Valida o primeiro digito verificador
	private boolean validarPrimeiro() {
		float result = 0;
		int indice = 0;
		for (int i = 10; i >= 2; i--) {
			result += this.getDigit(indice) * i;
			indice++;
		}
		result = (result * 10) % 11;
		if (result == this.getDigit(9)) {
			return true;
		}
		return false;
	}

	// Valida o segundo digito verificador
	private boolean validarSegundo() {
		float result = 0;
		int indice = 0;
		for (int i = 11; i >= 2; i--) {
			result += this.getDigit(indice) * i;
			indice++;
		}
		result = (result * 10) % 11;
		if (result == this.getDigit(10)) {
			return true;
		}
		return false;
	}

	// Verifica se os numeros não são repitidos
	private boolean validarRepetidos() {
		for (int i = 1; i <= 10; i++) {
			int antigo = this.getDigit(i - 1);
			int atual = this.getDigit(i);
			if (antigo != atual)
				return true;
		}
		return false;
	}
}

Muito obrigado!

romero.dias

@Allan2M, tranquilo!?

Acho que vale ressaltar alguns pontos:

A classe CPF está muito acoplada à ValidarCPF. Se necessário levar a classe CPF para outro sistema, você obrigatoriamente terá que levar a ValidaCPF.
E, ainda acredito não ser responsabilidade da classe CPF verificar se é o cpf é valido.

A classe ValidaCPF também está muito acoplada à classe CPF. Acredito que, o método validar poderia receber como parâmetro uma String e validar o cpf sobre a String e não validar um objeto CPF. Com a validação por String, você poderia reutilizar o ValidaCPF em outras partes do sistema. Ou ainda usar alguma anotação (BeanValidator) para validar o cpf.

Uma dica é: para construir uma classe, eu sempre me pergunto, “qual será o trablaho especifico desta classe” e, ao dar manutenção em alguma classe, sempre me pergunto “esta classe está fazendo algo a mais de sua responsabilidade”. Caso estiver fazendo algo a mais, refatoração nela!!!

Criado 19 de dezembro de 2016
Ultima resposta 19 de dez. de 2016
Respostas 3
Participantes 3