6 maneiras de fazer a mesma coisa, o que é considerado boas práticas?

As vezes tem tantas maneiras diferentes de fazer o mesmo código que nós ficamos na dúvida quanto a qual maneira usar. O que seria considerado "boa prática" pela comunidade e o que sua equipe entenderia melhor. Suponhamos que você esteja trabalhando dentro de um método de um Domain Service chamado UmDomainServiceChique(objetoDoDominio) que será chamado por uma API. Você tem uma regra de negócio chique para ser verificada que por enquanto chamarei de VerificaMinhaRegraChiqueComplexa(). Você chama UmDomainServiceChique(objetoDoDominio) e caso VerificaMinhaRegraChiqueComplexa() retorne true você vai querer que UmDomainServiceChique faça o que tem que fazer e a api retornar Ok 200, caso contrário você quer que a API responda um erro qualquer, tipo BadRequest, e retornar uma mensagem dizendo que VerificaMinhaRegraChiqueComplexa deu ruim. Eu vejo 6 maneiras de fazer isso, gostaria de saber a opinião de outrs devs sobre qual seria a maneira menos gambiarr...

Gerador / Validador de CPF e CNPJ no lazarus

Diferentemente do RG um CPF (ou CNPJ) é um identificador único. Um CPF identifica uma pessoa única no Brasil. É uma boa prática associar contas de usuário com seu CPF. Seguro para o usuário, pois diminui o risco de alguém se passar por ele, e seguro para quem presta algum serviço, pois pode identificar um usuário infrator.

Lógico que essa não deveria nem de longe ser a única medida de segurança e identificação de um usuário, mas já é um começo.

Quando você coloca o CPF como campo obrigatório em um formulário de cadastro no seu sistema torna-se difícil testar esse formulário porque você precisa validar o CPF para ver se é correto, mas também precisa de CPF's válidos e únicos para usar nos seus testes. O que fazer?

Pensando nisso coloquei esse gerador de CPF e CNPJ que você pode ver na barra lateral do blog, e fiz esse programinha em lazarus para gerar e/ou validar CPF's e CNPJ's. Você pode copiar esse algoritmo para validar documentos nos seus cadastros.

Funcionamento do CPF:

O CPF é formado por 11 dígitos (excetuando-se as pontuações e separadores), 9 do número do documento e 2 de verificação.

O 10° dígito é baseado em um algoritmo feito com os 9 dígitos precedentes, e o 11° é calculado usando-se os 9 primeiros mais o 10° recém calculado. Ou seja, se seu CPF é 74241980503 (ainda não chegamos nesse número espero) isso quer dizer que fazendo o referido algoritmo com 742419805 você encontrará o 0 e fazendo com 7424198050 você encontrará o 3.

O algoritmo consiste em 3 passos:
1) multiplicar, da esquerda para a direita, cada dígito por um valor n que começará com 10 e decrescerá (n-1 com n nunca sendo < 2) a cada casa para a direita, acumulando-se (somando-se) cada resultado. Por exemplo, o CPF que mencionamos iniciaríamos o calculo assim:
(7 x 10)+(4 x 9)+(2 x 8)+(4 x 7)+(1 x 6)+(9 x 5)+(8 x 4)+(0 x 3)+(5 x 2)  que seria 70+36+16+28+6+45+32+0+10=243
2) Obter o resto da divisão dessa soma por 11, ou seja 243 mod 11 = 1
3) Subtrair o resultado de 11, ou seja, 11 - 1. Se o resultado for maior que 9 colocamos 0. Então 11-1 = 10, portanto 0. Há alguns que verificam se o módulo por 11 é menor que 2, se for o resultado do dígito é 0, senão será 11 subtraindo-se  o módulo. Tanto faz a ordem dos fatores, dá no mesmo.

O 11° dígito obtem-se da mesma maneira, porém começando-se a multiplicação por 11, então se até agora temos 7424198050X devemos fazer:

(7 x 11) + (4 x 10) + (2 x 9) + (4 x 8) + (1 x 7 ) + (9 x 6) + (8 x 5) + (0 x 4) + (5 x 3 ) + (0 x 2) = 283
283 mod 11 = 8
11-8 = 3, por isso o último dígito é 3.

Criamos a seguinte função para calcular o DV do CPF, supondo-se que os 9 primeiros dígitos são dados:


function TfrmDocumentos.CalculaDVCPF(cpf: string): string;
var
   digitos: array [1..11] of integer;
