통계에 따르면 프로젝트에 존재하는 코드 중 90% 정도는 예외를 처리하는 부분이다. 그렇기 때문에 애러 처리는 어플리케이션 개발에 있어 굉장히 중요한 부분을 차지하다. 내 생각에 코드는 성공하는 로직을 위주로 작성되어야 가장 깔끔해 보인다. 일관성 있어야 하며 일반적인 제어의 흐름과 예외 처리는 분리되어야 한다. 그렇기 위해선 각 함수들의 애러는 우아하게 처리되어야 한다. 

 

 애러의 가능성을 피할 수 있으면, 해당 설계대로 작성하는게 가장 좋다. 그렇지 않다면 애러는 명시적으로 처리 되어야 한다. 아래는 rawData를 Movie list로 파싱해서 description이 있는 경우 해당 text의 길이를 더해서 반환하는 함수이다. 함수가 실패하면 의도적으로 nil(Null)을 리턴하여 해당 함수의 실패를 알리는 방식으로 코드를 작성했을때, nil을 전달 받은 getMovieDescriptionCount함수의 코드를 보자.

 

[nil(Null)을 사용하여 코드가 망가지는 모습]

 

  우리가 애러 표현을 위해 습관적으로 nil을 적용 했을때 망가지는 코드의 모습이다. getMovieDescriptionCount는 예외처리 코드 때문에 성공하는 로직을 작성할 수 없다. 코드도 길어지고 가독성도 떨어졌다. nil check하는 if문이 별게 아닌 것 처럼 보이지만 최소한의 인지적 노력을 해야하기 때문에 코드를 읽는데 방해가 된다.

 

[애러가 정상적인 동착 처럼 보이도록 default  value를 적용]

 

  이런 경우에는 nil이 아니라 default 값을 넘기고 array인 경우 empty array를 넘겨서 정상적인 동작으로 보이도록 처리 될 수 있다. (class인 경우엔 Null Object Pattern을 사용할 수 있다.) 우리가 작성하는 대부분의 코드는 "if {성공} else {아무것도 하지 않음}" 인 경우가 많기 때문에 습관적으로 사용하는 nil 처리만 잘해도 코드가 굉장히 깔끔해 질 수 있다. 

 

  정상적인 동작처럼 보이도록 디자인 하기 어려운 경우에는 아래와 같이 여러 상태들의 조건을 확인하여 예외 처리 코드를 적용한다.

 

[애러를 분기 하여 예외 처리]

 

  이 경우도 마찬가지로 if의 인지적 노력이 필요함으로 가독성이 매우 떨어지고 예외 처리 코드 때문에 코드의 일관성을 잃어 버리게 된다. 또한 2~10 line은 예외에 대한 내용이 구체적이지 않기 때문에 문제를 식별하기 굉장히 어렵다. 12~28 line은 Error Code를 정의하여 예외처리를 처리를 하였다. Error Code는 실패에 대한 명확한 이유를 알 수 있기 때문에 문제를 식별하고 구체적인 예외처리를 할 수 있다. 하지만 이 역시 일반적인 제어의 흐름 속에 예외 처리 코드가 섞여 있어서 코드의 일관성을 잃어 버렸다. 

 

[사용자 정의 Error]

 

  이런 경우 사용자 Error 정의하고 do try catch구문으로 처리한다. 사용자 Error의 사용은 오류를 명시적인 이름으로 정의 할 수 있으며, 오류의 가능성이 있는 함수를 사용하는 클라이언트 코드의 일반적인 제어의 흐름과 오류 처리 코드를 물리적으로 분리 시킬 수 있다.  따라서 이제 do try 블록 안에는 성공하는 로직만 존재한다. 

 

  사용자 정의 Error는 상위 레이어로 throw 될 때 OCP를 위배 할 수 있다. 상위 레이어로 오류를 전달은 wrapping하여 rethrow 하도록 디자인 할 수 있다.

 

'CleanCode' 카테고리의 다른 글

