앱 안정성을 향한 끊임없는 여정

서너 달 전 커머스 스타트업의 CTO인 아는 동생이 고민 상담을 해왔다. 운영 중인 앱에서 비슷한 버그가 여러 번 발생하고 중요한 기능이 어느날 갑자기 작동을 안하는 일이 반복되는데, 근본적으로 어떻게 개선할 수 있냐는 질문을 했다. 마침 올해 우리 팀의 눈에 띄는 성과가 앱의 안정성을 높인 일이다. 코드와 인프라와 개발 프로세스를 꾸준히 발전시키려는 노력을 한 끝에 앱의 안정성이 꾸준히 개선되고 있다. 동생네 팀의 현 상황을 좀 더 자세히 들어본 후 단기, 장기적으로 실행할 수 있는 여러 방안을 제안했다.

앱 안정성이라는 것은 사람마다, 팀마다 다르게 해석할 수 있기 때문에 측정하는 방법도 다양하다. 사용자 입장에서 보면 crash-free session, crash-free user 비율 등을 추적할 수 있다. 사용자에게 직접적인 영향을 주는 이런 수치는 출시 이후에 발생하는 일에 대한 정보다. 하지만 출시 이전부터 측정할 수 있는 안정성에 대한 수치도 매우 중요할 뿐더러 이 둘은 깊게 연관돼 있다. 일반적으로 기능 개발, 기능 테스트, 회귀 테스트를 거쳐 최종 사용자에게 코드가 배포된다. 회귀 테스트 도중, 혹은 배포 이후 발견되는 회귀 버그(regression bug)는 앱 안정성에 매우 치명적이다. 직접적으로 코드를 수정하지 않았다고 여겼던 부분에서 예상 못한 버그가 발생했다는건 1차적으로는 코드의 문제지만 코드 리뷰, 자동화 테스트, QA를 거친 후에 발견됐다는건 우리 팀의 프로세스에 버그가 빠져나갈 구멍이 있다는 뜻이므로 각 단계별로 개선점을 찾아서 메꿔야 한다. 각 단계별 개선 방법과 목적이 다르므로 그에 맞춰 대책을 도입하고 장기적으로 유지해야 한다.

1. 코드 품질을 높이고 테스트하기

가장 먼저, 버그의 근원지인 코드의 품질을 개선하고 테스트를 작성하는 것이 가장 확실하고 장기적으로 유지 가능한 해결책이다. 엉클밥이 말하길 “수학에서는 무언가가 옳다는걸 증명하지만 과학에서는 무언가가 틀렸다는걸 증명한다. 과학은 테스트 커버리지가 높기 때문에 신뢰할 수 있다. 소프트웨어는 과학이기에 테스트로 우리의 프로그램이 잘못됐다는걸 증명할 수 있다. 고로 커버리지가 낮은 소프트웨어는 신뢰할 수 없다.”(원문)고 했다. 내가 만든 소프트웨어가 의도한대로 동작하는걸 확인할 수 있는 방법은 테스트를 작성하는 것이다. 앱 개발 환경에서 테스트와 아키텍처는 밀접한 관계가 있다. iOS 앱 아키텍처의 기본값이라고 할 수 있는 MVC는 테스트하기 썩 적합하지 않다. 이런 문제를 해결하기 위해 다양한 아키텍처가 생겨났다. 만약 MVC가 현재 주 아키텍처라면, 중장기적인 계획을 세워 다른 아키텍처로 리팩토링을 감행하자. 또한 좋은 아키텍처는 개발자의 실수를 줄여주고 올바른 방향으로 코딩하는걸 더 쉽게 해준다.

테스트는 속도와 통합 정도를 기준으로 종류가 나뉘므로 상황과 사정에 맞게 우선 순위를 두고 도입하자. iOS의 XCTest 프레임워크는 유닛 테스트와 UI 테스트를 지원한다. 유닛 테스트는 모듈 단위로도 실행할 수 있기 때문에 일반적으로 실행 속도가 매우 빠르다. 유닛 테스트는 추가적으로 Application Test와 Library Test로 구분되니 기술적으로 적합한 것을 선택하면 된다. 또한 엑스코드가 유닛 테스트의 결과로 코드 커버리지를 알려주기 때문에 DerivedData에 생성되는 xcresult 파일을 읽어서 모듈별 커버리지를 쉽게 수집할 수 있다. 반면에 UI 테스트는 앱 전체를 빌드한 후 시뮬레이터에서 실행해야 해서 일반적으로 테스트 수행 시간이 길지만, 코드 일부분이 아니라 마치 사람이 앱을 사용하는 것처럼 기능을 테스트할 수 있기 때문에 중요한 기능이 정말 제대로 동작하고 있는지 확실하게 검수할 수 있다는 장점이 있다.