begin

     digitos[1] := StrToInt(cpf[1]);
     digitos[2] := StrToInt(cpf[2]);
     digitos[3] := StrToInt(cpf[3]);
     digitos[4] := StrToInt(cpf[4]);
     digitos[5] := StrToInt(cpf[5]);
     digitos[6] := StrToInt(cpf[6]);
     digitos[7] := StrToInt(cpf[7]);
     digitos[8] := StrToInt(cpf[8]);
     digitos[9] := StrToInt(cpf[9]);

     digitos[10] := 11 -
          (((digitos[1] * 10)+
          (digitos[2]  * 9)+
          (digitos[3]  * 8)+
          (digitos[4]  * 7)+
          (digitos[5]  * 6)+
          (digitos[6]  * 5)+
          (digitos[7]  * 4)+
          (digitos[8]  * 3)+
          (digitos[9]  * 2)) mod 11);

     if digitos[10] > 9 then
        digitos[10] := 0;

     digitos[11] := 11 -
          (((digitos[1] * 11)+
          (digitos[2]  * 10)+
          (digitos[3]  * 9)+
          (digitos[4]  * 8)+
          (digitos[5]  * 7)+
          (digitos[6]  * 6)+
          (digitos[7]  * 5)+
          (digitos[8]  * 4)+
          (digitos[9]  * 3)+
          (digitos[10] * 2)) mod 11);

     if digitos[11] > 9 then
        digitos[11] := 0;

     Result := IntToStr(digitos[10])+IntToStr(digitos[11]);

end;  

Para gerar números randômicos de 9 dígitos, para serem passadas para a função acima, usamos:

function TfrmDocumentos.GeraCPF: string;
var
   iNum: integer;
begin
     Randomize;
     iNum:=random(999999999);
     Result := ZeroEsquerda(IntToStr(iNum), 9);
end;  

A função ZeroEsquerda serve para completar uma string numérica com zeros a esquerda até a quantidade de dígitos definidas no segundo parâmetro e é definida como a seguir:


function TfrmDocumentos.ZeroEsquerda(num: string; tamanho: integer): string;
var i: integer;
    tamAtual: integer;
begin
     Result := num;
     tamAtual:= tamanho - Length(num);
     for i := 1 to tamAtual do
         Result := '0'+Result;
end; 

As funções abaixo são usadas para limpar um CPF ou CNPJ se ele possui caracteres de pontuação, ou para colocar os caracteres de pontuação nas posições corretas de acordo com o tipo de documento.


//elimina caracteres não numéricos da string
function TfrmDocumentos.Limpa(documento: string): string;
var
   i: integer;
begin
     for i := 1 to Length(documento) do
         if documento[i] in ['0'..'9'] then
            Result := Result+documento[i];
end;

//insere as pontuações nas posições corretas
function TfrmDocumentos.Mascara(documento: string): string;
var
   i: integer;
   temp: string;
begin

     temp := Limpa(documento);
     if Length(temp) = 11 then
     begin
               Insert('.', temp, 4);
               Insert('.', temp, 8);
               Insert('-', temp, 12);
     end
     else
     begin
          Insert('.', temp, 3);
          Insert('.', temp, 7);
          Insert('/', temp, 11);
          Insert('-', temp, 16);
     end;

     Result := temp;
end;  

Para gerar um CPF utilizamos:

procedure TfrmDocumentos.btGeraCPFClick(Sender: TObject);
var
   sCPF: string; //9 primeiros digitos
begin
  sCPF:=GeraCPF;
  //edit com o cpf completo
  txtGCPF.Text := sCPF+CalculaDVCPF(sCPF)
end;  

Para verificar se um CPF é válido obtemos os 9 primeiros dígitos, separamos dos dois posteriores e calculamos os DV's para esses 9 dígitos. Se eles forem iguais aos dois últimos dígitos que extraímos então o CPF é válido.


procedure TfrmDocumentos.btValidaCPFClick(Sender: TObject);
var
   temp, sCpf, sDv: string;
begin
     temp := Limpa(txtVCPF.Text);
     sCpf:=Copy(temp, 1, 9);
     sDv := Copy(temp, 10, 2);
     if (Length(temp) = 11) and (CalculaDVCPF(sCpf) = sDv)  then
     begin
        lbMsg.Caption := 'Cpf Válido';
        lbMsg.Font.Color:=clGreen;
     end
     else
     begin
        lbMsg.Caption := 'Cpf Inválido';
        lbMsg.Font.Color:=clRed;
     end;
end;   


A mesma lógica se aplica com o CNPJ. A única diferença é que o CNPJ se divide em três blocos:
O número (8 primeiros dígitos) o número da filial ou famoso "mil ao contrário/mil contra" que são os 4 posteriores e os dois últimos são o DV.

