필요한 rpm 설치

npm install typescript jest ts-jest @types/jest ts-node
npm i -D puppeteer jest-puppeteer
npm i -D @types/puppeteer @types/jest-environment-puppeteer @types/expect-puppeteer
npm i -D cross-env

tsconfig.json 수정

{
  "compilerOptions": {
  ..........
    "types": [
      "jest",
      "puppeteer",
      "jest-environment-puppeteer",
      "expect-puppeteer"
    ],
  ..........
  },
  "exclude": [
    "node_modules"
  ]
}

프로젝트 루트에 아래와 깉이 설정 파일들을 생성한다. 

preset.js 생성

const ts_preset = require('ts-jest/jest-preset')
const puppeteer_preset = require('jest-puppeteer/jest-preset')

module.exports = Object.assign(
  ts_preset,
  puppeteer_preset
)

jest.config.js 생성

module.exports = {
    preset: "./preset.js",
    testMatch: ["**/tests/*.test.(ts|tsx)"]
};

jest-puppeteer.config.js

module.exports = {
  launch: {
    headless: false,
    timeout: 20000,
  },
};

package.js에 아래 명령어 추가

//.........
  "scripts": {
    "test": "cross-env DEBUG=test JEST_PUPPETEER_CONFIG=./jest-puppeteer.config.js jest --colors --runInBand --detectOpenHandles --config=./jest.config.js"
  },
//.........

프로젝트 루트에 tests폴더 생성 및 테스트 파일 uitest.test.ts 생성

describe('Naver', () => {
  beforeAll(async () => {
    await page.setViewport({ width: 960, height: 540 });
    await page.goto('https://www.naver.com')
  })

  it('should display "naver" text on page', async () => {
      await expect(page.title()).resolves.toMatch('NAVER');
  })

  it('search keyword', async () => {
    await page.type('input[type=search]', 'develogs', {delay: 20})
    await delay(1000);
    const inputElement = await page.$(
      'button[type=submit]',
    );
    if(inputElement) {
      await inputElement.click();
    }
    await delay(1000);
    await page.screenshot({ path: 'search_result.png' }); // 결과 화면 스크린샷
    await delay(1000);
})
})

function delay(ms: number): Promise<void> {
	return new Promise((resolve, reject) => {
    	setTimeout(() => {
          resolve()
    	}, ms)
    })
}

테스트 명령어

npm run test

UI 테스트 자동화 결과

 

UI 테스트 중 특정 화면을 캡쳐할 수 있음.

 

'Log' 카테고리의 다른 글

carthage에서 googlemaps 사용하기  (1) 2019.10.20
Flutter vs. React Native  (2) 2019.09.28
심심해서 Flutter로 만든 TODO앱  (0) 2019.09.23
cocoapods cache 삭제  (0) 2019.09.22
Flutter sqflite에서 like 사용하기  (0) 2019.09.22

  아직 Swift에서는 DI 라이브러리를 많이 쓰이고 있진 않은 것 같다. github에 DI 라이브러리들이 몇몇 개 보이긴 하는데, Singleton을 지원하지 않거나 class를 key로 쓰고 있어 같은 타입의 object를 등록할 수 없는 등 불편한 점들이 여럿 있었다. 그래서 이런것들을 개선하여 필요한 기능들만 간단하게 만들어 쓰고 있었는데, 여러 프로젝트에 쓰다보니 코드 관리의 필요성이 생겨 라이브러리화 하여 github에 업로드 하였다. 

 

github: https://github.com/ezero9/SwiftInjection

 

Class Summary

Class DIContainer

    func configure()

    func resolve<T>() -> T

    func resolve<T>(key: String) -> T

    func contains(key: String) -> Bool

    func register<T>(_ assemble: @escaping () -> T)

    func register<T>(key: String, _ assemble: @escaping () -> T)

    func registerSingleton<T>(_ assemble: @escaping () -> T)

    func registerSingleton<T>(key: String, _ assemble: @escaping () -> T)

    func registerSingleton<T>(key: String, value: T)

    func destroy()

 

Class DIContainerManager

    func registerContainer<T>(container: T)

    func getObject<T>(key: String) -> T

    func resolve<T>() -> T 

    func destroy()

 

Usage

1.  Register class

  class를 등록하는 방법은 아래 [코드 1]처럼 DIContainer를 상속받아 configure를 override하여 사용하는 방법과 [코드 2]와 같이 DIContainer를 직접 사용하는 방법이 있다.

[코드 1]

 

[코드 2]

2. Get object

  등록된 class는 아래 [코드 3]과 같이 오브젝트를 생성할 수 있다. 

[코드 3]

