레오가 제안한 아이디어로 우리는 스터디 기간 동안 코드 컨벤션을 도입하려고 합니다. 스터디 기간 동안 쓰이지 않을 것으로 판단되는 부분은 제외하고 아래 두 가지 출처에서 참고한 내용을 통합하여 사용할 것입니다.
이러한 코드 컨벤션 도입은 프로젝트의 코드 일관성과 가독성을 향상시키며 협업과 유지보수를 더욱 원활하게 만들 것입니다.
모듈 임포트는 알파벳 순으로 정렬합니다. 내장 프레임워크를 먼저 임포트하고, 빈 줄로 구분해 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자를 넘지 않아야 합니다.
클래스와 구조체의 이름에는 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
파라미터와 리턴 타입이 없는 클로저 정의시에는 () -> 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는 사용하지 않습니다.
✔️ 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 키워드로 선언합니다.
✔️ Preferred
final class ViewController: UIViewController {
// ...
}
❌ Not Preferred
class ViewController: UIViewController {
// ...
}
프로토콜을 적용할 때는 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()
}