Para calcular o dígito 13 multiplicamos os 12 primeiros por 5,4,3,2,9,8,7,5,6,4,3,2 e depois somamos as parcelas, retiramos o resto da divisão por 11, subtraimos este resultado de 11 e tudo que for maior que 9 passa a ser 0, não for maior que 9 permanece.

Para calcular o 14° dígito multiplicamos os 13 primeiros, incluindo aí o primeiro DV, por 6,5,4,3,2,9,8,7,6,5,4,3,2 e repetimos o processo.

Esse é o algoritmo em lazarus:

function TfrmDocumentos.CalculaDVCNPJ(cnpj: string): string;
var
   digitos: array [1..14] of integer;
begin

     digitos[1] := StrToInt(cnpj[1]);
     digitos[2] := StrToInt(cnpj[2]);
     digitos[3] := StrToInt(cnpj[3]);
     digitos[4] := StrToInt(cnpj[4]);
     digitos[5] := StrToInt(cnpj[5]);
     digitos[6] := StrToInt(cnpj[6]);
     digitos[7] := StrToInt(cnpj[7]);
     digitos[8] := StrToInt(cnpj[8]);
     digitos[9] := StrToInt(cnpj[9]);
     digitos[10] := StrToInt(cnpj[10]);
     digitos[11] := StrToInt(cnpj[11]);
     digitos[12] := StrToInt(cnpj[12]);

     digitos[13] := 11 - ((

          (digitos[1] * 5)+
          (digitos[2] * 4)+
          (digitos[3] * 3)+
          (digitos[4] * 2)+
          (digitos[5] * 9)+
          (digitos[6] * 8)+
          (digitos[7] * 7)+
          (digitos[8] * 6)+
          (digitos[9] * 5)+
          (digitos[10] * 4)+
          (digitos[11] * 3)+
          (digitos[12] * 2)

          ) mod 11);


     if digitos[13] > 9 then
        digitos[13] := 0;

     digitos[14] := 11 - ((

          (digitos[1] * 6)+
          (digitos[2] * 5)+
          (digitos[3] * 4)+
          (digitos[4] * 3)+
          (digitos[5] * 2)+
          (digitos[6] * 9)+
          (digitos[7] * 8)+
          (digitos[8] * 7)+
          (digitos[9] * 6)+
          (digitos[10] * 5)+
          (digitos[11] * 4)+
          (digitos[12] * 3)+
          (digitos[13] * 2)

          ) mod 11);


     if digitos[14] > 9 then
        digitos[14] := 0;

     Result := IntToStr(digitos[13])+IntToStr(digitos[14]);
end;   

Respectivamente estes são os códigos para gerar, criar e validar CNPJ:

function TfrmDocumentos.GeraCNPJ: string;
var
   iNum: Int64;
begin
     Randomize;
     iNum:=random(999999999999);
     Result := ZeroEsquerda(IntToStr(iNum), 12);
end;  

procedure TfrmDocumentos.btGeraCNPJClick(Sender: TObject);
var
   sCNPJ: string;
begin
  sCNPJ:=GeraCNPJ;
  if cbMascara.Checked then
     txtGCNPJ.Text := Mascara(sCNPJ+CalculaDVCNPJ(sCNPJ))
  else
     txtGCNPJ.Text := sCNPJ+CalculaDVCNPJ(sCNPJ);
end;  

procedure TfrmDocumentos.btValidaCNPJClick(Sender: TObject);
var
   temp, sCNPJ, sDv: string;
begin
     temp := Limpa(txtVCNPJ.Text);
     sCNPJ:=Copy(temp, 1, 12);
     sDv := Copy(temp, 13, 2);
     if (Length(temp) = 14) and (CalculaDVCNPJ(sCNPJ) =  sDv)  then
     begin
        lbMsg.Caption := 'CNPJ Válido';
        lbMsg.Font.Color:=clGreen;
     end
     else
     begin
        lbMsg.Caption := 'CNPJ Inválido';
        lbMsg.Font.Color:=clRed;
     end;
end;   



Como essas regras e fórmulas são rígidas, bem como os tamanhos e posições dos documentos, optei por usar um vetor de tamanho fixo e armazenar cada valor em sua posição em vez de usar um loop for. Assim eu economizo algumas variáveis temporárias de somatório e módulo, efetuo as somas e divisões uma única vez e a transformação de str para int apenas uma vez, e as transformações de int para str apenas nos dois últimos dígitos, os DV.

Espero ter esclarecido como funciona essa validação.

Você pode baixar o programa aqui.

Comentários

Postagens mais visitadas deste blog

Botão Add This para adicionar seu post em qualquer rede

Busca de CEP com o Lazarus - Parte 1 - UrlEncode

Uso de memória no SQL Server