이번 주 개발 및 공부 정리 - 06/52

Reading time ~6 minutes

하루하루 코딩에만 매달리다보니 5주차 정리하는걸 깜빡했다. 심지어 이번 주도 넘어갈 뻔 했다. 최근에 팩토리오라는 게임을 시작했는데 매우매우 재밌다. 회사다니는 지인말로는 집에서도 일하는 기분이 드는 게임이라 했지만 나는 회사를 안다니니 그러려니 재밌게 하고있다.

공부 📖

  • 러스트 코딩 밋업에 가서 튜토리얼을 해봤다. 컴파일언어 백만년만에 해보는데 음 내취향은 아닌 것 같다.
  • 파이썬 코딩의 기술 책을 다시 한번 읽었다. 라이브러리 제작에 도움이 되는 정보를 몇몇 얻을 수 있었다.
  • 실전 파이썬 프로그래밍 책을 다 읽었다. 몇 달 전에 나갔던 파이조그에서 김슬님한테 받았는데 중급 파이썬 개발자한테 꼭 필요한 내용으로 가득했고 개인적으로 대만족했다. 덕분에 당장 도움이 되는 정보도 많이 얻었다.
  • 함수형 패러다임을 공부했다. 예전엔 좀 막연했다면 공부할수록 점점 재밌어진다.

개발 🖥

chatterbox 개발

  • 2주간의 커밋 기록에서 보듯 진짜 열심히 했다. 그동안 기 모으던게 한번에 빵 터진 느낌. 하루하루 코딩하는게 무척 재밌었다.
  • TDD을 실천해봤는데 게임 개발에선 모르겠으나 라이브러리 개발에서는 확실히 도움이 되고있다. Test case를 먼저 작성한 뒤 테스트를 통과하도록 모듈을 만들고 통과하면 리팩토링을 하는데 이 리팩토링 과정에서 내가 정한 테스트에 벗어나는지 알면서 작업하니 걱정없이 쭉쭉 코딩할 수 있었다.

2월 1주

route() 동작 방식

chatter.route()를 통해 데이터가 전달되면 상태 머신이 규칙에 따라 적절한 함수를 실행해 Response를 반환한다. 이 때 규칙에 따른 적절한 함수를 찾는데 일단 딕셔너리로 구현했고 규칙 네이밍을 'action_src_dest'같은 식으로 했으나 매우 못마땅했다. 이는 결국 나중에 바뀌게된다.

__all__ 버그 픽스

__init__.py에 포함되는 __all__ 리스트는 from chatterbox import * 구문을 실행할 때 임포트되는 클래스를 명시적으로 관리한다. 라이브러리를 만드는게 처음이다 보니 규칙을 몰랐는데 알고보니 리스트 원소는 문자열이어야 했다.

기초적인 Response 동작 테스트 케이스

chatterbox를 만들면서 제일 중요하게 생각했던게 chatter.route()고 그 다음이 Text('안녕') + Keyboard(['버튼1', '버튼 2'])처럼 동작하는 Response 구현이었다. 이 테스트 케이스를 통해 내가 어떤 동작을 원하고 있는지 먼저 정의하고 구현에 들어가니 한결 수월했다.

함수형 패러다임을 적용시켜보다

본래 Message의 하위클래스인 Text, Photo, MessageButton의 조합을 다음처럼 테스트했다. 3개만 있을 때, 2개만 있을 때 경우의 수를 일일이 적어줬는데 나중에 스펙이 바뀔 때나 Keyboard와의 조합까지 테스트 할 생각을 하니 너무 우아하지 못했다. 💩 마침 생각났던게 itertoolscombinations()(소마 코딩 시험때도 무척 요긴하게 사용했다.) 였고, iterable안에 있는 iterable 원소들을 flat하게 이어주는 chain()과 함수형의 기본인 reduce()map() 그리고 currying을 가능하게 해주는 functoolspartial()operatoradd까지 사용해서 개인적으로 무척 마음에 드는 테스트 구문을 만들 수 있었다. ✨

Message와 Keyboard의 조합 테스트

예상대로 Message와 Keyboard를 조합하게 됐다. Message는 3가지 타입이 있고 Keyboard는 2가지 타입이 있다. 일단은 Message + Keyboard 형태를 테스트 하려 했고 이를 위해 (3 * 2 * 1) * (2 * 1) = 12가지의 테스트 케이스가 필요했다. 심지어 나중엔 타입의 개수가 더 늘어날 수도 있다. 그랬기에 앞서 사용했던 방식에서 matrix 결과를 만들어 주는 itertoolsproduct를 사용해 지저분한 코드없이 테스트 구문을 만들 수 있었다.

