Skip to content

Latest commit

 

History

History
1014 lines (785 loc) · 19.5 KB

SwiftStyleGuide.md

File metadata and controls

1014 lines (785 loc) · 19.5 KB

Swift Style Guide

레오가 제안한 아이디어로 우리는 스터디 기간 동안 코드 컨벤션을 도입하려고 합니다. 스터디 기간 동안 쓰이지 않을 것으로 판단되는 부분은 제외하고 아래 두 가지 출처에서 참고한 내용을 통합하여 사용할 것입니다.

이러한 코드 컨벤션 도입은 프로젝트의 코드 일관성과 가독성을 향상시키며 협업과 유지보수를 더욱 원활하게 만들 것입니다.


Code Formatting

[ import ]

모듈 임포트는 알파벳 순으로 정렬합니다. 내장 프레임워크를 먼저 임포트하고, 빈 줄로 구분해 3rd-party 프레임워크를 임포트 합니다.

✔️ Preferred

import UIKit 
    
import SwiftyColor 
import SwiftyImage 
import Then 
import URLNavigator

❌ Not Preferred

import SwiftyColor 
import SwiftyImage 
import Then 
import UIKit 
import URLNavigator

파일이 필요로하는 최소의 모듈만 임포트 합니다. 예를들어, Foundation으로 충분하면 UIKit은 임포트 하지 않습니다.

✔️ Preferred

import UIKit 

var view: UIView 
var deviceModels: [String]
import Foundation 
var deviceModels: [String]

❌ Not Preferred

import UIKit 
import Foundation 

var view: UIView 
var deviceModels: [String]

목차로 이동 🔺

[ 들여쓰기 및 띄어쓰기 ]

탭을 눌렀을 때 4개의 space를 사용합니다.

✔️ Preferred

class ViewController: UIViewController { 

	private lazy var scrollView: UIScrollView = { 
		let scrollView = UIScrollView() 
		return scrollView 
	}() 
	
}

❌ Not Preferred

class ViewController: UIViewController { 

  private lazy var scrollView: UIScrollView = { 
    let scrollView = UIScrollView() 
    return scrollView 
  }() 
	
}

콜론( : )을 쓸 때에는 콜론의 오른쪽에만 공백을 둡니다. 단, 삼항연산자의 경우에는 콜론 앞 뒤로 공백을 둡니다.

✔️ Preferred

class ViewController: UIViewController { 

	let names: [String: String]? 
	let someDictionary: [String: AnyObject] = [ 
		"Something 1": true, 
		"Something 2": false 
	] 

	func someFunction(a: String, b: String) { 
		// code 
	} 

} 

someFunction(a: "iNeptune", b: "Code Adventurers") 

extension ViewController: UITableViewDataSource { 
	// code 
}
let isHappy = true 
let feeling = isHappy ? "행복" : "불행"

❌ Not Preferred

class ViewController : UIViewController { 

	let names: [String : String]? 
	let someDictionary : [String : AnyObject] = [ 
		"Something 1" : true, 
		"Something 2" : false 
	] 

	func someFunction(a : String, b : String) { 
		// code 
	} 

} 

someFunction(a : "iNeptune", b : "Code Adventurers") 

extension ViewController : UITableViewDataSource { 
	// code 
}
let isHappy = true 
let feeling = isHappy ? "행복": "불행"
let names:[String:String]?

일반적으로 콤마(,) 뒤에는 공백을 추가합니다.

✔️ Preferred

let myArray = [1, 2, 3, 4, 5]

❌ Not Preferred

let myArray = [1,2,3,4,5]

연산자 앞뒤로 공백을 추가합니다.

✔️ Preferred

let myValue = 20 + (30 / 2) * 3

❌ Not Preferred

let myValue = 20+(30/2)*3

화살표 양쪽에 빈 공백을 추가합니다.

✔️ Preferred

func doSomething() -> String { 
	// ... 
}
func doSomething(completion: () -> Void) { 
	// ... 
}

❌ Not Preferred

func doSomething()->String { 
	// ... 
}
func doSomething(completion: ()->Void) { 
	// ... 
}

불필요한 괄호는 생략합니다.

✔️ Preferred

if userCount > 0 { ... } 
switch someValue { ... } 
let evens = userCounts.filter { number in number % 2 == 0 } 
let squares = userCounts.map { $0 * $0 }

