[Swift] 좋은 코드 만들기

2026. 6. 15. 17:34·Apple/Swift
728x90
반응형

안녕하세요! 피피아노입니다 🎵

 

오늘은 "좋은 코드"에 대해 이야기해보려고 합니다.

개발을 하다 보면 한 번쯤 이런 생각을 하게 됩니다.

 

대체 좋은 코드가 뭘까요?

동작만 잘하면 좋은 코드일까요?
아니면 짧으면 좋은 코드일까요?
아니면 디자인 패턴이 많이 들어가 있으면 좋은 코드일까요?

 

음... 사실 좋은 코드를 한 문장으로 정의하기는 어렵습니다.

 

그런데 반대로 좋지 않은 코드는 생각보다 금방 느껴집니다.

 

읽기 어렵고, 수정하기 무섭고, 기능 하나 추가했을 뿐인데 여기저기서 문제가 터지는 코드 말이죠.

그래서 이번 글에서는 좋은 코드를 만들기 위한 기준을 크게 네 가지로 정리해보겠습니다.

  1. Naming
  2. 상수화
  3. 관심사의 분리
  4. View Component 분리

가볍게 시작해보겠습니다.

Naming

코드를 작성할 때 가장 많이 하는 일 중 하나가 바로 이름 짓기입니다.

 

변수 이름, 함수 이름, 타입 이름, 파일 이름...

 

처음에는 "이름이 그렇게 중요한가?" 싶을 수 있습니다.
그런데 코드는 작성하는 시간보다 읽는 시간이 훨씬 깁니다.

 

특히 협업을 하거나, 며칠 뒤의 내가 다시 코드를 읽을 때 이름이 이상하면 바로 이런 상황이 생깁니다.

let data = ["Apple", "Banana", "Orange"]
let flag = true

음...

 

data가 뭘까요?
flag는 어떤 상태를 의미할까요?

 

코드는 돌아갈 수 있습니다. 하지만 읽는 사람은 계속 추측해야 합니다.

 

조금만 바꿔보겠습니다.

let fruitNames = ["Apple", "Banana", "Orange"]
let isFavorite = true

훨씬 낫죠?

 

배열이라면 배열답게, Bool이라면 Bool답게 이름을 지어주는 것이 좋습니다.

Bool 값은 보통 이런 식으로 시작하면 읽기 좋습니다.

let isLoggedIn = true
let hasProfileImage = false
let canSubmit = true
let shouldShowAlert = false

읽을 때 자연스럽게 질문처럼 읽힙니다.

  • 로그인되어 있는가?
  • 프로필 이미지가 있는가?
  • 제출할 수 있는가?
  • alert를 보여줘야 하는가?

함수 이름도 마찬가지입니다.

 

함수는 보통 어떤 동작을 하니까, 동사 + 목적어 형태가 잘 어울립니다.

func fetchUserProfile() { }
func updateSelectedDate() { }
func calculateTotalPrice() -> Int { 0 }

이름만 봐도 "아, 이 함수가 이런 일을 하겠구나" 하고 예상할 수 있어야 합니다.

 

그리고 Swift에서는 타입을 이미 알고 있는 경우가 많기 때문에, 이름에 타입을 굳이 반복하지 않는 편이 좋습니다.

let nameString: String

이렇게 쓰기보다는,

let name: String

이렇게 쓰는 게 더 자연스럽습니다.

 

좋은 이름은 길고 화려한 이름이 아닙니다.
의도를 정확히 드러내는 이름입니다.

상수화

다음 코드를 한번 보겠습니다.

Text("안녕하세요")
    .font(.system(size: 18))
    .padding(.top, 24)

코드 자체는 어렵지 않습니다.

 

그런데 여기서 18은 뭘까요?
24는 어떤 기준으로 나온 값일까요?
"안녕하세요"는 나중에 다른 곳에서도 쓰일까요?

 

이런 식으로 코드 안에 갑자기 등장하는 숫자나 문자열을 보통 Magic Number, Magic String이라고 부릅니다.

 

왜 Magic이냐면요.

 

작성한 사람만 의미를 알고, 읽는 사람 입장에서는 마법처럼 갑자기 등장하기 때문입니다.

 

이럴 때는 의미 있는 이름을 붙여주는 것이 좋습니다.

private enum Layout {
    static let titleFontSize: CGFloat = 18
    static let topPadding: CGFloat = 24
}

private enum Strings {
    static let greeting = "안녕하세요"
}

Text(Strings.greeting)
    .font(.system(size: Layout.titleFontSize))
    .padding(.top, Layout.topPadding)