코드 리뷰도 코드 품질을 높이는 방법 중 하나다. 코드 리뷰를 잘하는 법은 구글이 공개한 내부 가이드가 많은 도움이 된다. (번역 요약본)

2. 인프라 구축하기

자동화 인프라

앱을 지속적으로 개발하고 배포하고 운영하려면 CI/CD라고 부르는 자동화 인프라가 필요하다. 가장 기본적으로 배포 자동화, 테스트 자동화를 할 수 있다. 매 커밋마다 배포를 할 수도 있고 매일 특정 시간에 배포를 할 수도 있고 필요에 따라 개발자가 원할 때 배포를 할 수 있다. Siri한테 시킬 수도 있다. 또한 풀리퀘스트 기반의 개발 프로세스를 가진 팀이라면 풀리퀘스트마다 테스트를 자동으로 실행시켜서 새 코드가 메인 브랜치에 작업이 합병되기도 전에 기존 기능이 망가지지 않았는지 확인할 수 있다. 자동화는 개발과 연관된 모든 프로세스의 기반이 된다. 자동화가 돼있는 만큼 개발자가 반복적인 작업에 시간과 노력을 낭비하지 않을 수 있고, 자동화된 테스트는 회귀 버그를 잡아낼 수 있는 1차적인 방어선이므로 인프라를 꼭 구축하자.

실시간 품질 모니터링

사용자에게 신기능을 배포한 후 서비스 운영 도중 문제가 발생했을 때 신속/정확하게 대응하기 위해서는 모니터링 기반이 갖춰져 있어야 한다. 최종 사용자가 느끼는 서비스 품질을 나타내는 수치를 Quality of Experience Metrics(이하 QEM) 라고 한다. 구글 애널리틱스 류의 사용성 분석 툴과는 결이 다르다. UI/UX를 개선하기 위해 쓰는게 사용성 분석이라면, QEM은 서비스의 품질을 실시간으로 감시하고 문제가 발생했을때 해결에 도움을 주는 도구이다. 대표적인 QEM 값으로는 X의 소요 시간, Y의 성공률/실패율 등이 있다. 예를 들어 앱의 로딩 시간을 기록하여 사용자가 실제로 경험하는 서비스 속도를 수치화할 수 있다. 그 수치를 근거로 행동을 할 수도 있고 얼마나 개선됐는지도 확인할 수 있다. 또한 핵심적인 서버 API 같은 중요한 작업의 성공/실패를 기록하여 실시간으로 모니터링하면 서비스 운영중 발생하는 변칙 상황을 빨리 파악할 수 있다.

모니터링 할 때는 도출된 수치가 현상을 관찰하는 목적에 부합하는지 주의해야 한다. 다양한 변수를 고려해서 값을 뽑아내고 분석해야 한다. 특정 네트워크 요청이 실패하는 횟수가 늘었다 하더라도 사용자가 늘어서 실패가 많은 것인지, 아니면 시스템에 문제가 생겨서 실패가 증가한 것인지에 따라 다른 행동을 취해야하기 때문이다. 또한 성능 관련 수치를 분석할 때도 신중하게 값을 봐야한다. 스마트폰의 성능, 지역별 네트워크 품질에 따라 사용자는 우리의 예상과는 전혀 다른 경험을 할 수도 있다. 그래서 의미있는 특징에 따라 모집단을 분할하는게 좋을 수도 있다. 또한 단순 평균 값 뿐 아니라 P50, P90, P95 값 등 여러 가지 통계적으로 유의미한 수치를 함께 봐야 현상을 더 종합적으로 파악하고 그에 맞는 행동을 취할 수 있다.

각종 현황판

