Skip to content

Instantly share code, notes, and snippets.

@FeMaffezzolli
Last active September 21, 2020 03:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FeMaffezzolli/408d8634b4ec0919787e0837b9fadbc6 to your computer and use it in GitHub Desktop.
Save FeMaffezzolli/408d8634b4ec0919787e0837b9fadbc6 to your computer and use it in GitHub Desktop.
valida.cpf.js
const isValidCpf = (cpfValue) => {
if (!cpfValue) return false
const formatToString = val => {
if (Array.isArray(val)) return val.join('')
if (typeof val === 'string') return val
return null
}
const cpfString = formatToString(cpfValue)
if (!cpfString) return false
let cpf = cpfString.replace(/[^\d]+/g, '')
// Validates length
if (cpf.length !== 11) return false
// Clear invalids
if (/^(\d)\1+$/.test(cpf)) return false
let sum = 0
let remain
for (let i = 1; i <= 9; i++)
sum = sum + parseInt(cpf.substring(i - 1, i)) * (11 - i)
remain = (sum * 10) % 11
if (remain == 10 || remain == 11) remain = 0
if (remain != parseInt(cpf.substring(9, 10))) return false
sum = 0
for (let i = 1; i <= 10; i++)
sum = sum + parseInt(cpf.substring(i - 1, i)) * (12 - i)
remain = (sum * 10) % 11
if (remain == 10 || remain == 11) remain = 0
if (remain != parseInt(cpf.substring(10, 11))) return false
return true
}
@alexbruno
Copy link

alexbruno commented Sep 14, 2020

Opa, legal cara, gostei da versão simples e concisa! É uma forma de implementar o algoritmo e funcionando para o seu caso está ótimo.

Já tive uma versão semelhante, mas acabei evoluindo até aquela que você viu por alguns pontos que descobri serem importantes no meu desenvolvimento e nos casos de uso que encarei.

Como você me pediu comentando na minha Gist cpf.validation.js, seguem alguns pontos abaixo. Espero que ajudem como uma crítica construtiva:

  • O que acontece se o método recebe um valor inválido? Deveria retornar false, pois não é um CPF válido.
    • Ex.: isValidCpf() , isValidCpf(0) , isValidCpf(null) , isValidCpf(undefined) , isValidCpf(AnyThing) , ... No caso do seu método, quebraria o fluxo da aplicação em um erro JS.
  • Posso receber o valor de CPF como número inteiro ou string, formatada ou não.
    • Ex.: isValidCpf(81850453721) , isValidCpf('81850453721') , isValidCpf('818.504.537-21'), isValidCpf('818.504.537.21')
  • Reutilizo funções de cálculo, ao invés de escrever os cálculos novamente para cada verificação. E ao invés de repetir várias vezes números que são constantes e têm base na quantidade de dígitos incluídos no cálculo, capturo diretamente do tamanho do próprio bloco. Isso torna a lógica mais compreensível e menos arbitrária.
    • Ex.: Confira seu código a partir da linha 12. Há 2 laços for que fazem a mesma coisa apenas com parâmetros diferentes. Veja quantas vezes você repete os números 9, 10 e 11 e faz isso parecer totalmente arbitrário no código, quando na verdade eles tem base na quantidade de dígitos incluídos no cálculo a cada verificação.

Além disso:

  • Meu código tem mais quebras de linha e comentários explicativos, visando maior clareza na leitura, o que aumenta muito a quantidade de linhas.
  • Uma condição muito incomum, mas que foi necessária nas aplicações que fiz, foi a possibilidade de o valor do CPF entrar como um Array de dígitos, sendo números ou strings.
    • Ex.: isValidCpf([8, 1, 8, 5, 0, 4, 5, 3, 7, 2, 1]) , isValidCpf(['8', '1', '8', '5', '0', '4', '5', '3', '7', '2', '1'])

@FeMaffezzolli
Copy link
Author

