[Popcorn] Swift Lint 적용기

Minw·2025년 2월 10일

프로젝트-팝콘

목록 보기
2/5
post-thumbnail

도입 이유

Popcorn-iOS 팀은 활발한 코드 리뷰를 목표로 세웠습니다. 따라서 원활한 코드 리뷰를 위해 일관된 코딩 컨벤션을 유지할 필요가 있었습니다.

코딩 컨벤션을 정함으로써 우려되는 문제점이 있었습니다.

  • PR을 올린 후 뒤늦게 “이런 부분 컨벤션이랑 안맞으니 수정해주세요~”라고 멘트를 날리면 수정할 때 까지 기다리는 시간이 소요됩니다.
  • 매번 컨벤션에 맞는 코드인지 확인해야하는 수고로움이 있습니다.

이러한 문제점을 해결하기 위해 방법들을 조사하던 중 SwiftLint라는 도구를 발견하게 되었습니다.

SwiftLint란?

https://github.com/realm/SwiftLint?tab=readme-ov-file
https://yagom.net/forums/topic/swift-lint-%EC%8D%A8%EB%B3%B4%EA%B8%B0/
https://hudi.blog/static-analysis/

Lint는 정적 분석 도구를 의미합니다.
SwiftLint는 Swift 코드의 스타일과 규칙을 검사하고 관리해주는 Linting 도구입니다.

코드 분석 방식에는 동적 분석(dynamic analysis)정적 분석(static analysis)가 존재합니다.

1. 동적 분석
런타임 환경에서 수행되는 분석으로 테스트 코드를 작성하여, 현재 코드가 테스트를 모두 통과하는지 확인합니다.

그러나 동적 분석만으로 코드의 모든 문제를 발견할 수 없습니다.
만약 나눗셈을 수행하는 코드에 잘못된 입력이 들어와 0으로 나누어 버린다면 앱은 크래시가 발생하게 됩니다.

따라서 발생할 수 있는 모든 경우에 대해 테스트 코드를 작성하지 않았을 경우, 동적 분석으로는 문제를 발견할 수 없다는 단점이 있습니다.

또한 중복 코드너무 긴 메서드처럼 실행에는 영향을 미치지 않는 코드의 경우 동적 분석으로 잡을 수 없기에 정적 분석 도구가 필요한 것입니다!

2. 정적 분석
정적 분석은 소스 코드의 실행 없이 정적으로 프로그램의 문제를 찾는 과정을 의미합니다. 런타임 시점에서 실행되는 동적 분석 도구와 달리, 정적 분석 도구는 비런타임 환경에서 수행됩니다.

정적 분석을 사용하는 이유에 대해 자세히 살펴보면

잠재적으로 버그가 발생할 수 있는 코드, 안티 패턴, 컨벤션 위반 여부, 오타, 사용되지 않는 코드 등의 코드 스멜이라고 불리는 문제들을 쉽게 발견할 수 있습니다.

이러한 코드 스멜은 실제 사례로부터 규칙화가 되어 있기 때문에 정적 분석 도구를 이용하여 발견할 수 있습니다.

SwiftLint Plugins

Popcorn 팀은 정적 분석 도구로서 SwiftLint Plugins을 적용하였습니다.

SwiftLint Plugins는 SwiftLint 바이너리를 종속성으로 포함하므로, SwiftLint를 소스코드로부터 다시 빌드할 필요가 없습니다. 따라서 빌드 시간이 단축된다는 장점이 있습니다.

또한 SwiftLint 플러그인만 제공하므로, 필요한 플러그인만 가져올 수 있어 프로젝트에 불필요한 파일을 추가하지 않기 때문에 SwiftLint Plugins를 적용하였습니다.

Note - SwiftLint Plugins가 아닌 SwiftLint 설치시 추가되는 패키지들

설치 방법

https://tngusmiso.tistory.com/59
https://www.youtube.com/watch?v=4xMUxfJKTVM&t=388s
https://medium.com/@golddol2003/cocoapods-vs-spm-ba7b7478236b

SwiftLint를 설치하는 방법으로는 SPM, brew, CocoaPods등 여러 방법이 있지만, SPM을 사용하여 설치하였습니다.

SPM은 애플에서 만든 First-party 의존성 관리 도구로서, 추가적인 설치나 설정 없이 Xcode에서 쉽게 패키지를 추가하고 관리할 수 있습니다. 또한 CocoaPods와 달리, Podfile과 같은 별도의 파일 관리가 필요하지 않으며, Package.swift 파일 하나로 프로젝트의 모든 의존성을 관리할 수 있다는 장점이 있습니다.

1. SPM을 이용해 SwiftLint Plugins 설치

SPM을 이용해 패키지를 설치할 때는 아래 사진과 같이 Xcode 내부에서 간단하게 설치 할 수 있습니다.

2. Run Script 추가