QEM 같은 실시간 모니터링 외에도 조직에 유용한 현황판을 구축할 수 있다. 현황판은 본래 목적 외에도 개인적으로 성취감과 동기 부여 차원에서 긍정적인 효과가 있었다. 모듈별 코드 커버리지를 표시하는 현황판에 커버리지가 너무 낮으면 빨강색이나 노랑색, 목표치를 달성하면 초록색으로 표시를 했는데 처음에는 빨강색 투성이던 현황판이 점차 초록색으로 물들어갔다. 테스트를 작성하는 일은 신규 기능 개발처럼 결과물을 눈으로 볼 수 없는데, 현황판을 이런식으로 구성하니 테스트 코드 작성 업무도 마치 결과물을 눈으로 볼 수 있게 되는 듯한 효과가 생겼다. 클라이언트 개발자라서 그런지 시각적인 결과물 덕분에 더 열의를 가질수 있던 것 같다.

다만, 현황판은 수시로 들어가서 보는 용도로 만들면 안된다. 그랩의 전 CTO였던 마크 포터는 “현황판을 계속 쳐다보고 있지 말아라. 현황판을 언제 들여다 볼지 알려주는건 경보의 역할이다” 라고 했다. 현황판을 보기 좋게 만드는 것에 그치지 말고, 이상 현상이 생기면 자동으로 알림을 받을 수 있게 만들어서 실수로 중요한 사건을 놓치거나 대응이 늦어지지 않게 하자.

3. 업무 문화 개선

프로세스는 조직마다 각양각색이기 때문에 모든 조직에 일률적으로 적용할 수 있는건 없으므로 올해 우리팀이 했던 것 중 효과가 좋다고 생각하는 사례 두 개를 정리했다.

QA 팀과 긴밀한 협업

우리 팀은 업무의 20%를 코드 리팩토링 및 구조 개선에 투입하기 때문에 신규 기능이 아닌 기존에 잘 동작하던 기능도 종종 영향을 받는다. 상반기에는 이런 리팩토링 업무에 QA 담당자가 할당되지 않고 개발 팀 내부에서만 진행됐다. 그러니 정보가 잘 공유되지 않아서 QA 팀이 정기적으로 회귀 테스트를 수행할때 어디를 유의해서 봐야하는지 몰라 중대한 회귀 버그가 몇 번 발생했다. 그 후로는 개발 팀과 QA 팀이 기술 과제의 영향 범위와 수동 테스팅의 필요성을 함께 평가하기 시작했고, QA 팀 모르게 기존 부분에 영향을 주는 일을 없애고자 했다. 최근에는 좀 더 자동화하여 개발자가 본인의 MR에 영향 범위를 표기하면, 회귀 테스트가 시작될 때 수정된 부분이 자동으로 수집되어 QA 팀에 보여지도록 하고 있다.

또한 그랩 앱은 피쳐 플래그(feature flag) 기법을 도입하고 있어서 기능을 전부 완성하지 않고도 마스터 브랜치에 코드를 병합할 수 있다. 하지만 현실에서는 피쳐 플래그로 관리할 수 없는 개발 업무도 있다. 이럴 때는 어쩔수 없이 피쳐 브랜치에서 개발해야 하는데, 단일 앱에 120명 이상이 기여하고 있다보니 병합이 늦어질수록 코드 충돌의 가능성이 매우 높고, 코드 충돌을 해결하는 도중 새로운 버그가 생길 수 있는 위험도 커진다. 따라서 이런 류의 기능도 QA 팀과 잘 조율해서 개발이 완료되면 최대한 빨리 수동 테스트를 거쳐 신속하게 병합할 수 있도록 프로세스를 갖춰가고 있다.

주간 버그 회고 미팅

우리 팀은 매주 한시간 다같이 그 주에 발생한 버그를 돌아보며 버그의 원인이 무엇이고 앞으로는 어떻게 다르게 해야 똑같은 문제가 발생하는걸 원천 차단할 수 있을지 논의한다. 버그를 유발한 사람을 찾거나 책임을 묻는 일은 일어나지 않는다. 누구나 실수할 수 있지만 똑같은 실수가 두 번 이상 발생하는 건 팀의 문제라는 생각에 기반한다. 테스트가 미흡했다는 걸 알게 되면 테스트를 보완한다. 여러 개발자가 동일하게 실수하는 부분이라면 문서를 만들어서 지식을 공유하거나 아키텍처를 수정한다. 새로 도입해야 하는 기술이나 프로세스가 있다면 누군가가 추가적으로 조사해서 후속 조치를 한다. 경험상 이 회고 미팅에서 활발하게 토론이 오고 갔고, 팀원 간에 정보와 지식 교류가 생겨 팀이 다함께 성장한다. 버그가 줄어서 회고 미팅이 짧게 끝날 때는 뿌듯함과 성취감을 느낀다.

