My Coding Convention/Style

J.Noma·2021년 12월 9일
0

JNoma

목록 보기
1/1

Naming: Swift API Design Guideline
Naming : 노수진님 블로그


정리본

Naming

✔️ 복수 표현

뒤에 Array, Dictionary 같은 타입을 직접 명시하기보다는 -s 접미사를 붙이는 것이 깔끔하다

단점이라면 -s라는 음절 자체가 작다보니 여러 개라는 느낌을 바로 파악하기 어려운 경우가 있을 수 있습니다 (특히 'data'같은 불규칙 복수형)

이 경우 선언부에 타입을 명시해주어 보완한다

let randomNumbers: [Int] = generateRandomNumbers()

✔️ 단순 unwrapping

타입캐스팅 혹은 어떤 인스턴스를 소스로 다른 타입의 인스턴스를 생성하는 등 대상은 같으나 타입이 변경되는 경우 일반적으로 변수명에 타입을 포함한 차이를 주면 명확성을 향상시킬 수 있을 것입니다

하지만, 아래 예제와 같이 unwrapping만 하는 경우 타입이 변경되기는 하지만 해당 인스턴스의 역할은 유의미하게 바뀌지 않는 경우가 많습니다

이런 경우 오히려 변수명에 차이를 주고자 unwrapped 등의 표현을 넣는 것이 큰 장점을 갖지 않고 오히려 코드의 가독성만 낮추는 단점이 훨씬 부각되므로 동일한 이름을 작성합니다

let foo: Int?

guard let foo = foo else {
    return
}

코드 위치

✔️ 메서드/프로퍼티 배치

기준1. 프로퍼티/메서드 종류별로 묶어줍니다
(ex. Life cycle메서드끼리, private메서드끼리 등)

기준2. 같은 종류 내에서는 open된 메서드를 우선합니다
(open된 메서드가 호출 시점이 빠르므로 위쪽에 배치하는 것이 읽기 편하다)

기준3. Life cycle 메서드의 경우, 호출 순으로 정렬

기준4. 뷰컨처럼 타입의 정의부가 커지는 경우 extension과 주석을 활용하여 분리

다른 방법으로,
하나의 flow를 따라가는데 있어 여기저기 이동하지 않아도 되도록 서로 연관있는 메서드끼리 묶어주는 방법이 있습니다. 이 방법도 일반적으로 선호되나 하나의 메서드가 여기저기서 재사용될 경우 배치가 모호해지는 단점이 있어 채택하지 않았습니다


//MARK: - Properties
class ViewController: UIViewController {
    let apple: String
    let banana: String
    
    @IBOutlet weak var appleButton: UIButton!
    @IBOutlet weak var bananaButton: UIButton!
}

//MARK: - Life Cycle Methods
extension ViewController {
    override func viewDidLoad() {
        ....
    }
    
    override func viewDidAppear() {
        ....
    }
}

//MARK: - Private Methods
extension ViewController {
    private func changeLabel() {
        ....
    }
    
    private func changeButton() {
        ....
    }
}

//MARK: - API Methods
extension ViewController {
    func setApple() {
        ...
    }
    
    func setBanana() {
        ...
    }
}

줄바꿈 / 들여쓰기

✔️ 들여쓰기는 Tab으로

스페이스바로 하면 여러 번 입력해야 하므로..

✔️ if/gaurd문 body 줄바꿈

if 문의 경우 항상 줄바꿈을 적용하고, guard 문은 99칸 이상인 경우에만 줄바꿈을 적용합니다

if let foo = foo else { 
    return 
}

guard let foo = foo else { return }

guard let foo = itIsAmazinglyAbsolutelylonglongFunction(onlyFor: foo) else { 
    throw AmazinglyAbsolutelyLongLongErrorType.LongLongError
}

✔️ 함수 정의부/호출부가 길 때의 줄바꿈

99칸 이상이 되면 줄바꿈 스타일 적용. 기존 Apple 스타일은 함수 이름이 길면 가독성이 떨어지는 문제가 있어 아래 스타일 채택

func longlongFunction1(
    firstArgument: String,
    secondArgument: String,
    thirdArgument: String
) -> Int {
    //한줄공백                   
    ....
    ....
    ....
}

또한, return line만으로도 99칸이 넘는 경우 아래와 같이 처리합니다

//return line만으로도 99칸이 넘는 경우
func longlongFunction2(firstArgument: String,
			secondArgument: String,
            		thirdArgument: String
) -> NetworkingAPI.ErrorType {
    //한줄공백                   
    ....
    ....
    ....
}

기타

✔️ 폴더/파일

🔘 폴더 정리

기준1. AppDelegate/SceneDelegate는 최상위 폴더에 그대로 둡니다