3. Manage container

  관심사에 따라 여러 컨테이너를 작성하여 물리적으로 분리하고 아래 [코드 4]와 같이 DIContainerManager에 등록하여 사용 할 수 있다. 그리고 상황에 따라 container들을 다르게 생성하여 다른 행동을 하도록 Strategy pattern을 적용할 수 있다.

[코드 4]

 

 

'iOS' 카테고리의 다른 글

Swift에서의 AOP  (0) 2020.08.03
Realm은 thread safe하지 않다.  (0) 2019.09.01
SwiftUI Bata 하루 사용해본 후기  (0) 2019.08.17

  AOP(Aspect-oriented programming)는 횡단 관심과 핵심 관심을 물리적으로 나누고 모듈화하는 것을 말한다. 이는 메소드 호출(Target)을 가로채서 특정 동작(Advice)를 추가해야 하므로 메소드를 Intercept 할 수 있도록 언어에서 기능을 지원해야 한다. 여러 언어에 따라 컴파일 타임 혹은 런타임에 이 기능을 지원하거나 둘 다 지원하기도 하는데, swift는 statically typed language로 메소드 호출을 C++처럼 static/vtable 방식으로 호출한다. 따라서 컴파일 타임에 이를 지원해야 하지만, 안타깝게도 swift 컴파일러는 이를 지원하지 않고 서드파티에서 개발된 관련된 도구도 없음으로 아직 컴파일 타임에 AOP를 사용 할 수 없다.

  Swift 5.1에서 새롭게 공개된 @propertyWrapper를 통해서 변수에 대해 before, after 정도의 AOP를 아래 [코드 1]과 같이 비슷하게 흉내 낼 수 있긴 하지만,  타깃에 어노테이션으로 직접 표시해야 하고 여러 어노테이션을 중첩할 수 없으며(중첩 안되는 건 버그이며 추후 릴리스에 개선 예정이라고 한다.) 메소드에는 아직 적용할 수 없어서 효용성이 크게 있진 않다.

 

[코드 1] Swift5.1의 PropertyWrapper

 

  반면 objective-c는 메시지 기반의 언어이므로 런타임에 메소드 호출을 인터셉트할 수 있다. (실제로 github에 objective-c 용 AOP 라이브러리들이 여러 존재하긴 한다.) 그리고 swift에서는 objective-c API를 호출할 수 있으며, @objc 프로퍼티로 swift class를 objective-c 형태로 초기화하여 사용할 수도 있다. 그리고 UIKit도 objective-c 기반으로 작성되어 있어 UIKit API에도 이 방법으로 AOP를 적용할 수 있다. 

 

  만약 애플리케이션 내에 모든 UIViewController의 viewWillAppear에 google analytics를 적용한다고 한다면, 아래 stackoverflow link에 나와있는 코드처럼 objective-c의 method swizzling을 통해 viewWillAppear에 AOP를 적용해 볼 수 있을 것 같다.

 

https://stackoverflow.com/questions/46417057/want-to-create-a-listener-that-detects-viewwillappear-calls-throughout-the-app

 

  단점은 objective-c이기 때문에 swift보다 메소드 콜 성능이 다소 저하가 발생할 수 있으며, objective-c로 작성된 UIKit과는 달리 Swift로 작성된 SwiftUI에서는 사용할 수 없다. 그리고 기반 코드를 작성하다 보면 배보다 배꼽이 더 커질 수도 있겠다.

 

  Spring에서 AOP를 사용했던 경험이 있어서 그런지 다른 언어로 개발을 하다 보면 AOP에 대한 향수(?)가 굉장히 크다. 객체지향을 좀 더 객체지향답게 코드를 더 깔끔하고 우아하게 만들 수 있는 이 막강한 기능을 사용할 수 없으니 답답한 경우가 굉장히 많다. 하루 빨리 iOS에서도 AOP를 손쉽게 사용할 날이 왔으면 좋겠다.

'iOS' 카테고리의 다른 글

Swift에서의 DI(Dependency Injection)  (0) 2020.08.11
Realm은 thread safe하지 않다.  (0) 2019.09.01
SwiftUI Bata 하루 사용해본 후기  (0) 2019.08.17

  Head First OOAD, Gof Design Pattern 같은 OOP 기본서에서 'Hollywood principle'이나 'Inversion of Control'이란 용어를 많이 들어봤을 것이다. 이런 디자인 관련 책들에서 굉장히 반복적으로 나오는 용어인 것을 보면 꽤 중요한 개념인 것 같은데, 설명은 아래와 같이 매우 간단한 문장으로 마치곤 한다.

 

