저는 학부 시절 CPF(Brazillian ID) 검증 알고리즘을 처음 접했던 기억이 생생합니다. 미나스 제라이스 연방 대학교 UFMG의 정밀 과학 연구소(Institute of Exact Sciences)에 인턴십을 지원하는 동안 우리는 알고리즘에 대한 간단한 설명을 마친 후 CPF 검사 숫자를 검증하기 위해 Java 코드를 직접 작성해 달라는 요청을 받았습니다.
그 이후로 저는 다양한 전문적 맥락에서 이 문제를 여러 번 접했으며 종종 인터넷에서 솔루션을 복사하고 일부 단위 테스트를 추가했습니다. 그러나 매번 이러한 솔루션에서 반복되는 문제에 놀랐습니다. 이는 Java 코드에 대해 예상되는 객체 지향 접근 방식보다 명령형 패러다임에 더 뿌리를 두는 경향이 있습니다. 그러나 나를 더욱 괴롭히는 것은 이러한 구현이 부과하는 높은 인지 부하로 인해 코드의 의도를 읽고 이해하는 것이 비현실적이라는 것입니다.
아직 이 코드를 구현할 필요가 없는 관심 있는 개발자는 모든 프로그래밍 언어에서 솔루션을 쉽게 찾을 수 있습니다. 그러나 모두 동일한 방식으로 제시되는 경향이 있습니다. 즉, CPF 검사 숫자가 구현되는 방법에 대한 설명을 순진하게 복제한 것입니다. 이러한 접근 방식의 이유를 이해하는 데 시간을 투자하는 사람은 거의 없는 것 같습니다.
소프트웨어 개발에서 충돌 회피 개념은 해시 코드 알고리즘, 특히 소수 계수를 사용할 때 자주 접하게 됩니다. CPF(브라질 ID) 및 CNPJ(브라질 회사 ID)의 확인 숫자는 충돌 방지에 중점을 두고 유사하게 작동합니다. 이렇게 하면 여러 조합으로 동일한 합계가 생성될 수 있으므로 간단한 숫자 합계로 인해 잘못된 항목이 실수로 확인되는 일이 발생하지 않습니다.
이를 완화하기 위해 일반적인 방법은 가중치 합계를 적용하여 각 자릿수에 특정 요소를 곱하는 것입니다. 이것을 선을 따라 숫자를 퍼뜨리는 것으로 생각할 수 있습니다. 곱셈을 사용하면 여러 자리 숫자가 같은 위치에 있을 가능성이 줄어듭니다. 그렇다면 숫자에서 숫자의 위치가 가중치를 결정한다는 것은 의미가 있습니다.
신뢰성을 더욱 높이고 충돌 위험을 최소화하기 위해 합계는 모듈로 11로 계산되며 이 결과는 동일한 소수에서 뺍니다. 검사 숫자가 한 자리로 유지되도록 10과 11의 결과를 0으로 변환합니다.
CPF 및 CNPJ의 검사 숫자를 계산하는 데 사용되는 알고리즘은 이해하기 어려울 수 있습니다. 알고리즘 이면의 전반적인 동기는 분명할 수 있지만 각 부분의 구체적인 역할을 파악하는 것은 어려운 경우가 많습니다. 이러한 복잡성은 부분적으로 계산에 하나의 대규모 방법으로 함께 묶이는 일련의 수학적 계산이 포함되기 때문에 발생합니다. 또한, 설명할 수 없는 배열로 제시된 가중치는 비논리적으로 보일 수 있습니다.
이 문제를 해결하기 위해 저는 설명이 부족한 코드의 양을 줄이는 데 중점을 둡니다. 저는 단일 책임 원칙(SOLID의 "S")을 준수함으로써 더 간단하고 이해하기 쉬운 방법을 만들기 위해 노력하고 있습니다. 또한 코드베이스 내에서 유비쿼터스 언어를 구축하는 것을 목표로 의미 있는 변수 이름을 통해 핵심 개념을 정의하려고 노력합니다. 이 접근 방식을 통해 CPF 검사 숫자에 사용되는 방법과 CNPJ에 사용되는 방법의 차이점을 식별하려고 했습니다. 소프트웨어에는 종종 다른 방법이 필요하기 때문입니다. 코드의 핵심 기능은 아래에 나와 있습니다. 전체 코드 및 관련 단위 테스트를 포함한 추가 내용을 보려면 내 GitHub 저장소를 방문하세요.
private String getCheckDigits(String document, int maxWeight) { final int lengthWithoutCheckDigits = getBaseDigitsLength(document); int firstWeightedSum = 0; int secondWeightedSum = 0; for (int i = 0; i < lengthWithoutCheckDigits; i++) { final int digit = Character.getNumericValue(document.charAt(i)); final int maxIndex = lengthWithoutCheckDigits - 1; final int reverseIndex = maxIndex - i; firstWeightedSum += digit * calculateWeight(reverseIndex, maxWeight); // Index is incremented, starting from 3, skipping first check digit. // The first part will be added later as the calculated first check digit times its corresponding weight. secondWeightedSum += digit * calculateWeight(reverseIndex + 1, maxWeight); } final int firstDigit = getCheckDigit(firstWeightedSum); // Add the first part as the first check digit times the first weight. secondWeightedSum += MIN_WEIGHT * firstDigit; final int secondDigit = getCheckDigit(secondWeightedSum); return String.valueOf(firstDigit) + secondDigit; } private int calculateWeight(int complementaryIndex, int maxWeight) { return complementaryIndex % (maxWeight - 1) + MIN_WEIGHT; } private int getCheckDigit(int weightedSum) { final var checkDigit = enhanceCollisionAvoidance(weightedSum); return checkDigit > 9 ? 0 : checkDigit; } private int enhanceCollisionAvoidance(int weightedSum) { final var weightSumLimit = 11; return weightSumLimit - weightedSum % weightSumLimit; }
CNPJ와 CPF 모두에 대한 검사 숫자 계산 결과를 인터넷에서 찾은 팁 솔루션과 비교하세요.
public class ValidaCNPJ { public static boolean isCNPJ(String CNPJ) { // considera-se erro CNPJ's formados por uma sequencia de numeros iguais if (CNPJ.equals("00000000000000") || CNPJ.equals("11111111111111") || CNPJ.equals("22222222222222") || CNPJ.equals("33333333333333") || CNPJ.equals("44444444444444") || CNPJ.equals("55555555555555") || CNPJ.equals("66666666666666") || CNPJ.equals("77777777777777") || CNPJ.equals("88888888888888") || CNPJ.equals("99999999999999") || (CNPJ.length() != 14)) return(false); char dig13, dig14; int sm, i, r, num, peso; // "try" - protege o código para eventuais erros de conversao de tipo (int) try { // Calculo do 1o. Digito Verificador sm = 0; peso = 2; for (i=11; i>=0; i--) { // converte o i-ésimo caractere do CNPJ em um número: // por exemplo, transforma o caractere '0' no inteiro 0 // (48 eh a posição de '0' na tabela ASCII) num = (int)(CNPJ.charAt(i) - 48); sm = sm + (num * peso); peso = peso + 1; if (peso == 10) peso = 2; } r = sm % 11; if ((r == 0) || (r == 1)) dig13 = '0'; else dig13 = (char)((11-r) + 48); // Calculo do 2o. Digito Verificador sm = 0; peso = 2; for (i=12; i>=0; i--) { num = (int)(CNPJ.charAt(i)- 48); sm = sm + (num * peso); peso = peso + 1; if (peso == 10) peso = 2; } r = sm % 11; if ((r == 0) || (r == 1)) dig14 = '0'; else dig14 = (char)((11-r) + 48); // Verifica se os dígitos calculados conferem com os dígitos informados. if ((dig13 == CNPJ.charAt(12)) && (dig14 == CNPJ.charAt(13))) return(true); else return(false); } catch (InputMismatchException erro) { return(false); } } }
CNPJ만을 위한 코드입니다!
결과 코드가 다소 장황해 보일 수 있지만, 명확성과 자기 설명을 강조한 결과 만족스러운 결과를 얻었습니다. 코드는 보다 직관적으로 설계되어 정확성에 대한 더 큰 확신을 제공하며 페이지를 아래로 스크롤하지 않고도 대부분의 핵심 기능을 볼 수 있습니다.
향후 개선을 위한 제안을 환영합니다. 피드백을 자유롭게 공유해 주세요.
위 내용은 CPF 및 CNPJ 검사 숫자 알고리즘 이해하기: 명확하고 간결한 접근 방식의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!