당신이 신뢰하는 라이브러리에 숨어 있는 몇 가지 공통적인 버그들

저는 Langchain, Vite, Bat와 같은 대규모 저장소에 작은 수정 사항들을 보내는 데 시간을 보냅니다.

이 프로젝트들은 서로 다른 언어와 도메인을 사용하며, 메인테이너들은 전문가들입니다.

놀라운 점은 버그의 개수가 아닙니다. 바로 패턴입니다. 대부분의 버그는 옷만 다르게 입었을 뿐, 몇 가지 정해진 형태를 띠고 있습니다.

이러한 형태를 파악하고 나면, 버그가 프로덕션에 도달하기 전에 잡아낼 수 있습니다. 제가 발견한 다섯 가지 공통 패턴은 다음과 같습니다.

  • 잘못된 입력 키 (Wrong Input Keys) Langchain에서 한 이름 변경(rename) 함수는 old_path라는 키를 찾았습니다. 하지만 시스템은 실제로 path라는 키를 보냈고, 코드는 충돌(crash)했습니다. 리뷰를 통과하는 이유: 개발자가 시스템이 실제로 보내는 입력값이 아니라, 함수가 원하는 입력값을 수동으로 만들어 테스트했기 때문에 유닛 테스트를 통과했습니다. 확인 방법: 함수가 특정 키를 읽는다면, 해당 객체가 생성되는 위치를 찾으세요. 아무도 그 키를 설정하지 않는다면 버그를 발견한 것입니다. 실제 호출자(caller)를 대상으로 테스트하세요.

  • Truthiness 함정 (Truthiness Traps) 흔한 실수는 "이 값이 설정되었는가"를 확인하려 할 때 truthiness 체크를 사용하는 것입니다. 예시: const clause = defaultValue ? \DEFAULT ${defaultValue}` : '';만약 값이 0이라면, 코드는 해당 분기를 건너뜁니다. 0은 유효한 값이지만 "falsy"로 취급되기 때문입니다. 확인 방법: 항상 0, 빈 문자열, 그리고false`를 테스트하세요. 코드가 "값이 없음"과 "값이 있지만 0임"을 구분하지 못한다면, 그 코드는 잘못된 것입니다.

  • 부호 없는 정수 언더플로우 (Unsigned Integer Underflow) Bat 프로젝트에서는 터미널 너비를 계산하기 위해 산술 연산을 사용했습니다. 너비가 너무 작으면 뺄셈 결과로 언더플로우가 발생합니다. 부호 없는 타입(unsigned types)에서 이는 매우 큰 숫자로 되돌아가거나(wrap) 충돌을 일으킵니다. 확인 방법: 사용자 입력을 사용하는 부호 없는 타입의 모든 뺄셈에는 포화 연산(saturating subtraction)이 필요합니다. 0과 1로 테스트하세요.

  • 인코딩 및 엣지 케이스 (Encoding and Edge Cases) 텍스트 규칙은 비 ASCII 문자를 보기 전까지는 단순해 보입니다. Mistune는 생성기(generator)가 생성할 수 있는 중첩된 구분자(stacked delimiters) 문제로 어려움을 겪었습니다. Wenmode는 유니코드 결합 문자(Unicode combining marks)를 처리할 때 실패했습니다. 리뷰를 통과하는 이유: ASCII는 모든 테스트를 통과합니다. 버그는 직접 타이핑하지 않는 입력값이 들어올 때만 나타납니다. 확인 방법: 차분 테스트(differential test)를 사용하세요. 여러분의 출력값을 검증된 다른 구현체와 비교해 보세요.

  • 안전하지 않은 파싱 (Unsafe Parsing) Vite에는 가드(guard) 없이 URL을 디코딩하는 미들웨어가 있었습니다. 잘못된 형식의 URL은 URIError를 발생시켜 미들웨어를 중단시켰습니다. 확인 방법: 직접 만들지 않은 데이터 중 디코딩하거나 파싱해야 하는 모든 것에는 try/catch 블록이 필요합니다. 깨진 문자열을 코드에 던져보고, 코드가 유연하게 대처하는지 아니면 망가지는지 확인하세요.

저의 습관은 간단합니다. 버그를 수정할 때, 바로 옆에 있는 코드도 살펴봅니다. 한 핸들러에 버그가 있다면, 형제 핸들러도 같은 형태의 버그를 가지고 있을 가능성이 높기 때문입니다.

출처: https://dev.to/greymothjp/the-same-few-bugs-keep-hiding-in-libraries-you-already-trust-1pgp