'Don't call us, we'll call you' (우리한테 연락하지 마세요. 우리가 당신에게 연락할게요.)

 

  물론 기본서이기 때문에 해당 개념에 대해 자세히 설명하고자 하면 더 복잡하게 될 것 같기도 하다. 나 같은 경우에는 대학생 때 Spring으로 웹서버를 개발하면서 Spring IoC를 통해 IoC에 대한 개념을 자연스럽게 익힐 수 있었다. 백문불여일견이라고 직접 사용해봐야 쉽게 이해할 수 있는 것 같다.

 

  IoC란 용어가 처음 등장한 건 Gof Design Pattern 저자 Ralph E. Johnson이 1988년 저술한 논문 'Designing Reusable Classes'에 처음 등장한다. 10년 전에 읽었을 땐 잘 이해가 안 갔는데, 사실 지금 다시 봐도 잘 모르겠다. 역시 영어는 어렵다. 그리고 그 후에 Gof Design Pattern 책에 'Hollywood principle'이란 용어로 재등장한다.

 

  물론 용어가 정립되고 일반화된 건 1988년이 최초이지만 위 논문에서 유추해 볼 수 있듯이 IoC 개념은 1978년 팔로알토 연구소에서 현대의 GUI와 가장 유사한 컴퓨터를 발표할 때 함께 최초로 GUI를 제공하는 객체지향언어인 Smalltalk와 MVC를 적용한 프레임워크를 공개하면서부터 시작한 것으로 보인다.

 

  일단, IoC에 대해 이해하기 위해서는 Dependency와 Dependency Inversion 그리고 Depdendency Injection에 대한 개념을 이해하고 있어야 하므로 Dependency 개념부터 차근차근 이해해보도록 하자.

Dpendency

[그림 1] Dependency

  [그림 1]에서 Client A는 Service B를 의존한다. (class들의 관계에서 Service는 특정 기능을 API를 통해 제공해주는 class를 의미하고 Client는 그 Service를 이용하는 class라고 이해하면 된다.) 여기서 의존한다는 뜻은 A가 B를 멤버 변수나 로컬 변수로 가지고 있거나 혹은 파라미터로 전달되거나 B의 메소드를 호출하는 것들을 의미한다.

 

  만약 Service B가 변경되면 Client A는 B를 강하게 Dependency하고 있음으로 컴파일이 안되거나 예상치 못한 동작을 하는 등의 영향을 받게 된다. 그리고 이러한 의존성은 A를 재사용하기 어렵게 만들기 때문에 A는 Component/Service가 될 수가 없다. 여기서 Component란 소스 코드의 아무런 수정 없이 다른 프로젝트에서도 바로 재사용이 가능한 수준의 모듈을 말하는데, 만약 현재 상태에서 A만 다른 프로젝트에 가져와 재사용하기 위해서는 A에서 B를 사용하는 부분을 수정해야 한다.

 

[그림 2] 관리되지 않은 Dependency

  여기서 더 문제는 우리 프로젝트 내에서 대부분의 class들의 의존관계가 [그림 2]와 같다는 것이다. 결국 Leaf에 존재하는 Class 말고는 재사용 할 수 있는 코드가 전혀 없다는 뜻을 의미한다. 본인 프로젝트 코드를 한번 돌이켜 보자. 과연 컴포넌트가 될 수 있는 클래스가 몇 개나 되는지? 그리고 UI이나 Data Source 등이 변경되었을 때 각 class간의 영향도가 어느 정도인지 말이다.

 

  이렇게 고차원 모듈이 저차원 모듈을 의존하고 저차원 모듈이 다시 고차원 모듈을 의존하는 것을 의존성 부패(Dependency Rot)이라 한다. 아래는 이런 의존성 부패를 없애는 일반적인 디자인 방법인 DIP에 대해 설명한다.

Dependency Inversion Principle (DIP)

'고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 모듈 모두 다른 추상화된 것에 의존해야 한다.

추상화 된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.'

- Martin, Robert C. -

 

  Dependency Inversion Principle은 class들 간의 의존성 부패(Dependency Rot)를 제거하기 위한 일반적인 디자인 방법이며, Martin, Robert C.가 1996년 'The Dependency Inversion Principle'을 발표하면서 많이 알려지기 시작했다. 

  위 [그림 1] Client A와 Service B에 DIP를 적용하면 아래와 같은 순서로 적용된다.

[그림 3]

  1. '고차원 모듈은 저차원 모듈에 의존하면 안된다.' [그림 3]에서 A가 B를 바라보는 Dependency를 제거한다.