다중 방어선과 자동화 우선주의

각종 대책들은 서로를 보완한다. 거친 가루는 체로 여러번 걸러야 고운 가루를 얻을 수 있듯이, 어느 방법 하나만으로 모든 버그와 문제를 잡아낼 순 없기 때문에 여러 종류의 대책을 다중으로 배치해야 한다. 그렇기 때문에 각 방법들이 가지는 장점과 한계까지 제대로 파악해서 빈 구멍을 메꾸는 것이 중요하다. 예를 들어 아무리 테스트 코드를 짰더라도 완벽할 순 없다. 테스트 케이스가 미흡하거나, 코드의 로직 자체가 잘못 작성되어있을 수도 있고, 테스트가 실패한 걸 발견한 개발자가 코드를 임의로 수정해서 통과한 것처럼 만들 수도 있다. 그래서 자동화 테스트도 다른 방법으로 보완해야 한다. 코드 리뷰로 여러 개발자가 수정 사항을 확인하게 함으로써 실수를 찾아낼 수도 있다. 뿐만 아니라 유닛 테스트만으로 코드의 일부분만 독립적으로 테스트하다보면 실제 환경에서 제대로 동작하지 않거나 크래시가 나는 상황이 생기기도 한다. 그래서 좀 더 통합적인 UI 테스트로 이를 보완할 수 있다. 그 뒤로도 QA 팀을 거치는 등, 새로 짠 코드가 배포되기 전 문제를 미리 발견할 수 있게 방어선을 겹겹이 구축한다고 생각하고 접근하자.

새로운 프로세스나 정책을 도입할때는 자동화하는 방안을 최우선적으로 모색해야 한다. 자동화하지 않은 프로세스가 늘어날수록 팀에 족쇄가 채워진다. 개발자가 수동으로 해야했던 단순 반복적인 업무가 어느날 소리소문 없이 사라지는걸 경험해본 사람이 있을 것이다. 그러면 최악의 경우, 예전에 발생했던 버그나 문제가 또 일어나게 된다. 프로세스는 자동화해야 장기적으로 지속 가능하다는 사실을 언제나 되새겨야 한다. 업무 프로세스든 앱의 기능이든 새롭게 추가하는건 쉽지만 무언가를 빼는 건 훨씬 어렵고 누구도 선뜻 나서서 하지 않기 때문에, 새로운 프로세스는 최대한 자동화할 수 있는 방법을 찾고 수동으로 할 수 밖에 없는 업무는 최대한 소극적으로 도입한다.

결론적으로,

앱의 안정성을 높이고 꾸준히 유지하기 위해서는 코드 품질부터 시작해서 개발 프로세스, 테스트, 출시, 운영 단계까지 모든 영역에서 개선을 해야만 한다. 팀원들이 문제 의식을 공유하는 것부터 시작이다. 다만 모든걸 한번에 할 수는 없으므로 팀의 상황과 역량, 우선 순위에 따라 단기/중기/장기로 나눠서 계획을 세우고 꾸준히 새로운 시도를 하면서 배워나가자. 앱 안정성을 높이면 궁극적으로 사용자의 만족도가 올라갈 뿐 아니라 개발자도 자신감이 생기고 행복해진다.

Tags: tests, app stability, regression  

테스트 코드 작성하면 좋은 점

코드를 수정하고나서 풀리퀘를 올렸는데 테스트가 실패한 것을 보고 내가 놓친 부분을 찾아서 수정하는 경험을 했었고 테스트의 가치를 피부로 느낄 수 있었다. 우리 팀은 커버리지를 KPI로 잡는다. 업무 시간의 20%는 테스트 작성, 빌드 타임 개선, 모듈화 등의 기술 과제에 할당한다. 테스트 코드를 작성하고 유지하는 경험을 통해 느낀 점이 있다.

더 나은 아키텍처를 좇게 한다

테스트가 어려운 코드는 좋지 않은 구조일 가능성이 매우 높다. 테스트 가능한 코드는 의존성에 대한 고민이 녹아있다. 특히 의존성 역전 원칙을 철저히 지키면서 소스 코드의 의존성을 정리하고 관리해야 하는데 이에 대해서는 엉클밥이 의존성 규칙dependency rule을 통해 잘 설명하고 있다.

