회사에 다니니까 개인 개발할 시간이 부족해지는데
공부는 더 하고 싶다고 생각이 들던 찰나
오픈소스 컨트리뷰션을 해보면 어떨까 생각이 들었다
- 새로운 라이브러리 탐색하고 익히면서 나중에 내 프로젝트나 사내 프로젝트할 때에도 좋은 라이브러리 도입할 수 있게 됨
- 다른 팀의 코드베이스 탐색하고 파악하는 경험 쌓기
- 좋은 경력
이와 같은 이유로 다짐했다!
Contribute할 프로젝트 및 이슈 고르기
Contribute을 하는 방법은 마음에 드는 프로젝트를 찾고, 그 프로젝트 내의 issue 중 내가 해결할 수 있을만 한 걸 찾아서
해결 후 커밋해서 PR을 날리는 게 일반적이다.
근데 어떤 프로젝트를 할지가 제일 고민이 됐다......!
처음에는 spring boot나 java 라이브러리 같은 유명한 프로젝트를 해보려고 했는데
issue들을 보니까 'teams-only'(개인 기여자가 아니라 스프링부트를 만든 팀이 해결해야하는 이슈)가 대부분이어서
처음 contribute하는 내가 기여할 수 있는 프로젝트를 찾아 나섰다.
이때 참고한 게 이 블로그(https://dev-hiro.tistory.com/23)의 팁이었는데, 깃허브의 고급검색을 활용하면 된다는 것!
깃허브에서 spring을 검색한 후 왼쪽 nav바 하단에 Advanced search를 누른 다음

- language: 선택한 언어로 개발
- 팔로워의 수나 스타 수: ex) > 5000
- 라벨: ex) "good first issue"
과 같이 고급 검색을 이용하면 기여할만 한 프로젝트 및 이슈를 찾기가 매우 쉬워진다!
이슈와 프로젝트 분석하기
내가 기여하고자 하는 프로젝트는 'Dependency-Check'(https://github.com/dependency-check/DependencyCheck/issues/8356)이다.

이 프로젝트는 소프트웨어 구성 분석(SCA, Software Composition Analysis) 도구로, 프로젝트에서 사용하는 외부 라이브러리(의존성) 내에 공개된 보안 취약점(CVE)이 포함되어 있는지 탐지하는 역할을 한다. Java, .NET, Ruby, Node.js 등 다양한 기술 스택을 지원하며, 각 라이브러리의 메타데이터를 수집하여 미국 국가 취약점 데이터베이스(NVD)와 대조함으로써 개발자가 사전에 보안 위험을 인지하고 조치할 수 있도록 돕는 오픈소스 보안 엔진이다.
그중에서 해결하고자 하는 이슈는 'Narrow down VersionFilterAnalyzer scope (#8356)'이다.
(https://github.com/dependency-check/DependencyCheck/issues/8356)

이 이슈는 VersionFilterAnalyzer 클래스가 설계된 의도와 다르게 너무 넓은 책임을 가지고 있는 문제를 말하는 이슈이다.
현재 VersionFilterAnalyzer의 JavaDoc에는 이 클래스가 JAR 형식에만 분석을 수행해야 한다고 하지만, 실제 구현상으로는 모든 타입의 의존성 파일을 대상으로 분석 로직을 실행하고 있다.
이는 불필요한 연산 자원을 낭비할 뿐만 아니라, 특정 파일 타입(JAR)을 위해 설계된 로직이 다른 타입의 파일에 적용되어 예기치 않은 부작용을 낳을 수 있다.
이 문제점을 해결하기 위해서는 다음과 같은 변경사항이 필요하다.
- 상속 구조의 변경: 현재 AbstractAnalyzer를 따르고 있는 VersionFilterAnalyzer가 AbstractFileTypeAnalyzer를 상속받도록 수정해야 한다.
- 분석 대상 제한: 상속받은 AbstractFileTypeAnalyzer의 기능을 활용하여, 분석 프로세스 진입 전 대상 파일이 JAR 확장자인지 확인하는 필터링 로직을 적용한다.
AbstractAnalyzer 과 AbstractFileTypeAnalyzer 의 차이는 다음과 같다.
- AbstractAnalyzer: 가장 기본적인 추상 클래스로, 파일의 타입이나 확장자에 상관없이 모든 리소스에 대해 분석 로직을 실행할 수 있는 범용적인 구조다. 현재 상태는 VersionFilterAnalyzer가 이 클래스를 상속받고 있어, 파이썬이나 자바스크립트 파일까지 불필요하게 분석 프로세스에 포함되고 있다.
- AbstractFileTypeAnalyzer: AbstractAnalyzer를 한 번 더 확장한 클래스로, 파일 확장자 기반의 자동 필터링 기능이 내장되어 있다. 이 클래스를 상속받으면 getSupportedExtensions()라는 메서드를 오버라이드해야 한다. 여기에 "jar"라고 명시해두면, 엔진이 파일을 스캔할 때 확장자가 일치하지 않는 파일은 아예 이 분석기를 거치지 않기 때문에 프로세스가 정확해진다.
Assign 받기


오픈소스 생태계에서는 작업 중복 방지 등을 이유로 이슈 해결 전 assign을 받는 문화가 있다.
따라서 나도 이렇게 assign을 요청했다.

assign을 요청한 후 issue 발행인의 댓글과 함께 assign을 받으면 작업을 시작하면 된다.
작업하기
오픈소스 기여는 일반적인 사내 협업과는 조금 다른 절차가 있으니 실수를 줄이기 위해 단계를 나눠 정리해봤다.
1. Fork & Clone: 내 IDE로 가져오기

오픈소스 원본 저장소(Upstream)에는 내 마음대로 커밋을 올릴 권한이 없다. 그래서 먼저 내 계정으로 통째로 복사해오는 과정이 필요하다.
- GitHub 페이지 우측 상단의 [Fork] 버튼을 눌러 내 계정으로 가져온다.
- 그다음, 내 컴퓨터로 소스코드를 내려받는다. (Clone)
git clone https://github.com/내-계정-ID/DependencyCheck.git cd DependencyCheck
2. Upstream 설정: 원본과 동기화하기
내가 작업하는 동안에도 원본 프로젝트에는 새로운 코드들이 계속 올라온다. 내 코드가 옛날 버전이 되지 않도록 원본 저장소를 연결해둔다.
git remote add upstream https://github.com/dependency-check/DependencyCheck.git
git fetch upstream
# 항상 최신 상태의 main에서 작업을 시작하는 게 국룰!
3. 이슈 전용 branch 생성
main 브랜치에서 바로 수정하는 건 위험하다. 이슈 번호나 작업 내용을 담은 새 브랜치를 만들어서 깔끔하게 관리하자.
어떤 브랜치 이름으로 만들지는 해당 프로젝트에 기존 브랜치 이름들을 참고하면 된다.
git checkout -b fix/8356-narrow-version-filter-scope
4. 코드 수정하기
드디어 인텔리제이를 켜고 코드를 수정할 시간! 내가 분석한 대로 extends를 변경하고 로직을 다듬는다.
- 구조 변경: 명세에 맞는 상속 구조 선택
기존 VersionFilterAnalyzer는 모든 파일을 대상으로 분석을 시도하는 AbstractAnalyzer를 상속받고 있었다. 이를 특정 확장자만 선별적으로 처리할 수 있는 AbstractFileTypeAnalyzer로 변경했다.
// 부모 클래스를 변경하여 파일 타입 기반 분석기로 정의
public class VersionFilterAnalyzer extends AbstractFileTypeAnalyzer {
// JAR 필터
private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions("jar").build();
// JAR 파일만 허용하는 필터 적용
@Override
protected FileFilter getFileFilter() {
return FILTER;
}
// 구조 변경에 따른 초기화 메서드 구현
@Override
public void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
// 별도 초기화 로직 없음
}
}
실제 주석은 영어로 코드 베이스에 맞게 작성해야 한다!
- 검증: 테스트 코드로 로직 증명
수정한 로직이 의도대로 동작하는지 확인하기 위해 테스트 케이스를 추가했다. .jar 파일은 정상적으로 수용하고, 그 외의 확장자(.war, .exe, pom.xml 등)는 제외하는지 검증했다.
@Test
void testAcceptOnlyJarFiles() {
VersionFilterAnalyzer instance = new VersionFilterAnalyzer();
instance.initialize(getSettings());
// JAR 파일은 통과
assertTrue(instance.accept(new File("example-1.2.3.jar")));
// 그 외 확장자는 차단 (설계 의도 확인)
assertFalse(instance.accept(new File("example-1.2.3.war")));
assertFalse(instance.accept(new File("example-1.2.3.exe")));
assertFalse(instance.accept(new File("pom.xml")));
}
테스트까지 완료했으면 그 다음 step으로 넘어간다.
작업이 완료되었으면?
커밋 메시지는 해당 프로젝트의 다른 커밋들을 보고 이와 형식을 맞춰서 작성한다.
내 프로젝트는 contribution 가이드라인이 있어서 그걸 따라서 작성했다.
커밋 관련 규칙은 다음과 같았다. - https://www.conventionalcommits.org/en/v1.0.0/
여기서 말하는 feat과 fix의 차이점은
- feat: 새로운 기능을 codebase에 추가하는 것
- fix: codebase의 버그(의도와 다르게 동작하는 것)을 수정하는 것
git add .
git commit -m "fix: narrow down VersionFilterAnalyzer scope to JAR files"
git push origin fix/8356-narrow-version-filter-scope
2. Pull Request (PR) 날리기
내 GitHub 저장소로 가보면 상단에 [Compare & pull request] 버튼이 떠 있을 것이다.
보통 PR 메세지는 아래의 내용을 포함하는데, 이것 또한 해당 프로젝트의 컨벤션을 확인하고 같은 형식으로 진행하면 된다.
- What: 무엇을 고쳤는지 (상속 구조 변경 등)
- Why: 왜 고쳤는지 (JavaDoc과 실제 코드의 불일치 해결 등)
- How: 어떻게 테스트했는지 이 내용들을 정성스럽게 적어서 PR을 날리면 끝!
나의 프로젝트의 PR 형식은 정해져있었는데 이는 다음과 같았다.
## Description of Change
<!--
Please add a description of the proposed change
-->
## Related issues
<!--
e.g
- fixes #xxxx
- relates to #xxxx
-->
## Have test cases been added to cover the new functionality?
*yes/no*
이제 다른 분들의 리뷰를 기다리기만 하면 된다!!