[그림 4]

  2. '이 모듈 모두 다른 추상화된 것에 의존해야 한다. 그리고 추상화 된것은 구체적인 것에 의존하면 안된다.' [그림 4]에서 A는 Abstract를 Reference하지만 Abstract는 B를 Dependency하면 안된다.

[그림 5]

  3. '구체적인 것이 추상화된 것에 의존해야 한다.'  [그림 5]에서 B가 Abstract를 Inherit하도록 하여 Dependency를 Inversion한다. 추상적인 그림만 봐서는 이해하기 어려울 수 있으니 아래 구체적인 예제 코드를 통해 보도록 하자.

 

[코드 1] Dependency 문제가 있는 SwitchButton

 

  [코드 1]은 SwitchButton을 toggle하면 Lamp가 on/off되는 간단한 예제 코드다. 위 class들의 관계를 그려보면 아래와 같다.

[그림 6] SwitchButton과 Lamp의 Class Diagram

  [그림 6] SwitchButton과 Lamp의 Class Diagram을 보면 [그림 1]과 동일한 의존관계인 것을 알 수 있다. 위에서 이미 설명했듯이 [그림 6]에서 재사용할 수 있는 건 Lamp밖에 없으며 SwitchButton은 재사용할 수 없다. 현재 상태에서 램프가 아닌 자동차 시동을 거는 버튼이 필요하다면 SwitchButton을 재사용하지 못하고 EnginSwitchButton을 또 구현해야 된다.

 

  [코드 2]는 [코드 1]의 의존성 문제를 DIP를 적용하여 해결한 코드이다.

[코드 2] DIP가 적용된 SwitchButton

 

[그림 7] Dependency Inversion이 적용된 모습

  [코드 1]에서는 SwitchButton이 Lamp를 직접적으로 Dependeny하고 있었다. 하지만 [코드 2]에서는 SwitchButton이 Lamp대신 SwitchButtonInterface를 참조하도록 하였고, 각 Lamp와 Engine이 SwitchButtonInterface를 Inherit하도록 하여 Dependency를 Inversion하였다. 따라서 이제 SwitchButton은 Lamp와 같은 구현체와 Dependency가 전혀 없음으로 재사용이 가능한 코드가 되었다.

 

  가끔 Class Diagram만 보고 Strategy Pattern과 혼동하는 사람들이 있는데, 아래 [그림 8]을 보면 개념적으로 전혀 다른것을 알 수 있다.

[그림 8] DIP와 Stractegy Pattern과의 차이점

  [그림 8]에서 DIP의 Interface는 A에서 정의하고 B에서 구현하고 있다. 하지만, Stractegy Pattern에서는 그 반대다. Interface를 B에서 정의하고 A에서 의존한다.

 

  Dependency Inversion이 적용된 디자인과 그렇지 않은 디자인을 Application Architecture 레벨에서 바라본 모습은 아래와 같다.

[그림 9] Dependency Rot이 발생한 프로젝트

  [그림 9]는 Dependency가 정리되지 않아 의존성 부패가 발생한 Application Architecture의 모습이다. class들이 전부 스파게티처럼 엮여 있어 모두 재사용을 할 수 없는 상태다. 가독성도 떨어지며 유지 보수성도 좋지 않다. Dependency가 매우 강하기 때문에 각 클래스들은  Mock으로 대체될 수 없어 Unit Test도 불가능한 코드가 대부분이다. 그리고 이러한 디자인 균열은 계속해서 더 큰 균열을 불러온다.

[그림 10] Dependency Inversion이 적용된 프로젝트

  [그림 10]은 Dependency Inversion을 적용하여 의존성이 잘 정리된 Application Architecture이다. 상위 레이어와 하위 레이어의 높은 의존관계가 제거 되었고, 추상 레이어를 추가하여 두 레이어 모두 필요한 서비스를 추상 레이어를 의존하도록 하였다. 레이어가 모두 명확하게 정의 되었으며, 각 레이어는 통제된 인터페이스를 통해 응집성 있는 서비스만을 제공 되도록 디자인 되었다. 

 

  우리가 많이 알고 있는 MVC에 DIP가 적용된 모습은 아래 [그림 11]과 같다. 