내 코드 개밥먹기dogfooding

테스트를 작성하면 본인이 짠 코드를 사용자로써 직접 체험해볼 수 있다. 마치 집을 짓고 내부를 꾸미다가 밖으로 나와서 외부에서는 어떻게 보이는지 확인하는 것이다. 창문은 어떻게 생겼으며 문은 어떻게 열고, 입구는 어디에 있는지 등등. 창문과 입구는 public API와 같다. 집 내부는 private 요소들이다. 테스트에서 객체의 동작을 검사할 때는 public API를 쓴다. 객체를 직접 생성도 해보고 설정을 하고 public 메서드를 호출 하거나 프로퍼티에 접근해본다. 그러면서 내가 만든 코드의 사용성을 확인해볼 수 있다. API가 엉성하면 테스트를 작성하기도 까다롭고 고통스럽다. ‘이런 것까지 테스트해야돼?’ 또는 ‘이 케이스는 왜 이렇게 테스트하기가 어렵지?’ 등등 여러 상황을 맞닥뜨린다. 따라서 API의 사용성에 신경쓸 수 밖에 없고, 신경 쓰다보면 더 심플하고 직관적인 API를 만들 수 있게 된다. 더 심플하다는건 여러가지 의미가 있는데 내 생각엔 public 메서드나 프로퍼티의 갯수가 적은게 특히 중요한 것 같다.

심플하고 직관적인 API 설계

기능 개발 → 테스트 작성 → 기능 수정 → 테스트 수정의 과정을 반복하다보면 API를 만들때 설계 단계에서 미리 큰 그림을 그려볼 수 있는 근육이 생기는 것 같다. 가령 기능 수정을 조금 하고나니 기존 테스트를 전부 폐기하고 새로 짜야할때가 발생한다. 이러면 설계가 처음부터 좋지 않았을 가능성이 높다. 쉽게 바뀌지 않는 정책적인 것을 public으로 공개하고, 상세 구현부는 숨기거나 외부로 옮겨야 한다. 설계가 잘못되면 테스트를 작성하는게 불가능하거나, 어찌저찌 되더라도 유지하는게 괴롭다. 이런 일이 반복되면 객체의 구조나 역할, 테스트 가능성testability을 고려할 수 밖에 없다. 무엇을 이 객체 안에 넣어야하며 무엇을 빼야하고, 어떻게 의존성을 둬야할지 고민하게 한다. 어떻게 코드를 설계해야할지 막막하거나 어떤 패턴이 더 나은지 판단하기 어렵다면 테스트 코드를 기준으로 삼는 것도 좋은 선택인 것 같다.

의미있는 테스트를 만들고 유지할 수 있게 팀의 역량 증가

안타깝지만 그냥 어느날 갑자기 테스트 코드를 짜고 싶다고 짤 수 있는건 아니다. 테스트를 짤 수 있는 코드 구조와 환경이 갖춰져 있어야 한다. 따라서 테스트를 짤 수 있는 아키텍처를 도입하고 유지해야 한다. 애플의 MVC는 테스트 짜기가 너무 힘들다. UI를 만들지 않고는 유저 액션 이벤트를 발생시킬수 없다는 문제점이 있다. 또한 뷰컨트롤러는 라이프사이클 관련 메서드가 복잡하고 갯수가 많아서 테스트가 까다롭다. 그리고 의존성을 주입하는 것 마저도 간단하지가 않다. 그래서 MVVM, VIPER, RIBs 등의 아키텍처가 생겨났다. 그러나 아키텍처를 도입한다고 끝이 아니다. 아키텍처는 은탄환이 아니기 때문에 도입하더라도 팀이 처한 상황과 해결해야 하는 문제에 따라 끊임없이 변형된다. 그래서 시간이 지나도 테스트가 가능한, 그리고 테스트를 짜는게 힘들지 않고 재밌는 구조를 유지하는 기술 리더십이 중요하다. 또한 테스트를 짜는 일이 힘들거나 생산성이 떨어지지 않는 환경을 구축하고 적절한 툴을 도입하거나 만드는 팀워크도 중요하다.

Tags: tests  