javascript AOP  (0) 2016.10.18
boolean type parameter의 모호성  (0) 2016.10.15

iOS Clean Architecture를 디자인하였고 해당 architecture를 통해 TODO list를 저장하는 샘플앱을 만들어 보았다.

github : https://github.com/ezero9/iOSCleanArchitecture

1. Architecture 

[그림 1. Architecture]

  Architecture는 일반적으로 많이 사용되고 있는 3 Layer Architecture로 구성한다. 본 Architecture[그림 1]는 명확하게 정의된 레이어를 가지고 있으며, 각각의 레이어는 잘 정의되고, 통제되는 인터페이스를 통해 응집성 있는 서비스의 집합을 제공한다. 

 

  Object간의 dependency 관계는 IoC Container를 통해 injection받는다. 각 class들의 동작에대한 구성은 아래와 같다. [그림 2] 하위 레이어에서 상위 레이어로의 change propagation은 RxSwift나 delegation pattern, callback등을 통해 notify하는 방식으로 active하게 디자인한다.

 

[그림 2. 동작 구성]

2. IoC Container

[그림 3. IoC Container Class Diagram]

  일반적인 IoC Container 라이브러리들의 무거운 기능들은 전부 제외하고 DI, Singleton지원과 같은 필수적인 기능들만 구현하여 심플하게 제공한다. (Swift IoC 라이브러리가 없어서 직접 구현함)

 

Swift에서의 DI(Dependency Injection): https://develogs.tistory.com/21

2.1 Dependency Injection

  Object를 생성하고 각 object들간의 관계를 wiring하는 책임은 모두 IoC가 갖는다. client는 자신이 사용할 object instance를 IoC를 통해 DI받는다. 따라서 client에서는 IoC가 주는 object가 어떤 concrete class로 부터 생성이 되었는지 알필요 없으며, 정의된 interface를 통해 동작하기만 하면된다. 이는 object들간의 관계가 변경 전파 없이 같은 interface로 구현된 mock나 다른 class의 object instance로 언제든 대체될 수 있음을 의미한다. IoC를 사용하여 object들과의 관계를 설정할때 되도록이면 constructor injection을 사용하는게 좋다.

 

[Post기능과 관련된 object들의 의존성을 구성하는 부분]

 

  IoC(Inversion of Control, 제어의 역전)과 DI(Dependency Injection, 의존성 주입)에 대한 자세한 설명은 아래 링크에서 확인 할 수 있다.

제어의 역전(Inversion of Control, IoC) 이란?: https://develogs.tistory.com/19

2.2 Singleton 지원

  Singleton이 필요할때는 Singleton Pattern으로 class로 디자인하지 않고 IoC에 singleton으로 register한다. clinet에서는 IoC를 통해 필요한 object가 injection되면 해당 object가 singleton인지 일반 object인지 모르는 상태에서 사용되어야 한다. 

 

Singleton Pattern으로 디자인 하지 않은 이유: https://develogs.tistory.com/8

3.  Presentation Layer

[그림 4. BaseViewController와 BaseViewModel Class Diagram]

  Presentation Layer는 [그림 2]에서 보았듯이 MVVM Design Pattern을 사용한다. BaseViewController는 toast, popup, progress같은 ViewController의 공통 로직이 구현되어 있으며, BaseViewModel도 해당 ViewController와 interaction을 위한 공통 로직이 구현되어 있다. ViewController는 controller의 역할보다는 하나의 View로 보고 있으며 거기에 더해 View와 ViewModel과의 glue code가 존재하는 곳이다.

(재사용 되는 뷰가 없으면 오히려 mvvm의 기반코드를 작성하는게 이점대비 비용이 더 클수도 있다. View들의 도메인 요구사항에 따라 MVVM, MVP, MVC, VIPER등 무엇을  쓸지 design을 잘 결정하자.)

3.1 Data Binding