❌ Not Preferred

if (userCount > 0) { ... } 
switch (someValue) { ... } 
let evens = userCounts.filter { (number) in number % 2 == 0 } 
let squares = userCounts.map() { $0 * $0 }

목차로 이동 🔺

[ 줄바꿈 ]

클래스 블럭과 클래스 내부 코드 사이는 공백으로 구분합니다.

✔️ Preferred

class ViewController: UIViewController { 

	private lazy var scrollView: UIScrollView = { 
		let scrollView = UIScrollView() 
		return scrollView 
	}() 
	
}

❌ Not Preferred

class ViewController: UIViewController { 
	private lazy var scrollView: UIScrollView = { 
		let scrollView = UIScrollView() 
		return scrollView 
	}() 
}

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

✔️ Preferred

func collectionView(
	_ collectionView: UICollectionView, 
	cellForItemAt indexPath: IndexPath 
) -> UICollectionViewCell { 
		// doSomething() 
} 

func animationController(
	forPresented presented: UIViewController, 
	presenting: UIViewController, 
	source: UIViewController 
) -> UIViewControllerAnimatedTransitioning? { 
	// doSomething() 
}

❌ Not Preferred

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 
	// doSomething() 
} 

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
	// doSomething() 
}

함수를 호출하는 코드가 최대 길이를 초과하는 경우에는 파라미터 이름을 기준으로 줄바꿈합니다. 단, 파라미터에 클로저가 2개 이상 존재하는 경우에는 무조건 내려쓰기합니다.

✔️ Preferred

let actionSheet = UIActionSheet(
	title: "정말 계정을 삭제하실 건가요?", 
	delegate: self, 
	cancelButtonTitle: "취소", 
	destructiveButtonTitle: "삭제해주세요"
)
UIView.animate(
	withDuration: 0.25, 
	animations: { 
		// doSomething() 
	}, 
	completion: { finished in 
		// doSomething() 
	} 
)

❌ Not Preferred

let actionSheet = UIActionSheet(title: "정말 계정을 삭제하실 건가요?", delegate: self, cancelButtonTitle: "취소", destructiveButtonTitle: "삭제해주세요")

if let 구문이 길 경우에는 줄바꿈하고 한 칸 들여씁니다.

✔️ Preferred

if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(), 
   let name = user.veryLongFunctionNameWhichReturnsOptionalName(), 
	   user.gender == .female { 
		   // ... 
}

❌ Not Preferred

if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(), let name = user.veryLongFunctionNameWhichReturnsOptionalName(), user.gender == .female { 
	// ... 
}

guard let 구문이 길 경우에는 줄바꿈하고 한 칸 들여씁니다. else는 guard와 같은 들여쓰기를 적용합니다.

✔️ Preferred

guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
      let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
          user.gender == .female
else {
    return
}

❌ Not Preferred

guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(), let name = user.veryLongFunctionNameWhichReturnsOptionalName(), user.gender == .female else {
    return
}

목차로 이동 🔺

[ 최대 줄 길이 ]

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


목차로 이동 🔺


Naming

[ 클래스와 구조체 ]

클래스와 구조체의 이름에는 UpperCamelCase를 사용합니다.

✔️ Preferred

class SomeClass {
    // class definition goes here
}

struct SomeStructure {
    // structure definition goes here
}

❌ Not Preferred

class someClass {
    // class definition goes here
}

struct someStructure {
    // structure definition goes here
}

목차로 이동 🔺

[ 함수 ]

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

✔️ Preferred

func doSomething(a: String) -> String?

❌ Not Preferred

func DoSomething(b: String) -> String?

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

✔️ Preferred

func name(for user: User) -> String?

❌ Not Preferred

func getName(for user: User) -> String?

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

Action 함수의 네이밍은 '주어 + 동사 + 목적어' 형태를 사용합니다.

✔️ Preferred

func backButtonDidTap() {
    // ...
}

❌ Not Preferred

func back() {
    // ...
}

func pressBack() {
    // ...
}

목차로 이동 🔺

[ 변수와 상수 ]

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

✔️ Preferred

let maximumNumberOfLines = 3

❌ Not Preferred

let MaximumNumberOfLines = 3
let MAX_LINES = 3

