Naver Financial 코드 품질 관리 — Part 2

soojin.jang
NAVER Pay Dev Blog
Published in
14 min readDec 6, 2022

안녕하세요. Naver Financial 에서 간편 결제 플랫폼 개발 업무를 담당하고 있는 장수진입니다.

Naver Financial 에서는 주문 / 결제 / 금융 서비스를 다양한 파트너사와 co-work 하여 사용자에게 제공하고 있습니다.

SonarQube 에서 제공하고 있는 코드 품질 분석 기능 및 연동 가이드에 대한 소개 그리고 NF 내에서 어떻게 SonarQube를 활용하고 있는지 설명합니다.

연재

SonarQube의 기능

SonarQube 에서 제공하고 있는 코드 품질 분석 기능에 대해 소개합니다.

Rules

코드 품질에 대한 규칙은 아래 네 개의 분류로 나뉘어집니다.

  • Code Smell (Maintainability domain): 유지 보수 관점으로 분석되는 코드로 리팩토링 하지 않는다면 코드 수정이 일어날 때 부수효과가 발생할 가능성이 높습니다.
  • Bug (Reliability domain): 수정이 반드시 필요한 잘못된 코드
  • Vulnerability (Security domain): 개발자의 리뷰가 필요한 보안에 민감한 코드
  • Security Hotspot (Security domain): 보안과 관련있는 이슈로 공격자들이 선택할 수 있는 방법에 대한 대비가 되어 있는지를 분석합니다.

각 Rule은 상세 설명 및 권장되는 해결 방안을 포함하며, 코드 작성자가 이슈 대응에 대한 우선 순위를 매길 수 있도록 심각도 등급을 가지고 있습니다.

취약점 (Vulnerability) 또는 핫스팟 (Security Hotspot)?

핫스팟과 취약점의 주요 차이점은 수정 적용 여부를 결정하기 전에 검토가 필요하다는 것입니다.

  • 핫스팟을 사용하면 보안에 민감한 코드 부분이 강조 표시되지만 전체 애플리케이션 보안에는 영향을 미치지 않을 수 있습니다. 코드를 보호하기 위해 수정이 필요한지 여부를 결정하기 위해 코드를 검토하는 것은 개발자의 몫입니다.
  • 취약점으로 인해 애플리케이션의 보안에 영향을 미치는 문제가 발견되었으며 즉시 수정해야 합니다.
Rule Example: 단일 요소 리스트를 생성할 때 Arrays.asList() 보다 Collections.singletonList() 의 사용을 권장한다. 해당 Bug는 Major 등급으로 분류됩니다.

Quality Profiles

프로젝트에 적용할 rule 들의 집합입니다.

모든 프로젝트에 같은 Quality Profile를 이용하여 코드 품질을 검증하는 것이 이상적일 수 있지만 항상 실용적이진 않습니다. 각 프로젝트에서 사용하는 기술들이 다르고 다른 환경에서 동작할 수 있기 때문에, 서로 다른 Quality Profile을 통해 코드 품질을 유지해야할 때가 있습니다.

Quality Gates

오늘 당신의 프로젝트를 운영환경에 제공할 수 있나요?

Maintainability 등급이 낮아 Quality Gate를 통과하지 못한 케이스

품질 게이트는 프로젝트가 릴리즈 되기 위해 만족해야 할 매트릭 조건셋을 말합니다. 이를테면 새로운 blocker 이슈 없음, 신규 코드에 대한 Coverage가 80% 이상 과 같은 조건을 설정할 수 있습니다.

기본적으로 SonarQube의 품질 게이트는 Clean As You Code (개발자는 높은 표준을 유지하고 신규 코드에 대해 책임을 져야 한다) 관점에서 다음 규칙에 중점을 두고 있습니다.

  • Focus on New Code — 오래된 코드를 수정하는데 많은 노력을 기울이기보다 신규 코드의 측정 값에 집중한다
  • Personal Responsibility — 신규 코드에 대해서만 측정하므로 다른 사용자의 코드에 대한 책임이 없다
SonarQube에서 BUILT-IN 으로 제공하는 Sonar Way 품질 게이트의 메트릭 조건셋으로, New Code에 대해서만 조건이 설정되어 있습니다.

SonarQube 연동 가이드