[그림 5. DataBinding]

  View와 ViewModel은 RxSwift, RxCocoa 라이브러리를 사용하여 two way binding을 한다. ViewModel은 binding해야 될 데이터를 정의해야하며, DataBinding Interface를 통해 바인딩 전략을 구현해야한다.

 

[AllPostViewModel에서 DataBinding 전략을 구현한 부분]

 

[AllPostViewController에서 AllPostViewModel과 binding하는 부분]

3.2 Routing Mechanism

[그림 6. Navigator Class Diagram]

  MVVM에서는 controller가 없기 때문에 Routing Mechanism이 필요한데, 그 역할은 Navigator가 담당하게 된다. Navigator는 자신이 이동해야 될 ViewController 혹은 Navigator를 IoC를 통해 전달 받아 정해진 interface를 통해 동작하게 된다. 

 

[BaseNavigator에서 View를 받아서 present하는 부분]

4. Domain Layer

[그림 7. 상위 레이어에서 requirement를 정의하고 하위 레이어에서 concrete를 구현]

  Application Logic을 담당하는 Layer임으로 Domain Layer는 반드시 다른 레이어들과 독립성을 유지해야한다. 다른 레이어들의 dependency가 존재하면 안되기 때문에 DIP(Dependency Inversion Principle)가 지켜져야 하는 레이어이다. Domain Layer에서는 필요한 requirements를 정의하고 Psersistent Layer에서는 이 정의된 requirement interface를 이용해 concrete class를 구현한다.

 

  여기서 다루는 내용중에 Domain Layer의 isolation이 가장 중요하다고 할 수 있다. 위에서 말했듯이 Presentation Layer에서의 view design pattern은 View 요구사항에 따라 바뀌겠지만 domain layer의 isolation은 반드시 지키는게 좋다.

5. Persistent Layer

[그림 8. Dependency Inversion 되어 구현되고 있는 Persistent Layer]

  Persistent Layer는 data source에 대한 decoupling을 위한 layer이며 Domain Layer에서 정의된 requirement interface를 통해 concrete class를 구현한다. Persistent Layer의 object들도 마찬가지로 IoC를 통해 Domain Layer에 injection되며, 여기서 injection된 object는 다른 구현체로 변경된다 하더라도 절대로 상위 레이어로 변경 전파가 이루어지지 않아야 한다.

6. Error Handling

[그림 8. TraceError Class Diagram]

  error stack trace를 확인하기 위해 ErrorTrace라는 최상위 error를 정의했다. ErrorTrace를 상속받아 명확한 이름을 가진 Error를 정의하고 예외는 do try catch 구문으로 처리한다. 

 

[그림 9. Error Wrapping]

  error는 하위 레이어에서 상위 레이어로 thorw될 때, 상위 레이어에서 requirement로 정의된 좀 더 구체적인 이름을 가진 error로 wrapping하여 올려 준다.

 

getStackTrace()을 찍어보면 아래와 같이 layer별로 명확한 이름으로 정의된 error의 stack trace를 확인 할 수 있다.

🔶[ERROR TRACE]🔶

🔶 iOSCleanArchitecture/Scenes/AllPost/AllPostViewModel.swift 50: viewDidLoad() - PostInterface.LoadFailError: not found Post Data. 

    🔶iOSCleanArchitecture/Domain/Model/AllPostModel.swift 42: parsing() - PostInterface.ParseFailError: parsing failed. 

        🔶 iOSCleanArchitecture/Repository/PostNetworkDAO.swift 35: network() - PostNetworkDAO.NetworkError: 404 error   

 

Error handling - 우아하게 실패하는 방법 : https://develogs.tistory.com/9

'Architecture & Design Pattern' 카테고리의 다른 글

제어의 역전(Inversion of Control, IoC) 이란?  (16) 2019.12.08
if문을 없애는 디자인  (0) 2019.11.17
Data Access Object Pattern  (0) 2019.09.07
Singleton Pattern의 함정  (0) 2019.07.14

+ Recent posts