목차로 이동 🔺

[ 열거형 ]

enum의 이름에는 UpperCamelCase를 사용하고 enum의 각 case에는 lowerCamelCase를 사용합니다.

✔️ Preferred

enum Result {
    case .success
    case .failure
}

❌ Not Preferred

enum Result {
    case .Success
    case .Failure
}

enum result {
    case .Success
    case .Failure
}

목차로 이동 🔺

[ 프로토콜 ]

프로토콜의 이름에는 UpperCamelCase를 사용합니다. 구조체나 클래스에서 프로토콜을 채택할 때는 콜론과 빈칸을 넣어 구분하여 명시합니다. extension을 통해 채택할 때도 동일하게 적용됩니다.

✔️ Preferred

protocol SomeProtocol {
    // protocol definition goes here
}

struct SomeStructure: SomeProtocol, AnotherProtocol {
    // structure definition goes here
}

class SomeClass: SomeSuperclass, SomeProtocol, AnotherProtocol {
    // class definition goes here
}

extension UIViewController: SomeProtocol, AnotherProtocol {
    // doSomething()
}

❌ Not Preferred

protocol someProtocol {
    // protocol definition goes here
}

struct some_Structure:SomeProtocol, AnotherProtocol {
    // structure definition goes here
}

class Some_Class:SomeSuperclass, SomeProtocol, AnotherProtocol {
    // class definition goes here
}

extension UIViewController:SomeProtocol, AnotherProtocol {
    // doSomething()
}

목차로 이동 🔺

[ 약어 ]

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

✔️ Preferred

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

❌ Not Preferred

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

목차로 이동 🔺

[ 일반 ]

일반적인 부분이 앞에두고 구체적인 부분을 뒤에 둡니다.

✔️ Preferred

let titleMarginRight: CGFloat
let titleMarginLeft: CGFloat
let bodyMarginRight: CGFloat
let bodyMarginLeft: CGFloat

❌ Not Preferred

let rightTitleMargin: CGFloat
let leftTitleMargin: CGFloat
let bodyRightMargin: CGFloat
let bodyLeftMargin: CGFloat

생략시 사용이 모호해지는 타입은 이름에 타입에 대한 힌트를 포함시킵니다.

✔️ Preferred

let titleText: String
let cancelButton: UIButton

❌ Not Preferred

let title: String
let cancel: UIButton

목차로 이동 🔺



Code Style

[ 클로저 ]

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

✔️ Preferred

let completionBlock: (() -> Void)?

❌ Not Preferred

let completionBlock: (() -> ())?
let completionBlock: ((Void) -> (Void))?

클로저 정의시 파라미터에는 괄호를 사용하지 않습니다.

✔️ Preferred

{ operation, responseObject in
    // doSomething()
}

❌ Not Preferred

{ (operation, responseObject) in
    // doSomething()
}

클로저 정의시 가능한 경우 타입 정의를 생략합니다.

✔️ Preferred

completion: { finished in
    // doSomething()
}

❌ Not Preferred

completion: { (finished: Bool) -> Void in
    // doSomething()
}

클로저 호출시 또 다른 유일한 클로저를 마지막 파라미터로 받는 경우, 파라미터 이름을 생략합니다.

✔️ Preferred

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

❌ Not Preferred

UIView.animate(withDuration: 0.5, animations: { () -> Void in
    // doSomething()
})

사용하지 않는 파라미터는 _를 사용해 표시합니다.

✔️ Preferred

someAsyncThing() { _, _, argument3 in
    print(argument3)
}

❌ Not Preferred

someAsyncThing() { argument1, argument2, argument3 in
    print(argument3)
}

한 줄 클로저는 반드시 각 괄호 양쪽을 공백을 추가해야 합니다.

✔️ Preferred

let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }

❌ Not Preferred

let evenSquares = numbers.filter {$0 % 2 == 0}.map { $0 * $0 }

목차로 이동 🔺

[ 클래스와 구조체 ]

구조체를 생성할 때는 Swift 구조체 생성자를 사용합니다.

✔️ Preferred

let frame = CGRect(x: 0, y: 0, width: 100, height: 100)

❌ Not Preferred

let frame = CGRectMake(0, 0, 100, 100)

목차로 이동 🔺

[ 타입 ]

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