이렇게 하면 숫자와 문자열이 단순한 값이 아니라, 의미를 가진 코드가 됩니다.

 

상수화는 단순히 값을 밖으로 빼는 작업이 아닙니다. 이 값이 왜 존재하는지 이름을 붙여주는 작업입니다.

 

조건문도 마찬가지입니다.

if user.age >= 19 && user.hasVerifiedEmail && !user.isBlocked {
    showMainView()
}

읽을 수는 있습니다.

 

그런데 조건이 조금만 더 복잡해지면 머릿속에서 해석해야 합니다.

 

이럴 때는 조건식에도 이름을 붙일 수 있습니다.

let canAccessMainView = user.age >= 19
    && user.hasVerifiedEmail
    && !user.isBlocked

if canAccessMainView {
    showMainView()
}

이제 읽는 사람은 조건의 세부 구현보다 먼저 의미를 이해할 수 있습니다.

 

"아, 메인 화면에 접근 가능한지 확인하는 조건이구나."

관심사의 분리

관심사의 분리라는 말은 처음 들으면 조금 어렵게 느껴질 수 있습니다.

그런데 아주 단순하게 말하면 이겁니다.

 

하나의 코드가 하나의 역할에 집중하게 만드는 것

 

예를 들어 SwiftUI에서 View가 있다고 해보겠습니다.

struct ProfileView: View {
    @State private var name = ""
    @State private var isLoading = false

    var body: some View {
        VStack {
            Text(name)

            Button("불러오기") {
                isLoading = true

                Task {
                    let fetchedName = await fetchName()
                    name = fetchedName
                    isLoading = false
                }
            }
        }
    }

    func fetchName() async -> String {
        "PPiano"
    }
}

작은 예제라면 괜찮아 보입니다.

 

하지만 실제 앱에서는 여기에 네트워크 요청, 에러 처리, 로딩 처리, 상태 전환, 화면 분기까지 들어가기 시작합니다.

 

그러면 View는 화면을 그리는 역할뿐 아니라 로직 처리까지 떠안게 됩니다.

 

이럴 때 ViewModel로 역할을 나눌 수 있습니다.

@MainActor
final class ProfileViewModel: ObservableObject {
    @Published var name = ""
    @Published var isLoading = false

    func loadProfile() async {
        isLoading = true
        defer { isLoading = false }

        name = await fetchName()
    }

    private func fetchName() async -> String {
        "Lumi"
    }
}

그리고 View는 화면을 그리는 일에 집중합니다.

struct ProfileView: View {
    @StateObject private var viewModel = ProfileViewModel()

    var body: some View {
        VStack {
            Text(viewModel.name)

            Button("불러오기") {
                Task {
                    await viewModel.loadProfile()
                }
            }
        }
    }
}

이렇게 하면 코드가 조금 늘어난 것처럼 보일 수 있습니다.

 

하지만 역할이 명확해집니다.

 

View는 화면을 그리고,
ViewModel은 상태와 로직을 관리합니다.

 

관심사의 분리는 코드를 무조건 잘게 쪼개는 것이 아닙니다.

 

변경되는 이유가 같은 코드는 모으고, 다른 코드는 나누는 것에 가깝습니다.

View Component

SwiftUI를 쓰다 보면 이런 고민을 자주 하게 됩니다.

 

"이 View를 따로 빼야 할까요?"

"아직 한 번밖에 안 쓰는데 컴포넌트로 만들어야 할까요?"
"body가 길어졌는데 분리해야 할까요?"

 

정답은 없습니다.

 

반복되는 컴포넌트만 분리하는 팀도 있고, 의미 단위가 보이면 바로 분리하는 팀도 있고, depth가 깊어지면 분리하는 팀도 있습니다.

 

예를 들어 이런 UI가 여러 곳에서 반복된다면 분리하는 게 자연스럽습니다.

struct UserCardView: View {
    let name: String
    let description: String

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(name)
                .font(.headline)

            Text(description)
                .font(.subheadline)
                .foregroundStyle(.secondary)
        }
        .padding()
        .background(.gray.opacity(0.1))
        .cornerRadius(12)
    }
}

그런데 반복되지 않더라도, 화면 안에서 의미가 명확하다면 분리할 수도 있습니다.

 

예를 들어 ProfileHeaderView, ProfileActionButton, EmptyStateView처럼요.

 

다만 무조건 많이 분리한다고 좋은 것은 아닙니다.

 