Message와 Keyboard의 조합을 구현 할 때는 둘은 서로 다른 클래스였기 때문에 하나의 새로운 상위 클래스를 만들어서 상속해줘야했다. 그래서 최종적으로 Response 모듈은 다음과 같은 구조를 가지게 되었다.

Response
├── Message
│   ├── Text
│   ├── Photo
│   └── MessageButton
└── Keyboard
    ├── ButtonType
    └── TextType

2월 2주

Lazy evaluation method chaining한 테스트

제일 중요한 기능인 chatter.route() 구현에 들어갔다. 역시 시작은 테스트 케이스 작성부터 들어갔는데 테스트 코드 중 제일 마음에 드는 모습이 됐다. 아쉽게도 파이썬에서는 indent가 중요한 의미를 갖고있어 다른 언어들처럼 dot method를 이어서 쓰려면 한줄에 적거나, \를 사용해 이어주거나 ()로 감싸는 수밖에 없다.

처음엔 아래같이 함수를 만들어 테스트 했는데 심히 보기 안좋았다.

def check_after_route(user_key, response, src, dest, messages, home=False):
    assert ... == src
    assert ... == dest
    assert isinstance(response, ...)
    for msg in messages:
        assert msg in ...
    assert 'keyboard' in response
    if home:
        assert ... == home_keyboard

check_after_route(user_key, response, '홈', '소개', ['text'])
check_after_route(user_key, response, '소개', '홈', ['text', 'photo'], True)

그래서 처음엔

check = checker(chatter).user(user_key)
check.res(res).src('홈').dest('소개').msg(['text'])
check.res(res).src('소개').dest('홈').msg(['text', 'photo']).home()
check.res(res).src('홈').dest('홈').msg(['text', 'button']).home()

위와 같이 식으로 검사기 인스턴스를 만들어 두고 깔끔하게 method를 chaining하는 방법으로 검사했는데 만족스러웠다.

문제점이 있다면 dest('홈'), msg(['text'])가 반복된다는 점이었고 이를 해결하고자 check = checker(self.chatter).user(user_key).dest('홈').msg(['text']).home()처럼 보편적으로 자주 검사되는 항목끼리 엮어 검사기를 만들어준 다음 chatter.route()에서 반환된 response를 전달해 검사하려 했으나 dest(), msg(), home() 메서드가 즉시 평가되는 바람에 내부적으로 response가 필요한 부분에 정보가 없어 제대로 검사가 되지 않았다.

그래서 Lazy chaining method 키워드로 검색해 ChainChainWrapper방식으로 js의 lodash를 구현한 pydash 프로젝트를 발견했다. 소스에서 아이디어를 차용해 클래스 기반으로 동작하는 do()를 evalution trigger로 사용한 테스트를 만들 수 있었다.

좋아서 올린 트윗 사진

Text input 처리하기

카카오톡 자동응답 API 스펙을 보면 주관식 답변을 받을 수 있게 Keyboard타입으로 text형식이 있다. 이렇게 비정형화된 답변이 들어올 수도 있었기에 기존 방식의 FSM을 그대로 사용할 순 없었다. 그래서 이러저러한 고민을 많이 해봤는데 일단은 yield구문을 이용한 방식을 사용하고자한다. 내가 생각하기에 최대한 깔끔한 방식같다. (그러나 지금은 완전 잘못됐다는 걸 알았지만 이는 나중에 포스팅하는 걸로…)

route() 구현

Red - Green - Refactor 방식을 따라서 처음엔 완전 날로 썼다. (소스) 이를 1차적으로 개선했는데 군데군데 side effect가 발생하고 text input context를 처리하는 방법 등이 마음에 들진 않는 상태다. (소스) 2차적으로는 rule을 찾는 행위를 Rule객체에게 양도하고 context를 클래스로 변경하고 state update에 일관성을 주어 군살을 더 뺐다. (소스)

Rule 동작 변경

SQLAlchemy처럼 동작하도록 바꿨다. action(), src(), dest()메서드로 필터된 결과를 포함하는 Rule객체가 반환되고 one(), first(), all()로 그 결과를 받아볼 수 있다.

다음주 계획 📅

  • 이력서 업데이트 (이번엔 진짜로)
  • 함수형 패러다임 적용시켜본 경험 포스팅
  • chatterbox 스펙 변경 사항 문서화

텐서플로와 시퀀스 모델을 배우기 좋은 <딥러닝의 정석> 리뷰

본 게시글은 한빛미디어의 나는 리뷰어다 이벤트로 제공받은 [딥러닝의 정석](http://www.hanbit.co.kr/store/books/look.php?p_code=B5128867520) 책에 대한 리뷰입니다.![]({{ site.baseur...… Continue reading

기술 문서 정리 - 06

Published on March 24, 2018