1. 소스 저장소와 연동된 프로젝트 생성

  • SonarQube를 Github과 통합하기 위해, 연동용 GitHub App을 생성해 Github Organization에 설치합니다. App의 자세한 설정은 GitHub integration 을 참고 부탁드립니다.
  • SonarQube 내에서 새 프로젝트를 생성합니다. 연동되어 있는 Github을 통해 가이드 받거나 Manually로 직접 생성할 수 있습니다. 어떤 방법으로 생성하든 설정 값 중 프로젝트 키는 SonarQube 인스턴스 내에서 유니크해야 합니다.

2. CI 연동 및 소스코드 분석

가장 많이 사용되는 CI 툴인 Jenkins, Github Actions를 사용할 경우의 연동 가이드에 대해 간단하게 소개합니다.

Jenkins 사용시

1) Jenkins 멀티브랜치 파이프라인 잡 생성

모든 브랜치와 PR을 자동으로 분석하기 위해 멀티브랜치 파이프라인 잡 생성이 필요합니다. Jenkins 대시보드에서 잡을 생성할 때 Branch Source 로 Github 를 선택해 코드 저장소와 연동될 수 있게 합니다.

2) Github 웹훅 생성

코드 푸시가 발생하거나 PR이 생성되었을 때 Jenkins 잡을 트리거할 수 있도록 아래와 같이 설정한 웹훅을 생성합니다.

  • URL: ***JENKINS_SERVER_URL***/github-webhook/
  • 트리거 대상 이벤트: Pushes, Pull Requests

3) Jenkinsfile 생성

정적 분석에 사용되는 저장소에 사용하는 빌드 도구에 따른 Jenkinsfile을 생성합니다. 아래는 Gradle의 예제 코드입니다.

pipeline {
agent any
environment {
SONAR_SCANNER_HOME = tool 'sonar-scanner'
SONAR_PROJECT_KEY = 'your-project-key'
SONAR_ANALYSIS_VERSION = new Date().format('yyyy-MM', TimeZone.getTimeZone('Asia/Seoul'))
}
tools {
jdk 'openjdk11'
}
stages {
stage("Build") {
steps {
sh './gradlew clean build test'
}
}
stage("Analysis") {
steps {
withSonarQubeEnv("sonarqube-server") {
script {
if (env.CHANGE_ID) {
// pull request
sh """
${SONAR_SCANNER_HOME}/bin/sonar-scanner \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.pullrequest.key=${env.CHANGE_ID} \
-Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} \
-Dsonar.pullrequest.base=${env.CHANGE_TARGET} \
-Dsonar.sources=. \
-Dsonar.inclusions=**/src/main/** \
-Dsonar.tests=. \
-Dsonar.test.inclusions=**/src/test/** \
-Dsonar.junit.reportPaths=**/build/test-results \
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml
"""
} else {
sh """
${SONAR_SCANNER_HOME}/bin/sonar-scanner \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.branch.name=${env.BRANCH_NAME} \
-Dsonar.projectVersion=${SONAR_ANALYSIS_VERSION} \
-Dsonar.sources=. \
-Dsonar.inclusions=**/src/main/** \
-Dsonar.tests=. \
-Dsonar.junit.reportPaths=**/build/test-results \
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml \
-Dsonar.test.inclusions=**/src/test/**
"""
}
}
}
}
}
stage("Quality Gate") {
steps {
timeout(time: 1, unit: 'HOURS') {
waitForQualityGate abortPipeline: true
}
}
}
}
}

SonarScanner 실행시 사용한 주요 분석 옵션은 다음과 같습니다.

  • sonar.sources: 소스 파일을 포함하는 디렉토리 설정
  • sonar.tests: 테스트 소스 파일을 포함하는 디렉토리 설정
  • sonar.coverage.jacoco.xmlReportPaths: jacoco 커버리지 리포트 경로 설정
  • sonar.junit.reportPaths: XML 형식의 Surefire 리포트 파일 경로 설정

자세한 분석 옵션은 Analysis Parameters, Test Coverage, Narrowing the Focus 를 참고 바랍니다.

4) 분석 결과 확인

Jenkins 트리거를 통해 저장소에 변경이 있을 경우 분석을 진행합니다.

Jenkins 대시보드에서 분석 결과 확인이 가능합니다.
PR일 경우 코멘트에서도 결과 확인이 가능합니다.

Github Actions 사용시

1) Github Secret 생성

Github Actions 에서 사용할 다음 환경 변수를 저장소의 Secret에 정의합니다.

  • SONAR_TOKEN: SonarQube의 사용자를 식별하고 인증하는 토큰으로 사용자 계정 설정에서 생성할 수 있습니다.
  • SONAR_HOST_URL: SonarQube 호스트 URL을 입력합니다.