너무 잘게 나누면 오히려 코드를 읽을 때 파일을 계속 왔다 갔다 해야 합니다. 반대로 너무 안 나누면 하나의 body가 너무 커져서 읽기 힘들어집니다.

 

그래서 중요한 것은 "정답"이 아니라 "컨벤션"입니다.

 

팀에서 이런 기준을 정해두면 좋습니다.

  • 같은 UI가 두 번 이상 반복되면 분리한다.
  • body가 길어져 한눈에 읽기 어려우면 분리한다.
  • 상태 관리와 화면 표현은 가능하면 분리한다.
  • 재사용 목적이 없어도 의미 단위가 분명하면 분리한다.

View Component 분리에는 항상 trade-off가 있습니다.

 

분리하면 읽기 좋아질 수 있지만 파일이 많아질 수 있습니다.
분리하지 않으면 흐름은 한 파일에 있지만 코드가 길어질 수 있습니다.

 

그래서 상황을 보고 선택해야 합니다.

마무리

좋은 코드는 거창한 기술에서만 나오지 않습니다.

 

이름을 잘 짓고, 의미 없는 숫자와 문자열에 이름을 붙이고, 하나의 코드가 너무 많은 일을 하지 않게 하고, View를 팀의 기준에 맞게 나누는 것

 

이런 작은 습관들이 쌓여서 좋은 코드가 됩니다.

결국 좋은 코드는 이런 코드라고 생각합니다.

 

지금의 내가 작성하기 편한 코드가 아니라, 미래의 나와 동료가 읽고 고치기 쉬운 코드

 

코드는 한 번 작성하고 끝나는 것이 아니라 계속 읽히고, 수정되고, 확장됩니다.

 

그래서 좋은 코드를 만들고 싶다면 먼저 질문해보면 좋겠습니다.

"이 코드는 읽기 쉬운가요?"
"이 코드는 바뀌기 쉬운가요?"
"이 코드는 오래 유지될 수 있을까요?"


감사합니다.

 

잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!

궁금하신 부분은 댓글로 질문 부탁드립니다!

728x90
반응형

'Apple > Swift' 카테고리의 다른 글

[Swift] 왜 동시성 프로그래밍이 필요할까?  (0) 2026.05.24
[Swift] ARC(Automatic Reference Counting)란 무엇인가  (2) 2026.05.16
[Swift] 왜 Class는 Initializer를 자동으로 만들어주지 않을까?  (1) 2026.04.21
[Swift] SwiftData 상속과 스키마 마이그레이션 알아보기  (3) 2026.03.18
[Swift] Swift 6로 마이그레이션 하기  (3) 2026.03.04
'Apple/Swift' 카테고리의 다른 글
  • [Swift] 왜 동시성 프로그래밍이 필요할까?
  • [Swift] ARC(Automatic Reference Counting)란 무엇인가
  • [Swift] 왜 Class는 Initializer를 자동으로 만들어주지 않을까?
  • [Swift] SwiftData 상속과 스키마 마이그레이션 알아보기
P_Piano
P_Piano
Apple 생태계 개발자가 되기 위해 학습과 경험을 기록하고 있습니다.
    반응형
    250x250
  • P_Piano
    피피아노의 개발 일지
    P_Piano
  • 전체
    오늘
    어제
    • 분류 전체보기 (235) N
      • Apple (151)
        • iOS (25)
        • visionOS (5)
        • Swift (80)
        • UIKit (2)
        • SwiftUI (27)
        • RxSwift (2)
        • Xcode (6)
        • Metal (2)
      • Apple Developer Academy (1)
      • C언어 (5)
      • C++ (8)
      • Dart (1)
      • Python (3)
      • JavaScript (17)
      • Git (1)
      • CS (40)
        • 디자인 패턴 (6)
        • 네트워크 (20)
        • 운영체제 (8)
        • Database (5)
        • 자료구조 (0)
      • IT 지식 (2)
      • IT 뉴스 (4)
      • 경험 (2) N
      • 출처 표기 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    SWIFT
    데이터베이스
    동시성
    UIKit
    visionOS
    프로퍼티 래퍼
    Initializers
    클래스
    함수
    ios
    스위프트
    운영체제
    이니셜라이저
    자바스크립트
    티스토리챌린지
    비동기
    프로세스
    swiftUI
    코딩테스트
    제어문
    배열
    Apple
    오블완
    네트워크
    Xcode
    Vision Pro
    변수
    회고
    상속
    디자인패턴
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
P_Piano
[Swift] 좋은 코드 만들기
상단으로

티스토리툴바