View

300x250

최근에 개발을 공부하면서 ValueObject라는 녀석을 배웠다. 잘만 활용하면 코드를 무척이나 깔끔하게 다듬을 수 있을 것 같아서, 기록으로 남겨두려고 한다.

Identifier 라는 String을 담는 객체가 있다고 해보자. 여기에서 Idnetifier에는 한글, 영어, 띄어쓰기만 들어갈 수 있다는 규칙이 있다고 가정하자.

만약에 뷰모델에서 가진 함수 중에서 뷰에서 input을 받아와 Identifier 객체를 만들어주는 함수가 있다고 해보자. 그러면 뷰모델은 아래처럼 input 값이 유효한지 검사하고, 유효한 경우에 Identifier 객체를 생성해주어야 한다.

ViewModel {
    func saveIdentifier(input: String) {

        let pattern = "^[가-힣a-zA-Z\\s]+$"
        if !input.range(of: pattern, options: .regularExpression) {
            return
        }

        let id = Identifier(identifier: input)
        // id 객체 활용하기
    }
}

그런데, 막상 생각해보면 ViewModel의 함수에서 객체 생성에 대한 유효성까지 검사를 하는게 지저분한 코드일 수 있다. 아래 2가지 이유만으로도 생산성 측면에서는 구린 코드라는 생각이 확 든다.

  • Identifier를 결정하는 조건이 변경되었을 때 ViewModel의 함수를 변경해야 한다.
  • Identifier를 결졍하는 조건이 변경되면 조건 검사를 하는 모든 코드를 변경해야 한다.

여기에서 유효성 검사에 대한 코드를 ViewModel이 아닌 Identifier 클래스가 직접 담당하도록 코드를 작성해줄 수 있다. 아래 코드에서는 init 시점에 유효성을 검사하고, 유효하지 않은 경우에는 nil을 반환하게 만들어버린다.

struct Identifier {
    private let identifier: String

    init?(identifier: String) {
        let pattern = "^[가-힣a-zA-Z\\s]+$"
        guard let _ = identifier.range(of: pattern, options: .regularExpression) else {
            return nil
        }

        self.identifier = identifier
    }

    func getValue() -> String {
        return self.identifier
    }
}

그러면 ViewModel 쪽 코드는 아래처럼 너무 예쁘고 간단해진다.

ViewModel {
    func saveIdentifier(input: String) {
        guard let id = Identifier(identifier: input) else { return }
                // id 활용하기
    }
}

이에 더해, 앞서 확인했던 구린 코드 이유 2개도 동시에 해결된다.

  • Identifier를 결정하는 조건이 변경되었을 때 ViewModel의 함수를 변경해야 한다.
    • Identifier 클래스를 변경하기 때문에, [변경 사유 : 변경 부분]이 일치하게 된다.
  • Identifier를 결졍하는 조건이 변경되면 조건 검사를 하는 모든 코드를 변경해야 한다.
    • Identifier 클래스만 변경해주면 이를 사용하는 로직은 변경할 필요가 없다.

이런 방식으로 역할을 쪼개고 책임을 넘겨 코드를 정리하는 방법은 꽤나 유용하다고 생각되고, 개인 프로젝트를 진행할 때에도 자주 사용할 것 같다. 좋았어!

320x100
Share Link
reply
반응형
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30