1. 이벤트는 NestedScrollingChild에서 발생, NestedScrollingParent, Behavior 순으로 전달된다
2. Behavior에서는 onStartNestedScroll -> onNestedScrollAccepted -> onNestedPreScroll -> onNestedScroll -> onStopNestedScroll 순서로 이벤트를 받는다.
3. AppBarLayout은 스크롤 이벤트에 따라 숨겨지거나 나타나야할 경우 onNestedPreScroll에서 스크롤을 소모(Consume)하고 자신의 Offset을 조정한다.
4. RecyclerView는 onNestedPreScroll에서 스크롤을 소모한 만큼 스크롤 움직임이 감소한다.
5. AppbarLayout의 ScrollingViewBehavior를 사용하는 View는 AppBarLayout의 Offset이 변경될 경우 그에 맞춰 자신의 Offset을 조정한다.
Teddy Choi
2016년 9월 10일 토요일
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"
)
2014년 12월 11일 목요일
BynamoDB: High Level DynamoDB Interface for Python
AWS에서는 DynamoDB라는 NoSQL 데이터베이스 서비스를 제공한다. 파이썬 프로젝트라면 AWS에서 제공하는 boto를 이용해 DynamoDB를 관리할 수 있다. boto의 DynamoDB 인터페이스는 DynamoDBConnection 부분과 그 외의 부분으로 나눌 수 있다. DynamoDBConnection은 저수준 인터페이스로 DynamoDB 명세를 충실하게 구현해 두었고, 나머지는 이를 쉽게 이용할 수 있게 만든 고수준 인터페이스다.
프로젝트에서 DynamoDB를 이용하기로 결정해서 boto를 사용하기로 했다. 하지만 boto를 이용해 관리해보니 몇가지 불편한 점이 있었다. 가장 크게 불편했던 것은 테이블의 schema를 정의할 수 없다는 것이었다. 테이블의 schema를 정의할 수 없다보니 속성 이름과 아이템 속성에 어떤 타입이 들어가야 하는지 헷갈리는 경우도 있었고, 고수준 인터페이스를 이용해 가져온 아이템의 속성값은 문자열 키를 이용해 접근해야만 했다. 다른 불편함은 Complex Lookup을 포함한 몇 몇 기능들이 고수준 인터페이스에서 숨겨져 있다는 것과 실제 사용하기에도 불편하다는 것이었다.
어딘가에는 이런 불편함을 해결해준 패키지가 있을 것 같아서 다른 DynamoDB 파이썬 인터페이스를 찾아보다가 PynamoDB를 발견하게 되었다. 개발 중 가장 크게 불편하게 느꼈던 schema부분을 해결해줄 수 있었다. 상당히 잘 짜여진 인터페이스처럼 느껴져서 사용해볼까 하다가 말았다. boto와는 동떨어진 독립적인 프로젝트 이면서도 DynamoDB의 모든 기능을 제공하지 않아 제공하지 않는 기능을 사용하려면 boto를 사용해야 했기 때문이다. Complex Lookup이 그 중 하나인데 이 기능을 사용할 수 있는 방법이 PynamoDB에 아예 없었다. 또 boto에서 제공하지 않는 기능은 똑같히 제공하지 않고 있어 고쳐 사용하려면 boto, PynamoDB 둘 다 고쳐야 했기 때문이다. boto에서 List와 몇가지 타입을 제공하지 않고 있었는데, PynamoDB도 똑같히 제공하지 않고있었다. (관련 boto issue). 한마디로 DynamoDB를 사용하는데 두개의 독립된 패키지에 의존하게 되는게 불편했다.
그래서 PynamoDB를 패치할까, boto에 기능을 추가하고 더 쉽게 사용할 수 있는 인터페이스를 만들어 사용할까 하다가 후자를 선택했다. 그리고 나와 같은 불편함을 느끼는 사람이 있을 것 같아서 파이썬 패키지로 GitHub에 공개해두었다. BynamoDB에서 확인할 수 있다. 이름의 의미는 그냥 boto에 의존성을 가지고 있어서 B로 시작하게 했다. BynamoDB의 특징은 각 프로젝트에서 사용하고 있는 boto에 의존해 사용할 수 있다는 것이다. 사용하고 있는 boto에서 List 타입 패치를 적용하면 BynamoDB에 ListAttribute만 추가해 사용하면 된다. 또 하나는 모든 API가 boto의 DynamoDBConnection을 이용한다는 것이다.
내 책임 하에 있는 패키지를 처음 작성해 본 것이라 어설픈 점이 많이 보일 수 있다. 그리고 우리 프로젝트에 사용하는 것을 우선해 우리 프로젝트에서 사용하지 않는, 혹은 중요도가 떨어지는 기능은 아직 구현하지 않았다. 예를 들면 batch get API라던가 filter expression에서의 몇 몇 연산자들이 있다. 사용하고 싶은데 어색한 부분이나 추가하고 싶은 기능이 있으면 알려주시거나 패치해서 풀리퀘 부탁드립니다 :)
2014년 9월 3일 수요일
코드의 중복제거보다 코드의 기능구분이 우선되야한다.
비교적 최근에
Clean Code
를 읽었다. 이 책은 깨끗한 코드를 작성하기 위한 많은 원칙들을 제시하고 있는데, 이 중에 하나가 소프트웨어에서 모든 악의 근원은 코드 중복이라고 말하며 설명하는 DRY원칙(Don’t Repeat Yourself)이다. 코드에서 중복이 존재한다면 이는 한가지 기능단위로 구분할 수 있으니 함수로 추출해서 가독성도 높히고 관리도 쉽게 하라는 요지다. 하지만 저번 주의 경험을 통해 코드 중복을 제거할 시 중복되는 코드를 정말 한 개의 기능단위로 정의하고 함수로 추출할 수 있는지에 대한 판단이 동반되어야 한다고 생각하게되었다.
스터디서치 모바일 웹을 만들 때 이야기다. 모바일웹이라도 같은 서비스이다보니 기존의 데스크탑 웹과 동일한 자원과 유저 플로우를 이용하게 된다. 따라서 우리는 모바일 웹을 개발할 때 빠르게 개발하기 위해서 모바일 웹이 데스크탑웹과 같은 Django View, View 함수를 사용하게하고, 모바일 웹의 HTML페이지 요청은 html경로에 mobile/만 추가되도록 하였다. 이를 통해 우리는 서버단에서 중복되는 코드 없이 모바일웹을 작성할 수 있었다.
하지만 저번 주에 한가지 문제가 발생했다. 데스크탑 웹을 리뉴얼하면서 회원가입 페이지를 모달로 띄워주기로 결정하였고, 따라서 데스크탑 웹에서는 회원가입 페이지를 보여주기 위한 View 함수가 필요 없어지게 되었다. 순간 모바일웹의 존재를 잊어버린 나는 그 View 함수를 지워버렸고, 그 상태에서 릴리즈하여 내가 12시간 뒤 ‘아차’ 할 때까지 같은 View 함수를 사용하던 모바일웹에선 회원가입페이지를 볼 수 없었다.
실제로 모바일 웹을 제공함에 있어 데스크탑 웹에서 사용하던 View를 거의 그대로 이용해도 될만큼 동일한 기능들이 대부분이다. 따라서 회원가입 페이지를 보여주는 함수의 기능단위를 “회원가입에 필요한 페이지를 보여주는 함수"라고 정의해도 큰 문제가 없을 수 있다. 하지만 좀 더 좋은 디자인은 "모바일웹에서 회원가입 페이지를 보여주는” 함수와 “데스크탑웹에서 회원가입 페이지를 보여주는” 함수를 분리하고, 그 함수들의 같은, 혹은 중복된 부분의 코드를 함수로 추출해 두 개의 함수에서 호출하는 디자인일 것이다. 이와 같이 기능단위를 어떻게 구분할 지 결정하는 것은 많은 노련미가 필요할 것 같고, 아마 Clean Code 저자분들은 너무 당연한 이야기라 적어두지 않았나 싶다.
2014년 8월 23일 토요일
블로그를 시작하려합니다.
어렸을 적부터 남들에게 나를 드러내는 일을 싫어하곤 했습니다. 내 경험과 생각을 남들에게 공유하기엔 아직 깊이가 부족한 사람이라는 부끄러움과 가만히 있어 중간이라도 가자는 생각때문이였던 것 같습니다. 하지만 이러한 태도가 저의 성장에 장애물이 된다는 것을 요즘들어 많이 느껴서 고치려고 합니다.
이를 위한 첫번 째 노력으로 블로그 포스팅을 시작합니다. 블로그에 쓴 글이 얕고 틀린 생각이라 나중에 혼자 이불킥 할지라도 매 일, 매 주 경험하고 느낀 것을 기록해두고, 내 생각을 전달하기 위해 차분히 글을 작성하고, 남들에게 보여준 후 피드백을 받고 그 과정에서 한 번 더 생각해 보는 경험이 저에게 많은 가치를 줄 것이라 생각하기 때문입니다. 더 나아가 누군가에게 저의 글이 도움이 되었으면 하는 바람, 또 그런 글을 쓸 수 있는 사람이 되기 위함도 있습니다.
포스팅의 내용은 주로 회사에서의 생활과 개발 과정에서 배운 점과 느낀 점이 될 것입니다. 아낌없는 조언 부탁드립니다 !
피드 구독하기:
글 (Atom)