Opa, legal cara, gostei da versão simples e concisa! É uma forma de implementar o algoritmo e funcionando para o seu caso está ótimo.

Já tive uma versão semelhante, mas acabei evoluindo até aquela que você viu por alguns pontos que descobri serem importantes no meu desenvolvimento e nos casos de uso que encarei.

Como você me pediu comentando na minha Gist cpf.validation.js, seguem alguns pontos abaixo. Espero que ajudem como uma crítica construtiva:

  • O que acontece se o método recebe um valor inválido? Deveria retornar false, pois não é um CPF válido.

    • Ex.: isValidCpf() , isValidCpf(0) , isValidCpf(null) , isValidCpf(undefined) , isValidCpf(AnyThing) , ... No caso do seu método, quebraria o fluxo da aplicação em um erro JS.
  • Posso receber o valor de CPF como número inteiro ou string, formatada ou não.

    • Ex.: isValidCpf(81850453721) , isValidCpf('81850453721') , isValidCpf('818.504.537-21'), isValidCpf('818.504.537.21')
  • Reutilizo funções de cálculo, ao invés de escrever os cálculos novamente para cada verificação. E ao invés de repetir várias vezes números que são constantes e têm base na quantidade de dígitos incluídos no cálculo, capturo diretamente do tamanho do próprio bloco. Isso torna a lógica mais compreensível e menos arbitrária.

    • Ex.: Confira seu código a partir da linha 12. Há 2 laços for que fazem a mesma coisa apenas com parâmetros diferentes. Veja quantas vezes você repete os números 9, 10 e 11 e faz isso parecer totalmente arbitrário no código, quando na verdade eles tem base na quantidade de dígitos incluídos no cálculo a cada verificação.

Além disso:

  • Meu código tem mais quebras de linha e comentários explicativos, visando maior clareza na leitura, o que aumenta muito a quantidade de linhas.

  • Uma condição muito incomum, mas que foi necessária nas aplicações que fiz, foi a possibilidade de o valor do CPF entrar como um Array de dígitos, sendo números ou strings.

    • Ex.: isValidCpf([8, 1, 8, 5, 0, 4, 5, 3, 7, 2, 1]) , isValidCpf(['8', '1', '8', '5', '0', '4', '5', '3', '7', '2', '1'])

Fala Alex, muito obrigado pelo seu tempo e atenção.

De fato tinham algumas coisas básicas ali para corrigir (como tratar undefined, null e vazio). Fiz isso de imediato.
E implementei também a possibilidade de receber um array, assim como no seu caso.

Após isso, eu fiz um script de test com alguns cenários e medindo o tempo. A sua implementação ficou incrivelmente mais rápida, quase 12 vezes mais rápida.

Obs.: Result true indica sucesso na validação do CPF.

Run test number 0 - Result: true - Payload: [40010845895]
Run test number 1 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 2 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 3 - Result: true - Payload: [794.752.070-40]
Run test number 4 - Result: true - Payload: [266.422.050-06]
Run test number 5 - Result: true - Payload: [40010845896]
Run test number 6 - Result: true - Payload: [40010845896]
Run test number 7 - Result: true - Payload: [null]
Run test number 8 - Result: true - Payload: [undefined]
Run test number 9 - Result: true - Payload: [[object Object]]
First: 8.096ms
Run test number 0 - Result: true - Payload: [40010845895]
Run test number 1 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 2 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 3 - Result: true - Payload: [794.752.070-40]
Run test number 4 - Result: true - Payload: [266.422.050-06]
Run test number 5 - Result: true - Payload: [40010845896]
Run test number 6 - Result: true - Payload: [40010845896]
Run test number 7 - Result: true - Payload: [null]
Run test number 8 - Result: true - Payload: [undefined]
Run test number 9 - Result: true - Payload: [[object Object]]
Second: 0.698ms