다른 표준시간대 다국적 개발팀에서 원격으로 일하는 법

우리팀은 모바일 개발자 9명으로 이뤄져있고 출신국이 정말 다양하다. 대한민국, 중국, 인도, 벨라루스, 이탈리아, 방글라데시 6개 국가에서 모였는데 비슷한 규모의 팀 중 사내에서도 손에 꼽히는 다양성인 것 같다. 원래는 모두 싱가폴이 근무지인데, 코로나로 인해 국가간 여행이 제한되기 직전 모국으로 휴가를 갔던 동료들이 여태 돌아오지 못했다. 그래서 팀원 두 명은 인도와 벨라루스에서 재택 근무를 하고 있다. 애초에 싱가폴 직원은 전부 재택 근무 중이기 때문에 크게 다를바가 없어 보였지만 시간대가 다르기 때문에 업무 방식에 변화가 생길 수 밖에 없었다. 생전 처음 원격으로 근무하랴, 시간대가 다른 팀원들과 협업하랴 정신없이 보내다가 이제 어느정도 안정적인 업무 방식을 찾은거 같다.

1. 시차 고려해서 메시지 보내기

미팅 시간을 잡을 때는 당연히 시차를 고려하게 된다. 두 시간 반 느린 인도 지사에 개발자가 많이 있기 때문에 평소에도 미팅은 오후에만 잡는 것이 생활화 돼있었다. 하지만 몇 달 업무를 해보니 채팅도 시차에 맞춰서 보내는 편이 좋다는걸 알게됐다. 처음에는 ‘메시지 보내놓으면 출근해서 보고 답장을 주겠지’ 라고 생각하고 내 편의에 맞춰 메시지를 보냈다. 그러다보니 상대방이 답장을 안주면 나도 잊어버리고 나중에 메시지를 다시 보내야하는 일이 잦았다.

생각해보니 나같은 사람이 많다면 메시지를 받는 사람 입장에서는 출근하자마자 메시지가 많이 와있을테고, 묻히는 메시지가 생겼을 것이다. 이제는 상대방 업무 시간에 맞춰서 메시지를 보낸다. 그랬더니 답장을 더 빨리, 확실하게 받을 수 있게 됐다. 이걸 잘하기 위해 쓰고 있는 도구가 두 개 있는데 하나는 맥 상태바에 여러 시간을 볼 수 있는 간단한 앱이고 다른 하나는 슬랙 리마인드 기능이다. 슬랙에서 특정 메시지에 알람을 걸어놓을 수 있어서 상대방 출근시간 즈음 맞춰놓으면 까먹지 않고 관련 내용을 참고해서 메시지를 보낼 수 있다.

👇 상태바에 여러 시간 표시해주는 맥 앱. 인도와 두시간 반, 벨라루스와 다섯 시간 시차가 있다.



👇 슬랙에 있는 메시지 리마인더 기능

2. 팀원 출근시간 전에 코드리뷰 남기기

서로의 업무에 가장 직접적으로 영향을 주는게 코드리뷰다. 일정 인원의 승인을 받아야만 마스터 브랜치에 병합할 수 있고 CI 작업이 두시간이나 걸리기 때문에 MR을 올리고 코드 리뷰를 받고 수정하고 승인을 받고 병합하는데에만 최소 반나절, 길게는 2~3일이 걸리기도 한다. 시차가 5시간인 벨라루스에서 일하는 팀원과 리뷰를 주고 받을때 특히 신경써야 했다. 리뷰가 늦어지면 코드 병합이 하루 이틀 더 늦어지기 십상이었다. 예를 들어 내가 오후 느지막이 리뷰를 남기고 퇴근을 하면 벨라루스 팀원은 댓글을 보고 코드를 수정하거나 아니면 의견을 댓글로 남긴다. 내 시각으론 밤 늦게거나 새벽이라 다음날이 돼야 확인할 수 있다. 그런데 내가 다음날에도 퇴근 직전에 MR을 검토하면 팀원의 두번째 응답은 또 그 다음날 확인할 수 밖에 없다. 이렇게 댓글이 한 두번만 오고가도 이틀이 지나가버린다.