[그림 11] MVC에서의 DIP

  요즘 MVC를 사용하면 Controller가 비대해진다느니 UI와 Business logic이 섞인다느니 하면서 MVC 자체에 문제가 있는 것처럼 말하는 사람이 많은데, 사실 내가 본 프로젝트들은 대부분 MVC 때문이 아닌 이런 의존성을 관리하지 못한 문제였다. 아마도 Dependency에 대한 이해가 부족한 상태면 MVC가 아닌 그 어떤 디자인 패턴을 적용해도 문제가 발생할 것이다.

  (예로 들면, 안드로이드에서 Activity를 Controller처럼 사용한다든지, 마치 대학교 1학년 때 절차 지향 언어인 C언어로 main함수 안에서 CUI 프로그램 개발하듯 모든 제어를 하나의 컨트롤러에서 중앙 집중 제어 방식으로 디자인하고 있었다. 혹은 dynamic behavior에 대한 고민 없이 물리적인 logic만 class로 나눈 static structure만 보고 만족해하는 경우도 있다. (MVC에 대한 자세한 이야기는 다른 글에서 설명하겠다.))

 

  다시 돌아가 DIP가 적용된 [코드 2] 예제에 대해 이야기해보자. SwitchButton과 Lamp에 Dependency만 Inversion한다고 의존성 문제가 해결될까?

 

[코드 3] Class Dependency

 

  위 [코드 3]에서 2line을 보면 SwitchButton이 concrete class인 Lamp를 직접 생성하고 있다. 의존성을 뒤집어 Interface를 참조하도록 하였지만, 아직 class dependency가 남아 있다. Factory Pattern을 적용하면 아래와 같이 Lamp와의 class dependency를 제거할 수 있다.

 

[코드 4] Factory Pattern 적용

 

  위 [코드 4]에서 Factory Pattern을 적용하여 SwitchButton의 Lamp class dependeny를 제거 하였다. Factory는  SwitchButton이 알지 못하게 SwitchButtonInterface를 구현한 어떤 concrete class를 반환할 것 이다.

[그림 12] Factory Pattern을 적용한 SwitchButton

  하지만, [그림 12] Factory Pattern을 적용한 SwitchButton을 보면 이제 Lamp 대신 Fatory를 강하게 Dependency 하고 있다. 이제 SwitchButton은 Factory와 한 몸이 되었다. 다른 프로젝트에서 재사용 하려면 결국 Factory를 수정해서 적용해야한다. 따라서 SwitchButton은 아직도 component가 되지 못한다. 또한 decoupling을 이런식으로 적용하게 되면 프로젝트 내의 모든 클래스들이 각각 자신만의 factory를 갖게 될지도 모른다. 

Dependency Injection (DI)

  의존성 주입(Dependency Injectioin, DI)라는 말을 들어 봤을 것이다. 용어만 보고 어려워하는 사람이 간혹 있는데, 사실 우리는 이미 프로그래밍을 처음 배울 때부터 사용하고 있었다.

 

'int main(int argc, char *argv[]) {}' 

 

  위 코드는 c언어에서의 entry point인 main 함수다. argument로 argc와 argv를 주입받고 있다. 이렇게 외부로부터 전달받는 것을 의존성 주입(Dependency Injection, DI)이라 한다. 그렇다면 이제 SwitchButton에 DI를 적용해 보자.

 

[코드 5] DI를 적용한 SwitchButton

 

  [코드 5]는 Constructor Inecjtion을 적용한 SwitchButton이다. (DI는 크게 Constructor Injection, Interface Injection, Method Injection으로 사용되지만, 좀 더 명확한 Constructor Injection을 선호한다.) 이제 SwitchButton은 Factory도 Lamp도 의존하지 않은 독립적인 존재가 되었다. 어떤한 concrete에 대한 dependency가 없으니 외부로부터의 변경사항에 대한 영향도가 매우 적어졌다. 따라서 SwitchButton은 어디에서도 수정 없이 곧바로 재사용 가능한 Component가 되었다.

 

Inversion of Control (IoC)

 DIP와 DI가 적용된 SwitchButton은 어떻게 사용될까? 아래는 SwitchButton을 사용하는 client 코드다.

 

[코드 6] SwitchButton을 사용하는 Client

 

  [코드 6]에서 Client가 Lamp를 생성해 SwitchButton에 주입하고 있다. Switchbutton은 Lamp를 모르게 됐지만, Client가 Lamp를 생성하고 SwitchButton과의 관계를 설정하고 있는 오히려 더 이상 해진 상황이 됐다. Client는 Lamp를 알 이유도 없으며 알아서도 안되는데 말이다.

[그림 12] 일반적인 프로그램에서 제어의 방향

  IoC가 적용되지 않은 일반적인 프로그램의 흐름은 [그림 12]처럼 entry point에서 다음에 사용할 오브젝트를 결정하고, 생성하고, 생성된 오브젝트의 메서드를 호출하고, 그 오브젝트 메서드 안에서는 또다시 다음에 사용할 것을 결정하고 호출하는 식의 작업이 반복된다. 각각의 오브젝트는 프로그램 흐름을 결정하거나 사용할 오브젝트를 구성하는 작업에 능동적으로 참여한다. 즉, Service를 사용하는 Client쪽에서 모든 걸 제어하고 있는 구조이다. 제어의 역전(Inverson of Control, IoC)이란 이러한 제어의 흐름을 Inversion하는 것을 의미한다.

 