E eu achei isso extremamente interessante. Por que embora no seu código não tenha nenhum laço explícito, as funções 'map' e 'reduce' não deixam de ser um laço, e você utiliza 5 vezes (versus dois laços no meu). Eu vou um pouco mais fundo para entender o motivo dessa discrepância.

Abs.

@FeMaffezzolli
Copy link
Author

FeMaffezzolli commented Sep 18, 2020

Opa, legal cara, gostei da versão simples e concisa! É uma forma de implementar o algoritmo e funcionando para o seu caso está ótimo.
Já tive uma versão semelhante, mas acabei evoluindo até aquela que você viu por alguns pontos que descobri serem importantes no meu desenvolvimento e nos casos de uso que encarei.
Como você me pediu comentando na minha Gist cpf.validation.js, seguem alguns pontos abaixo. Espero que ajudem como uma crítica construtiva:

  • O que acontece se o método recebe um valor inválido? Deveria retornar false, pois não é um CPF válido.

    • Ex.: isValidCpf() , isValidCpf(0) , isValidCpf(null) , isValidCpf(undefined) , isValidCpf(AnyThing) , ... No caso do seu método, quebraria o fluxo da aplicação em um erro JS.
  • Posso receber o valor de CPF como número inteiro ou string, formatada ou não.

    • Ex.: isValidCpf(81850453721) , isValidCpf('81850453721') , isValidCpf('818.504.537-21'), isValidCpf('818.504.537.21')
  • Reutilizo funções de cálculo, ao invés de escrever os cálculos novamente para cada verificação. E ao invés de repetir várias vezes números que são constantes e têm base na quantidade de dígitos incluídos no cálculo, capturo diretamente do tamanho do próprio bloco. Isso torna a lógica mais compreensível e menos arbitrária.

    • Ex.: Confira seu código a partir da linha 12. Há 2 laços for que fazem a mesma coisa apenas com parâmetros diferentes. Veja quantas vezes você repete os números 9, 10 e 11 e faz isso parecer totalmente arbitrário no código, quando na verdade eles tem base na quantidade de dígitos incluídos no cálculo a cada verificação.

Além disso:

  • Meu código tem mais quebras de linha e comentários explicativos, visando maior clareza na leitura, o que aumenta muito a quantidade de linhas.

  • Uma condição muito incomum, mas que foi necessária nas aplicações que fiz, foi a possibilidade de o valor do CPF entrar como um Array de dígitos, sendo números ou strings.

    • Ex.: isValidCpf([8, 1, 8, 5, 0, 4, 5, 3, 7, 2, 1]) , isValidCpf(['8', '1', '8', '5', '0', '4', '5', '3', '7', '2', '1'])

Fala Alex, muito obrigado pelo seu tempo e atenção.

De fato tinham algumas coisas básicas ali para corrigir (como tratar undefined, null e vazio). Fiz isso de imediato.
E implementei também a possibilidade de receber um array, assim como no seu caso.

Após isso, eu fiz um script de test com alguns cenários e medindo o tempo. A sua implementação ficou incrivelmente mais rápida, quase 12 vezes mais rápida.

Obs.: Result true indica sucesso na validação do CPF.

Run test number 0 - Result: true - Payload: [40010845895]
Run test number 1 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 2 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 3 - Result: true - Payload: [794.752.070-40]
Run test number 4 - Result: true - Payload: [266.422.050-06]
Run test number 5 - Result: true - Payload: [40010845896]
Run test number 6 - Result: true - Payload: [40010845896]
Run test number 7 - Result: true - Payload: [null]
Run test number 8 - Result: true - Payload: [undefined]
Run test number 9 - Result: true - Payload: [[object Object]]
First: 8.096ms
Run test number 0 - Result: true - Payload: [40010845895]
Run test number 1 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 2 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
Run test number 3 - Result: true - Payload: [794.752.070-40]
Run test number 4 - Result: true - Payload: [266.422.050-06]
Run test number 5 - Result: true - Payload: [40010845896]
Run test number 6 - Result: true - Payload: [40010845896]
Run test number 7 - Result: true - Payload: [null]
Run test number 8 - Result: true - Payload: [undefined]
Run test number 9 - Result: true - Payload: [[object Object]]
Second: 0.698ms