그래서 이제는 출근하면 가장 먼저 MR부터 확인한다. 내가 아침에 의견을 남겨 놓고 다른 일을 하다보면 오후에 팀원이 출근해서 확인하고, 당일에 한번 더 대화가 오고갈 수 있다. 합의가 빠르면 승인까지 할 수 있고 그날 병합을 할 수도 있다. 단지 서너 시간의 차이로 코드 병합이 하루 단위로 늦춰질 수 있다는걸 생각하면 업무 순서를 조정하는건 쉬운 일이다. 의식적으로 하다보니 습관이 들었다.

3. 1대1 또는 소규모 통화 하기

원격 근무를 하면 의사 소통이 예전만큼 활발하지 않다. 옆자리에 있었더라면 쉽게 대화를 시작했을 텐데 이제는 대답을 기다려야 해서 간단한 대화나 잡담이 힘들다. 그런데 소소한 대화의 중요성을 요즘 부쩍 느끼고 있다. 동료와 친밀감을 쌓을수도 있고 개인 업무에 직접적인 도움을 받기도 하고, 외국 생활에 필요한 정보를 주고 받기도 한다. 원격 근무 때문에 대화도 의도적으로 하지 않으면 교류가 너무 없다. 보통 줌 미팅에 들어가면 사람들이 다 모이기 전까지 시간이 약간 빈다. 자투리 시간에라도 인사를 나누고 근황을 묻는다.

별도로 시간을 마련하면 더 좋다. 최근에 큰 프로젝트를 끝낸 후 그 동안 배운 것들을 정리하고 팀의 지식으로 남길겸 문서를 만들었고, 팀원들에게 앞으로의 방향을 제안하고 아이디어를 공유하는 시간을 두어 번 가졌다. 긴 시간은 아니었지만 다른 팀원들의 의견을 다양하게 들어볼 수 있었고, 무엇보다 미팅 이후에도 관련 주제로 기술적인 논의가 계속 이어지는 것을 봤다. 그리고 업무 미팅과 달리 정보 공유차 부담없이 모이면 다양한 얘기가 오고간다. 그러는 와중에 새로운 주제가 떠오르기도 했고 서로의 문제를 해결해주기도 했다.

원격 근무하는 현 상황은 주니어 개발자에게는 특히 더 어려운 시기같다. 쉽게 도움을 요청할 수 있는 사람이 옆에 없어서 힘들 것 같다. 기회가 있을때 1대1 통화로 도움을 주고는 있지만, 예전보다는 많이 부족하지 않나 싶다. 주니어 개발자라면 더 적극적으로 도움을 요청하고, 시간을 잡아서 통화를 하는 등의 노력을 해서 다른 개발자들과 접촉을 더 늘려야 할 것 같다.

앞으로

우리팀 사무실의 임대차 계약이 만료됐는데 연장을 안했다고 한다. 아무래도 내년 중순 쯤 완공될 사옥에 입주하기 전까지 이런 근무 형태가 계속될 예정이다. 코로나로 인한 여행 제한, 이동 제한, 만남 제한 때문에 여러모로 힘들고 업무 방식도 급변해서 쉽지 않지만 이런 상황 속에서 더 행복하고 지속 가능하게 일할 수 있는 방법이 뭘까 계속 고민하게 된다.

Tags: WFH, multinational, multi-timezone, collaboration  

개발자와 라면 조리법

라면 봉지 뒷면에 써있는 조리법을 한번 들여다보자.

(1) 물 550ml와 건더기 스프를 넣고 물을 끓인다.
(2) 분말 스프와 면을 넣은 후 4분 더 끓인다.

3번은 먹는 방법에 대한 권장사항이니 제외하면 두 단계라고 볼 수 있다. 근데 실제로 라면을 끓이는 자신의 모습을 상상해보면 이보다 훨씬 많은 단계들이 숨어있다.

(1) 냄비를 꺼낸다.
(2) 냄비에 물 550ml와 건더기 스프를 넣는다.
(3) 물과 재료가 담긴 냄비를 가열한다.
(4) 물이 끓기를 기다린다.
(5) 물이 끓기 시작하면 면과 분말 스프를 넣는다.
(6) 4분 더 끓인다.
(7) 불을 끈다.
(8) 그릇에 옮겨 담는다.

이보다 더 자세하게 서술할 수도 있다. 들여다보면 ‘냄비를 가열한다’에도 여러 단계가 생략돼있다. 심지어 장비에 따라서도 다르기 때문에 분기가 필요하다.

