혼자만의 지식으로 클린 코드를 작성하는 데 어려움을 느껴 정적 코드 품질 분석 도구인 SonarQube의 도움을 받아보려고 합니다.
이번에는 프로젝트 소스코드를 바탕으로 간단하게 결과 보고서를 받고, 결과를 바탕으로 리팩토링해보겠습니다.
이번에는 로컬 환경에서 빠르게 테스트를 진행해 볼 것이기 때문에, Docker image를 통해 SonarScanner CLI를 다운받아 사용할 것입니다.
SonarQube설치 과정은 공식 문서도 잘 되어 있고 레퍼런스도 많기 때문에 생략하겠습니다.
저는 간단하고 익숙한Docker를 활용해 설치했습니다.
공식 문서를 보면 아래의 명령어를 입력하는 간단한 방법으로 테스트할 수 있음을 알 수 있습니다.
# 공식문서 명령어
docker run \
--rm \
-e SONAR_HOST_URL="http://${SONARQUBE_URL}" \
-e SONAR_TOKEN="myAuthenticationToken" \
-v "${YOUR_REPO}:/usr/src" \
sonarsource/sonar-scanner-cli
주의할 점은 localhost 대신 host.docker.internal을 사용해야한다는 것입니다.
도커 컨테이너 내부에서의 localhost는 컨테이너 자신을 가리키기 때문에, 컨테이너를 실행한 호스트 머신을 가리키려면 host.docker.internal을 사용해야 합니다.
그렇지 않으면 Failed to connect to /localhost:9000 에러를 만나게 됩니다.
따라서 SonarQube를 실행한 포트 번호와 함께 host.docker.internal:9000로 설정해줬습니다.
아래와 같이 Administration > Security > Users에서 새로 토큰을 만들어 복사했습니다.

분석할 코드가 있는 프로젝트 디렉토리로 이동해서 실행할 것이기 때문에,$(pwd)로 바꿔줬습니다.
이대로 실행하면 아래처럼 필수 속성을 정의하라는 에러 메시지가 나옵니다.
You must define the following mandatory properties for 'Unknown': sonar.projectKey
따라서 projectKey를 프로젝트 이름으로 설정해줬습니다.
테스트 코드는 포함하지 않을 것이므로 분석할 코드 경로를 정하는 sources는 src/main으로 설정하고, 추가적으로 sourceEncoding은 UTF-8로 적어줬습니다.
SonarQube는 자바 코드를 분석할 때 소스 코드(.java)뿐만 아니라, 컴파일된 바이너리 파일(.class)도 필요하기 때문에,
.class 파일의 경로도 설정해줘야 합니다.
이 설정을 안해주면 아래와 같은 에러가 발생합니다.
org.sonar.java.AnalysisException: Your project contains .java files, please provide compiled classes with sonar.java.binaries property, or exclude them from the analysis with sonar.exclusions property.
따라서 build/classes/java/main으로 설정했습니다.
최종적으로 프로젝트 빌드 후 아래 명령어를 실행했습니다.
docker run --rm \
-e SONAR_HOST_URL="http://host.docker.internal:9000" \
-e SONAR_TOKEN="발급받은 토큰" \
-v "$(pwd):/usr/src" \
sonarsource/sonar-scanner-cli \
-Dsonar.projectKey=프로젝트 이름 \
-Dsonar.java.binaries=build/classes/java/main \
-Dsonar.sonar.sourceEncoding=UTF-8 \
-Dsonar.sources=src/main
드디어 코드 분석이 끝나서 웹페이지에서 결과를 볼 수 있습니다. ♻️

위 화면은 Overviews 섹션입니다.
신뢰성, 특히 유지보수성에서 많은 이슈들이 있고 🧐 테스트 코드를 포함하지 않았더니 커버리지는 0.0%가 나왔습니다.
코드 중복도 0.3% 발견됐네요.
다음으로 Issues 섹션에서 더 자세히 살펴보면서 리팩토링을 진행해보겠습니다.
먼저 신뢰성의 3가지 이슈 모두 형변환과 관련된 내용이었습니다.
그 중 하나를 살펴보겠습니다.
아래 코드에서 LocalDate 객체의 minusDays의 파라미터는 long 타입이어야 하는데, int 값이 주어지면서 결과가 예상대로 나오지 않을 위험이 있기에 신뢰성과 의도성을 해치게 되는 것입니다.
public LocalDateTime getMondayDateTime(LocalDate pivotDate) {
int pivotDay = pivotDate.getDayOfWeek().getValue();
int monday = DayOfWeek.MONDAY.getValue();
return pivotDate.minusDays(pivotDay - monday).atStartOfDay();
이를 해결하려면 두 피연산자를 최종 타입인 long으로 변환한 후 연산을 진행해야 합니다.
연산식에서 서로 다른 데이터 타입이 나타날 경우, 자바 컴파일러는 가장 큰 데이터 타입으로 통일하기 때문에 피연산자 중 하나를 long 타입으로 바꾸었습니다.
public LocalDateTime getMondayDateTime(LocalDate pivotDate) {
int pivotDay = pivotDate.getDayOfWeek().getValue();
long monday = DayOfWeek.MONDAY.getValue();
return pivotDate.minusDays(pivotDay - monday).atStartOfDay();
}
유지보수성 관련 이슈들은 다음과 같은 리팩토링을 진행했습니다.
@Getter
public class APIConstants {
private static final String VERSION = "/v1";
public static final String API_PREFIX = "/api" + VERSION;
private APIConstants() { //private 생성자
throw new UnsupportedOperationException("Cannot be instantiated");
}
}
사실 위 클래스는 단순히 API prefix를 적어둔 static final 필드를 모아 둔 클래스이므로 인스턴스를 생성할 이유도 실수할 가능성도 거의 없긴 하지만, 인스턴스화를 아예 막아두기 위해 private 생성자를 추가했습니다.
roomRequest → roomrequest
accessHandler → accesshandler
등의 리팩토링을 진행했습니다.
이후에는 검사 대상에 테스트 코드까지 포함시켜 JaCoCo로 커버리지를 측정하는 것과,
CI/CD 파이프라인을 구축하고 SonarQube를 연동하여 품질 검사를 자동화하는 것이 과제가 될 것 같습니다. 🙂↕️
https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/scanners/sonarscanner/