이제 23년이 이틀 남았다. 였었는데. 어제 완성을 못해서 오늘이 23년의 마지막 날이다.
23년에 있었던 업무적 경험과 개인적 경험에 대해서 회고해 본다.
GMS는 케이타운포유의 WMS로서 Global warehouse Management System을 의미한다. 케이타운포유가 200여 개 국으로 배송을 하고 있어서 Global이라는 단어가 추가된 WMS이다. 많은 노력을 통해 이제 GMS를 사용해서 다양한 종류의 배송을 처리할 수 있게 되었다.
입고, 집품, 포장, 배송, 출고 등의 프로세스를 갖는 GMS는 일반적인 웹 애플리케이션에 비해 복잡도가 높다. 주로 컬렉션에 대해서 어떤 기능을 수행해야 하는 경우가 많다. GMS 개발을 도와주면서 코드리뷰와 몇몇 개발 원칙 등에 대한 노력을 많이 했던 것 같다.
코드리뷰의 목적은 코드의 품질을 높여서 버그, 장애를 최소화하고 향후 변경 비용을 낮추는 것
이라고 생각한다. 코드의 품질은 여러 가지로 설명할 수 있겠지만, "가독성"과 "유지보수성"이 가장 적합한 설명이라고 생각한다. 꾸준히 PR(Pull Request)를 통해 코드리뷰를 했지만 다음과 같은 어려움이 종종 발생했다.
코드가 쉽게 이해가 되지 않는다
테스트를 구현 후 추가하는 경우 테스트 추가가 어렵다
YAGNI, Prematured Refactoring테스트가 잘 깨진다
코드가 이해가 어려운 이유는 맥락(Context) 때문이라고 생각한다. 코드를 작성하는 사람의 머리에는 있는 맥락이 리뷰하는 사람의 머리에는 없다. 심지어 코드를 작성한 개발자도 시간이 지나면 맥락을 잃게 되어 자신이 이전에 작성한 코드가 이해가 어렵게 되기도 한다.
이런 경우 나는 다음과 같은 절차를 취했다.
코드를 천천히 읽으면서 이해 시도(필요한 경우 scratch refactoring을 하기도 하고, PR을 commit 단위로 보기도 했다)
궁금한 점, 개선 사항 등을 메모
코드를 작성한 개발자와 오프라인으로 리뷰
서로가 이해되는 코드로 개선(삼각측량)
이후에 좀 더 리뷰를 잘할 수 있는 방법을 다음과 같이 정리했고, 사용하고 있다.
초안 검토(Review Draft): 초기에 잦은 빈도의 논의를 통해 방향성에 대해서 합의한다. 아직 코드가 없는 초기는 적은 노력으로 생산성에 큰 영향을 미칠 수 있는 시기이다. Step-Down Rule을 준수하는 의사 코드(Pseudo-code)를 한글로 쉽게 읽을 수 있도록 작성하여 함께 하는 개발자들과 리뷰하는 것이 복잡한 기능 구현에 도움이 된다.
구현
그래도 코드리뷰가 어려울 것 같으면 Code walk-through를 선택적으로 진행한다. 화면을 공유하고 팀원들을 대상으로 어떤 코드를 왜 변경했는지 설명한다. 화면을 공유하고 IDE에서 변경한 코드를 보여주면서 설명을 덧붙인다.
코드리뷰
대부분의 라이프사이클에서 후반부가 수정 비용이 비싸다. 그래서 코드 리뷰도 초안 검토, Code walk-through를 활용하면 리뷰를 수월하게 할 수 있다.
"이것이 필요할 것이다"라는 생각이 든다면 바로 뭔가를 하기보다 "이게 필요 없다면 어떻게 되나?"를 먼저 생각해 보라고 한다. 도널드 크누스의 말(premature optimization is the root of all evil)처럼 조속한 무엇인가는 많은 어려움을 낫는다. 리팩터링도 마찬가지다. 코드 리뷰를 하다 보면 메서드 추출 등으로 좋은 구조를 추구했지만 이해하기 어려운 코드를 자주 본다.
이럴 때 나는 다음과 같은 과정을 진행한다.
inline method: 추출한 메서드를 다시 합친다(tidy first에서는 one pile)
관련된 기능, 변수들이 모이도록 라인을 위, 아래로 이동시킨다(slide statement)
서로 구분되는 코드 블록들 사이에 빈 라인을 추가해서 구분한다.
빈 라인으로 구분된 블록들에 코멘트를 추가해서 이해가 되도록 한다.
이제 다시 의도를 드러내는 이름을 갖는 메서드로 추출한다(이 과정은 좋은 이름을 지을 수 있을 때만 진행한다. 그렇지 않으면 나중에 다시 인라인 하고 싶은 충동을 느낄 것이다).
자신이 할 수 있다고 메서드로 추출하고, 복잡한 stream function을 사용하면 그 코드를 읽는 사람들을 힘들게 한다. 할 수 있다고 하기보다 꼭 필요한 경우에 해야 한다. 그것도 잘해야 한다.
대개 애플리케이션 로직은 다음과 같은 구조를 갖는다.
1. 요청을 처리하기 위해 필요한 데이터를 읽어오기
2. 데이터를 가지고 처리
3. 변경된 데이터를 저장
1,3번이 샌드위치에서 빵에 해당하고, 2번이 샌드위치에서 "속/토핑"에 해당한다.
애플리케이션 로직도 이와 같은 구조로 분리를 하면 우리는 2번 "속"에 해당하는 코드에만 집중할 수 있다. "속'만 잘 테스트해도 큰 효과를 얻을 수 있다.
우리는 코드가 "빵 구조"에 맞는다, 안 맞는다라고 말하며 "빵 구조"를 지향하고 있고, 좀 더 DDD스럽게 로직을 구성해서 테스트 가능성을 높이면서 개발을 했다.
SW 개발은 복잡성을 다루는 일이다. SoC를 준수하여 우발적 복잡성과 본질적 복잡성을 분리하면 빵과 속이 분리된다.
이 구조를 만들기 어려운 경우들이 있는데 "DDD Trilemma"라고 검색을 해 보면 좀 더 자세히 알 수 있다.
GMS에는 컬렉션을 가지고 어떤 처리를 하는 로직이 많았다. 이런 경우 다음과 같은 방법으로 Value Object를 추출하고, Value Object가 갖는 데이터에 대해서 동작하는 코드도 해당 Value Object에 위치시켜서 응집도를 높이고, 캡슐화를 통해 불변식이 준수될 수 있도록 했다.
Introduce Parameter Object: 값 객체로 추출할 변수를 사용하는 기능을 메서드로 추출 후 인자로 전달된 변수를 값 객체로 추출하고, 해당 메서드를 값 객체로 이동시킨다.
Extract Delegate for attribute: 관련된 일련의 엔터티의 속성들과 그 속성들에 동작하는 행위를 값객체로 추출한다.
2개의 이상의 관련된 데이터를 변경하는 코드 블록을 메서드로 추출하면 새로운 값 객체가 생성된다
케이타운포유의 운영툴(Backoffice)은 C# 기반 윈도즈 애플리케이션으로 구현한 것을 사용하고 있었다. C#은 그래도 어떻게 공부하면 좀 이해하겠는데 윈도즈는 이제 내겐 너무 어렵다. 나는 2006년 8월 즈음부터 Mac을 사용했고, 이후 윈도즈는 꼭 필요한 경우(공인인증서 등)에만 사용했다. 개발자로서 경력의 시작할 때 Microsoft VisualStudio와 VisualC++를 사용했지만 이제 내게 윈도즈는 넘을 수 없는 수준의 장벽이다.
대부분의 운영툴에 대한 변경 요청은 조회와 관련된 것이어서 아파치 제플린으로 요구사항을 반영해 왔다. 하지만 데이터의 변경을 수반하는 요구사항을 위해서는 제플린으로는 불가했다.
우리는 새로운 기능은 우리가 제일 잘할 수 있는 Java, Spring-boot, JPA, Rest, Graphql, React 등을 이용해서 웹 애플리케이션(새 운영툴 이름은 머큐리 Mercury임)으로 제공하기로 했다. 현업에서는 새로운 웹 애플리케이션 사용에 많은 거부감이 있었다. 변화를 수용해야 하는 부담이 있었고, 초기 머큐리에 오류가 있을 때는 한동안 이전 시스템을 사용하는 등의 어려움이 있었다. 지속적인 노력으로 올해는 머큐리에 많은 기능을 제공해서 현업분들에게 많은 가치를 제공했다.
위에서 설명한 GMS는 OAS(OpenAPI Specification)와 육각형(Hexagonal) 아키텍처를 이용했었다. 이때는 개발자들의 기술역량이 지금처럼 높지 않을 때여서 좀 더 많은 규칙을 정의하고 이를 준수하면서 구현하는 방식을 취했다. 또 GMS는 교육이 잘 된 정직원들만 사용하는 것이 아니라 일이 많을 때는 일일 아르바이트 직원들도 사용을 해야 해서 매우 사용성이 편하고, 조금의 오류도 있어서 안되었기에 보다 엄격한 조건을 준수하면서 개발하는 방향을 택했다.
하지만 머큐리를 시작할 때는 개발자들의 역량도 많이 올라왔고, 머큐리는 GMS와 달리 내부에서 운영자들만 사용하기 때문에 버그가 있으면 바로 고쳐서 다시 배포하면 되는 상황이었다. 그래서 머큐리에서 우리는 최대한 수직 슬라이스(Vertical Slice) 방식으로 구현하고 노력했다. 컨트롤러에 유스케이스를 구현하고 필요하면 다른 클래스로 추출하는 방식을 취했다. 대부분의 조회성 기능은 컨트롤러에서 유스케이스의 모든 기능을 구현하는데 큰 어려움이 없었다.
조회 기능을 구현할 때 육각형 아키텍처를 도입하면 아래 그림과 같은 구조를 갖는다.
하지만 수직 슬라이스를 도입하면 아래와 같이 컨트롤러 하나에 기능 구현이 완료된다.
우리는 한 회사 내에서 한 가지 통일된 아키텍처를 사용하기보다는 업무에 맞는 아키텍처를 사용하기로 했다.
모든 업무에 하나의 해결책을 사용하기 위해서는 모든 경우를 검토하고 그중 가장 복잡한 요구사항을 만족시킬 수 있는 해결책을 채택해야 하는데, 그 해결책은 가장 복잡하고, 무겁고, 느린 해결책이어서 대개의 경우 과한 해결책이 되기 때문이다.
내가 케이타운포유에 입사하기 전에 이미 "상품 2.0"이라는 이름으로 기획을 하고 있었다. 현재의 시스템이 카탈로그와 전시 상품을 효율적으로 관리하기에 적합하지 않고, 수정이 어려우니 새로운 시스템을 만들어서 이관하겠다는 계획이었다. 이런 Big Bang 방식의 성공률이 20~30%로 매우 낮다고 알려져 있다. 그리고 기존 시스템을 운영하면서 별도로 신규 시스템을 구축할 만큼 개발 인력이 여유가 있지도 않았다.
그렇다면 점진적, 반복적으로 이관해야 한다. 이를 위해 고민을 하다가 나는 "1.5 전략"이라는 것을 정리해서 구성원들과 공유를 했다.
"1.5 전략은"
기존 코드나 테이블 변경을 하지 않고, 대신 새로운 코드나 테이블을 추가해서 문제를 해결한다.
새로운 기능은 추가된 코드, 테이블을 사용해서 새로운 요구사항을 충족시키고
기존 코드나 테이블의 변경을 최소화하거나, 변경을 안 해서 기존에 동작하는 기능에 영향을 미치지 않도록 한다.
는 것을 골자로 한다.
이렇게 하면 중복된 데이터나 코드가 발생 가능하지만 중복된 영역을 향후 변경 시 격리할 수 있을 것으로 기대하였기에 이 방식으로 진행 중에 있다.
케이타운포유의 대고객 서비스(https://kr.ktown4u.com/)는 사실 열악하다. 우리 시스템이 좋아서 고객들이 우리 시스템을 많이 사용한다기보다는 K-POP 자체가 워낙 인기가 많고, 우리 회사 영업조직의 역량이 좋아서 다양한 우리의 고객들이 우리 시스템을 사용하는 것이다. 일반 고객이 우리 사이트에 들어와서 이것저것 보다가 구매(자연 매출)로 이어지기보다는 대작이 나올 때 이벤트 트래픽으로 짧은 시간에 많은 고객들이 대규모로 구매를 한다.
올해 케이타운포유는 공동구매 등을 통한 영업 외에 자연 매출에 신경을 쓰기 시작했다. 이를 위해
- 리뷰, 빅배너 관리, 이벤트 응원 댓글, K-Style 메인, 실시간을 변경 가능한 GNB 메뉴, 콘텐츠..
등을 추가해 가고 있다.
이를 위해서 다양한 이해당사자들과 효율적인 협업, 빠르게 가치 있는 기능을 사용자에게 제공하고 개선해 나가기 등을 위한 다양한 고민과 시도가 있었다.
우리 회사의 상품은 사용자 입장에서는 단순해 보이지만 내부적으로 굉장히 다양하고 복잡하다. 그래서 새로운 기능을 론칭한 후에 상품상세 등에 적용을 하면 대부분의 경우는 문제가 없지만 특정한 경우 문제가 발생하는 경우가 많았다. 처음에는 이런 문제가 발생하면 롤백하고 문제를 찾고, 해결책을 적용한 후 배포하였다. 꽤나 시간이 오래 걸리는 방식이었다. 또한 한두 번 오류를 경험한 경우 배포 전에 좀 더 안전을 기하려고 노력을 했다. 이 중에는 내가 구현한 내용을 타 부서의 구성원이 찾아주기를 바라는 것과 같은 경우도 있었다. 마치 개발자가 사소한 기능을 변경, 배포하고 문제를 겪은 후에 얻은 트라우마로 문구 하나 정도를 변경하고도 QA에게 많은 영역에 대한 거대한 검증을 요구하는 것처럼...
개발 팀장님 한분이 멋진 아이디어를 내주셨다. 우리는 이 방식을 "Black List"라고 부른다. 방식은 다음과 같다.
선 오픈 후 대응
모든 경우를 완벽하게 검증하느냐고 늦게 오픈하지 말고
다소 부족한 경우가 있을 것 같아도 먼저 오픈하고
이슈가 있을 때 해당 기능만 기존 기능을 사용하도록 기능 토글을 사용한다
이 방식은 매우 효과적으로 작동하고 있다.
GMS, 머큐리, 고객 경험 개선을 위한 주요 과제 등에는 대표님, 영업, 기획, 현업 등 다양한 이해당사자가 함께 한다. 이때 신뢰가 부족하거나 서로에게 친숙하지 않아서 잘잘못을 따지는 경우가 생기거나, 우선순위/일정 등에서 불편한 논쟁을 해야 하는 경우가 있다. 이런 경우 대개 개발자들은 반대를 해야 하는데도 따르거나, 자신들도 불필요한 주장을 펴는 경우가 있다. 이런 일이 있을 때마다 다음과 같은 원칙을 세우며 준수하자고 얘기를 했다.
우리는 예측 가능성이 매우 낮은 VUCA의 시대에 살고 있다.
다양성과 전문성이 높아져서 소품종 대량 생산 시대에 통하던 소수 천재들의 계획으로 성공하는 것이 어려워진다.
국민소득이 3만 불을 넘어서면서 다품종 소량 생산을 해야 한다.
혁신을 의한 시도 90%가 실패한다고 한다.
그러니 누가 옳고 그름을 따지기보다는 실패할 수도 있다는 마음으로 작게 나눠서 빠르게 실험하며 진전해야 한다.
빠르고 안전하게 완료할 수 있는 업무 수행으로 신뢰 구축을 먼저 해야 한다.
중요한 일을 먼저 하면 일정 내에 중요한 기능 구현이 완료되고, 배포에 Blocker가 없으면 배포를 한다. 이방식을 통해 덜 중요한 일로 논쟁하는 낭비를 제거할 수 있다.
계속해서 밀리는 덜 중요한 일들은 일정이 종료되면 안 할 수도 있다
이걸 나는 "아사패턴(Starvation Pattern)"이라고 부른다.
타 영역의 전문가들의 결과물(기획, UX 등)에 대해 같이 논의는 할 수 있으나
잘못했다고 할 수는 없다
합의되면 부족해도 진행해야 한다.
해 보고, 완성해 보기 전에는 잘할 수 있는지, 잘하는 것인지 알 수 없다. 잘하기 위해 빨리 해봐야 한다. 다 해 봐야 무슨 이슈가 있을지? 어떻게 대응해야 할지? 알 수 있다. 문제와 해결책을 이해하기 위해 어떤 이슈가 있는지 예측하는 가장 좋은 방법은 직접 해 보는 것이다.
Make it work, Make it Right.
Kent Beck
이 말과,
소프트웨어의 첫 번째 가치는 향후 요구사항 변경을 효과적으로 수용해야 하는 것이다.
라는 말이 처음 내게는 상호배타적으로 여겨졌다. 히지만 이제는 두 말이 같은 것을 의미한다고 생각한다.
TDD에서 실패하는 테스트를 추가한 후에는 최대한 빠르게 동작하게 만들라고 한다. 이를 "작은 골프 게임(Little Golf Game)"으로 표현하기도 하고, duct tape programming으로 표현하기도 한다.
왜 최대한 빨리 구현하나?
첫 번째 이유는 빠르게 하면서 잘할 수는 있지만 둘을 동시에는 할 수 없기 때문이다. 서로 배타적이기에... 그러니 빨리 동작하게 만들고, 후에 좋은 구조를 갖도록 개선해야 한다.
두 번째 이유는 아무리 예측을 잘해도, 실제로 모든 것을 구현해 보기 전에는 어떤 이슈들이 있고, 어떻게 해결할 수 있는지 알 수 없기 때문이다. 그래서 모든 이슈와 해결책을 직접 경험해 보기 위해 빠르게 완료해야 하는 것이다.
그 후에 리팩터링을 통한 설계 개선을 해서 향후 변경 비용을 낮추는 좋은 설계를 갖도록 해야 한다. 이 단계는 개발자의 역량에 따라 많은 차이가 있을 수 있는 단계이다.
뛰어난 개발 역량이 있어서 리팩터링을 통해 코드 악취와 중복을 없애고, 좋은 패턴을 적용하지 못하더라도 가독성 높은 코드는 작성할 수 있다. 정 안되면 코멘트를 많이 작성하더라도 말이다.
결국 잘하기 위해서는 빠르게 가독성을 갖춘 코드를 작성하는 것이 우선이다. 이후 리팩터링, 설계, 디자인 패턴 역량을 동원하여 품질을 높이는 것은 두 번째 단계인 것 같다. 이 두 가지가 항상 같이 수행되어야 한다면 이제 막 개발을 시작한 개발자는 당분간(?)은 배포를 하여 팀에 기여할 수 없을 것이다.
완전하지는 않지만 우리는 3개의 스쿼드로 나눠서 개발을 하고 있다. 그러던 중 자신은 하나의 스쿼드에만 속해있으면 좋겠다는 의견이 있었다. 내가 어느 스쿼드에 속해 있어서 그 스쿼드에 할당된 일만 할 수 있으면 몰입도 되고, 마음도 편할 것 같다. 하지만 우리처럼 인력이 부족한 경우는 다양한 업무에서 기여할 수 있어야 한다. 통합되어 E2E로 사용자에게 가치를 전달하는 것에 집중해야 한다. 내가 담당한 컴포넌트가 잘 동작하는 것에만 집중해서는 안된다. 전체가 제대로 동작하는 것에 집중해야 한다.
우리 팀 에이스에게 처음에는 그 분만 레거시를 알아서 CS를 처리할 수 있어서 미안하지만 CS를 많이 부탁했었다. 하지만 올해는 CS를 처리할 수 있는 경력자가 입사해서 많은 도움을 주고 있다. CS를 분담하고 개발도 분담할 수 있게 되었다. 우리 팀 에이스는 다른 팀원들이 일을 잘할 수 있도로 도와야 한다. 초안 검토 등에서 에이스가 도와야 한다. 에이스의 속도는 저하될 수 있겠지만, 팀으로서 전체의 속도는 상승할 것이다. 에이스가 바쁘면 팀의 병목이 된다.
이 강연은 수강생들이 회사에서 겪는 실제적인 문제에 대해
레거시코드에 테스트 추가하기
TDD로 기능 추가하기
리팩터링 하기
등을 같이 해 보는 것을 목표로 했었다. 하지만 온라인 진행, 내 역량 부족 등으로 서로 주고받기보다는 강사로 참여한 나와 기원님이 우리가 아는 것을 알려주는 단방향으로 진행되었다.
아쉬웠지만 모두 내가 부족한 문제에 기인한 것이라고 생각하고 다음에 좀 더 작게 나눠서 시도해 보고 싶다.
Presenting TDD는 TDD 강연 혹은 TDD를 선물한다는 의미로 준비한 강연이다. 강연 요청을 받고 1시간 30분가량을 거의 대부분 라이브 코딩으로 진행하면서 전통적인 TDD(Classical, Inside-out TDD), Outside-in TDD, Outside-in with Mock 등 3가지 방식으로 TDD를 시연했었다. 본래 오프라인 강연이었으나 온라인으로 변경되면서 수강하는 분들의 표정을 통한 피드백을 볼 수 없어서 아쉬웠던 강연이었다.
강연을 준비하면서 스스로 정리하려고 쌓아두었던 주제들을 정리할 수 있어서 좋았다. 이 정리를 기반으로 매주 조금씩 브런치 스토리 "지속 가능한 소프트웨어 개발하기"를 쓰고 있다.
코드리뷰 강연은 여러 차례 해서 이제 그만해야 하겠다는 생각을 하지만 할 때마다 약간씩 변화가 추가되어 가치가 있다고 생각한다. 올해도 2차례 외부 강연을 했고, 코드리뷰와 관련된 생각과 자료는 조금 더 쌓였다.
24년에는 GMS가 케이타운포유의 물류 전반에 적용되어 회사에 크게 기여하고,
머큐리가 기존 운영툴을 대체하여 협업 운영 업무가 효율적으로 되고,
고객 경험 개선을 위한 다양한 실험이 빠르게 진행되고,
향후 변경 비용을 낮추는 높은 품질을 갖는 아키텍처를 추구하는 케이타운포유 개발 조직이 기대된다.
그리고 개인적으로는 업무 수행을 통해 얻은 경험과 학습한 내용을 보다 많이 공유해서 도움을 주고받는 나를 생각해 본다. 내가 가진 것을 공유하고, 또 내가 풀어야 할 문제를 접하면서 나도 성장하는...
올해 케이타운포유에서는 개발자를 매우 소극적으로 채용했었다.
24년에는 보다 적극적으로 채용을 하려고 한다.
기본기가 충실하여 성장 가능성이 높은 1,2년 차 백엔드 개발자,
이커머스 상품에 대한 경험과 이해가 높은 경력 개발자,
데이터분석 플랫폼, DevOps에 관심이 많은 개발자
등을 채용하려고 한다.
관심 있는 분들에 msbaek@ktown4u.com으로 메일 부탁드립니다(커피챗도 삼성역, 분당 정자동 주변에서 가능합니다).
마지막으로 이 글은 읽는 모든 분들이 24년 새해에 모두 건강하시고, 행복하시길 기원합니다.