기준2. 외부 모듈을 extension하는 파일은 해당 모듈이름으로 된 폴더로 묶습니다
(ex. Int는 Swift / NumberFormatter는 Foundation)

기준3. custom type은 Model/ViewController/Controller/Error로 분류합니다

기준4. 정말 어디 넣을지 모를 파일이 있다면 Meta 폴더로 정리
(ex. Asset 이름 리스트를 정리해놓은 열거형)

🔘 Extension 파일이름

타입+Extension 형태로 지정합니다

Int+Extension.swift
String+Extension.swift

✔️ 인스턴스 프로퍼티/메서드에 self 명시

self는 파라미터명과 겹치거나 클로저에서 캡처되는 등 반드시 써야하는 경우와 구분하기 위해 self를 사용하지 않아도 되는 경우에는 명시하지 않습니다

특히 메모리 해제를 컨트롤하기 위해 클로저 캡처리스트에 [weak self] 를 명시해줘야 할 곳을 찾을 때, 이렇게 꼭 필요한 부분에만 self를 명시하는 것이 도움이 됩니다

✔️ subscript 추출

Optional type을 반환하는 새로운 subscript를 구현합니다

다른 방법으로, Collection의 count 프로퍼티와 index를 비교하는 방법이 있습니다
이 방법은 subscript를 새로 구현하는 것보다 코드량이 상대적으로 적다는 장점이 있습니다
하지만, 이 방법은 count와의 비교구문을 항상 기억해야 하므로 실수를 유발하기 쉽습니다

반면, subscript를 새로 구현하면 따로 기억할 필요없이 optional binding이 필요함을 컴파일러가 알려주기에 실수 예방에 유리합니다

// subscript 구현
extension Array where Element == Int {
    subscript(safe index: Int) -> Int {
        return indices.contains(index) ? self[index] : nil
    }
}

// 사용 예시
guard let element = arr[safe: 5] else {
    return
}

✔️ import

(Foundation을 제외하고) 최소한의 모듈만 import하도록 합니다

해당 파일에 어떤 타입들이 사용되는지 import 모듈로 대략적인 range를 파악할 수 있게 하기 위함입니다

프레임워크 내 특정 한 모듈만 사용하는 경우, 그 모듈만 import합니다
(ex. import UIKit.NSDataAsset)

✔️ 프로퍼티/메서드 접근제어

다른 인스턴스와 소통하는 API 메서드를 제외한 모든 프로퍼티/메서드는 private을 부여하도록 노력합니다


아카이브

indent depth 최소화

else 미사용

해당 block에 대한 정확한 condition 파악이 어려운 문제로 else는 사용하지 않는 것이 좋다.

//수정 전
func goodbyeElse() throws {
    if name == "yohan" {
        age = 5
    } else {
        throw HumanError.invalidOwner
    }
}

//1차 수정
func goodbyeElse() {
    if name == "yohan" {
        age = 5
    }
    
    throw HumanError.invalidOwner
}

throw 구문의 condition은 좀 더 명확해졌지만 Error Handling이 이렇게 밖에 나와있으면 누군가의 실수로 바로 Error로 직행하게 된다. Defensive Programming 관점에서 이를 고쳐보자

//2차 수정
func goodbyeElse() {
    guard name == "yohan" else {
        throw HumanError.invalidOwner
    }
    
    age = 5
}

Swift의 guard문은 이럴 때 쓰라고 있는거구나

외부 타입의 정보는 최소화

어떤 타입이 외부 타입의 정보를 지나치게 많이 알게 되면 Open-Close Principle을 위반하여 수정이 필요할 가능성이 높아진다.

예로, 뷰컨 내 특정 버튼의 text style을 변경하는 경우를 살펴보자. 이런 코드는 없겠지만 극단적인 예시를 들어보았다

//수정 전
func changeText(viewController: UIViewController) {
    viewController.button.titleLabel?.text = "yohanBlessYou"
}

위 코드의 경우, 불필요하게 뷰컨의 정보를 많이 알고 있다. 함수의 기능이 Label의 text를 바꾸는 것뿐임에도 만약 뷰컨이 변경될 경우, 매우 높은 확률로 위 코드도 변경되어야 한다.

외부 타입의 정보를 최소화하여 이를 어느정도 해결할 수 있다

//수정 후
func changeText(label: UILabel) {
    label.text = "yohanBlessYou"
}

이를 디미터의 원칙 혹은 최소 지식의 원칙이라고도 한다

함수 세분화 기준

함수의 메인 기능, 메인 알고리즘을 보여주는 곳은 길더라도 본문에 남기고 그 외의 부분은 최대한 그룹핑하여 세분화한다

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글