2) Workflow YAML 파일 생성

사용하는 빌드 도구에 맞춰 Workflow 파일을 .github/workflows/build.yml 경로에 생성합니다. 아래는 Maven의 예제 코드입니다.

name: Build
on:
push:
branches:
- master
- develop
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11 # SonarQube Scanner 실행 환경의 JDK 최소 요구 버전은 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar

3. 로컬 환경에 SonarLint 설정

SonarQube는 로컬 환경에서도 품질 이슈를 미리 감지하고 수정할 수 있도록 IDE 플러그인 SonarLint를 제공하고 있습니다. SonarLint는 Eclipse, IntelliJ IDEA, VS Code 등 다양한 IDE를 지원하며, 본 게시글에서는 IntelliJ IDEA 기준으로 공유합니다.

  1. IDE에 SonarLint 플러그인을 설치합니다.
  2. IDE 설정 (Preferences > SonarLint) 에서 SonarQube 서버를 등록합니다. Connection Type은 SonarQube이며 토큰은 사용자 계정 설정에서 생성할 수 있습니다.
  3. 프로젝트 설정 (Prefrences > SonarLint > Project Settings) 에서 위에서 등록한 SonarQube 서버와 프로젝트를 연결합니다.

NF 내 품질 분석과 활용 사례

Quality Gates in NF

Quality Gates League in Naver Financial

레거시 코드가 많은 프로젝트의 경우, SonarQube에서 기본적으로 제공하는 엄격한 기준의 품질 게이트를 적용한다면 코드 품질 개선에 대한 의욕이 되려 반감될 수 있습니다. 기존 코드에서 한 줄만 변경했을 뿐인데 파일 전체의 버그를 모두 개선해야 하는 경우가 생길 수 있으니까요. 🤔

따라서 프로젝트의 상황에 따라 점진적으로 코드 품질을 향상시킬 수 있도록 네이버 파이낸셜에서는 아래와 같이 단계별 리그를 개설하여 제공하고 있습니다.

  • Basic League: 기존에 서비스 되고 있던 프로젝트에서 초기 도입시 사용을 추천하는 단계
  • Pro League: 코드 품질을 점진적으로 높여나가고 싶은 프로젝트를 위한 단계
  • Champions League: 품질 최종 목표 단계
품질 게이트는 [Project Settings > Quality Gate] 에서 프로젝트 별로 설정할 수 있습니다

SonarQube 탐지 사례 in NF

1. SonarQube의 제안에 따라 대응 가능한 건

Rule에서 제기한 성능 혹은 정확성 등의 문제점에 공감하며 SonarQube에서 제안한 해결 방법이 적용 가능한 경우, 코드를 수정해 이슈를 해결하는 것이 바람직합니다.

tip: Extend Description 을 통해 설명을 보충할 수 있습니다

2. 리뷰를 통해 해결해야 하는 건

SonarQube에서 이슈 탐지가 되었다고 해서 반드시 제안대로 수정해야 하는 것은 아닙니다. 프로젝트 혹은 조직에 적용하기 적합하지 않다고 판단된 경우 코드를 수정하지 않고도 이슈를 해결할 수 있습니다.

Rule에 대해서는 공감하나 프로젝트의 컨텍스트 혹은 레거시 코드로 인해 피치 못하게 해결 방안 적용이 어렵다면 개별 이슈의 해결 상태를 변경할 수 있습니다. Resolve as won’t fix (오류이나 수정하지 않음) 를 선택해 일시적으로 해결 상태로 만들고 이후 해당 이슈에 대해 대응하고자 할 때 재오픈이 가능합니다.

Example: Too Many Fields 룰에 대한 팀 내 리뷰
Resolve as won’t fix 를 선택해 해결 상태로 만들 수 있습니다

Rule 에서 제기한 문제점에 공감이 어렵거나 조직 내의 정책과 적합하지 않은 경우에는 Rule 을 끄거나 심각도를 변경할 수 있습니다. 이 경우, 설명 확장 기능을 이용하여 SonarQube 사용자에게 Rule을 조정한 이유를 공유할 수 있습니다.

Example: 성능과 코드 가독성의 트레이드 오프를 고려하여 Rule의 심각도를 조정한 케이스

다음 “SonarQube 구성 및 운영” 편에서는

SonarQube 시스템 및 NaverFinancial 의 SonarQube 환경 구성과 운영 사례에 대해 소개할 예정입니다.

--

--