(1) 인덕션이라면 전원을 켜고 버튼을 눌러 온도를 높인다.
(2) 가스레인지라면 가스밸브를 열고 다이얼을 돌려 점화한다.
(3) 휴대용 버너라면 가스통을 흔들어 넣고, 잠그고, 점화한다.

하지만 그 어떤 라면 조리법에도 위 같은 내용이 포함되진 않는다. 왜냐면 이정도 상세한 내용은 독자가 이미 알고 있다고 간주하거나, 실은 라면 회사가 알 바 아니기 때문이다. 너무 당연한 말이라고 생각할 수도 있겠지만 이런 류의 코드는 꽤 흔하다. 함수 이름에 표현된 것보다 더 많은 일을 하거나 지나치게 세부적인 구현부가 드러나기도 한다. 이런 경우 보통 if문, for문이 많고 코드가 장황하다.

가령 fetchRecentArticles 라는 함수를 마주쳤다고 상상해보자. 이 함수가 수행하는 작업은 아래와 같다고 예상할 수 있다.

(1) 저장된 아티클을 적당히 불러온다.
(2) 최근 X일 내에 생성된 것들을 골라낸 후 반환한다.

그런데 막상 함수를 들여다보니 함수 안에서 로컬 캐시를 확인하고, 없으면 서버 API 호출을 해서 데이터를 가져와서 파싱하고, 저장소에서 데이터를 불러오기 위한 키 값도 정의하고, switch문으로 아티클의 종류에 따라 각기 다른 필터링 로직을 생성하고 있다면 어떨까. 막상 핵심 부분은 장황한 코드 속에 파묻히게 된다. 코드를 수정하려고 해도 어디를 건드려야할지 한참을 찾아야한다. 라면 물을 끓인다는 것만 알면 되는데 불을 어떻게 지펴야 하는지까지 설명하고 있는 셈이다.

‘왜 자석은 서로 밀어내는가?’란 기자의 질문에 리처드 파인만이 대답을 한 영상에서도 비슷한 의미를 찾을 수 있다. 같은 질문이라도 질문자가 누구냐(물리학 전공자, 일반인, 외계인 등)에 따라, 또는 질문자가 무엇을 알고 싶어하느냐에 따라 설명은 천차만별이 된다. ‘그건 그냥 그런거야’와 같이 한마디 답변이 될 수도 있고, 설명이 꼬리에 꼬리를 물고 끝도 없이 자세하게 설명할 수도 있다.

본인이 쓴 코드가 적절한 수준의 로직만 드러내고 있는지 고민해보자. 함수의 역할에 맞는 작업만 하고 있는지, 아니면 온갖 세부 로직을 장황하게 늘어놓고 있는게 아닌지 살펴보고 손봐야 한다. 그런데 ‘적절한 수준’이란 것엔 정답이 없다. 코드가 속해있는 클래스나 모듈에 따라 다르다. 코드를 읽는/쓰는 사람의 역량에 따라서도 달라질 수 있다. 누구에게는 간결한 코드가 누구에게는 더 이해하기 어려운 코드일 수 있다. 코드를 리팩토링 하다보면 또 기준이 바뀔 수도 있다. 모든게 유동적이다. 딱 떨어지는 기준은 없지만 독자의 입장에서 자신의 코드를 한번이라도 더 읽어보고, 함수를 짧게 만드는 연습을 하고, 동료끼리 코드 리뷰를 하면서 적절한 수준으로 맞춰갈 수 있다.

객체는 하나의 책임만 가져야 한다는 단일 책임 원칙을 지키는게 결코 쉽지는 않지만 함수 단위에서부터 시작해보자. 함수를 간결하게 만들수 있게 됐다면 다음으로 클래스 수준에서도 적용해본다. 단일 책임 원칙은 모든 패턴이나 아키텍처의 시작이다. 즉 단일 책임 원칙을 지키지 못하면 어떤 패턴을 쓰던, 어떤 아키텍처를 도입하던 시간이 흐를수록 결국 스파게티 코드가 될 확률이 높다. 언어 문법도 코드를 더 간결하게 표현하기 위한 수단으로써 공부를 하면 길을 잃지 않을 수 있다. 함수를 짧게 만들고 클래스를 작게 만드는 연습을 많이 하자.

같이 읽어볼만한 글

스위프트로 다시보는 객체지향 프로그래밍: 피해야할 코딩 습관