[그림 13] 역전된 제어의 흐름

  [그림 13]은 역전된 제어의 흐름을 보여준다. entry point에서 IoC Container에게 모든 관계 설정에 대한 책임을 위임한다. 따라서 컴파일 타임의 static한 class dependency가 런타임의 dynamic한 object dependency로 변경된 것을 볼 수 있다. [그림 14]는 SwitchButton에 IoC개념이 적용한 모습이다.

 

[그림 14] IoC가 적용된 SwitchButton

 client가 IoC Container에게 필요한 Object를 요청하면 IoC Container는 SwitchButton이 필요한 object를 생성하고 관계를 wiring 하여 전달한다. 각 class들이 다른 class에 대한 dependency가 모두 사라졌으니 이제 모든 class들은 component가 될 수 있다. 그리고 분리된 모든 class들은 전부 mock으로 대체될 수 있어 testability도 높아졌다.  IoC만 바뀌면 dynamic 하게 전혀 다른 동작을 하는 프로그램이 될 수도 있다.

 

  가끔 IoC Container를 Factory Pattern과 혼동하여 IoC 관련 라이브러리를 그냥 Factory처럼 사용하는 경우도 많이 보았는데, Factory는 단순히 object를 생성하는 assembler에 가깝고 IoC Container는 거기에 제어의 역전 개념이 적용되어야 한다. IoC Container를 그냥 사용한다고 제어가 역전 되는 게 아니다.

Conclusion

  지금까지 의존성의 방향과 제어에 대한 설명을 하였다. 애플리케이션 개발에 있어서 기초적인 것은 아니지만 반드시 필요한 기본적인 개념이다. 모든 class에 대해 해당 개념이 적용되어야 한다는 뜻은 아니다. 분명히 재사용성과 유지 보수성이 좋아지겠지만 코드의 복잡도는 증가할 것이다. 하지만, 지속 가능한 소프트웨어를 개발하기 위해서는 의존성이 완전히 부패되기 전에 기본적인 관리가 필요하다. 기본적인 관리라 함은 Layer를 명확히 정의해야 하며, 각 Layer는 통제되어야 하고 약속된 인터페이스를 통해서만 커뮤니케이션을 해야 하는 것을 말한다. 즉, 적어도 각 Layer들끼리의 Dependency 관리는 반드시 필요하다 게 개인적인 생각이다. 물론, 간결한 코드와 우아한 코드의 밸런스를 적절히 맞추는 게 쉽진 않겠지만 의존성이 망가져버린 프로젝트를 유지 보수하는 것보단 나을 것이다.

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

if문을 없애는 디자인  (0) 2019.11.17
Data Access Object Pattern  (0) 2019.09.07
Singleton Pattern의 함정  (0) 2019.07.14
iOS Clean Architecture with Swift  (1) 2019.07.13

  if문은 특정 조건에 따라 프로그램을 제어하기 위해 사용한다. 그렇기 때문에 요구사항(조건)이 변경되면 영향도가 가장 많이 발생하는 코드 중 하나이다. 일반적으로 우리는 서비스를 분기할때 다형성을 통해 클래스를 디자인하여 (ex. strategy, policy pattern 등) if문을 없애곤 한다. 하지만 이렇게 나뉘어진 서비스들도 각 함수내에 목적 코드를 실행하기 전 제어를 위한 중복된 if문들이 생기게 된다. 경험상 모두가 알다시피 이러한 중복된 if 조건들은 변경사항이 발생하면 프로젝트내에 모두 찾기하여 일일이 수정해야 하기 때문에 분명 좋은 코드가 아니다.

 

  아래 몇가지 패턴은 내가 자주 쓰는 패턴 중 하나다. if 조건들을 오브젝트화 하여 핵심관심(Core Concerns)과 횡단관심(Crosscutting Concerns)을 분리(Computer Science, SoC)하고 중복코드를 없애 재사용이 가능한 상태로 만드는 디자인이다. (물론, AOP를 적용하면 좀 더 우아하게 처리 할 수도 있다.) 

Interceptor Filter Pattern

  코드에서 대부분의 if문들은 아래와 같은 형태를 띄고 있다. [그림 1]은 함수내에서 원하는 목적 코드를 수행하기 전 인증, 권한, 데이터 유효성 확인 등과 같은 전처리를 수행하는 코드를 예로 든다. 보통 이러한 전처리 코드들은 경험상 중복되는 경우가 많으며 변경 가능성이 높은 코드들이다. 