Run Script란?
Xcode에서 빌드 프로세스 중 특정 명령어를 실행하도록 설정하는 기능입니다.
SwiftLint를 Xcode 프로젝트에 통합하여 IDE 상에 경고나 에러를 표시할 수 있는데, Run Script를 추가하여 빌드 과정에 통합시켜주면 됩니다.

현재 애플 실리콘 기반 맥이므로, SwiftLint Plugins README를 참고하여 아래와 같은 스크립트를 추가하였습니다.

이 스크립트는 SwiftLint가 설치되어있는지 확인하고, 설치되어 있다면 SwiftLint를 실행 후 코드 스타일 검사를 수행합니다.

if [[ "$(uname -m)" == arm64 ]]; then
    export PATH="/opt/homebrew/bin:$PATH"
fi

if which swiftlint > /dev/null; then
  swiftlint
else
  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi

Note - Swift Lint Plugins README

3. YAML 파일 추가

SwiftLint에서 기본적으로 제공하는 규칙이 있지만, 팀의 컨벤션에 맞춰 수정하기 위해서는 YAML 파일을 추가하여 커스텀 할 수 있습니다.

전체 룰은 해당 링크에서 확인할 수 있습니다.

위 링크의 Default Rule에서 제외할 규칙을 스크립트의 disabled_rules에 넣어주고, Opt-in Rules에서 선택적으로 적용할 규칙들을 opt_in_rules에 넣어주면 됩니다.

커스텀 규칙을 만들 때 정규식을 이용해 새로운 규칙을 생성할 수도 있습니다.

룰 적용 여부를 설정하는 주요 파라미터로는 아래와 같습니다.

  • disabled_rules: 기본 활성화된 룰 중에서 비활성화 할 룰들을 지정.
  • opt_in_rules: 기본 룰이 아닌 룰들을 활성화.
  • whitelist_rules: 지정한 룰들만 활성화되도록 화이트리스트로 지정. disabled_rulesopt_in_rules와는 같이 사용할 수 없음.
  • included: 포함시키려는 룰을 여기에 작성.
  • excluded: 린트 과정에서 무시할 파일 경로를 작성.

아래는 SwiftLint 공식문서에서 예시로 제공한 룰 설정 파일입니다.

disabled_rules: # 실행에서 제외할 룰 식별자들
  - colon
  - comma
  - control_statement
opt_in_rules: # 일부 룰은 옵트 인 형태로 제공
  - empty_count
  - missing_docs
  # 사용 가능한 모든 룰은 swiftlint rules 명령으로 확인 가능
included: # 린트 과정에 포함할 파일 경로. 이 항목이 존재하면 `--path`는 무시됨
  - Source
excluded: # 린트 과정에서 무시할 파일 경로. `included`보다 우선순위 높음
  - Carthage
  - Pods
  - Source/ExcludedFolder
  - Source/ExcludedFile.swift

# 설정 가능한 룰은 이 설정 파일에서 커스터마이징 가능
# 경고나 에러 중 하나를 발생시키는 룰은 위반 수준을 설정 가능
force_cast: warning # 암시적으로 지정
force_try:
  severity: warning # 명시적으로 지정
# 경고 및 에러 둘 다 존재하는 룰의 경우 값을 하나만 지정하면 암시적으로 경고 수준에 설정됨
line_length: 110
# 값을 나열해서 암시적으로 양쪽 다 지정할 수 있음
type_body_length:
  - 300 # 경고
  - 400 # 에러
# 둘 다 명시적으로 지정할 수도 있음
file_length:
  warning: 500
  error: 1200
# 네이밍 룰은 경고/에러에 min_length와 max_length를 각각 설정 가능
# 제외할 이름을 설정할 수 있음
type_name:
  min_length: 4 # 경고에만 적용됨
  max_length: # 경고와 에러 둘 다 적용
    warning: 40
    error: 50
  excluded: iPhone # 제외할 문자열 값 사용
identifier_name:
  min_length: # min_length에서
    error: 4 # 에러만 적용
  excluded: # 제외할 문자열 목록 사용
    - id
    - URL
    - GlobalAPIKey
reporter: "xcode" # 보고 유형 (xcode, json, csv, checkstyle, junit, html, emoji, markdown)

아래는 커스텀 룰을 지정하는 예시입니다.

custom_rules:
  pirates_beat_ninjas: # 룰 식별자
    included: ".*.swift" # 린트 실행시 포함할 경로를 정의하는 정규표현식. 선택 가능.
    name: "Pirates Beat Ninjas" # 룰 이름. 선택 가능.
    regex: "([n,N]inja)" # 패턴 매칭
    match_kinds: # 매칭할 SyntaxKinds. 선택 가능.
      - comment
      - identifier
    message: "Pirates are better than ninjas." # 위반 메시지. 선택 가능.
    severity: error # 위반 수준. 선택 가능.
  no_hiding_in_strings:
    regex: "([n,N]inja)"
    match_kinds: string

