Skip to content

Code Convention

Rey edited this page Aug 11, 2022 · 13 revisions

코드 레이아웃

들여쓰기 및 띄어쓰기

  • 들여쓰기에는 탭(tab)을 사용합니다.

  • 콜론(:)을 쓸 때에는 콜론의 오른쪽에만 공백을 둡니다.

    let names: [String: String]?

줄바꿈

  • 함수 정의가 최대 길이를 초과하는 경우에는 아래와 같이 줄바꿈합니다.

    func collectionView(
      _ collectionView: UICollectionView,
      cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
      // doSomething()
    }
    
    func animationController(
      forPresented presented: UIViewController,
      presenting: UIViewController,
      source: UIViewController
    ) -> UIViewControllerAnimatedTransitioning? {
      // doSomething()
    }
  • 함수 호출 코드가 최대 길이를 초과하는 경우에는 파라미터 이름을 기준으로 줄바꿈합니다.

    let actionSheet = UIActionSheet(
      title: "정말 계정을 삭제하실 건가요?",
      delegate: self,
      cancelButtonTitle: "취소",
      destructiveButtonTitle: "삭제해주세요"
    )
  • if let 구문이 길 경우에는 줄바꿈하고 한 칸 들여씁니다.

    if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
      let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
      user.gender == .female {
      // ...
    }
  • guard let 구문이 길 경우에는 줄바꿈하고 한 칸 들여씁니다. else는 guard와 같은 들여쓰기를 적용합니다.

    guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
      let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
      user.gender == .female
    else {
      return
    }
  • 인자가 없는 함수의 연속적 호출은 개행을 하지 않는다.

    someThing.firstFunc().secondFunc().End()

    단, SwiftUI View의 Modifier는 예외를 둔다.

      ``` swift
      Text("This is Text")
          .bold()
          .padding()
      ```
    
  • 인자가 있는 함수의 호출과 후행 클로저가 있는 경우엔 개행을 한다. 단, 첫번째 호출되는 코드에 대해서는 예외를 둔다.

    // ex. 1
    someThing.singleFunc(with: parameter)
        .secondFunc(with: someValue)
        .End(to: endValue)
    
    // ex. 2
    someThing.map {
        $0 * 2
    }
    .reduce(0) { result, intValue in 
        result + intValue
    }
  • self, Self 키워드에는 개행을 하지 않고, 작성합니다.

    self.myProperty.setting()
    Self.self

최대 줄 길이

  • 한 줄은 최대 120자를 넘지 않아야 합니다.

    • 단, if let, guard let 구문은 최대 70자로 제한

    Xcode의 Preferences → Text Editing → Display의 'Page guide at column' 옵션을 활성화하고 120자로 설정하면 편리합니다.

Import

모듈 임포트는 알파벳 순으로 정렬합니다.

내장 프레임워크를 먼저 임포트하고, 빈 줄로 구분하여 서드파티 프레임워크를 임포트합니다.

네이밍

클래스

  • 클래스 이름에는 UpperCamelCase를 사용합니다.
  • 클래스 이름에는 접두사를 붙이지 않습니다.

함수

  • 함수 이름에는 lowerCamelCase를 사용합니다.

  • 함수 이름 앞에는 되도록이면 get을 붙이지 않습니다.

    • 좋은 예:

      func name(for user: User) -> String?
    • 나쁜 예:

      func getName(for user: User) -> String?
  • Action 함수의 네이밍은 '주어 + 동사 + 목적어' 형태를 사용합니다.

    • Tap(눌렀다 뗌)*은 UIControlEvents의 .touchUpInside에 대응하고, *Press(누름)*는 .touchDown에 대응합니다.
    • will~은 특정 행위가 일어나기 직전이고, did~는 특정 행위가 일어난 직후입니다.
    • should~는 일반적으로 Bool을 반환하는 함수에 사용됩니다.

    좋은 예:

    func backButtonDidTap() {
      // ...
    }

    나쁜 예:

    func back() {
      // ...
    }
    
    func pressBack() {
      // ...
    }

변수

  • 변수 이름에는 lowerCamelCase를 사용합니다.

상수

  • 상수 이름에는 lowerCamelCase를 사용합니다.

    좋은 예:

    let maximumNumberOfLines = 3

    나쁜 예:

    let MaximumNumberOfLines = 3
    let MAX_LINES = 3

열거형

  • enum의 각 case에는 lowerCamelCase를 사용합니다.

    좋은 예:

    enum Result {
      case .success
      case .failure
    }

    나쁜 예:

    enum Result {
      case .Success
      case .Failure
    }

약어

  • 약어로 시작하는 경우 소문자로 표기하고, 그 외의 경우에는 항상 대문자로 표기합니다.

    좋은 예:

      let userID: Int?
      let html: String?
      let websiteURL: URL?
      let urlString: String?

    나쁜 예:

      let userId: Int?
      let HTML: String?
      let websiteUrl: NSURL?
      let URLString: String?