[그림 1] if 조건에 따른 필터 처리

  [그림 1]에서 글쓰기 권한을 '준회원'에서 '일반회원'으로 변경되면 우리는 해당 if문들을 찾아 일일이 수정해야한다. 예시가 간단하기 때문에 쉽게 수정할 수 있다고 생각하겠지만, if문이 많은 그리고 복잡도가 높은 실제 프로젝트내에서 중복 코드의 변경은 개발자의 실수가 충분히 발생할 수 있다. 그리고 이러한 횡단관심 코드들은 해당 함수의 핵심관심을 읽기 어렵게 만든다. 즉, 한 마디로 코드가 더러워 진다. 

 

  위와 같은 경우에는 Interceptor Fileter Pattern을 사용하여 조건들을 오브젝트화 하여  중복코드를 없애면 코드를 좀 더 깔끔하게 만들 수 있다.

[그림 2] Interceptor filter class diagram

  Interfceptor Filter pattern은 조건들을 오브젝트화 하여 중복 코드를 없애고 여러 조건들을 조합하여 재사용할 수 있도록 한다. 또한, 조건 코드와 목적 코드의 디팬던시가 제거됨으로 변경에 따른 영향도를 최소화 할 수 있다.

[그림 3] Interceptor filter sequence diagram

  시퀀스는 간단하다. Client는 FilterChain에 target과 filter들을 추가하고 FilterChain.doFilter()를 호출한다. FilterChain은 첫 번째 Filter를 호출하고 호출된 해당 Filter는 주어진 조건을 확인하고 판단하고 다시 FilterChain..doFilter를 호출하면 다음 Filter를 호출하게 되고 FilterChain.stopFilter를 호출하면 중단되며 target은 실행되지 않는다. 모든 filter들이 실행되어 통과되면 target은 그 때 실행될 수 있다.  각 요구사항에 따라 디테일한 동작은  변형하여 디자인 하면 된다. 수도코드를 보면 아래와 같다.

 

1. FilterChain

2. Filter

3. Target

4. Client에서 사용할 때

 

Chain of Responcibility Pattern

  [그림 1]처럼 하나의 목적코드를 실행하기 위해 전처리 조건들을 사용할때도 있지만, [그림 4] 다음과 같이 조건에 따른 여러 분기를 하여 그에 알맞은 목적 코드를 실행 할 때도 있다. 이럴때는 Chain of Responsibility Pattern를 사용하여 조건들을 오브젝트화할 수 있다. 

[그림 4] if 조건에 따라 목적코드를 분기하는 경우

  [그림 4]는 특정 조건에 따라 목적 코드를 분기하는 경우다. Chain of Responsibility Pattern의 컨셉은 자신의 조건이 맞으면 수행하고 그렇지 않으면 패스한다. else if로 만들수도 있고 if, if, 조건으로 만드는 등 각 요구사항에 맞게  Varient하여 디자인 하면 된다. [그림 5] 아래는 log level에 따라 각 Logger들이 로깅을 할지 말지를 판단하는 체인을 구현한 예시다. 

[그림 5] Logger class diagram

 

[그림 6] Logger sequence diagram

1. CommonLogger

2. concrete Logger 

3. Client 사용할 때

5. 호출 결과

consoleLogger.logMessage(logLevel: .info, message: "info message!!")

 -> console logger info message!!

 

consoleLogger.logMessage(logLevel: .error, message: "error message!!")

 -> console logger error message!! 

      file logger error message!! 

      error logger error message!!

 

1. binary 사용하는 방법

GoogleMaps.json파일 만든 후 아래와 같이 추가

{

    "3.1.0" : "https://dl.google.com/dl/cpdc/d308af63f78a5a1a/GoogleMaps-3.1.0.tar.gz"

}

 

Cartfile에 위에서 만든 GoogleMaps.json을 추가

binary "GoogleMaps.json" ~> 3.1.0i

 

2. 누군가 올려놓은 github 사용하는 방법

Cartfile에 아래 github 추가

github "leoneparise/GoogleMaps-Carthage" ~> 2.7

 

https://github.com/leoneparise/GoogleMaps-Carthage

'Log' 카테고리의 다른 글

NodeJS Typescript Jest와 Puppeteer로 UI Test 자동화 하기  (1) 2023.08.16
Flutter vs. React Native  (2) 2019.09.28
심심해서 Flutter로 만든 TODO앱  (0) 2019.09.23
cocoapods cache 삭제  (0) 2019.09.22
Flutter sqflite에서 like 사용하기  (0) 2019.09.22

[그림1. Swift로 만든 날씨 앱]
[그림2. React Native로 만든 날씨 앱]
[그림3. Flutter로 만든 TODO 앱]

  React Native는 작년에 해봤고, 몇 주전엔 Flutter로도 앱을 만들어 보았다. React Naitve는 Web(html, css, js)과 거의 유사한 구조로 개발을 할 수 있게 되어 있어서 러닝 커브가 매우 적었다. 동일한 앱을 만드는데 [그림 1] 익숙한 Swift로는 반나절 정도 걸렸고 [그림 2] react native로는 공부 포함 하루 이틀 정도 걸렸다. flutter는 Dart라는 새로운 언어를 학습해야 하지만, 이전에 java나 C++, C#, swift등 oop언어를 다뤄본적 있다면 쉽게 익힐 수 있다. [그림 3] Flutter로 만든 TODO앱도 공부 포함 이틀 정도 걸린 것 같다. 

 

  개인적인 취향으로 js를 별로 선호하지 않으며, open source 기반으로 서드 파티들에게 디팬던시가 강한게 싫어서 react native보단 flutter가 더 마음에 든다. 앞으로 개인적인 프로젝트는 flutter로 진행할 것 같다. 

 

  Flutter React Native
만든 회사

Google

카메라, 네비게이션 기능 같은것을 구글이 직접 모든것을 지원함

Facebook

페이스북이 모든것을 지원하지 않으며, open source 기반으로 외부 라이브러리에 의존해야함.

따라서, 플랫폼이 뭔가 큰 변경사항이 있을때 각 서드파티들이 호환성을 맞출때까지 기다려야함.

공식 출시 2018 년 12 월 Google I / O 2015 년 3 월, F8 컨퍼런스
프로그래밍 언어 Dart (Object Oriented Programming) Javascript (Functional Programming)
핫 리로드 지원 지원
생태계

아직 성숙하지 않음

커뮤니티가 별로 없고 인터넷에 자료가 얼마 없음

성숙함

커뮤니티 활발하고 자료도 많고 open source도 많음

성능 AOT를 지원하며 Native로 배포됨, 성능 좋음 JIT로 컴파일 되며, VM에서 동작하기 때문에 Flutter 보단 느림 (하지만, 일반적인 동작엔 체감 못느낌)
UI

Indicator, Button, Label 같은 기본 위젯들이 iOS, Android와 UI가 동일하게 표시됨.

UI는 Dart를 사용하여 declarative syntax 방식으로 표현함.

각 위젯들이 Host 기반으로 각각 ios, android UI에 맞게 표현됨.

UI는 Javascrip로 declarative syntax 방식으로 표현하며, color같은 style은 웹과 유사한 css로 디자인함

결론  기존에 android나 ios같은 native programming을 했던 사람이라면 react native보단 flutter를 더 잘 맞을 듯. 웹을 했던 사람이라면 html, css로 UI를 작성하고 로직을 javascript로 작성하는 것이 익숙할테니, 이와 비슷한 react native가 잘 맞을 듯.

 

 

  주말에 심심해서 flutter 공부하면서 만든 간단한 TODO + D-Day앱

공부 + 구현하는데 2틀정도 걸린듯. 개인적으로 react native보단 괜찮은거 같다. 

'Log' 카테고리의 다른 글

NodeJS Typescript Jest와 Puppeteer로 UI Test 자동화 하기  (1) 2023.08.16
carthage에서 googlemaps 사용하기  (1) 2019.10.20
Flutter vs. React Native  (2) 2019.09.28
cocoapods cache 삭제  (0) 2019.09.22
Flutter sqflite에서 like 사용하기  (0) 2019.09.22

 rm -rf ~/Library/Caches/CocoaPods; rm -rf Pods; rm -rf ~/Library/Developer/Xcode/DerivedData/*; pod deintegrate; pod setup; pod install;

  보통 flutter에서 query를 사용할때, query에 ?를 사용하고 whreArgs로 대체하는데, 이상하게 Like만 해당 방법이 먹히지 않느다.  그래서 아래와 같이 whreArgs를 사용하지 않고 $로 바꾸니 정상적으로 동작한다. 아마 버그인듯하다. 

 

await db.rawQuery("select * from tbl_todo where title like '%$keyword%'");

 

'Log' 카테고리의 다른 글

NodeJS Typescript Jest와 Puppeteer로 UI Test 자동화 하기  (1) 2023.08.16
carthage에서 googlemaps 사용하기  (1) 2019.10.20
Flutter vs. React Native  (2) 2019.09.28
심심해서 Flutter로 만든 TODO앱  (0) 2019.09.23
cocoapods cache 삭제  (0) 2019.09.22

+ Recent posts