Note - 적용 결과 예시

Popcorn 팀이 적용한 스크립트

YAML 파일의 문법에 맞춰서 작성하기 위해 https://www.yamllint.com/를 이용할수도 있습니다.

Note
블로그나 공식문서를 찾아보면 스크립트의 includedSource라고 적혀있는데, 본인의 프로젝트 이름을 작성해야 린트가 제대로 적용됩니다.

disabled_rules: # 실행에서 제외할 룰 식별자들
  - redundant_optional_initialization # 옵셔널 변수를 명시적으로 nil로 초기화하는 중복 제거 규칙 제외
  - force_cast # 강제 형변환 사용 제한
  - force_try # 예외 처리 없이 try! 사용 제한
  - function_body_length # 함수 본문의 행 길이 제한
  - statement_position # 조건문, 반복문 등의 코드 블록 위치를 일정하게 유지하는 규칙 제외
  - closure_end_indentation # 클로저 끝 부분 들여쓰기 규칙 제외
  - file_length # 파일 길이 규칙 제외 (400줄 이상 허용)
  - function_parameter_count # 함수 파라미터 개수 제한 규칙 제외 (5개 초과 허용)
  - identifier_name # 식별자 이름 길이 제한 규칙 제외
  - large_tuple # 튜플 멤버 개수 제한 규칙 제외 (2개 초과 허용)

opt_in_rules: # 선택적으로 추가하는 규칙
  - empty_count # .count == 0보다 isEmpty 사용 권장
  - empty_string # == ""보다 isEmpty 사용 권장
  - sorted_imports # import 구문 알파벳 순서로 정렬
  - vertical_whitespace # 불필요한 개행 줄 제한

analyzer_rules:  # 코드 분석을 위한 규칙
  - unused_import # 사용되지 않는 import 구문 경고
  - unused_declaration # 사용되지 않는 선언 경고
included:
  - Popcorn-iOS    # 프로젝트 이름
excluded: # SwiftLint 검사에서 제외되는 경로
  - Carthage
  - Pods
  - Popcorn-iOSTests
  - Popcorn-iOSUITests

# 커스텀 최대 행 길이 규칙
line_length:
  warning: 120 # 120자를 초과할 시 경고
  ignores_urls: true # URL을 최대 길이 규칙에서 제외
  ignores_comments: true # 주석은 최대 길이 규칙에서 제외
  ignores_interpolated_strings: true # 문자열 보간법 사용 시 최대 길이 규칙 제외

# 커스텀 공백 규칙
trailing_whitespace:
  ignores_empty_lines: false # 빈 줄에서 공백 허용 제외

reporter: "xcode" # 리포트 형식 설정 (xcode 형식)

적용하면서 마주친 에러

https://forums.developer.apple.com/forums/thread/740567

1. Plug-in ended with uncaught signal: 5)

설치 후 빌드를 해보니 위와 같은 에러가 발생하였습니다.

원인

문제의 원인은 Xcdoe의 기본 개발자 도구 경로 설정이 제대로 되지 않았기 때문입니다.

SwiftLint와 같은 도구들은 Xcdoe의 개발자 도구 경로를 참조하여 필요한 파일과 리소스를 찾는데, 이 경로가 잘못되었거나 올바르게 설정되지 않았을 경우, 해당 도구가 제대로 작동하지 않게 되는것입니다.

해결책

터미널에서 아래의 명령어를 통해 Xcode의 기본 개발자 도구 경로를 변경해주면 됩니다.

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

린트가 정상적으로 동작하는 것을 확인할 수 있습니다.

참고) Xcode 개발자 도구 경로

Xcode 개발자 도구 경로란 Xcode와 관련된 command line tool 도구들이 참조하는 기본 위치를 의미합니다.

SwiftLint 같은 플러그인과, xcodebuild 등의 명령줄 도구가 필요한 파일을 올바르게 찾도록 돕습니다.

xcode-select 명령어를 통해 Xcode가 설치된 위치를 개발자 도구의 경로로 지정할 수 있습니다.

특히, 여러 버전의 Xcode를 설치할 때, 프로젝트나 도구가 사용할 특정 Xcode 버전을 지정할 때 유용합니다.

2. Build input file cannot be found

https://vanillacreamdonut.tistory.com/241

이 에러는 린트와 관련된 에러는 아니지만 셋팅하면서 발생하였기에 간단하게 작성했습니다.

원인

프로젝트 초기 셋팅하면서 폴더링을 변경하며 info.plist 파일의 위치를 옮겼습니다. 이때Build Settingsinfo.plist 경로와 실제 info.plist 파일의 경로가 달라서 발생한 에러입니다.

해결책


따라서 사진에 표시된 경로를 실제 경로로 변경해주면 해결 됩니다.

0개의 댓글