전각 공백 때문에 예금주명 검증이 계속 실패했다
예금주명 비교가 특정 사업자에서만 계속 틀렸습니다. 눈으로는 같은 문자열이었고요. 전각 공백(U+3000)이 원인이었고, 고치다가 빈 문자열 관련 문제를 하나 더 찾았습니다.
계좌를 등록할 때 사용자가 입력한 이름과 은행에서 조회한 예금주명을 비교합니다. 주식회사, ㈜ 같은 접두어랑 공백을 지우고 나서 비교하는 식이었습니다.
1
private static final Pattern REMOVE_EMPTY_SPACE_PATTERN = Pattern.compile("\\s+");
특정 사업자만 계속 불일치가 났습니다. 로그를 찍어보니 두 값이 똑같이 나왔습니다. 주식회사 가상상사 하고 주식회사 가상상사. 내가 생각한 거랑 좀 다른데.
바이트로 찍어봤다
처음엔 인코딩 문제인가 했습니다. DB 컬럼 charset 부터 봤는데 이상 없었고요. 그래서 문자열을 코드포인트로 찍어봤습니다.
공백이 달랐습니다. 한쪽은 0x20, 다른 쪽은 U+3000. 전각 공백입니다. 한글 입력기에서 한자 모드로 스페이스를 누르면 이게 들어가고, 일부 기관 시스템에서 데이터를 내려줄 때도 섞여 옵니다.
그리고 자바 정규식의 \s 는 U+3000 을 공백으로 안 봅니다. \s 는 [ \t\n\x0B\f\r] 만 잡습니다. 전각 공백은 없습니다. 저는 \s 가 그냥 모든 공백인 줄 알고 있었습니다.
1
2
// 전각 공백(U+3000) 포함
private static final Pattern REMOVE_EMPTY_SPACE_PATTERN = Pattern.compile("[\\s\\u3000]+");
Java 21 입니다. \p{Z} 나 \p{javaWhitespace} 로 범위를 넓히는 방법도 있는데, 그러면 어떤 문자까지 걸리는지 다 확인해야 해서 필요한 것만 넣었습니다.
테스트 쓰다가 빈 문자열이 걸렸다
테스트 케이스를 채우다가 로직을 다시 봤습니다. 대략 이런 모양이었습니다.
1
2
3
String cleanedUser = removePrefix(removeSpace(userName));
String cleanedOwner = removePrefix(removeSpace(bankOwnerName));
return cleanedUser.contains(cleanedOwner) || cleanedOwner.contains(cleanedUser);
접두어랑 공백을 다 지우고 나서 빈 문자열이 되면 어떻게 되나. "가상상사".contains("") 는 true 입니다. 빈 문자열은 모든 문자열에 포함되니까요.
예금주명이 주식회사 만 들어오면 정규화 후에 빈 문자열이 되고, 그러면 검증이 그냥 통과합니다. 계좌 검증인데요. 통과하면 안 되는 게 통과합니다.
1
2
3
if (cleanedUser.isEmpty() || cleanedOwner.isEmpty()) {
return false;
}
가드 넣고 테스트로 고정했습니다.
남은 생각
전각 공백은 검색하면 나오는 얘기라 새로울 게 없습니다. 그보다 두 가지가 남았습니다.
문자열이 눈으로 같은데 비교가 틀리면 바로 코드포인트를 찍어야 합니다. 값만 로그로 출력하면 아무것도 안 보이거든요. 처음부터 그랬으면 시간을 덜 썼을 겁니다.
그리고 입력을 깎아내는 로직은 다 깎여서 아무것도 안 남는 경우를 봐야 합니다. 그 빈 값이 contains, startsWith 같은 데서 조용히 통과해 버립니다. 실패가 아니라 통과라서 로그에도 안 남고요.
원래 잡으려던 것보다 이쪽이 더 위험했습니다.