개념적 네이밍

  • View, ViewModel에 해당하는 개념은 끝에 View, ViewModel을 붙인다.

    ex)
    class SoundViewModel
    class SettingView
  • Model에 해당하는 개념은 Model을 붙이지 않고, 개념 그대로를 이름으로 사용한다.

    ex)
    struct Sound
    struct PlayList
  • Singleton 객체는 Manager, Controller, Service 중 하나를 붙인다.

    ex)
    enum NetworkManager
    struct AppController
    struct BackgroundSoundController
    class NotificationService

클래스와 구조체

  • 클래스와 구조체 내부에서는 self를 명시적으로 사용합니다.
  • 내장 라이브러리가 존재한다면, 불필요한 초기화 함수를 재구성 하지 않습니다.
let frame = CGRect(x: 0, y: 0, width: 100, height: 100)

타입

  • Array<T>와 Dictionary<T: U> 보다는 [T][T: U]를 사용합니다.

    // 좋은 예:
    var messages: [String]?
    var names: [Int: String]?
    
    // 나쁜 예:
    var messages: Array<String>?
    var names: Dictionary<Int, String>?
  • Int, Float, String, Bool 타입은 변수/상수 선언 시, 명시하지 않습니다.

    var isShowAnyView = false
    var referenceCount = 0

ViewBuilder

  • ViewBuilder의 이름은 대문자로 시작합니다.

  • 하나의 View안에서 사용되는 ViewBuilder라면 같은 파일 안에서 extension을 사용하고, MARK를 남깁니다.

    struct SomeView: View {
        // Code
    }
    
    // MARK: - ViewBuilder
    extension SomeView {
        @ViewBuilder
        func BuildView() -> some View { // 대문자로 시작
            // Code
        }
    }
    
  • 여러 View에서 사용되는 ViewBuilder라면 ViewBuilder가 아닌 View로 정의하여 파일을 분리합니다.

클로저

  • 파라미터와 리턴 타입이 없는 Closure 정의시에는 () -> Void를 사용합니다.

    let completionBlock: (() -> Void)?
  • Closure 작성 시 줄 수에 따라 개행하여 코드 블럭을 작성합니다.

    • 1줄 표현식의 경우, 선택적으로 개행하여 작성합니다.

      단, 클로져 인자 라벨은 줄 수에 포함하지 않습니다.

    oneLineIntArray.map {
        $0 * 2
    }
    oneLineIntArray.map { $0 * 2 }
    oneLineIntArray.map { age in age * 2 }
    • 2줄 이상의 표현식의 경우, 반드시 개행하여 작성합니다.
    intArray.map {
        let age = $0 * 2
        return Person(age: age)
    }
  • Closure 인자 작성 시, 가능한 경우 타입 정의를 생략합니다.

    intArray.map {
      $0 * 2
    }
    • 인자 라벨의 정의는 필수가 아닌 권장사항이지만, Closure 내의 코드블럭이 2줄 이상인 경우 반드시 라벨을 사용합니다.

      intArray.map { intValue in
          let isOdd = (intValue % 2 == 1)
          return isOdd
      }
  • Closure 정의시 파라미터에는 괄호를 사용하지 않습니다.

    { operation, responseObject in
      // doSomething()
    }
  • Closure 호출시 또 다른 유일한 Closure를 마지막 파라미터로 받는 경우(후행 클로저), 파라미터 이름을 생략합니다.

    좋은 예:

    UIView.animate(withDuration: 0.5) {
      // doSomething()
    }

    단, 파라미터에 후행 클로저가 2개 이상 존재하는 경우에는 아래 두 가지 조건을 준수합니다.

    • 모든 클로저는 축약 표현을 사용합니다.
    • 두번째 클로저부터는 앞에 있는 클로저의 끝} 에서 줄바꿈을 하지 않고 라벨을 표시합니다.
    // UIView.animate(withDuration: TimeInterval, animations: () -> Void, completion: ((Bool) -> Void)?)
    UIView.animate(withDuration: 0.25) {
        // doSomething() about animations
      } completion: { finished in
        // doSomething() 
      }
    
    // SwiftUI Button
    Button {
    
    } label: {
    
    }

프로그래밍 권장사항

  • 상수를 정의할 때에는 enum를 만들어 비슷한 상수끼리 모아둡니다. 재사용성과 유지보수 측면에서 큰 향상을 가져옵니다. struct 대신 enum을 사용하는 이유는, 생성자가 제공되지 않는 자료형을 사용하기 위해서입니다.

  • 더이상 상속이 발생하지 않는 클래스는 항상 final 키워드로 선언합니다.

  • 프로토콜을 적용할 때에는 extension을 만들어서 관련된 메서드를 모아둡니다.

    좋은 예

    final class MyViewController: UIViewController {
      // ...
    }
    
    // MARK: UITableViewDataSource
    
    extension MyViewController: UITableViewDataSource {
      // ...
    }
    
    // MARK: UITableViewDelegate
    
    extension MyViewController: UITableViewDelegate {
      // ...
    }

    나쁜 예

    final class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
      // ...
    }

Reference

[GitHub - StyleShare/swift-style-guide: StyleShare에서 작성한 Swift 한국어 스타일 가이드]

[GitHub - DeveloperAcademy-POSTECH/swift-style-guide: 아카데미에서 사용 하는 코딩 스타일 가이드 입니다.]