E eu achei isso extremamente interessante. Por que embora no seu código não tenha nenhum laço explícito, as funções 'map' e 'reduce' não deixam de ser um laço, e você utiliza 5 vezes (versus dois laços no meu). Eu vou um pouco mais fundo para entender o motivo dessa discrepância.

Abs.

Na verdade acho que eu entendi (e talvez seja alguma coisa relacionada ao node em si). Eu simplesmente troquei a ordem de execução, rodei primeiro seu código e então o meu. E adivinha o que aconteceu: o seu passou e levar os mesmos 8 ms (que antes o meu levava) e o meu 0.8ms (similar ao seu).

    Run test number 0 - Result: true - Payload: [40010845895]
  Alex"s 0 run: 6.939ms
    Run test number 1 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
  Alex"s 1 run: 0.086ms
    Run test number 2 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
  Alex"s 2 run: 0.057ms
    Run test number 3 - Result: true - Payload: [794.752.070-40]
  Alex"s 3 run: 0.060ms
    Run test number 4 - Result: true - Payload: [266.422.050-06]
  Alex"s 4 run: 0.073ms
    Run test number 5 - Result: true - Payload: [40010845896]
  Alex"s 5 run: 0.052ms
    Run test number 6 - Result: true - Payload: [40010845896]
  Alex"s 6 run: 0.068ms
    Run test number 7 - Result: true - Payload: [null]
  Alex"s 7 run: 0.030ms
    Run test number 8 - Result: true - Payload: [undefined]
  Alex"s 8 run: 0.042ms
    Run test number 9 - Result: true - Payload: [[object Object]]
  Alex"s 9 run: 0.028ms
Alex"s total: 7.955ms
    Run test number 0 - Result: true - Payload: [40010845895]
  Felipe"s 0 run: 0.298ms
    Run test number 1 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
  Felipe"s 1 run: 0.047ms
    Run test number 2 - Result: true - Payload: [4,0,0,1,0,8,4,5,8,9,5]
  Felipe"s 2 run: 0.034ms
    Run test number 3 - Result: true - Payload: [794.752.070-40]
  Felipe"s 3 run: 0.035ms
    Run test number 4 - Result: true - Payload: [266.422.050-06]
  Felipe"s 4 run: 0.024ms
    Run test number 5 - Result: true - Payload: [40010845896]
  Felipe"s 5 run: 0.027ms
    Run test number 6 - Result: true - Payload: [40010845896]
  Felipe"s 6 run: 0.024ms
    Run test number 7 - Result: true - Payload: [null]
  Felipe"s 7 run: 0.021ms
    Run test number 8 - Result: true - Payload: [undefined]
  Felipe"s 8 run: 0.019ms
    Run test number 9 - Result: true - Payload: [[object Object]]
  Felipe"s 9 run: 0.025ms
Felipe"s total: 0.900ms

Estou falando por achismo, mas me parece que o processo de inicialização do laço (ou do próprio console.time) leva uns 7 ms, e acaba inflando o primeiro contador.

E na análise de performance individual, os tempos ficaram muito próximos.

Abraço!

@alexbruno
Copy link

Fala cara, como vai?
Interessante! Eu não saberia dizer de fato o que pode estar causando isso.
Não sei como você testou, mas imagino que talvez uma suite de testes usando Jest ou Mocha e rodando os testes de cada método em arquivos de testes separados possa ser mais preciso.
Essas ferramentas de teste já mostram o tempo de execução no resultado no console.
Acredito que o seu script possa performar um pouco melhor que o meu, mas a diferença deve ser mínima.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment