2015년 7월 6일 월요일

스터디서치에 적용한 Flux

최근에 스터디서치 웹을 React 프레임워크와 Flux 구조를 사용해 리뉴얼했다. Flux 구조를 사용하면서 느낀 Flux의 장점과 겪었던 문제, 해결방법을 짧게나마 공유해보려고 한다.

Flux의 장점

웹은 모바일 앱처럼 한 화면에 하나의 MVC셋이 아닌 한 화면에 여러 컴포넌트들이 존재할 여지가 있다. Flux는 이러한 웹 특성에 잘 맞는 구조다.

1. Single source of truth

위 사진에서 내 프로필 정보를 사용하고 있는 컴포넌트는 오른쪽 위의 마이페이지 링크와 중간 위쯤 있는 마이페이지 내에서의 네비게이션, 그리고 프로필 수정 페이지 총 세개다. 이 세개의 컴포넌트는 각기 다른 모델 객체를 사용하는 것이 아닌, 스토어에서 제공하는 하나의 데이터만을 사용한다.

2. Event handling

프로필 사진이나 이름을 변경하게되면 스토어에 변경사항이 저장되고, 스토어는 변경사항을 알림받아야 할 컴포넌트들에 데이터가 변경되었음을 알려준다. 따라서 한 컴포넌트에서 변경 이벤트가 발생할지라도, 이 컴포넌트가 데이터 변경을 알리기 위해 다른 컴포넌트들을 참조할 필요가 없다.

부수적인 이벤트 처리

Flux구조의 장점을 지키려면 데이터는 단방향으로 흘러야하며, 컴포넌트가 데이터를 직접 참조하거나 변경해서는 안된다. 그렇다면 이벤트 완료 후 완료 알림과 같은 부수적인 이벤트들은 어떻게 처리할까? 처음에는 별 고민없이 ChangeListener에서 처리를 해주었다.
...
componentDidMount: function() {
  MySelfStore.addPhotoChangeListener(this._onPhotoChange);
  MySelfStore.addNameChangeListener(this._onNameChange);
  ...
},
componentWillUnmount: function() {
  MySelfStore.removePhotoChangeListener(this._onPhotoChange);
  MySelfStore.removeNameChangeListener(this._onNamgeChange);
  ...
},
_onPhotoInput: function() {
  MySelfActions.changePhoto(...);
},
...
_onPhotoChange: function() {
  ToastActions.show("사진이 변경되었습니다");
  this.setState({mySelf: MySelfStore.getMySelf()});
}
...
이런 구조는 몇가지 단점이 있는데, 우선 데이터 변경이 해당 컴포넌트에서 발생된 이벤트에 의해 이뤄진 것인지 확인하지 못한다. 또 이벤트 발생 부분과 완료부분이 떨어져있어 코드의 가독성도 떨어진다. 마지막으로 저렇게 하다간 수많은 ChangeListener가 존재해 코드가 상당히 무거워질 것이다. 이를 해결하기 위해 Promise 패턴을 이용하였다.
...
componentDidMount: function() {
  MySelfStore.addChangeListener(this._onMySelfChange);
},
componentWillUnmount: function() {
  MySelfStore.removeChangeListener(this._onMySelfChange);
},
_onBookmarkButtonClick: function() {
  MySelfActions.changePhoto(...).done(function() {
    ToastActions.show("사진이 변경되었습니다");
  }
},
_onMySelfChange: function() {
  this.setState({mySelf: MySelfStore.getMySelf()});
}
...
이렇게 함으로써 onPhotoChange, onNameChange와 같은 ChangeListener들을 줄여 코드를 가볍게 할 수 있었고, 이벤트 완료와 데이터 변경을 나누어 처리할 수 있어 가독성도 높힐 수 있었다.

2015년 5월 21일 목요일

하스켈 도전기

이 글에서 글쓴이는 하스켈을 배우면 좋은 점 몇가지를 제시한다. 첫 번째는 데이터를 먼저 생각하는 연습을 할 수 있어 전반적인 코딩 능력을 향상시킬 수 있다는 것이다. 두 번째는 하스켈이 가독성도 좋고 문제에 집중할 수 있게 해주는 언어라 하스켈로 개발을 하면 생산성이 높다는 것이다. 이 글을 보고 예전에 배우려다 포기했던 하스켈에 다시 도전해보고 싶은 마음이 생겨 공부해보았다. 목표는 이 알고리즘 문제를 푸는 것으로 설정했다.
예전에 공부할 때에도 느꼈고, 이번에 공부할 때에도 느끼는 거지만 하스켈에는 지금까지 배워본적 없던 새로운 개념이 너무 많다. 그리고 더 절망스러운건, 메인 함수의 형태를 이해하려면 이 새로운 개념들을 거의 다 이해해야 한다는 것이다. Type system, IO, Functor, Applicative Functor, Monad.. 물론 이해하지 않고도 다른 코드들을 흉내내면 여차저차 코딩을 할 수 있겠지만, 그러면 배우는 의미가 없을 것 같아 우선 개념의 큰 덩이들을 이 교재를 통해 빠르게 습득하려고 노력했다. 그래도 양이 많아 시간이 오래 걸렸다. 하스켈 배우기를 더 빠르게 할 수 있는 방안이 무척 궁금하다.
큰 개념들을 모두 익히고 나서 세세한 부분 (어떤 generic함수를 사용해야 하는가. 함수의 형태는 어떤 것들이 있는가 등등)은 검색해가면서 문제를 풀어봤다. 그리고 나온 결과물이 놀라웠다. 일단 가독성이 정말 파이썬보다 훨씬 좋았다. 코드의 길이는 파이썬 코드보다 길어도, 코드가 무엇을 하는지를 안봐도 함수가 무엇을 하는 함수인지가 한눈에 보였다. 또, 문제에 집중할 수 있다는게 이런걸 말하는 것인지, 아니면 단순히 운이 좋았던 것인지는 모르겠지만 예전에는 떠올리지 못했던 더 멋진 풀이법을 구해 문제를 해결할 수 있었다. 이 부분은 앞으로 몇 번 더 하스켈로 코딩을 해봐야 제대로 말할 수 있을 것 같다. 그리고, 실행속도가 파이썬의 실행속도의 오분의 일이였다. 앞으로 알고리즘 문제를 풀 때
재미없는 C++ 말고 하스켈로 풀어도 실행속도에서 걸리는 일은 없을 것 같다.
실제 실무에서 쓸 기회가 있을지는 모르겠지만, 알고리즘 문제 풀 때에는 틈틈히 사용해봐야겠다.

결과물

import Control.Monad

readInt = do
    input <- getLine
    return $ (read input :: Int)

getInts = do
    input <- getLine
    return $ map (\n -> read n :: Int) $ words input

getDragonChar n
    | n `mod` 6 == 0 = 'F'
    | n `mod` 6 == 1 = 'X'
    | n `mod` 6 == 2 = getDragonAt n
    | n `mod` 6 == 3 = 'Y'
    | n `mod` 6 == 4 = 'F'
    | n `mod` 6 == 5 = getDragonAt n

getDragonAt n
    | n `mod` 2 == 1 = getDragonAt ((n-1) `quot` 2)
    | n `mod` 12 == 8 = '-'
    | otherwise = '+'

main = do
    n <- readInt
    forM [1..n] (\_ -> do
            _:begin:end:[] <- getInts
            putStr $ [getDragonChar x | x <- [begin-1..begin+end-2]]
            putStr "\n"
        )