클린 코드 적용기 - 반복하지 마라, 의도를 분명하게

유재경·2022년 5월 27일
0

Clean Code 적용기

목록 보기
3/4

(※ 실제 코드 유출 방지를 위해 약간의 코드 변형을 하였습니다.)

반복하지 마라

  • 중복은 소프트웨어에서 악의 근원이다.
  • 불필요한 반복은 코드를 읽는 시간과 노력을 소모시키고, 여러 번 반복수정해야할 수 있다.

의도를 분명히 해라

  • 변수, 함수, 클래스 이름은 존재 이유, 수행 기능, 사용 방법이 명확히 드러나 있어야 합니다.
  • 코드의 목적을 쉽게 이해할 수 있다.

Bad Practice

struct FirstButtonView: View {
    let focused: Bool
    let action: () -> Void

    var body: some View {
        Button {
            action()
        } label: {
            Text("시작하기")
                .foregroundColor(focused ? Color.white : Color.lightGreen)
                .padding(.vertical, 14)
        }
        .frame(height: 50)
        .background(focused ? Color.green : Color.white)
        .cornerRadius(8)
        .overlay(
            Group {
                if !focused { RoundedRectangle(cornerRadius: 8).stroke(Color.green) }
            }
        )
    }
}


struct SecondButtonView: View {
    let focused: Bool
    let action: () -> Void

    var body: some View {
        Button {
            action()
        } label: {
            Text("둘러보기")
                .foregroundColor(focused ? Color.white : Color.lightGreen)
                .padding(.vertical, 14)
        }
        .frame(height: 50)
        .background(focused ? Color.green : Color.white)
        .cornerRadius(8)
        .overlay(
            Group {
                if !focused { RoundedRectangle(cornerRadius: 8).stroke(Color.green) }
            }
        )
    }
}

...

VStack(spacing: 10) {
    FirstButtonView(focused: false, action: { ... })
    SecondButtonView(focused: true, action: { .... })
}
  
...

문제점

  1. FirstButtonView와 SecondButtonView의 이름이 분명하지 않다. 즉, 두 개의 View가 무슨 UI를 그리는지 이름만 보고는 직관적이지 않으며 모든 코드를 읽은 후에나 '시작하기' 와 '둘러보기' 역할을 버튼임을 알 수 있다.
  2. FirstButtonView, SecondButtonView 내의 action의 클로져명 또한 직관적이지 않다. ~ButtonView이기 때문에 버튼의 action임을 대충은 짐작할 수 있으나 View내의 여러 action을 포함하게 된다면 클로져명을 구체적으로 할 필요가 있다.
  3. FirstButtonView와 SecondButtonView는 버튼 UI의 이름만 다를 뿐 코드가 반복되고 있다.

Good Practice


enum IntroButtonType: Codable {
    case start
    case lookaround
    
    func getTitle() -> String {
        switch self {
        case .start:
            return "시작하기"
        case .lookaround:
            return "둘러보기"
        }
    }
}

struct IntroButton: Hashable, Codable {
    let type: IntroButtonType
    let focused: Bool
}

struct IntroButtonView: View {
    let button: IntroButton
    let IntroButtonAction: (IntroButtonType) -> Void

    var body: some View {
        Button {
            IntroButtonAction(button.type)
        } label: {
            Text(button.type.getTitle())
                .foregroundColor(button.focused ? Color.white : Color.lightGreen)
                .padding(.vertical, 14)
        }
        .frame(height: 50)
        .background(button.focused ? Color.green : Color.white)
        .cornerRadius(8)
        .overlay(
            Group {
                if !button.focused { RoundedRectangle(cornerRadius: 8).stroke(Color.green) }
            }
        )
    }
}

...

VStack(spacing: 10) {
    ForEach(viewModel.buttonList, id: \.self) { button in
        IntroButtonView(button: button, IntroButtonAction: viewModel.introButtonAction(_:))
    }
}  
  
...  

변경점

  1. IntroButtonView라는 View로 공용화하였다. IntroButtonView는 버튼의 title과 action만 다를 뿐, UI는 동일하다.
  2. IntroButtonType이라는 enum을 만듦으로써 버튼 title을 수정하기 위해 View를 보지 않고도 enum의 getTitle() 함수의 return 값만 변경해주면 된다. 즉, IntroButtonView는 소유하고 있는 구조체를 그대로 보여주기만 하는 View 역할에 충실하고 있다.
  3. IntroButtonView, IntroButtonAction 등 변수명을 구체화하였다.
  4. ViewModel에서 그려줘야할 buttonList(타입은 IntroButton)를 정해주고, View에서는 그려주기만 하는 역할만 한다. 버튼 action도 ViewModel에서 수행하도록 넘겨주고 있다. 역할 분리를 충실히 하도록 변경하였다.

회고

막 현업에서 일하기 시작했을 때, 코드 복사/붙여넣기를 많이 했었고 습관을 고치는 데에 시간이 꽤나 들었다.
공용화할 수 있는 View를 최대한 사용하여 동일한 구조의 View 코드 반복을 줄이거나 상속 받아 코드 중복을 줄이기 위해 노력을 했다.
View와 ViewModel이 각자의 역할에 충실한 MVVM이 되도록 항상 신경써야 한다.

profile
iOS 개발

0개의 댓글