✔️ Preferred

var messages: [String]?
var names: [Int: String]?

❌ Not Preferred

var messages: Array<String>?
var names: Dictionary<Int, String>?

목차로 이동 🔺

[ 타입추론 사용 ]

컴파일러가 문맥속에서 타입을 추론할 수 있으면 더 간결한 코드를 위해 타입을 생략합니다.

✔️ Preferred

let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)

❌ Not Preferred

let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)

목차로 이동 🔺

[ self ]

문법의 모호함을 제거하기 위해 언어에서 필수로 요구하지 않는 이상 self는 사용하지 않습니다.

✔️ Preferred

final class Listing {
    private let isFamilyFriendly: Bool
    private var capacity: Int
  
    init(capacity: Int, allowsPets: Bool) {
         Preferred 
        self.capacity = capacity
        isFamilyFriendly = !allowsPets
    }

    private func increaseCapacity(by amount: Int) {
         Preferred 
        capacity += amount

         Preferred 
        save()
    }
}

❌ Not Preferred

final class Listing {
    private let isFamilyFriendly: Bool
    private var capacity: Int
  
    init(capacity: Int, allowsPets: Bool) {
        ⛔️ Not Preferred 
        self.capacity = capacity
        self.isFamilyFriendly = !allowsPets
    }

    private func increaseCapacity(by amount: Int) {
        ⛔️ Not Preferred 
        self.capacity += amount

        ⛔️ Not Preferred 
        self.save()
  }
}

목차로 이동 🔺

[ 튜플 ]

튜플의 맴버에는 명확성을 위해 이름을 붙여줍니다. (만약 필드가 3개를 넘는 경우 struct를 사용을 고려해보는 것을 권장합니다.)

✔️ Preferred

func whatever() -> (x: Int, y: Int) {
    return (x: 4, y: 4)
}

❌ Not Preferred

func whatever() -> (Int, Int) {
    return (4, 4)
}

목차로 이동 🔺

[ final ]

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

✔️ Preferred

final class ViewController: UIViewController {
    // ...
}

❌ Not Preferred

class ViewController: UIViewController {
    // ...
}

목차로 이동 🔺

[ 프로토콜 extension ]

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

✔️ Preferred

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

❌ Not Preferred

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

생략시 사용이 모호해지는 타입은 이름에 타입에 대한 힌트를 포함시킵니다.

✔️ Preferred

let titleText: String
let cancelButton: UIButton

❌ Not Preferred

let title: String
let cancel: UIButton

목차로 이동 🔺

[ 사용하지 않는 코드 ]

Xcode가 자동으로 생성한 템플릿을 포함한 사용하지 않는 코드는 placeholder 코멘트를 포함해 모두 제거합니다.

✔️ Preferred

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return Database.contacts.count
}

❌ Not Preferred

override func didReceiveMemoryWarning() {
  super.didReceiveMemoryWarning()
  // Dispose of any resources that can be recreated.
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  // #warning Incomplete implementation, return the number of rows
  return Database.contacts.count
}

목차로 이동 🔺

[ 주석 ]

///를 사용해서 문서화에 사용되는 주석을 남깁니다.

✔️ Preferred

/// 사용자 프로필을 그려주는 뷰
class ProfileView: UIView {

    /// 사용자 닉네임을 그려주는 라벨
    var nameLabel: UILabel!
}

❌ Not Preferred

// 사용자 프로필을 그려주는 뷰
class ProfileView: UIView {

    // 사용자 닉네임을 그려주는 라벨
    var nameLabel: UILabel!
}

// MARK:를 사용해서 연관된 코드를 구분짓습니다. (MARK 구문 위와 아래에는 공백이 필요합니다.)

✔️ Preferred

// MARK: Init

override init(frame: CGRect) {
    // doSomething()
}

deinit {
    // doSomething()
}


// MARK: Layout

override func layoutSubviews() {
    // doSomething()
}


// MARK: Actions

override func menuButtonDidTap() {
    // doSomething()
}

❌ Not Preferred

// MARK: Init
override init(frame: CGRect) {
    // doSomething()
}

deinit {
    // doSomething()
}

// MARK: Layout
override func layoutSubviews() {
    // doSomething()
}

// MARK: Actions
override func menuButtonDidTap() {
    // doSomething()
}