읽은 기록

필독! 개발자 온보딩 가이드(The Missing README)

멘토님의 추천으로 읽기 시작한 책으로 북클럽 6회차 선정 책이다.

개발자가 온보딩되기 전에 읽으면 좋은 내용을 포함하고 있으며, 되게 넓게 다룬다.

1장 여정을 시작하며

개발자로서의 첫 출발, 앞으로 어떤 길이 펼쳐질까

이책은 앞으로 소프트웨어 엔지니어로서 경력을 쌓아나갈 견습자를 위한 책이다.

실제 회사의 입장도 신입에게 큰 기대 없이 협업이 가능한 인력을 원한다. 이후 교육을 통해 적합한 인재로 키우는 것이 목표이다.

따라서 이 책은 그 과정에서 좀 더 멀리보고, 쉽게 접근할 수 있도록 도움을 주는 책이다.

목표를 세우자

누구든 초보 엔지니어부터 시작한다. 더 나은 엔지니어로 성장하려면 몇가지 핵심 분야에 익숙해져야 한다.

  • 기술 지식: 기본적인 컴퓨터 과학의 기초 지식으로 IDE, 빌드 시스템, 디버거, 테스트 프레임워크, CI/CD, 버전 관리 시스템 등을 이해하고 사용할 수 있어야 한다.
  • 실행력: 코드를 이용해 문제를 해결함으로써 가치를 만들어내며 비즈니스 간의 연결고리도 이해해야 한다.
  • 의사소통: 문서는 물론, 구두로도 명확하게 소통이 가능해야 한다.
  • 리더십: 범위가 잘 정의된 작업을 독립적으로 수행할 수 있어야 하며, 실수로부터 빠르게 배울 수 있어야 한다.

여정을 위한 지도

누구든 목표에 도달하려면 지도가 필요하다.

초보자

초보자는 업무 방식에 익숙해지는 것이 먼저다. 그 과정은 온보딩회의로 시작하며 기본적인 업무 프로세스에 대해서 배운다. 이 과정에서 능동적인 요청과 행동이 필요하다.

온보딩이란, 조직에 새로 합류한 사람이 빠르게 조직 문화를 익히고 적응하도록 돕는 과정이다.

  • 커닝햄의 법칙과 바이크셰딩
    • 커닝햄의 법칙: 인터넷에서 올바른 답을 얻는 가장 좋은 방법은 질문이 아닌 잘못된 답을 게시하는 것이다.
    • 바이크셰딩: 그다지 중요하지 않은 문제에 대해 사소한 논의가 길어지는 상황

온보딩과정에선 팀 프로세스에 대해서 명확하게 이해하고, 이를 잘 이해하고 학습해야 한다. IDE나 미팅과정, 코드리뷰 과정 등 협업에서 이뤄나는 행위를 잘 이해하고 팀에 맞출 수 있도록 해야한다.

질풍노도의 성장

처음에는 혼란스럽겠지만, 대부분은 기준의 코드베이스 혼돈스럽거나 겁이 나는 작업일 수도 있다. 모르는 점이 있다면 질문하고, 리뷰를 요청한다. (피드백)

성장을 위한 학습은 매우 중요하다. 코드를 빌드, 테스트, 배포하는 방법을 잘 살펴보자.

이후 팀장과의 관계를 발전시키고 업무 방식에 대한 이해, 대화를 통해 회의, 업무 방식을 발전시켜 나간다.

첫 번째 회의 때 로드맵이나 팀 성격에 대한 이해도 중요하다.

신뢰할 수 있는 기여자

지속해서 성장하다 규모가 있는 작업, 프로젝트에 참여하게 되면서 신뢰할 수 있는 기여자 단계에 접어든다. 이때는 좀 더 독립적으로 일할 수 있고, 더욱 신뢰받게 된다.

이때는 손쉽게 운영할 수 있는 프로덕션 수준의 코드를 작성하는 방법, 의존성을 적절히 관리하는 방법, 깔끔한 테스트를 작성하는 방법 등을 배우자.

이제는 다른 팀원도 도와주어 진정한 협업에 참여해야 한다. (도움이아닌 교류)

운영의 바다

작업 규모가 커짐에 따라(할당 받은 책임이 커짐에 따라) 사용자에게 전달되는 과정에 대해 더 많이 알게 될 것이다. 이 과정에서는 테스트, 빌드, 릴리스, 배포, 롤아웃, 등 많은 일이 일어난다.

이 과정에선 스킬적인 부분이 많이 필요하다.

능력자의 땅

이제는 어느정도 소규모 프로젝트를 직접 주도하는 역량을 갖췄다고 생각할 것이다. 이 능력자 단계에서는 기술 설계 문서를 작성하고 프로젝트 계획 수립을 거들어야 한다.

소프트웨어를 설계하다 보면 또 다른 수준의 복잡도를 마주하게 되기 마련이다. 첫 설계에 절대로 안주하지 말고 트레이드 오프를 생각하여 시간이 지나도 지속할 수 있는 시스템을 위한 계획을 세우자.

처음에 설계한 좋아보이던 것들도 시간이 지남에 따라 점차 사라진다. 이때 아키텍처, 빌드 시스템, 테스트 환경등이 눈에 띄게되고 여기서 균형잡기가 시작된다.

전진, 앞으로

이제 초보자로서의 여정에 필요한 지도가 준비되었다. 이 지도를 참고하여 자신만의 로드맵을 그리며 나아가자.

2장 역량을 높이는 의식적 노력

경쟁력을 갖춘 개발자가 되기 위해 스스로 해야 할 일

마틴 브로드웰의 학습을 위한 가르침이라는 책에서 나오는 능숙함을 4 단계로 나눠 정의한다.

  • 무의식적 능력부족(unconscious incompetence)
    • 업무를 올바르게 수행할 역량도 안 되고 자신의 부족함을 인지하지도 못하는 상태를 말한다.
  • 의식적 능력부족(conscious incompetence)
    • 업무를 제대로 수행하지는 못하지만 자신의 부족함은 인지하는 상태를 뜻한다.
  • 의식적 능숙(conscious competence)
    • 노력을 통해 해당 업무를 수행할 역량이 갖춰진 상태를 의미한다.
  • 무의식적 능숙(unconscious competence)
    • 해당 업무를 수월하게 수행할 수 있는 역량이 갖춰진 상태를 의미한다.

모든 엔지니어는 의식적이든 무의식적이든 능력부족 단계에서 시작한다. 모든 엔지니어링 기술을 알고 있더라도 해당 회사, 팀의 절차와 규칙은 반드시 익혀야 한다.

목표는 빠르게 의식적 능숙 상태에 들어가는 것이다.

실전에 앞서 익혀야 할 자기주도 학습 방안

배움은 능숙한 엔지니어에 이르고 향후 몇 년간 제대로 성장하는 데 도움이 되는 방법이다. 소프트웨어 엔지니어링 분야는 끊임없이 발전한다. 따라서 노련한 사람이든 초짜이든 배움을 지속하지 않으면 도태된다.

본격적인 학습을 위한 몸풀기

입사 후 몇 달간은 일단 상황이 어떻게 돌아가는지 파악하는 데 집중하자. 그러면 설계 회의나 온콜 교대 업무, 운영 이슈, 코드 리뷰 등에 참여하는 데 도움이 된다.

직접 부딪혀보며 배우자

가장 좋은 학습방법으로 직접 실습하며 학습하는 것이다. 팀에 합류한 신입이라면 적절한 난이도의 미션을 팀장이 부여해줄 것이기 때문에 너무 겁먹지 않는 것이 좋다. 실수는 피할 수 없다.

코드 동작을 이해하기 위해 다양한 실험을 해보자

여러 가지 시도를 해보며 코드가 어떻게 동작하는지 알아두자. (기록)

디버거를 통한 생명주기를 파악하거나 프로그램 로직등을 학습한다.

문서 읽는 습관은 몸에 배야 한다

매주 일정 시간을 할애해 자료를 읽는 습관을 들이자.

여기서 가장 중요한 점은 매주 일정한 시간이라는 점이다. 가장 안좋은 형태는 불태우는 과정이고 그 과정에서 금방 질리기 마련이다.

코드는 결코 거짓을 전하지 않는다. 하지만 주석은 간혹 거짓을 말할 때가 있다.

할일은 티켓이나 이슈로 관리한다. 서로 무슨일을 하는지 알 수 있도록, 무슨 일을 해야하는지 공유가 되는 것이 가장 먼저이다.

발표 영상을 찾아서 보자

사내 발표나 기술에 대한 외부 발표 영상을 보고 이를 공유하자. (정리를 통해 개념을 학습)

때로는 밋업과 컨퍼런스도 참여하자

컨퍼런스와 밋업에 참석하는 것은 네트워크를 구축하고 새로운 아이디어를 접할 수 있는 좋은 방법이다.

시니어 엔지니어의 업무를 체험하고 협업하자

  • 체험하기(shadowing)
    • 다른 사람이 업무를 수행하는 동안 그림자처럼 따라 다니는 것을 말한다.
    • 따라다니며 배우고 이후에는 바꿔보는 것도 좋은 방법이다.
  • 짝 프로그래밍(pair programming)
    • 두 명의 엔지니어가 하나의 컴퓨터에서 작업하는 방식이다.
    • 코드의 품질을 높이기 좋은 방법으로 권한다면 무조건 해보는 게 좋다.

개인 프로젝트 활동에서 배움을 얻을 수 있다

사이드 프로젝트는 새로운 기술과 아이디어를 접할 수 있는 방법이다. 다만 항시 ‘본업’에 충실해야 한다.

오픈소스 프로젝트에 참여하는 것도 한 방법이다.

가장 중요한 것은 배워야 하는 소재라는 근거로 프로젝트를 선택해서는 안 된다. 해결하고 싶은 문제를 찾아내고, 자신이 배우고 싶은 도구를 이용해 그 문제를 풀어야 한다. 목표가 있어야 더 오래 몰두하고 더 많이 배울 수 있는 본질적인 동기가 스스로에게 부여된다.

대체로 회사에는 업무 외 활동에 대한 규정이 정해져 있으므로 자신이 속한 회사의 정책을 확인하는 것이 좋다.

제대로 질문하자

모든 엔지니어는 질문을 해야 한다. 질문은 배움에 있어 매우 중요한 비중을 차지한다. 신입 엔지니어는 팀 동료에게 폐가 될까 싶어 모둔 것을 혼자 알아내려고 한다. (매우 비효율적)

스스로 문제를 해결해보자

먼저 스스로 답을 찾아보자. 설령 동료가 답을 알더라도 그에 앞서 스스로 노력해봐야 한다. 그 과정에서 매우 많이 배우게 되고, 직접 찾아본 내용을 토대로 도움을 청할 수 있다.

무작정 인터넷을 뒤지는 방법은 옳지않고, 정보는 문서와 코드에 다 존재한다.

제한 시간을 정하자

문제를 해결하기 위한 시간을 정해두자, 스스로 시도해보기 전에 제한 시간을 정해두면 학습 효과도 높아질 뿐 아니라 부적합한 결론이 도출되는 상황을 미연에 방지할 수 있다.

앞서 말한 하루에 몰아서하는 좋지 못한 상황에 대한 방어법이다.

자신이 시도한 방법을 공유하자

질문을 할 때는 자신이 이미 파악한 내용을 설명해야 한다. 기록해둔 내용을 그대로 전달하라는 의미가 아니다. 시도한 방법을 간결하게 요약하자.

앨리스 님께,
testkey가 왜 testKVStore에 저장되는지 혹시 아시나요?
그것 때문에 피드가 굉장히 느려지고 있거든요.

고맙습니다.

ooo드림
앨리스 님께,
저는 지금 testKeyValues가 (DistKV 레포에) TestKVStore에 저장되는 문제를 해결 중이에요. 숀이 앨리스 님께 물어보라고 하더라구요. 이와 관련하여 도움을 받고 싶습니다.

테스트를 실행하면 두 번까지는 성공적으로 넘어가는데 세 번째 테스트에서 매번 실패합니다. 랜덤하게 실패하는 것 같습니다. 그 테스트를 따로 실행해봤는데도 여전히 실패하고 있는걸 보면 다른 테스트에 영향을 받는 것은 아닌 것 같습니다. 테스트 루프안에서 실행해도 원인을 찾지 못하고, 소스코드를 읽어도 명학환 원인을 찾을 수 없었습니다. 일종의 경합 상태 같은데 어떻게 생각하시나요?

프로덕션 환경에선 큰 영향이 없을거라고 하니 급한 문제는 아니지만, 테스트 실패시마다 빌드가 20~30분씩 느려져서 꼭 해결하고 싶습니다. 혹시 몰라 테스트 환경과 로그를 같이 첨부합니다.

감사합니다.

ooo드림

두 메일은 명확한 차이가 있다. 처음 관계는 불만이 있어보이지만 명확한 컨텍스트와 문제를 설명하기에 상세 내용을 파악하는 것에서 시간을 허비하지 않는다.

동료를 방해하지 말자

모든 동료는 각자가 해야할 일이 명확하게 존재한다. 그래서 집중이 필요하다. 설령 쉬운 문제일지라도, 그에게 해결책이 있다고 해도 다른 이를 쉽게 방해해서는 안된다.

이에 대한 순간은 개개인마다 다를 수 있지만, 어느정도 눈치로 파악이 가능하다. 헤드폰을 쓰거나 자신만의 공간에 가는 등

비동기식 멀티태스킹 의사소통을 시도하자

멀티태스킹, 비동기는 컴퓨터의 영역뿐만 아니라 사람 간의 의사소통에도 적용할 수 있다.

질문은 여러 사람이 각자의 상황에 따라 응답할 수 있는 곳에 올리자. 모든 사람이 볼 수 있는 곳에 올려두면 문제가 해결됐다는 점도 모두가 알 수 있게 된다.

동기식 요청은 한 번에 보내자

간단한 질문은 이메일이로 해결 가능하지만, 복잡한 논의는 적합하지 않다. 직접 만나 대화하면 ‘많은 내용’을 빠르게 전달할 수 있지만 다소 비용이 든다.

성장의 장애물을 극복하자

스스로 학습하는 방법을 아는 것만으로는 충분하지 않다. 반드시 성장에 방해되는 요소는 제거해야 하고 가장 보편적인 장애물은 가면 증후군과 더닝 크루거 효과이다.

가면 증후군

대부분의 신입은 의식적 능력부족 상태에서 시작한다. 배울 것도 많고 자신을 제외한 모든 사람이 자신보다 훨씬 앞서가는 느낌을 받는다. 어쩌면 어울리지 않는 자리에 있는 것 같다거나 단지 운이 좋아 이 직업을 갖게 된 듯해서 온갖 걱정에 휩싸이기도 한다. 그러다 보면 간혹 스스로에게 너무 업격해지기도 한다.

이런 현상을 바로 가면 증후군이라고 하며 자신을 의심하며 스스로에 대한 불신이 강해지는 경향이 있다. 사실 누구나에게 있는 보편적인 현상이라는 점을 기억해야 한다.

가면 증후군은 점점 더 강화되는 경향이 있으며 모든 오류는 자신의 능력 부족에 대한 증명으로 여기는 반면, 모든 성공은 남을 ‘기만’하는 증거로 바라본다.

이 상황에선 적절한 자기인식(메타인지)가 중요하다. 운이 좋은 것이 아닌 자신이 증명한 성과로 봐야하며 지속적인 기록을 통해 자신을 바라봐야 한다.

피드백을 받는 것도 도움이 된다.

임포스터 증후군이라는 비슷한 증후군도 있다.

더닝 크루거 효과

더닝 크루거 효과는 가면 중후군과 반대되는 현상으로, 스스로 과대평가하는 인지적 편견을 갖는 것이다. 무의식적 능력부족 상태의 엔지니어는 자신이 뭘하는지 뭘 모르는지 모른다.

그래서 자신의 역량을 제대로 평가하지 못한다. 자신감이 넘친 나머지, 자신이 속한 회사의 기술 스택을 비난하고 코드 품질에 불만을 가지며 설계를 무시한다. 이러 부류는 늘 자신의 생각이 옳다고 확신하며 기본적으로 피드백을 거부한다.

이도 마찬가지로 메타인지의 영역으로 해결이 가능하고 타인의 의견을 통해 되돌아볼 수 있다.

개발자 필수 체크리스트

이것만은 지키자 이것만은 피하자
코드를 이용해 자주 실험하자 이무 생각 없이 코드만 찍어내서는 안 된다
설계 문서와 다른 사람이 만든 코드를 읽자 위험과 실패를 두려워하지 말자
밋업, 커뮤니티, 관심 그룹, 멘토링등 프로그램에 참여하자 너무 잦은 컨퍼런스는 금물이다
논문과 블로그를 읽자 질문하기를 두려워하지 말자
멀티캐스트/비동기식으로 소통하자  
인터뷰나 면접, 긴급대응 교대 업무를 따라 다녀보자  

3장 코드와 함께 춤을

레거시 코드에 임하는 우리의 자세

코드베이스는 한 세대가 여러 계층을 작성하고 지속적으로 변한다. 많은 사람이 코드에 손을 대고, 누락된 부분이 생기고 오래된 가설을 강제하는 테스트가 있을 수 있다. 요구사항의 변화는 코드의 사용 방식과 얽혀 있다. 그래서 코딩 작업이 어렵다.

소프트웨어 엔트로피는 늘어나기 마련이다

코드를 살피다 보면 단점을 발견하게 될 것이다. 여기저기 코드를 바꾸다 보면 지저분해지는 것은 자연스러운 일이다. 코드가 지저분하다고 개발자를 욕하지는 말자. 이렇게 코드가 지저분해지는 것을 소프트웨어 엔트로피라고 한다.

소프트웨어 엔트로피는 여러 이유로 생겨난다. 다른 사람의 코드를 이해하지 못하거나, 코등 스타일이 다르거나, 요구사항을 개선하다 보면 혼란이 생긴다. 이런 엔트로피를 개선하기 위해선 코딩 스타일, 디버거, 코드리뷰로 방지 가능하다.

코드 리뷰, 지속적인 리팩터링이 필요하다.

결코 피할 수 없는 기술 부채

기술 부채(technical debt)는 소프트웨어 엔트로피를 가중시키는 주요 요인이다. 기술 부채란 기존 코드의 단점을 수정하면서 나중으로 미뤄둔 작업을 말한다. 금융과 마찬가지로 원금과 이자가 있다. (원금은 수정해야할 원래 단점, 이자는 수정하지 않고 개선할 때 나오는 비용)

스스로 혼자 동의하지 않는 기술적 의사결정, 또는 별로 마음에 들지 않는 코드를 기술 부채라고 부를 수는 없다. 기술부채로 정의하려면 팀이 ‘이자를 지불해야 하는’것이거나 심각한 문제를 유발할 수 있는 위험 요인이어야 한다. (너무 남용 금지)

  신중하지 못한 선택 신중한 선택
의도한 선택 “설계할 시간이 없어요” “일단 출시 후 결과를 보고 대처합시다.”
의도하지 않은 선택 “계층화가 도대체 뭔가요?” “뭘 실수했는지 이젠 알겠네요.”

신중히 고민하고 의도적으로 만들어진 부채는 보편적으로 발생하는 기술 부채 유형이다. 코드의 단점과 출시 속도 사이에서 고민하다 결정한 실용적인 트레이드오프인 것이다. 나중에라도 팀이 해결 가능하도록 훈련된 부채라면 이는 좋은 부채라 할 수 있다. (이런 확장성을 적절히 조절하는 것이 균형잡기이다.)

신중하지도 못하고 의도치도 않았던 부채는 알려지지 않은 미지(unknown unknowns), 즉 몰라서 모르는 것들 때문에 발생한다. (레빗홀) 이런 부채 유형은 사전에 구현 계획을 작성해 피드백을 받고 코드 리뷰를 수행하는 방법으로 완화할 수 있다.

건전한 팀은 회고 같은 절차를 활용해 의도치 않았던 부채를 찾아내고 부채의 해결 여부와 적절한 해결 시점을 논의한다.

기술 부채를 상환하는 방법

지속적인 리팩터링과 변경사항은 작고 독립적인 커밋과 PR로 만든다.

가끔은 대규모 리팩터링이 필요할 때가 있다. 소규모와 대규모 둘다 장단점이 존재하고(트레이드 오프가 존재하고) 균형잡기를 통해 결정해야 한다

코드 변경으로 인한 고통을 조금이라도 줄이려면

코드 변경은 새로운 리포지토리에 코드를 작성하는 것과는 다른 차원의 문제다. 기존의 동작을 유지하면서 변경해야 하기 때문이다. 다른 개발자의 사고 방식을 이해하고 기존 스타일과 패턴을 유지해야 한다. 또한 업무를 진행하면서 코드베이스를 서서히 개선해야 한다.

새로운 기능을 추가하던, 지우던, 리팩터링하던, 버그를 수정하던 대부분의 코드를 벽옇나느 기법은 비슷하고 이러한 기법을 혼합하여 사용한다.

레거시 코드 변경 알고리즘을 활용하자

  • 변경 지점을 확인한다.
  • 테스트할 지점을 확인한다.
  • 의존성을 나눈다.
  • 테스트를 작성한다.
  • 변경을 적용하고 리팩터링한다.

쉽게 정리하여 야생동물들이 날뛰지 않도록 울타리를 치고 코드를 리팩터링하는 것이다.

의존성을 나누는 방법은 다음과 같이 여러가지다.

  • 크고 복잡한 메소드는 더 작은 크기의 메소드로 나눠서 분리된 기능이 독립적으로 테스트될 수 있게 한다.
  • 인터페이스(또는 그 밖의 간접성)를 이용해서, 복잡한 객체를 완전하지 않아도 테스트하기에는 충분한 단순 구현체로 대체할 수 있는 방법을 마련한다.
  • 시간의 흐름같이 제어하기 어려운 실행 환경을 시뮬레이션할 수 있는 명시적 제어 지점을 주입한다.

리팩터링에 관한 내용이라 리팩터링 정리 링크로 첨부한다.

코드는 처음보다 더 깔끔하게 유지하자

“캠핑장을 떠날 때는 도착했을 때보다 깨끗하게 정리하라.”

코드베이스도 공유 공간이므로 가능하면 깔끔한 상태로 유지하는 편이 좋다.

그래서 브랜치를 기능별, 리팩터링 등 목적에 맞게 작게 분리해야 병합할 때 최대한 깔끔하게 유지할 수 있다. 늘어남에 따라 힘들어지는 것 같다.

코드 냄새에 관한 내용으로 클린 코드 정리 링크

점진적으로 변경하자

리팩터링은 두 가지 유형을 띈다.

첫 번째는 수십 개의 파일을 한 번에 수정하는 모조리 바꿔 식의 변경이다. 두 번째는 리팩터링과 기능이 섞인 PR이다.

이 두 가지 변경은 리뷰하기가 훨씬 어렵기 때문에 최대한 리팩터링 커밋은 작게 유지해야 한다.

리팩터링은 실용적으로 진행하자

리팩터링이 항상 현명한 결정은 아니다. 마감일도 생각해야 하고, 우선순위도 고려해야 한다. 리팩터링에는 시간이 필요하다. 팀에서 출시를 위해 미루거나 기술 부채를 늘리지만 옳은 결정일 수 있다.

결국은 트레이드 오프다.

IDE를 활용하자

IDE는 특히 리팩터링 단계에서 도움이 많이 된다.

버전 제어 시스템의 권장 기법을 활용하자

작은 커밋과 메시지를 통한 명확한 의사전달, 백업등의 기능으로 코드 변경을 원할하게 유지할 수 있다.

실제 깃 커밋 메세지 작성 요령을 참고하거나 팀의 컨벤션을 따라가는 것이 좋다.

소프트웨어 개발에서 빠지기 쉬운 함정을 최대한 피하려면

기존 코드의 라이브러리, 프레임웤, 패턴등이 이미 많은 것이 담겨 있을 수 있다. 그 중에 마음에 들지 않아 더 깔끔하고 현대적인 방법으로 변경하고 싶지만 이런 행동은 매우 위험하다. (개인이라면 몰라도 회사라는 판매자 입장에서 위험하다.)

코드를 재작성하거나 규칙을 깨뜨리거나 또는 기술 스택에 새로운 기술을 추가할 때는 주의를 기울이자. 고부가가치를 낼 수 있는 상황에서만 코드를 재작성하자.

되도록 검증된 기술을 사용하자

당연하게 기술이 발전함에서 더 유용한, 편리한 기술이 등장할 수 있다. 하지만 오래된 패턴으로 견고한 코드를 유지함에는 그만한 이유가 있다. 성공을 거두는 데 시간이 걸리고, 기술을 갈아타는 것은 집중을 방해하는 요소가 되기 때문이다.

새로운 기술을 도입했을 때의 장점이 도입 비용보다 커야 한다. 회사가 비용과 장점 사이의 균형을 맞추려면 고부가가치 영역을 담당하고 광범위한 사용 사례에 적용 가능하며 여러 팀이 도입할 수 있는 기술에 투자해야 한다.

제발 악동은 되지 말자

단지 마음에 들지 않는다는 이유로 회사의 표준을 무시해서는 안 된다. 비표준 코드를 작성하는 것은 기업 환경에 맞지 않는 코드를 작성한다는 뜻이다.

더닝 크루거, 오만함으로 작성한 코드는 그에 따른 비용을 계속 뽑아내고 있다는 것이다.

가장 중요하게 생각하는 부분이다.

업스트림 커밋 없이 포크만 하는 것은 금물이다

당장 코드에 기여할 생각이 없으면서 해당 레포만 포크하는 것은 그다지 권장되지 않는다.

코드 재작성에 대한 욕구를 견디자

리팩터링을 하다 보면 기존 코드를 완전히 버리고 새로 작성하게 되는 경우가 있다. 종종 기존 코드를 버리고 모두 새로 작성하는 것이 좋을 것 같다는 생각을 하지만, 코드를 새로 작성하는 것은 최후의 수단이라고 생각해야 한다. (대부분 좋지 않은 선택이다.)

코드 재작성도 마찬가지로 그에 들어가는 비용보다 장점이 클 때만 시도해야 한다. 코드 재작성은 위험할 뿐더러 소요 비용도 높기 때문이다.

개발자의 필수 체크 리스트

이것만은 지키자 이것만은 피하자
점진적으로 리팩터링하자 기술 부채라는 단어를 남용하지 말자
리팩터링 커밋과 기능 관련 커밋은 분리하자 테스트를 목적으로 메소드나 변수를 외부에 공개해서는 안 된다
변경사항을 작게 유지하자 특정 언어에 연연하지 말자
처음 상태보다 작게 유지하자 회사의 표준과 도구를 무시해서는 안 된다
평범한 기술을 사용하자 업스트림 커밋 없이 포크만 하지 말자

4장 운영 환경을 고려한 코드 작성

개발 환경과 프로덕션 환경은 엄연히 다르다

프로덕션 소프트웨어는 지속적으로 동작해야 한다. 따라서 운영 가능한 코드를 작성해야 예상하지 못한 상황에 대처할 수 있다. 운영 가능한 코드란 보호 장치, 분석 장치, 제어 장치가 내장된 코드를 말한다. 안전하고 회복성 있는 코딩 기법을 이용해 방어적으로 프로그래밍해서 시스템을 보호해야 한다.

장애에 대비하기 위한 방어적 프로그래밍 방안

방어적 코드는 코드를 운영하는 모두를 위한 배려다. 방어적 코드는 장애가 발생하는 빈도가 낮으며, 장애가 발생하더라도 대부분 복구가 가능하다. 코드를 안전하고 회복성 있게 즉, 안정한 코드(safe code)는 컴파일 타임의 유효성 검사를 통해 런타임 장애를 최소화한다.

불변 변수, 접근 제어자로 범위 제한, 정적 타입 검사를 통한 버그 최소화

null값 사용은 피하자

대부분의 언어는 값이 할당되지 않은 변수의 기본값으로 null을 할당하여 빈번하게 NullPointerException이 발생한다. 변수에 대한 널 체크나 널 객체 패턴을 통해 예외를 방지하자.

불변 변수를 사용하자

C#에선 레코드라는 불변을 보장할 수 있는 타입이 있다.

타입 힌트와 정적 타입 검사를 활용하자

변수를 선언할 때는 가장 구체적인 타입을 사용한다.

입력값을 검사하자

코드로 전달되는 입력값은 절대로 신뢰해선 안 된다. 사전 조건 및 사후 조건을 이용해서 메소드에 전달되는 입력 변수의 유효성을 검사해야 한다.

예외를 활용하자

매직값을 통한 리턴으로 예외를 표한하는 것은 금물이다. 최신 개발 언어는 예외나 표준 예외 처리 패턴을 제공한다. 특정한 리턴값은 메소드 시그니처에 명확하게 드러나지 않는다. 따라서 개발자는 리턴값이 에러를 의미한다는 점을 모를 수 있다.

따라서 예외를 통해 명확하게 나타내자.

예외는 구체적으로 사용하자

예외를 구체적으로 정의하면 코드를 더 쉽게 사용할 수 있다. 예외를 직접 정의할 때는 너무 포괄적인 의미가 담기지 않게 하자.(추상적인 예외보다 직접적이고 명확한 예외)

예외는 일찍 던지고 최대한 나중에 처리하자

‘일찍 던지고 늦게 잡는’ 원칙을 따르자.

일찍 던진다는 말은 개발자가 관련 코드를 신속하게 찾을 수 있도록 에러가 발생한 지점으로부터 최대한 가까운 지점에서 예외를 던진다는 뜻이다. (실제 에러 위치를 파악하기 위해서) 예외를 늦게 잡는다라는 말은 예외를 처리할 적절한 위치에 도착할 때까지 계속 호출 스택을 통해 전파시킨다는 뜻이다.

정리하면 호출하는 코드가 예외를 던지면 예외를 완전히 처리하거나 혹은 상위 스택으로 전파하자.

재시도는 현명하게

에러를 적절하게 처리하는 방법 중 하나는 단순히 다시 시도하는 것이다. 여러 차례 재시도하려는 시도는 주로 원격 시스템을 호출할 경우에 나타난다.

예외에서 재시도는 현명해보일 수 있어도 재시도의 시기와 빈도를 판단하려면 노하우가 필요하다. 가장 손쉬운 방법은 단순히 예외를 잡아서 그 즉시 작업을 다시 시도하는 것이다. 하지만 이런 방법은 재시도를 무한정 반복할 수 있기 때문에 위험하다.

따라서 백오프(backoff)라는 전략을 사용하는 것이 좋다. 백오프는 비선형으로 대기 시간을 늘려 시간의 상한까지 재시도에 대한 빈도를 제어한다.

모든 클라이언트가 이런 알고리즘을 사용한다면 무수히 많은 요청으로 인해 천둥떼 현상을 겪을 수 있다. (지터를 사용해서 해결)

시스템에 멱등성을 부여하자

재시도를 처리하는 가장 좋은 방법은 멱등성이 있는 시스템을 구현하는 것이다. 멱등성이란 동일한 작업을 여러 번 실행해도 같은 결과가 출력됨을 의미한다. (어떤 값을 해시셋에 추가하는 것은 멱등 작업이다.)

리소스를 해제하자

장애가 발생하면 모든 리소스를 해제해야 한다. 더 이상 필요하지 않은 메모리, 데이터 구조, 네트워크 소켓, 파일 핸들 등을 모두 해제하자.

C#은 Dispose 패턴을 사용해서 리소스를 해제한다. C++은 직접 메모리를 해제해야 한다.

문제 원인을 찾기 위한 로깅 방안

로깅은 간단하게 로그를 찍어 확인하는 편리한 방법으로 어떤 로그를 기록할지 제어하는 것이 중요하다.

로그 레벨을 사용하자

로깅 프레임워크는 운영자가 중요도에 따라 메시지를 필터링할 수 있도록 로그 레벨을 지원한다. 로그 레벨을 활용하면 매우 세세한 디버깅 로그부터 정상적인 운영 상화에서 주기적으로 기록되는 로그까지, 주어진 상황에 맞춰서 로그의 양을 조정할 수 있다.

로그는 원자적으로 작성하자

만일 데이터화 결합했을 때만 정보가 유용하다면 한 메시지에 모든 정보를 원자적으로 저장하자.

로그는 신속하게 기록하자

로그를 너무 많이 기록하면 성능에 영향을 미친다. 로그는 디스크나 콘솔, 원격 시스템 등 어딘가에 반드시 기록돼야 하며, 기록되기 전에 올바른 형태의 한 문자열로 결합해야 한다.

유니티에서도 잘 활용되는 듯 하다.

애플리케이션 동작 측정을 위한 지표 활용 방안

Unity는 따로 프로파일링을

지표는 숫자로 표현한 로그와도 같아서 애플리케이션 동작을 측정한다.

표준 지표 라이브러리를 사용하자

이미 구현된 라이브러리를 사용하라.

모든 것을 측정하자

측정은 비용이 적게 드는 작업이므로 가급적 많은 지표를 수집해야 한다. 다음과 같은 데이터 구조, 작업, 동작은 모두 측정하자.

  • 리소스 풀
  • 캐시
  • 데이터 구조
  • CPU 집약적 작업
  • IO 집약적 작업
  • 데이터 크기
  • 예외와 에러
  • 원격 요청 및 응답

오늘날 분산 환경에서 더욱 중요해진 추적

RPC 클라이언트는 추적 라이브러리를 이용해서 요청마다 호출 추적 ID를 붙인다.

설정으로 런타임 동작을 손쉽게 조정하려면

설정정보등을 Json, TAML처럼 일반 텍스트이면서 읽을 수 있는 형식, 환경변수, 명령줄 플래그 등등으로 설정값을 관리한다.

지나치게 창의적인 설정은 금물이다

이는 그냥 개발에서도 적용되는 모호하거나 오용될 수 있는 설정값을 사용하지 말라는 것이다.

모든 설정을 로그에 기록하고 검증하자

애플리케이션이 어떤 설정값을 사용하는지 알 수 있도록 시작 시점에 즉시 설정값을 로그에 기록하자.

기본값을 제공하자

대부분의 사용자가 시스템을 곧바로 사용할 수 있도록 적절한 기본값을 제공하자.

관련된 설정을 그룹화하자

애플리케이션 설정은 특히 중첩된 설정을 지원하지 않는 키-값 형식인 경우 금새 크기가 커져서 관리가 불가능해진다.

설정도 코드처럼 테스트하자

코드로서의 설정은 설정도 코드처럼 취급해야 한다는 철학이다. 설정을 안전하게 변경하려면 설정도 버전 제어로 관리하고 리뷰하고 테스트하고 배포한다.

설정 파일은 깔끔하게 유지하자

설정 파일이 깔끔하면 다른 사람이 이해하고 변경하기가 쉽다. 사용하지 않는 설정은 지우고 표준 형식과 공백을 사용해야 하며, 잘 모르면서 다른 파일에서 설정을 복사해 붙여넣는 일은 금하자.

배포된 설정은 변경하지 말자

특정 머신의 설정을 수작업으로 수정하는 일은 금하자.

개발자의 필수 체크리스트

이것만은 지키자 이것만은 피하자
런타임 에러보다는 컴파일 타임에 에러를 검출하자 예외를 이용해 애플리케이션의 로직을 결정하면 안된다
가능하면 불변 변수를 사용하자 리턴 코드로 예외를 처리해서는 안 된다
입력과 출력을 검증하자 처리할 수 없는 예외는 잡지 말자
10대 애플리케이션 취약점을 숙지하자 로그를 여러 줄로 나누지 말자
버그 확인도구는 물론 타입 또는 타입 힌트도 사용하자 보안 정보나 민감한 데이터를 로그에 기록하지 말자
코드에 지표를 추가하자 비밀번호나 보안 정보를 설정 파일에 기록하지 말자
애플리케이션 설정을 추가해두자 커스텀 설정 형식은 채택하지 말자
모든 설정을 검증하고 로그에 기록해하자 불필요한 동작 설정은 사용하지 말자

5장 피할 수 없는 코드 의존성의 관리

복잡한 프로그램을 짜봐야 비로소 깨닫는 의존성의 진실

코드에 의존성을 추가하는 것은 그다지 어려운 결정이 아니다. ‘같은 일을 반복하지 말라(DAY)’는 매우 보편적인 원칙이다. 우리가 직접 라이브러리의 기능을 일일이 구현할 필요가 없듯이 말이다. 하지만 의존성에는 다양한 위험성이 내포되어 있다.

따라서 의존성을 다룰 땐, 이런 위험을 인지하고 사전에 완하할 수 있는 방법을 반드시 고려해야 한다.

의존성 관리를 이해하기 위한 필수 개념

문제점과 권장 기법을 살펴보기 앞서 먼저 의존성과 버저닝의 기본적인 개념을 이해해야 한다. 의존성이란, 코드가 의존하는 코드를 말한다. 그 의존성이 필요한 시점, 즉 컴파일 시점, 테스트, 런타임 등을 의존성 범위라고 부른다.

의존성은 패키지 관리 파일 또는 빌드 파일에 선언한다. 꼭 언어레벨이 아니더라도 유니티의 어셈블리나 언리얼의 Target.cs 파일도 의존성을 선언하는 파일이다.

책에서 말하는 버저닝은 깃헙의 릴리즈 버전과 같이 각각 주, 보조, 패치등의 담당을 말하며 이는 꼭 의존성 버저닝에만 해당되는 말은 아니다.

이행적 의존성에서 트리형태가 가지는 의존성에 대해서 보여주는데, 실제 상속 구조나 하위레벨을 가지는 컴포넌트로 생각해도 언어를 넘어 툴 레벨에서 의존성을 가질 수 있음을 간접적으로 보여준다.

현업이면 누구나 한 번은 겪는 의존성 지옥

현업자에게 의존성 지옥을 묻는다면 동일 라이브러리 버전 충돌이나 라이브러리 업그레이드 후의 비호환성 문제를 일으켜 런타임 장애를 유발하는 등 다양한 문제를 겪어본 경험이 있을 것이다. (다이아몬드 의존성, 버전 충돌 등)

어디서나 순환은 문제가 되는데, 의존성에서도 마찬가지다. 순환 의존성은 한 라이브러리를 업그레이드하면 다른 라이브러리가 깨지게 된다.

의존성 지옥에서 탈출하자

의존성은 피할 수 없지만 뭐가 됐든 새로운 의존성이 추가될 때마다 비용이 따른다. 의존성의 가치가 그 비용보다 큰지 여러분 큰지 스스로 판단해야 한다.

  • 이 기능은 정말 필요한가?
  • 의존성은 얼마나 잘 관리되고 있는가?
  • 뭔가 잘못됐을 때 의존성을 수정하는 것은 얼마나 쉬운가?
  • 의존성은 얼마나 성숙해 있는가?
  • 의존성에서 하위 호환되지 않는 변경이 얼마나 자주 일어나는가?
  • 나 자신과 우리 팀, 우리 조직은 의존성에 대해 얼마나 이해하고 있는가?
  • 코드를 직접 작성한다면 구현 난이도는 어느 정도인가?
  • 어떤 유형의 코드 라이선스가 적용돼 있는가?
  • 의존성 내에서 내가 사용하는 코드의 사용하지 않는 코드의 비율은 어느정도 인가?

내 생각엔 의존성에 대해서 같이 작업을 하려면 개인의 이해도 중요하지만 그만큼 성숙한 팀이 필요한 것도 사실이다. 따라서 위 조건들을 만족하고 다룰 수 있다면 의존성 관리 기법을 통해 관리하자.

의존성을 격리하자

코드 복사와 같이 자동화 관리를 포기하는 수준의 작업을 극도로 싫어할 수 있지만(DAY원칙) 실용적으로 생각했을 때, 더 큰 의존성 문제나 불안정한 의존성 문제를 피하는 데 도움이 된다면 코드를 복사하는 것에 대하여 지나친 죄의식을 가질 필요는 없다.

과거 유니티로 레이어를 정렬하는 컴포넌트를 만들었을 때, 해당 컴포넌트의 오름차, 내림차 정렬에 해당하는 것을 public으로 bool로 조절할 수 있게 해둔 기억이 있는데 결국 나중에는 오름차, 내림차 정렬을 위한 컴포넌트를 따로 만들어서 사용했던 기억이 있다. 이는 결국 의존성을 격리한 것이다. (네이밍으로 구분)

다른 방법으론 의존성 가리기가 있다. 의존성을 피하기 위해 다른 네임스페이스에 위치하도록 하는 고급 기술이며, 현재는 많은 언어가 이를 지원한다.

의존성은 신중하게 추가하자

라이브러리는 명시적으로 선언하는 것이 좋다. 이행적 의존성 라이브러리가 제공하는 메서드와 클래스는 괜찮아 보이더라도 버전이 달라짐에 매우 취약하다.

버전을 고정하자

모든 의존성의 버전 번호를 명시하라. 이 방법을 버전 고정이라고 부른다. 버전을 고정해두지 않으면 빌드나 패키지 관리 시스템이 버전을 결정하게 된다.

라이브러리, 의존성 실상 개발 과정에서 버전 업그레이드는 매우 위험한 작업이다. 마이그레이션은 물론 테스트, 릴리즈, 배포 등의 과정을 거쳐야 하기 때문이다.

의존성의 범위를 좁히자

의존성의 ‘범위’는 의존성이 빌드 수명주기 내에서 언제 사용되는 것인지를 결정한다. 범위는 계층 구조를 갖는다. 컴파일타임 의존성은 런타임에도 사용되지만, 반대로 런타임 의존성은 컴파일타임에 사용되지 않으며 단지 실행될 때만 사용된다.

의존성은 최대한 범위를 좁혀서 사용해야 한다. 모든 의존성을 컴파일 타임 범위에 추가하는 것은 좋은 방법이 아니다. 범위를 좁히면 충돌과 런타임 바이너리의 크기를 줄일 수 있다.

순환 의존성에 주의하자

절대로 순환 의존성이 발생해서는 안된다.

6장 테스트! 개발자의 든든한 지원군

업무 부하를 낮추면서 시스템 동작도 검증하는 테스트 방안

테스트를 작성하고 실행하고 수정하는 일은 별반 쓸모없는 작업처럼 보일 수 있다. 실상 테스트는 쓸모없는 일이 되기 쉽다. 좋지 않은 테스트는 아무런 효과도 거두지 못하고 개발자의 업무 부하만 가중시키며 테스트 스위트의 불안정성만 증가시킬 뿐이다.

공감하는 말이며, 의미없는 테스트는 오히려 프로젝트에 악 영향이 간다. 기본적으로 테스트 코드가 필요한 이유와 명세가 명확해야 한다.

테스트 꼭 해야 할까

테스트 코드는 표면적으론 코드의 동작을 확인하는 용도이지만, 실제론 더 다양한 목적이 있다. 의도지 않게 코드의 동작이 바뀌는 것을 방지하고 깔끔한 코드를 작성하게 도와준다. 개발자가 자신의 API를 사용하도록 강제하며 컴포넌트를 어떻게 사용하는지에 대한 문서 역할을 한다.

그외에도 더 구조적인 코드를 짜게 해주거나 방어적인 코드를 작성하게 도와준다.

가장 좋은 점은 역시 테스트 코드가 개발자의 역할을 대신하기에 시간이 지남에 따라 변경에 대한 안전성을 보장해준다. 또한 개발자는 테스트를 작성하면서 프로그램의 인터페이스와 실제 구현에 대해 고민해볼 수 있다.

이 부분이 테스트 코드가 주는 가장 좋은 학습효과라고 생각한다.(학습 효과중에)

이 과정에서 스파게티 코드의 문제점을 발견하거나 미숙한 인터페이스 나아가 TDD까지 고민해볼 수 있다.

테스트의 유형과 기법

테스트의 유형과 기법은 매우 다양하여 따로 책을 보는 것이 좋다고 생각한다. 기본적인 단위 테스트, 통합 테스트, 시스템 테스트, 성능 테스트, 인수 테스트 등이 있으며, 게임 엔진에서는 또 다른 형태의 테스트가 필요하다.

기본적인 단위 테스트는 메서드나 동작 하나를 검증한다. 단위 테스트 특성상 속도가 중요하다. 통합 테스트는 여러 객체의 인스턴스를 생성해서 서로 의존하며 동작하는 코드를 테스트하기 위한 코드이다. (컴포넌트 테스트도 여기에 해당된다고 생각)

시스템 테스트는 시스템 전체를 검증한다. 종단 간 워크플로는 프로덕션 환경의 전반적인 단계에서 실제 사용자의 동작 시뮬레이션한다. 시스템 테스트를 자동화하는 방법은 다양하다. 어떤 조직에서는 릴리스 전에 시스템 테스트를 모두 통과하도록 규정해둔다.

성능 테스트는 주어진 설정하에서 시스템의 성능을 측정하는 것으로, 예를 들어 부하 테스트나 스트레스 테스트가 있다. 서버나, 게임의 사양, 그래픽 환경등 다양한 요소로 성능 테스트 진행

이 외에도 게임에선 QA도 일종의 테스트로 볼 수 있다. 게임의 플레이 테스트, 릴리즈 테스트, 릴리즈 후 모니터링 등이 있다.

테스트 도구

모킹과 같은 테스트 작성 도구를 사용하면 더 효율적인 테스트를 작성할 수 있다. 코드 품질 도구는 커버리지와 복잡도 등을 분석하거나 정적 분석을 통한 에러, 코드 스타일 등을 검사한다.

하지만 도구를 추가함으로써 그만큼의 엔트로피는 증가하니 잘 생각해야 한다. 도구의 일원화는 중요하다.

모킹 라이브러리

과거 TDD를 공부하며 C#의 모의 객체를 만드는 라이브러리인 NSubstitute를 사용해봤다. 인터페이스를 모방하여 해당 메서드가 호출되었는지, 호출되었을 때 어떤 값을 반환해야 하는지 등을 정의할 수 있다.

이를 통해 복잡한 의존성을 가진 코드를 테스트할 때 테스트 대상 코드가 의존하는 객체를 모킹하여 테스트를 진행할 수 있다.

하지만 모의 객체에 과도하게 의존한다는 것은 결국 코드가 강하게 결합되어 있음을 의미하기 때문에 결국 악취다. 이때는 의존성을 제거하도록 리팩터링을 해야한다.

테스트 프레임워크

테스트 프레임워크는 테스트 코드를 작성하고 실행하는 도구이다. C#에서는 NUnit, xUnit을 써봤지만 결국 엔진이라는 더 상위의 개념에 의존적일 수 밖에 없다. 따라서 유니티나 언리얼 자체에서 제공하는 테스트 프레임워크를 사용하는 것이 좋다.

코드 품질 도구

요즘에는 대부분 IDE가 많이 좋아져서 책에서 나오는 기능들이 다 IDE에 포함되지 않을까 싶다. 코드 복잡도 도구나 이를 시각화해주는 도구가 따로 있다면 너무 좋을 것 같다.

개발자 스스로 직접 테스트를 작성하자

누군가가 내 코드를 대신 정리해줄 것이라 기대하지 말고 직접 테스트를 작성하자.

테스트는 깔끔하게 작성하자

다른 코드와 마찬가지로 오히려 더욱 테스트 코드는 깔끔하게 작성해야 한다. 테스트 코드 그 자체로 문서이기 때문이다. 상세 구현보다는 근본적인 기능을 테스트하는 것이 중요하다. 또한 테스트에 필요한 의존성은 실제 코드의 의존성과 분리해서 관리하자.

과도한 테스트는 삼가자

테스트를 작성하는 데 너무 많은 노력을 기울이지는 말자. 테스트를 너무 많이 작성하면 어떤 테스트가 꼭 필요한 것인지에 대한 감각을 잃어버리기 쉽다.

위에서 말한 것처럼 세세한 내용보단 전체적인 맥락에 대한 테스트가 더 중요하다..

커버리지 지표를 올리기 위한 테스트 코드를 작성하지는 말자. 커버리지가 높다고 좋은 소프트웨어인 것은 아니다.

테스트 결정성: 항상 동일한 테스트 결과를 만드려면

결정적 코드란 입력이 같으면 그 출력도 항상 같은 코드를 말한다. 반면 비결정적 코드는 입력이 같아도 출력은 다를 수 있다. 마치 수학의 연속함수와 불연속함수처럼 말이다.

비결정적 코드는 테스트의 가치를 떨어뜨린다. 테스트가 간헐적으로 실패한다는 것은 매번 동일한 현상이 발생하지는 않는다는 뜻이므로 재현과 디버깅이 어렵다. 코드가 문제인지 테스트가 문제인지 알 수 없기 때문이다. 즉, 테스트하려는 도메인도 결정적이어야 한다.

테스트의 실행 순서에 의존하지 말자

테스트를 실행 순서에 의존해서는 안 된다. 실제 게임에서는 많이 발생하는 경우다. 의존되어야 하는 테스트는 하나로 묶어서 관리할 것..! 이는 싱글톤을 사용하면 자주 발생하는 문제이다. 이를 위해 공통되는 작업을 Setup 메서드로 빼서 관리하자.

7장 올바로 주고받는 코드 리뷰

원활한 팀 협업과 높은 코드 품질을 목표로

대부분의 팀은 변경한 코드를 머지하기 전에 리뷰를 진행한다. 고품질 코드 리뷰 문화를 갖추는 것은 엔지니어 수준과 관계없이 모두가 함께 성장하며 코드베이스에 대한 이해를 서로 공유하는 데 도움이 된다. 반면 잘못된 코드 리뷰 문화는 혁신을 방해하고 개발 속도를 늦추며 사적인 감정 소모만 쌓이게 된다.

테스트 코드와 마찬가지로 잘못된 코드리뷰, 성숙하지 못한 팀에서는 오히려 악 형향이 된다.

코드 리뷰는 왜 필요한가

잘 수행된 코드 리뷰는 엄청난 가치를 지닌다. 버그를 찾아내고 코드를 깔끔하게 유지할 수 있다는 표면적이지만, 명확한 장점도 있는 한편, 자동화된 테스트나 런터 외에도 사람이 개입을 한다는 것 이상의 가치를 지닌다.

이는 교육 도구로서 동작하며, 인식을 확장시키고, 구현 결정 사항을 문서화하며, 보안 및 규정 준수와 관련해 변경 기록을 제공한다.

진짜 맞는 말만 하지만, 위에서 말한대로 이는 성숙한 팀에서만 가능한 이야기일 수 있다..

코드베이스 변경을 리뷰한다는 것은 곧 두 명 이상의 엔지니어가 한 줄 한 줄 모든 프로덕션 코드를 숙지한다는 의미다. 코드베이스에 대한 이해를 공유하는 것은 팀이 코드를 더 응집력 있게 개선하는 데 도움이 된다.

리뷰의 댓글 기록은 왜 이렇게 작성했는지 설명하는 문서로서의 역할도 수행한다. 코드가 왜 이와 같은 방식으로 작성됐는지 그 이유를 항상 명확히 이해할 수 있는 것은 아니다. 이럴 때 코드 리뷰는 세부 구현에 대한 결정 과정의 기록으로 활용할 수 있다.

일종의 문서화가 자동적으로 진행된다.

코드 리뷰의 모든 장점은 참여자가 신뢰도 높은 다른 말로 서로 믿는 관계에서 진행해야 효과가 나타난다. 형편없는 코드 리뷰는 오히려 장애물이 되기 때문에 개발속도가 더디게 된다

코드 리뷰는 얼마나 똑똑한지 증명하는 기회가 아닐 뿐더러 상대방이 관료주의적인 장애물이라고 낙인 찍는 기회도 결코 아니다.

코드 리뷰를 제대로 받는 방법

코드가 변경되는 과정을 보면, 우선 필요한 코드를 변경하고 이를 제출한 후 승인 받아 머지하는 과정으로 이뤄진다. 이는 PR(Pull Request)를 통해 이뤄진다.

코드 리뷰를 받을 때 준비해야 할 사항

잘 준비된 리뷰 요청은 결국 어떤 작업을 했는지 다른 개발자에게 쉽게 피드백을 전달할 수 있다. 이를 버전 제어 시스템을 통해 변경을 더욱 작게 유지하고 리팩터링 작업은 다른 리뷰로 분리하고, 테스트 등등의 작업까지 모두 포함해야 한다.

리뷰 초안이 있으면 위험을 낮출 수 있다

초안은 어디서나 시작할 수 있는 강제성을 주기에 매우 효과적이다. 다른 양식이 아닌 일원화시키거나 체크리스트의 역할도 한다.

테스트 실행을 위한 리뷰 제출은 금물이다

대규모 프로젝트는 복잡한 테스트 도구를 사용하는 경우가 많다. 이 경우 CI시스템을 사용하여 이 문제를 해결하기도 한다. 하지만 테스트를 실행할 목적으로 코드 리뷰를 제출하는 것은 낭비다.

따라서 테스트를 로컬에서 실행할 수 있는 방법을 알아보자. 로컬에서 더욱 디버깅이 용이하기 때문에 빨리 알아낼 수 있다.

코드 변경사항이 많을 때는 좀 더 면밀하게

변경한 코드가 많으면 다른 사람에게 설명하는 시간을 갖는 것이 좋다. 같은 멘탈 모델을 공유하고, 코드 변경사항을 이해하는 데 도움이 된다.

자신의 코드에 너무 집착하지 말자

코드에 비판적인 의견이 달리는 것은 견디기 어려운 일이다. 그렇다고 그런 의견을 너무 감성적으로 받아들여서는 안된다. 리뷰는 코드에 대한 것이지, 개발자에 대한 것이 아니다.

공감력을 갖되 무례함은 참지 말자

의사소통 방식은 누구나 다르지만 무례함은 그냥 넘겨서는 안 된다. 누군가의 말이 ‘요점만 간단히’가 아니라 ‘퉁명스럽고 무례한’ 방법이 될 수 있다. 리뷰어에게 자유는 허락하되, 리뷰어의 댓글이 요점을 벗어나거나 무례한 경우에는 사실대로 알려주자.

주도적으로 행동하자

다른 사람에게 여러분의 코드를 리뷰해달라고 부탁하는 일을 너무 부끄러워 하지 말자. 리뷰어는 수많은 코드 리뷰 요청과 타켓 관련 알림을 받으므로 속도가 빠른 프로젝트에선 리뷰를 놓치는 경우도 있다.

코드 리뷰를 제대로 해주는 방법

훌륭한 리뷰어는 리뷰 요청을 몇 단계로 나눈다. 요청을 선별함으로써, 요청의 긴급도와 복잡도를 결정하고 그 변경사항을 리뷰할 시간을 마련한다.

리뷰 요청을 선별하자

리뷰에 대한 요청의 중요도를 선별하는 것부터 시작해야 하며, 중요도가 높으면 곧바로 리뷰를 진행해야 한다. 그 외에도 해당 팀의 선별 기준이나 리뷰어의 선호도에 따라 리뷰 요청을 선별할 수 있다.

리뷰를 위한 시간을 마련하자

코드 리뷰는 운영 업무와 유사하다. 리뷰의 횟수와 빈도는 다소 예측 불가능하다. 리뷰 요청을 받을 때마다 하던 일이 중단돼서는 안 된다. 리뷰 시점을 잘 선택하지 않으며 리뷰를 하느라 오히려 생산성이 떨어진다.

따라서 일정상에 코드 리뷰를 위한 시간을 마련해두자.

코드 변경사항을 이해하자

다짜고짜 의견부터 남기는 식으로 리뷰를 시작해서는 안 된다. 먼저 읽어본 다음, 필요한 질문을 충분히 해본다. 결국 코드 리뷰란, 리뷰어가 해당 코드의 변경 목적을 이해할 때 비로소 그 가치가 나온다.

포괄적인 피드백을 제시하자

변경의 올바름, 구현, 유지보수성, 적합성, 보안성 등에 대한 피드백을 제공하라. 스타일 가이드나 읽어 어렵다거나 헷갈리는 부분도 지적하고, 테스트 코드를 읽어보고 발생할 수 있는 버그에 대해서 조언하라.

의견을 지나치게 간결하게 쓰지도 말자. 정중하게 표현하려는 방식과 그 이유를 설명하자.

좋은 점은 인정하자

코드를 리뷰하다 보면 문제를 찾는 데만 급급해질 수 있지만 코드 리뷰가 항상 부정적일 필요는 없다. 좋은 점에 대해서도 의견을 남기자! 코드를 읽으면서 뭔가 새로운 것을 배웠다면 그 점을 언급하자.

이슈, 제안, 사소한 흠결은 잘 구분하자

리뷰 의견은 중요도가 서로 다르다. 중립적인 제안과 사소한 흠보다는 중요한 이슈에 더 신경을 써야 한다. 스타일에 대한 피드백을 남기는 것을 주저하지는 말되 사소한 일임을 명확히 표시하자.

  • 선택적 제안, 원저자에게 맡김, 승인과는 무관 등의 접두어를 사용하여 반드시 필요한 것은 아님을 표시하자.

대충대충 리뷰는 금물

코드를 제대로 보지도 못했는데 리뷰를 승인해야 하는 부담에 시달릴 수 있다. 급한 변경이나 동료로부터의 압박, 얼핏 보기에 쉬운 듯한 변경, 반대로 변경 규모가 너무 큰 리뷰를 마주하면 이런 부담을 느낄 수 있다.

따라서 그의 입장을 고감하여 리뷰를 빨리 마무리하자라고 생각할 수 있지만, 이런 유혹에 넘어가 대충 리뷰한다면 오히려 더 많은 시간을 낭비하게 된다.

웹 기반 리뷰 도구에만 의존하지는 말자

코드 리뷰는 보통 깃허브 PR 인터페이스 같은 전용 UI로 처리한다. 이런 코드 리뷰는 단지 코드만 살펴본다는 점을 잊지 말자. 언제든 변경사항을 체크아웃하고 다운로드해서 로컬에서 실제로 실행해볼 수 있다.

가능하다면 로컬에서 코드를 실행해보고, 테스트를 실행해보자.

테스트 리뷰도 잊지 말자

테스트 코드를 먼저 읽어보고 리뷰를 시작하는 편이 유용할 때도 종종 있다. 테스트 코드는 실제 코드의 동작 방식과 사용 방법을 설명해준다. 코드의 유지보수와 간결함을 위해 테스트를 반드시 확인하자.

어떻게든 결론을 맺어야 한다

개선사항을 너무 오래 질질 끌지는 말자. 리뷰 요청자가 신속히 승인을 받을 수 있게 도와주자. 지나치게 완벽을 추구하지는 말고 변경의 범위를 확장하지 말며 어떤 댓글이나 의견이 중요한지 명확히 묘사하고 서로 동의하지 않는 부분이 곪아 터지지 않도록 주의하자.

좋은 품질을 추구해야 하지만 고집스런 불통의 자세는 금물이다.

8장 고객 앞으로! 소프트웨어 전달

마침내 프로덕션 환경에 안착시킬 소프트웨어의 종착지

실제 고객에게 소프트웨어가 전달되는 흐름을 이해하고 제작하는 것은 전대 다르다. 소프트웨어 전달은 릴리스, 배포, 롤아웃 등의 단계를 거치며 그 과정에서 소스코드 컨트롤 전략 등을 알아본다.

소프트웨어 전달의 4가지 단계

소프트웨어 전달의 단계는 업계에서 정해진 표준이 없으며 기업이나 프로젝트에 따라 다를 수 있다. 하지만 크게 빌드, 릴리스, 배포, 롤아웃 단계로 나눌 수 있다.

소프트웨어는 반드시 패키지로 빌드돼야한다. 패키지는 반드시 불변이며 버전이 지정돼야 한다. 또한 패키지는 생성되면 반드시 릴리스돼야 하며, 릴리스 과정에서 릴리스 노트와 변경 로그도 업데이트한다. 그 다음 중앙식 레포로 발행한다.

발행된 릴리스 산출물은 반드시 테스트 환경이나 프로덕션 환경에서 배포돼야 하며 배포된 소프트웨어는 아직까지는 사용자가 접근할 수 없다. 단지 지금 막 인스톨됐을 뿐이다.

이러한 과정으로 흘러간다는 흐름만 이해해도 될 것 같다. 실제 실무를 겪고 나면 다르게 보일 것

효과적인 버전 제어를 위한 브랜치 전략

책에서 나오는 트렁크도 있지만, 대부분 깃 플로우나 깃허브 플로우를 사용한다. 이 두가지 방법은 브랜치 전략을 사용한다.

자주 머지하는 방식을 CI(지속적 통합) 브랜치 전략이라고 한다. 이 전략은 각 개발자가 너무 오래 최신 버전의 코드로부터 벗어나는 일이 적어지므로 위험을 줄일 수 있다. 개발자 간에 코드베이스가 잘 동기화돼 있으면 마지막 순간에 통합하는 과정에서의 장애물을 피할 수 있으며 버그나 호환성 문제도 일찍 발견할 수 있다.

빌드 단계

소프트웨어를 전달하기에 앞서 반드시 먼저 패키지를 빌드해야 한다. 소프트웨어 빌드는 의존성 해석 및 링킹, 런터 실행, 컴파일, 테스트, 소프트웨어 패키징 등 여러 단계로 구성된다.

패키지에 버전을 명시하자

패키지에는 버전을 명시해야 하며 고유한 식별자를 할당해야 한다. 고유한 식별자를 할당해두면 운영자와 개발자는 실행중인 애플리케이션의 특정 소스 코드, 기능, 문서와 엮을 수 있다.

리소스는 각각 별도로 패키징하자

소프트웨어는 단순히 코드로만 구성되지 않는다. 설정, 스키마, 이미지 언어팩도 소프트웨어의 일부다. 각각 리소스는 서로 다른 릴리스 방식을 가지고 다른 검증이 필요하다. 게임은 더 다양한 레포를 필요로 할 것 같다..

릴리스 단계

사용자가 소프트웨어를 사용할 수 있게 하는 릴리스는 소프트웨어 전달의 다음 단계인 배포를 위한 단계다. 릴리스는 소프트웨어의 종류와 크기 및 사용자의 숙련도에 따라 다양한 방법으로 수행한다.

릴리스 관리는 안정적이며 문서화가 잘된 소프트웨어를 예측 가능한 시점에 발행하는 기술이다. 깃허브에서도 이런 기능을 지원하기에 사용하면 좋다.

릴리스를 남의 일로 여기지 말자

소프트웨어 릴리스에 대한 책임 의식을 가지고 있어야 한다. 조직에 있으면 좋지만 없더라도 릴리스는 다양한 이유, 위와 같은 이유로 중요하다.

패키지를 릴리스 레포로 발행하자

릴리스 패키지는 대부분 패키지 레포에서 발행하거나 깃 같은 버전 제어 시스템에 간단히 태그로 생성하기도 한다. 두 방법 다 괜찮지만 가능하다면 릴리스 패키지는 전용 패키지 레포에서 발행하는 편이 좋다.

실제 오픈소스중에는 릴리스 레포만 따로 파고 개발은 다른 곳에서 진행하는 경우가 종종 있다.

릴리스는 불변성을 갖게 하자

일단 릴리스 패키지를 발행했다면 절대 변경하거나 덮어쓰지 말자. 불변 릴리스는 동일한 버전을 실행하는 모든 애플리케이션 인스턴스가 완전히 동일한 코드를 실행하도록 보장해준다.

릴리스 패키지가 동일하면 개발자는 애플리케이션의 어떤 코드를 실행하며 어떻게 동작해야 하는지를 유추할 수 있다. 버전이 할당된 패키지가 바뀌는 것은 패키지에 버전을 할당하지 않는 것과 매한가지다.

자주 릴리스하자

릴리스는 최대한 자주 수행하자. 릴리스가 주기가 늘어지면 거짓된 안정감을 심어줄 수 있다. 릴리스 사이의 주기가 길면 변경사항을 테스트할 충분한 시간이 있다고 착각하기 때문에 간격을 줄이면 버그를 좀 더 쉽게 처리할 수 있다.

그렇다고 너무 자주도 별로일 것 같다. 마일스톤에 맞게 릴리스를 하는 것이 좋을 것 같다.

릴리스 일정은 투명하게 공유하자

릴리스 일정은 말 그대로 소프트웨어 일리스 빈도를 결정한다. 예측 가능한 기준으로 해서 매 분기나 매년 릴리스를 수행하는 프로젝트도 있다. 책에서 말하는 마일스톤을 릴리스로 잡는 것을 생각해보자. 팀원 모두에게 알려줘야 할 것

변경 로그와 릴리스 노트를 발행하자

변경 로그와 릴리스 노트는 릴리스에 어떤 변경사항이 반영됐는지를 사용자와 고객지원팀이 이해하는 데 도움이 된다. 로그 자체로 문서화가 되고 어떤 변경사항이 발생했는지 알 수 있게 해준다. 스팀에서 제공하는 패키지 노트와 비슷한 것 같다.

배포 단계

소프트웨어 배포란 패키지를 실행할 곳으로 옮기는 것이다. 배포 메커니즘은 다양하지만 기본원리는 같다.

배포를 자동화하자

소프트웨어 배포는 직접 수행하지 말고 스크립트를 이용하자. 자동화를 통해 배포를 반복하면 버전 제어, 예측 가능하게 하거나 실수자체가 방지된다. 이 과정을 CI/CD로 자동화하면 더 좋다. (github Action 참고) 게임에서는 젠킨스를 많이 사용하는 듯하다.

자동화를 고도화하면 지속적 전달(CD)로 이어진다. 지속적 전달을 적용하면 사람은 배포 절차에서 완전히 배재된다. 패키징, 테스팅, 릴리스, 배포, 심지어 롤아웃까지 모두 자동화할 수 있다.

배포는 원자적으로 수행하자

인스톨 스크립트는 여러 단계로 구성되는 경우가 많다. 따라서 매번 실행할 때마다 모든 단계가 성공할 것이라고 간주하지 말자. 머신의 디스크 용량이 부족해지거나 엉뚱한 시간에 재시작하거나 또는 예상치 못한 파일 권한 오류가 발생할 수 있다.

다른 의존 관계 없이 배포는 원자적으로 수행되어야 문제가 생기지 않는다.

롤아웃 단계

새 코드가 배포되고 나면 이제 그 코드를 동작시킬 수 있다. 한 번에 모조리 새 코드로 전환하는 일은 위험하다. 테스트를 아무리 많이 해도 버그 발생 가능성을 없앨 수는 없으며 한 번에 모든 사용자에게 코드를 롤아웃하면 모두가 동시에 문제를 겪을 수 있다.

롤아웃 전략은 기능 플래그, 서킷 브레이커, 다크 론치, 카나리 배포, 블루-그린 배포등 여러 가지가 있다.

  • 기능 플래그: 코드 경로를 실행하는 사용자의 비율을 제어할 수 있다
  • 서킷 브레이커: 문제가 발생하면 자동으로 코드 경로를 바꿔준다.

너무 복잡한 롤아웃 전략은 오히려 운영 복잡도만 증가할 뿐이다. 그래서 관리자가 따로 필요한 이유가 아닐까?

롤아웃을 모니터링하자

새 코드를 활성화할 때는 에러율, 응답 시간, 리소스 사용량 등 상태 지표를 모니터링하자. 통계값과 롤아웃가정을 반드시 관찰해야 하며 범위를 늘리거나 줄이는 것은 로그와 지표를 관찰하는 사람이 결정하는 경우가 보편적이다.

코드는 커밋한다고 끝이 아니며 코드가 롤아웃된 이후에도 여전히 끝난 것이 아님을 기억하라!

기능 플래그를 활용하자

기능 플래그(feature flag)를 이용하면 개발자는 언제 새로운 코드를 사용자에게 릴리스할지를 결정할 수 있다. 코드가 플래그를 확인해서 특정 코드를 실행할지 여부를 결정하는 if 구문으로 감싸져 있다.

if feature_flags['new_game_mode']:
    # 새로운 게임 모드를 활성화하는 코드
else:
    # 기존 게임 모드를 유지하는 코드

서킷 브레이커를 이용해 코드를 보호하자

대부분 기능 플래그는 사람이 제어한다. 반면 서킷 브레이커는 레이턴시나 예외 같은 운영 이벤트에 의해 자동으로 제어되는 기능 플래그다. 서킷 브레이커는 간단히 토글되며 영구적이고 자동화됐다는 독특한 특징이 있다.

아마 게임쪽에선 서버 관련해서 특정 서킷 브레이커를 많이 사용할 것 같다.

서비스 버전은 병렬로 올리자

웹 서비스는 기존 버전을 계속 실행하면서 새로운 버전을 배포하는 것이 가능하다. 이런 경우 패키지는 동일한 머신에 함께 배포할 수도 있고 완전히 새로운 하드웨어에 배포할 수도 있다. 병렬 배포를 이용하면 천천히 버전을 올리면서 위험을 완화할 수 있으며 뭔가 잘못된 경우 신속하게 롤백할 수도 있다.

책에서 말하는 블루그린, 카나리 등을 게임에 덧대어 조금 쉽게 말하면 테스트 버전과 실제 버전을 병렬로 운영하는 것 같다.

9장 긴급대응 온콜 업무

언제 일어날지 모르는 장애에 대응하는 절차와 방안

많은 기업이 엔지니어에게 긴급대응 업무를 요구한다. 실제 게임업계에서도 매우 자주 일어나는 일로 라이브 서비스중인 게임에서 많이 발생한다.

긴급한 비상상황에 대응하는 온콜 업무

온콜업무란 주로 버그 보고, 팀이 담당하는 소프트웨어 동작 방식이나 사용법에 대한 질문 같은 일회성 지원 요청 처리 등을 말한다. 온콜 엔지니어는 이와 같은 요청을 분류해서 가장 급한 업무부터 대응한다.

게임에서는 라이브서비스 중인 게임의 업데이트, 서버 점검등이 포함될 것 같다.

반드시 갖춰야 할 온콜 스킬

온콜 업무는 늘 분주하며, 스트레스가 높은 경험이 될 수 있다. 하지만 다행스럽게도 장애와 지원 요청 모두를 처리할 수 있는 기본적인 스킬이 몇 가지 있다.

항시 언제라도 대응할 준비를 갖추자

가용성으로 온콜 업무의 대부분은 요청과 알람에 대응하는 것이다. 요청을 무시하거나 감춰서는 안 된다. 온콜 업무를 수행하는 동안에는 언제든 긴급 요청이 발생할 수 있음을 인지하고 그런 만큼 업무에 집중하기 어렵다는 사실을 받아들이자.

온콜 개발자가 하루종일 컴퓨터 근처에서 대기해야 경우도 있기 때문에 언제라도 대응할 수 있는 가용성이 필요하다.

주의를 늦추지 말고 집중하자

온콜 업무는 다양한 정보를 다루게 되는데 그 채널은 채팅, 메일, 전화, 등등.. 매우 다양하게 전달된다. 따라서 관련된 사항과 채널 정보는 미리 알아야 한다.

개인적으로 계속 당연한 말을 하는 듯한 느낌이 들었다.

업무 우선순위를 정하자

우선순위가 가장 높은 작업부터 시작하자. 그 작업이 끝나거나 막히면 우선순위가 높은 순부터 낮은 순으로 업무를 수행해 나가면 된다. 업무를 수행하다보면 알람이 울리고 새로운 질문이 들어올 것이다.

이것도 당연한 이야기…

실제 기업에서는 P0, P1, P2, P3 등의 우선순위를 부여하여 처리한다.

명확하게 의사소통하자

명확한 의사소통은 운영 업무를 처리하는 데 매우 중요하다. 상황이 급변하므로 의사소통이 잘못되면 큰 문제가 발생할 수 있다. 명확한 의사소통을 위해서는 공손하고 직접적이며 즉각적이고 철저해야 한다.

업무 진척사항을 추적하자

슬랙과 같은 채팅 채널을 이용해도 좋지만, 나중에 찾아보기 어려우므로 모든 사항을 티켓이나 문서로 요약해두자.

  • 시간을 기록
  • 완료된 이슈는 왜곡되지 않게 바로 처리

장애 처리의 5가지 단계

장애 처리는 온콜 엔지니어의 가장 중요한 책임이다. 대부분의 개발자는 장애 처리를 프로덕션 환경의 문제를 해결하는 것이라고 생각한다. 물론 문제 해결도 중요하지만 심각한 장애 상황에서의 최우선 목표는 문제의 영향을 완화하고 서비스를 복구하는 것이다.

두 번째 목표가 문제가 왜 발생했는지 나중에 분석할 수 있도록 정보를 수집하는 것이다. 장애 원인을 파악하고 정확한 지점을 찾아 문제를 해결하는 것은 그저 세 번째 목표일 뿐이다.

  1. 선별: 문제를 찾아내고 심각도를 파악한 뒤 누가 수정할 지 결정한다.
  2. 조율: 팀은 반드시 이슈에 대해 전달받아야 한다. 온콜 엔지니어가 스스로 문제를 해결할 수 없다면 해결 할 수 있는 사람을 찾아야 한다.
  3. 완화: 엔지니어는 반드시 최대한 빠른 시간 내에 서비스를 안정화해야 한다. 완화는 장기적인 해결책이 아니다.
  4. 해결: 문제가 완화되면 엔지니어는 숨을 돌리고 해결책에 대해 생각을 가다듬으며 작업할 시간을 벌게 된다.
  5. 후속 조치: 애당초 왜 장애가 발생했는지 근본적인 원인에 대한 조사를 수행한다.

장애 조치 단계는 추상적으로 보일 수도 있지만, 이 흐름이라는 것을 기억하라.

지원 업무도 엄연한 온콜 업무다

온콜 엔지니어는 장애를 처리하지 않을 때는 지원 요청을 처리한다. 지원 요청은 상당히 표준화된 흐름을 따른다. 요청을 받으면 요청을 접수했음을 알리고 문제를 제대로 이해했는지 일단 확인한다.

영웅이 되려 하지는 말자

온콜 업무를 하다 보면 만족감을 느낄 수 있다. 하지만 온콜 업무를 너무 많이 수행하다 보면 번아웃이 올 수 있다.

결국 자신을 관리하는 능력도 중요하다는 이야기인 것 같다.

결론

전체적으로 당연한 이야기를 당연하게 한다는 느낌을 받았지만, 그래도 실제 업무에 들어가 다시 이 책을 읽으면 느끼는 점이 많을 것 같다는 생각이 든다.

10장 견고한 소프트웨어를 위한 기술 설계 절차

대규모 변경에 적합한 소프트웨어 설계와 문서화 기법

대부분 초급 엔지니어는 생각을 짧게 하고 바로 코딩이 가능한 업무가 주어지기에 어렵지 않게 업무를 진행할 수 있지만, 결국 큰 업무를 맡게 될 것이고 그런 상황에선 견고한 기술 설계를 고민해야 한다.

고깔형의 기술 설계 절차

소프트웨어의 설계란 조사와 브레인스토밍을 거쳐 문서화하고 승인을 받는 선형적인 과정이 아니다. 오히려 단독 업무와 팀 협업 사이를 전환하면서 모든 단계마다 설계를 명확히 하고 재정의하는 나선형 업무에 가깝다.

설계 문서는 과정을 반복할 때마다 더 명확해지고 상세해진다. 실험, 개념 증명, 벤치마크 등 설계 외 업무를 수행하면서 해결책에 대한 문서 작성자의 확신도 커진다. 설계에 참여하는 인원 수와 다양성도 시간이 지날수록 증가한다.

처음에는 고깔의 바닥에서 시작한다. 문제의 범위와 요구사항, 가능한 해결책 등이 불명확한 상태다. 그래서 절차를 처음 시작할 때는 확실한 해결책 마련이 불가능하다.

결국 이 과정의 목표는 확실성과 명확성을 높이기 위해 문제에 대해 알아가는 것이다. 계속해서 문서를 수정하고 토론하고 확장해 나가는 과정에서 점점 넓어지고 이 과정의 반복이다. 중요한 점은 시작을 해야하며, 수정에 대해 열려 있어야 한다는 점이다.

올바른 기술 설계를 하려면

소프트웨어 설계는 탐구에서 출발한다. 설계를 시작하기 전에 문제 영역과 요구사항을 반드시 이해해야 한다. 탐구 과정에는 고민과 조사, 실험, 논의 등이 필요하다.

문제를 정의하자

가장 먼저 할 일은 해결하고자 하는 문제를 정의하고 이해하는 것이다. 올바른 해결 방법을 찾기 위해 문제의 경계부터 이해해야 한다. 어쩌면 문제가 없거나 해결할 필요조차 없다는 점을 알아낼지도 모른다.

이 과정에서 문제에 대한 이해는 이해하려고 하는 자가 문제 자체를 명확하게 인지하는 단계에서부터 출발한다. 정의하는 과정에선 너무 큰 덩어리보다 작은 단위로 쪼개서 이해하는 것이 많이 도움되는 듯 하다.

“이 문제를 해결하지 않으면 어떻게 될까?”

위와 같은 질문을 해보고 실제로 이 문제가 해결할 가치가 있는지 생각해보자. 문제 자체를 이성적으로 바라보고 이해하는 것이 중요하다.

해결 방법을 조사하자

문제를 정의했다고 해서 곧바로 ‘최종’ 설계를 마련하라고 서두르지는 말자. 관련 내용에 대한 조사, 다른 해결책, 트레이드 오프 등을 고려해야 한다. 스스로 생각해낸 설계는 최초의 방안이 아니라 최선의 방안이어야 한다.

다양한 실험을 해보자

일단 아이디어가 떠올랐으면 대략적으로 코드를 작성하고 테스트를 실행해서 실햄해보자. API 명세를 작성하고 부분적으로 구현해본다. 해당 문제에 대한 자신감이 붙기 시작한다면 설계상의 트레이드 오프가 보이기 시작하고 좀 더 명확한 정의가 떠오르기도 한다.

충분한 시간을 투자하자

좋은 설계에는 창의력이 많이 필요하다. 앉은 자리에서 한 번에 설계를 끝낼 수 있다고 기대해서는 안 된다. 자신에게 충분한 시간을 주고, 휴식도 취하며, 시각으로 바꿀 수 있는 환경도 만들어보고, 인내심을 갖자.

개인적으로 가장 좋아하는 시간이다. 설계에 창의력을 발휘하고 좀 더 좋은 구조를 고민해볼 수 있는 기회가 많았으면 좋겠다. 개인적으로 샤워할 때 가장 많이 떠오른다.

의사소통을 위한 설계 문서 작성 방안

설계 문서는 아이디어를 명확하게 소통할 수 있는 확장 가능한 방법이다. 문서를 작성하는 과정에서 생각을 정리할 수 있고 약점도 드러날 수 있다. 아이디어를 문서화하는 작업이 항상 수월하지만은 않다. 유용한 설계 문서를 작성하려면 가장 중대한 변경사항에 집중하고 목표와 대상을 유념하면서 글쓰기를 연습하고 문서를 계속 업데이트해야 한다.

개인적으로 문서에 해당 구조에 대한 약점도 같이 기재하는 것이 좋지 않을까? 라는 생각이 든다. 모든 구조에는 약점이 있을텐데 관련된 내용도 같이 적어놔야 실질적 도움이 될 것 같다.

중요한 변경사항은 문서화해두자

모든 변경사항에 대해 설계 문서를 작성할 필요는 없으며 공식적으로 설계 리뷰 절차가 필요한 경우도 드물다. 각 조직에 맞는 컨벤션을 따라가는 것이 좋다.

설계 문서를 작성하는 이유를 이해하자

표면적으로 설계 문서는 소프트웨어 컴포넌트의 동작 방식을 설명하는 것 정도로만 보인다. 하지만 설계 문서의 기능은 단순한 문서 이상이다. 설계 문서는 생각할 수 있게 하며, 피드백을 받을 수 있게 하며, 팀원과 소통할 수 있게 한다. 도구의 역할, 테스트코드도 하나의 문서다.

또한, 문서화는 모르는 것을 드러내기 좋은 방법이기도 하고, 사고방식의 형상화이다. 이를 통해 다른 협업자에게 피드백을 좀 더 쉽게 요청할 수 있으며 다른 사람도 이 문서를 보고 같은 멘탈 모델을 공유할 수 있다.

글쓰는 법을 배우자

글쓰기 스킬 역시 다른 스킬과 마찬가지로 개선할 수 있는 영역이고 개발자에게 필요한 영역이다. 자신의 생각을 명확하게 글로 전달하고 기록하는 것은 소프트웨어 개발과정에서 당연하게 필요하다. 스스로 작성한 글을 본인이 아닌 독자의 관점으로 읽어보자. 남이 쓴 글을 읽어보면 어떤 부분을 놓치고 있는지 명확하게 알 수 있다.

설계 문서는 최신 상태로 유지하자

실제 구현이 시작되면서 설계 문서는 단순한 제안서에서 소프트웨어 구현 방법을 서술하는 문서로 바뀐다. 이는 살아있는 문서로서 팀원들이 모두 같은 멘탈 모델을 공유할 수 있게 한다. 다만 제안서를 문서로 전환하는 과정에서 보편적으로 범하는 실수는 크게 두 가지를 들 수 있다.

첫 번째로 제안 문서를 더 이상 업데이트하지 않고 내버려두는 것이다. 구현 방식은 여러 가지일 수 있으므로 업데이트하지 않은 문서는 나중에 사용자에게 잘못된 정보를 제공하게 된다. 두 번째 실수는 문서가 업데이트되면서 제안 단계에서 기록된 이력을 잃어버리는 것이다. 그러면 나중에 합류하는 개발자는 그 설계 결정에 이르게된 논의 과정을 전혀 알 수 없으며 과거의 실수를 반복할 사능성이 커질 수 있다.

업무를 진행하는 중에는 문서를 계속 업데이트하자. 설계 제안과 설계 문서를 둘로 나눈 경우라면, 구현된 제안 내용에 따라 각 문서를 계속해서 업데이트 해야 할 것이다. 위 내용을 쉽게 다룰려면 설계 문서도 결국 버전 관리가 되어야 함을 의미한다.

설계 문서 탬플릿의 기본 구조

설계 문서는 현재의 코드 설계, 변경 이유, 고려해 볼 수 있는 여러 해결책과 그중에서 채택하고자 하는 해결책 등을 서술해야 한다. 특히 채택하고자 하는 해결책에는 아키텍처 다이어그램, 주요 알고리즘, 공개 API, 스키마, 대안과의 트레이드오프, 가설, 의존성 등 상세 내용이 무도 담겨야 한다.

참고할만한 구조

  • 개요
  • 현재의 상태와 컨텍스트
  • 변경해야 하는 이유
  • 요구사항
  • 고려할 수 있는 해결책
  • 채택하려는 해결책
  • 설계와 아키텍처
    • 시스템 다이어그램
    • UI/UX 변경
    • 코드 변경
    • API 변경
    • 영속 계층 변경
  • 테스트 계획
  • 롤아웃 계획
  • 미결 사항
  • 부록

설계 과정에서도 협업은 중요하다

팀과 건설적으로 협업하면 더 나은 설계를 구현할 수 있다. 하지만 항상 쉽지만은 않은 것이 협업이다. 개발자는 완고하다. 개발자의 피드백을 해석하고 압축해서 의미있는 설계에 녹여내는 것은 결코 쉬운 일이 아니다. 팀의 설계 절차를 따르고, 일찍 자주 소통해서 혼선을 줄이며, 설계 논의를 통해 브레인 스토밍을 진행하는 등의 방법으로 협업하면서 설계를 완성하자.

팀의 설계 리뷰 절차를 이해하자

아키텍트는 설계 리뷰를 통해 앞으로 발생할 거대한 변경사항을 알게 되고, 기술 리드는 피드백을 제공할 기회를 얻게 된다. 확고한 리뷰 정책을 운영하는 조직도 있는 반면 비공식적으로 리뷰를 진행하는 조직도 있다.

마찬가지로 팀 내의 리뷰나 피드백 절차를 따라가야 한다.

갑작스런 상황은 만들지 말자

사람들에게 설계를 제안할 때는 정중하게 그리고 점진적으로 시도하자. 정식 설계 문서를 다른 팀이나 기술 리드에게 처음 선보일 때는 실패할 수 있음을 염두에 두자. 사람들마다 각자 시각이나 관심 분야가 다른데다 사전에 미리 언급된 적 없는 설계 문서를 갑자기 들이밀면 서부 반응을 보일 수도 있다.

그보다는 처음 조사를 시작할 시점에 다른 팀과 기술 리드로부터 일찍이 피드백을 받아보자. 그러면 설계도 더 향상될 뿐 아니라, 다른 사람도 지금 하고 있는 일을 인지해 설계에 참여할 수 있다. (협업)

설계를 논의하며 브레인스토밍을 하자

설계에 대한 논의는 문제 영역에 대한 이해, 지식 공유, 트레이드오프에 대한 고민, 더 견고한 설계 등에 도움이 된다. 이와 같은 브레인스토밍 세션은 비공식적이며 화이트 보드를 이용해 자유로운 형태로 대화하는 시간이다.

설계에 대한 논의는 문제에 대해서는 어느 정도 이해하고 있지만 설계 방식은 아직 결정되지 않은 시점에 주로 이뤄진다. 이 기간에서는 대화가 수월하게 진행될 수 있도록 사람을 2~5명정도로 제한하고 최소 2시간 정도로 생각해야 한다. 목적은 자유로운 토론으로 시작해 충분한 정보를 제공함을 목적으로 한다.

이 과정에서 회의 내용을 기록하는 것이 오히려 더 방해가 될 수 있다. 이 때는 돌아가면서 회의록을 작성해야 하며 중간중간 상태를 저장하고, 이미지/키워드 중심으로 요약된 회의록을 남긴다.

설계에 참여하자

스스로 설계뿐만 아니라 팀의 설계 업무에도 참여해야 한다. 마치 코드 리뷰처럼 설계에 참여하는 상황이 편치는 않을 수 있다. 본인보다 설계 경험이 많은 개발자가 설계를 주도한다면 스스로 기여할 것이 하나도 없다고 생각할 수 있지만, 설계 문서를 읽고 브레인스토밍 회의에 참여하는 것이 불필요하게 여겨질 수도 있다.

그래도 참여해야 한다. 팀의 설계를 개선하는데 도움이 될 뿐만 아니라 다른 배움을 얻을 수 있는 기회도 된다. 참여한 이후 궁금한 것은 물어보자.

결론

먼 이야기긴 하지만, 다른 장보다는 확실히 재밌게 읽은 것 같습니다. 설계에 관심이 많고 궁극적으로 하고 싶은 일이라 현업에서는 어떤 방식으로 진행하는지 궁금했는데, 결론은 팀의 방식에 맞춰서 참여하라 인 것 같습니다.

논의사항

  • 진행하는 프로젝트에서 설계는 어떤 방식으로 진행하시나요?

11장 소프트웨어 수명주기 관점의 진화하는 아키첵처 구현

성장하고 발전하는 소프트웨어를 만들기 위한 핵심 원칙

고객의 수요 변화에 따라 요구사항이 바뀌는 것은 소프트웨어 프로젝트에서는 어쩔 수 없는 과제다. (당연함), 제품 요구사항과 컨텍스트는 시간이 지나면 바뀌므로 여러분의 애플리케이션 도한 그에 따라 바뀔 수밖에 없다. 하지만 요구사항이 변화하면 불안정석을 야기하고 개발 일정에 악영향을 준다.

엔지니어링과 관리자들은 애자일(Agile)개발 방법론 같은 반복적인 프로세스를 이용해 요구사항의 변동을 관리한다. 한편 요구사항의 변경에 대응하려면 우리는 진화하는 아키텍처를 만들어야 한다. 진화하는 아키텍처란, 진화에 방해되는 복잡성을 배제하기 위한 것이다.

복잡도를 이해하자

복잡도란, 시스템을 이해하고 수정하는 것을 어렵게 만드는 시스템의 구조와 관련된 모든 것이라고 한다. 복잡한 시스템은 높은 의존성과 모호성이라는 두 가지 특성을 가진다. 여기서 세 번째 특성인 높은 관성을 추가하고자 한다.

의존성이 높은 소프트웨어는 다른 코드의 API나 동작에 의존하게 된다. 의존성이 분명히 불가피한 것으로서, 때로는 필요한 경우도 있지만 반드시 균형을 맞춰야 한다. 새로운 연결과 추정이 많아질수록 코드를 변경하기는 어려워진다. 의존성이 높은 시스템은 강한 결합과 변경의 영향도 확대로 인해 수정이 어렵다.

강한 결합이란, 어떤 모듈이 다른 모듈에 심하게 의존하는 것을 말한다. 그러면 변경의 영향도가 확대되어 하나를 변경하려면 의존성도 변경해야 한다. 그러면 변경의 영향도가 확대되어 하나를 변경하려면 의존성도 변경해야 한다.

모호성이 높으면 프로그래머는 변경의 부작용, 코드의 동작, 변경이 필요한 지점 등을 예측하기가 어려워진다. 모호한 코드는 학습에도 오랜 시간이 걸리며 개발자의 부주의로 문제가 일어날 확률 또한 높아진다. 너무 많은 작업을 담당하는 신 객체(싱글톤), 부작용을 유발하는 전역 상태, 코드를 이해하기 어렵게 만드는 과도한 간접성, 프로그램의 전혀 무관한 부분의 동작에 영향을 주는 원격 작용등 모두 높은 모호성의 증상이다.

관성은 소프트웨어를 계속 사용하려는 성향을 말한다. 간단한 실험에 사용하고 난 뒤 쉽게 없앨 수 있는 코드의 관성은 낮다고 볼 수 있다. 반면 비즈니스에 요긴한 수십 가지 애플리케이션을 실행하는 서비스는 관성이 높다고 할 수 있다. 복잡도의 비용은 시간이 지나면서 누적되므로 관성이 높고 변경이 잦은 시스템은 반드시 간소화해야 하는 반면, 관성이 낮고 변경이 드문 시스템은 복잡한 상태 그대로 남겨둬도 무방하다.

복잡도를 항상 제거할 수 있는 것은 아니지만 적어도 복잡하게 남겨둘 부분을 선택할 수는 있다. 변경사항의 하위 호환성을 고려하면 코드를 사용하기 쉽게 만들 수는 있지만 구현하기는 훨씬 복잡해진다.

진화하는 아키텍처를 위한 설계 원칙

미래에 발생할 요구사항을 알 수는 없으므로 엔지니어는 대체로 두 가지 전략 중 한 가지를 택한다. 나중에 어떤 필요가 발생할지 예측하려 하거나 나중에 좀 더 쉽게 코드를 변경할 수 있는 추상화를 구현하려 한다. 하지만 결국 두 가지 선택 모두 복잡도를 증가시킬 뿐이다. 최대한 간단하게 구현하자. 건결함을 염두에 두고 시스템을 구현할 수 있도록 하자. (Keep things simple, KISS)

코드를 간결하게 유지하는 가장 쉬운 방법은 모든 것을 한꺼번에 작성하지 않는 것이다.

YAGNI원칙: 당장 필요치 않다면 구현하지 말 것

YAGNI란, “당장 필요치 않다면 구현하지 말 것”을 의미한다. 일반적으로 갭라자가 코드에 대한 의욕이 넘치거나 집착하거나 우려가 있는 경우에 이 원칙을 위반하는 일이 생긴다. 이 원칙을 준수하기 위한 간단한 습관은 최소 기능 제품 기능, 불필요하게 유연한 추상화, 너무 이른 최적화 등을 피하면 된다.

개발자들은 대부분 플러그인 아키텍처, 래퍼 인터페이스, 키-값 쌍으로 표현하는 포괄적인 데이터 구조 같은 유연한 추상화에도 끌리는 경향이 있다. 개발자는 추상화를 통해 새로운 요구사항도 쉽게 처리할 수 있을 것이라고 생각하지만 추상화에는 항상 비용이 따른다. 추상화가 상세한 구현을 유연하지 않은 경계 안으로 숨겨버린 탓에 개발자는 이후에 더 많은 노력을 들여 코드를 수정해야 한다.

최소 충격 원칙: 사용자를 놀래키지 말 것

최소 충격 원칙은 매우 명확하다. 사용자를 놀라게 하지 말라는 것이다. 우리가 구현한 기능은 사용자가 처음 예상한 방식대로 동작해야 한다. 학습 곡선이 매우 높아 배우기 어렵거나 이상하게 동작하는 기능은 오히려 사용자를 불안하게 만든다.

도메인 지식은 캡슐화돼야 한다

소프트웨어는 비즈니스 요구사항에 따라 계속 변화한다. 그러므로 비즈니스 도메인을 중심으로 소프트웨어을 그룹화해서 도메인 지식을 캡슐화해야 한다. 비즈니스 도메인에 부합하는 소프트웨어 컴포넌트를 개발하면 코드를 더 집중적이고 깔끔하게 변경할 수 있다.

도메인의 캡슐화는 본질적으로 높은 응집도와 낮은 결합도를 지향하게 된다. 응집도가 높은 소프트웨어의 결합도가 낮아지면 변경사항의 ‘영향 반경’이 줄어들므로 좀 더 진화하는 코드를 작성할 수 있다.

서로 관련 있는 메소드, 변수, 클래스 등을 가까운 모듈이나 패키지로 모아둘 때 코드의 응집도가 높다고 한다. 결합도가 낮은 코드는 그 자체로 완결성이 높다. 따라서 로직을 변경하더라도 다른 소프트웨어 컴포넌트를 변경할 필요가 없다.

DDD의 필요성

진화하는 API를 위한 설계 원칙

요구사항이 변경되면 코드가 공유하는 인터페이스인 API를 변경해야 한다. API 변경은 쉽지만 올바르게 변경하기는 쉽지 않다. 작고 합리적인 변경사항도 많이 만들다 보면 혼잡해질 수 있다. 더욱이 API의 소소한 변경으로 인해 호환성이 완전히 무너지기도 한다. API를 변경하면서 호환성을 유지하지 않으면 클라이언트에 문제가 생긴다.

API 크기는 작게 유지하자

API를 작게 만들면 이해하고 개선하기 쉽다. API의 크기가 크면 개발자의 인지 부하가 높아지며 문서화, 지원, 디버그, 유지보수할 코드도 늘어난다. 모든 새로운 메소드나 필드는 API를 켜지게 하며 특정 사용 패턴에 묶이게 된다.

따라서 API에도 YAGNI 철학을 적용하자. API메소드나 필드는 당장 필요할 때만 추가하자. API 데이터 모델을 개발할 때는 지금 현 시점에 필요한 메서드만 추가하자.

잘 정의한 서비스 API를 노출하자

API 변경에 호환성을 유지하면 클라이언트 버전과 서버 버전을 독립적으로 관리할 수 있다. 호환성은 상위 호환성과 하위 호환성의 두 가지로 나뉜다. 변경사항이 상위 호환성을 가지면 클라이언트는 새로운 버전의 API를 이용해 이전 버전의 서비스도 호출할 수 있다. 1.0 버전의 API를 실행 중인 웹 서비스가 1.1 버전의 API를 이용하는 클라이언트의 요청을 수신할 수 있다면 상위 호환성을 갖춘 것이다.

변경사항이 하위 호환성을 갖는 것은 그 반대의 경우로 새로운 라이브러리나 서비스를 사용하기 위해 기존의 클라이언트 코드를 변경할 필요가 없다. API 1.0 버전을 이용해 개발된 코드가 1.1 버전의 API를 사용할 때 컴파일 및 실행이 가능하다면 하위 호환성을 갖춘 것이다.

API의 버전을 관리하자

시간이 지나면서 API가 진화하면 여러 버전의 호환성을 처리할 방법을 결정해야 한다. API가 상위 호환성과 하위 호환성을 완전히 제공한다면 기존의 모든 버전은 물론 미래의 API 버전과도 상호운용이 가능하다.

그러다 보면 코드의 유지보수가 어렵고 이제는 지원하지 않는 필드도 처리해야 하는 로직 같은 불편한 상황이 생긴다. 호환성 정책을 완화하면 더욱 급진적인 변화도 포용할 수 있다.

진화하는 데이터를 위한 설계 원칙

API는 영구적인 데이터에 비하면 수명이 짧은 편이다. 클라이언트와 서버 API가 업그레이드되면 업무는 종료된다. 하지만 데이터는 애를리케이션의 변화에 따라 반드시 진화해야 한다.

데이터베이스를 격리하자

공유 데이터베이스는 진화가 어려우며 자울성을 잃는 결과를 야기할 수 잇따. 여기서 자율성이란 개발자나 팀이 독립적으로 시스템을 변경할 수 있는 능력을 말한다. 공유 데이터 베이스는 다른 사람이 데이터베이스를 사용한다는 점을 고려해야 하므로 안전하게 스키마를 변경할 수 없음은 물론 심지어 데이트를 읽거나 쓰는 것조차 못할 수도 있다.

스키마를 사용하자

컬럼과 타입이 엄격하게 정해져 있고 이를 개선하기 위한 프로세스는 어렵다는 사실로 인해 스키마 없는 데이터 관리라는 기법이 나타나 유행하게 됐다. 대부분의 최신 데이터스토어는 미리 정해둔 구조가 없어도 JSON과 같이 객체를 저장하는 기능을 제공한다.

하지만 현실적으로 스키마 없는 방식은 상당한 데이터 무결성 및 복잡도 문제를 지니고 있음이 드러났다. 강려한 타입을 사용하는 스키마 지향 방식은 모호성을 줄여서 애플리케이션의 복잡도를 낮춘다.

느낀점

책 전반적으로 개발자의 삶에서 필요한 영역을 매우 넗게 다루기 때문에 전에 읽었던 책에 비해 내용이 겉핥기 느낌이 있습니다. 그래서 조금 가볍게 읽는 것이 좋겠다는 생각이 드네요

논의사항

개발자들은 대부분 플러그인 아키텍처, 래퍼 인터페이스, 키-값 쌍으로 표현하는 포괄적인 데이터 구조 같은 유연한 추상화에도 끌리는 경향이 있다. 개발자는 추상화를 통해 새로운 요구사항도 쉽게 처리할 수 있을 것이라고 생각하지만 추상화에는 항상 비용이 따른다. 추상화가 상세한 구현을 유연하지 않은 경계 안으로 숨겨버린 탓에 개발자는 이후에 더 많은 노력을 들여 코드를 수정해야 한다.

  • 개발자가 유연한 추상화에 끌리는 이유는 무엇일까요?

12장 효율적인 협업을 위한 애자일 문화

모두가 알지만 실천하기는 쉽지 않은 애자일

소프트웨어 개발은 계획적이어야 하며 추적 가능해야 한다. 동료는 효율적인 협업을 위해 서로가 무슨 일을 하고 있는지 알고 싶어 한다. 팀은 진척 상황을 추적해서 미래 업무를 계획해야 하며, 개발 과정에서 새로운 정보가 드러나며 업무 방향을 조율할 수 있어야 한다. 신중하게 계획된 절차를 대비해놓지 않는다면 프로젝트는 늘어지고, 끊임없는 외부 요구사항으로 인해 개발 과정은 산만해지며, 운영 이슈는 개발자를 끝없이 방해할 것이다.

애자일 선언문

애자일 개발 기법을 맛보려면 먼저 애자일 철학부터 이해해야 한다. 애자일은 2001년 익스트림 프로그래밍, 스크럼, 기능 주도 개발, 실용주의 프로그래밍 같은 기존 개발 방법론의 릳들이 협업해 만들었다.

애자일 선언문은 다음과 같다.

우리는 소프트웨어를 개발하고 다른 사람들의 개발을 도움으로써 소프트웨어 개발의 더 나은 방법을 찾아가고 있따. 이를 통해 우리가 추구하는 가치는 다음과 같다.

  • 절차와 도구보다는 각 개인 그리고 서로 간의 상호작용
  • 포괄적인 문서보다는 동작하는 소프트웨어
  • 계약 협상보다는 고객과의 협업
  • 계획을 따르기보다는 변화에의 대응
    각 문자에서 앞쪽에 언급한 것이 가치가 없다는 뜻이 아니라 뒷쪽에 언급한 것에 더 큰 가치를 둔다는 뜻이다.

애자일 방법론은 폭포수 방법론과는 반대되는 방식으로 언급된다. 또한 애자일이 유행을 하게 되며 전문가, 자격증, 개발방법론 등이 넘처나면서 가장 첫번째 원칙이 훼손되는 현상도 나타났다.

애자일 방법론 프레임워크

애자일 방법론 프레임워크 중 가장 보편적인 두 가지는 스크럼과 칸반이다. 스크럼이 가장 널리 알려져 있는데 계힉을 수정하는 체크포인트를 빈번하게 가져가면서 짧은 개발 주기를 반복한다. 개발 업무는 스프린트로 나눈다. 스프린트의 기간은 다르지만 2주가 가장 보편적이다. 각 팀은 스프린트를 시작할 때 스프린트 계획 회의를 통해 사용자 스토리나 태스트라고 표현하는 개발 업무를 팅뭔들에게 할당한다.

계획이 끝나면 개발자들은 각자 맡은 업무를 시작한다. 진척 상황은 티켓팅이나 이슈시스템을 이용해 추적한다. 매일 아침 짤막한 스탠드업 회의를 통해 업데이트를 공유하고 문제점을 논의한다. 각 스프린트가 끝나면 팀은 완료한 작업을 리뷰하고 새로 발견한 부분을 논의하며 주요 지표를 확인하고 절차를 세밀하게 조정하는 회고회의 시간을 갖는다.

중요한 점은 팀이 스크럼이나 칸반의 이상적인 애자일을 그대로 따라가는 경우는 드믈다. 보통은 일부 원칙만 가지고 나머지는 팀에 맞게 변경하거나 무시하는 경우가 많다.

스크럼으로 하는 애자일 개발 방안

대부분의 소프트웨어 팀은 어떤형태로든 스크럼을 채택하고 있으므로 스크럼이 어떻게 돌아가는지 정도는 이해해둘 필요가 있다. 모든 계획은 대부분 사전 업무로 시작한다. 개발자와 제품 관리자는 새로운 사용자 스토리를 생성하며 백로그의 티켓은 선별돼 있다.각 스토리는 복잡도를 예측해 스토리 포인트를 부여하고 태스크로 분리한다. 규모가 큰 스토리는 스파이크 스토리를 부여하고 태스크로 분리한다.

규모가 큰 스토리는 스파이크 스토리를 이용해 설계하고 연구한다. 스트린트 계획을 진행하는 동안 팀은 다음 스트린트에서 어떤 스토리를 완료할지 결정하고 스토리 포인트를 이용해 업무 부하를 관리한다.

사용자 스토리

사용자 스토리는 사용자의 관점에서 필요한 기능을 정의하는 특별한 종류의 티켓이다. 이 티켓은 “나는 <사용자>로서 <목적>을 하기 위해 <기능>을 원한다." 라는 형태로 이뤄진다. 요구사항을 서술하므로 사용자에게 가치를 전달하는 것에 더 집중할 수 있다.

태스크

하나의 스토리는 소요 시간에 대한 예측, 여러 개발자 간의 작업 분배, 구현 진도의 추적 등을 위해 더 작은 크기의 태스크 여럿으로 분리해야 한다. 업무를 더 작은 단위로 나누는 가장 좋은 방법은 아주 상세한 설명을 작성하는 것이다.

스토리 포인트

팁의 업무량은 스토리 포인트로 측정한다. 스토리 포인트란, 팀 전체가 동의한 작업량 단위를 말한다. 스프린트의 업무량은 개발자의 수에 개발자당 스토리 포인트를 곱한 값이다. 예를 들어 4명의 엔지니어에게 각각 10포인트씩을 할당했다면 스프린트 업무량은 40포인트가 된다. 사용자 스토리에 대한 시간 예측 역시 스토리 포인트로 정의한다.

백로그 선별

백로그 선별 또는 그루밍은 주로 계획 회의를 하기 전에 시행한다. 백로그는 앞으로 처리해야 할 스토리의 목록이다. 이런 스토리의 작업 내용을 최신 상태로 유지하고 우선순위를 결정하는 과정이 바로 ‘선별’이다.

스프린트 계획

스프린트 계획 회의는 사전 작업을 완료한 시점에 진행한다. 계획 미팅에서는 협업이 중요하다. 엔지니어링 팀은 제품 관리자와 함께 어떤 업무를 처리할지를 결정한다. 우선순위가 결정된 스토리에 대한 논의를 진행하고, 엔지니어는 제품 관리자와 함께 그 스토리를 이번 스프린트에 할당할지 여부를 결정한다.

신속한 업무 공유를 위한 스탠드업 회의

스프린트 계획을 끝내면 이제 업무가 시작되고 팀은 스탠드업 회의를 진행하게 된다. 스탠드업은 스크럼 회의 또는 허들이라고도 한다. 또한 모든 팀원이 서로 친적사항을 공유함으로써, 각자의 업무에 집중할 수 있으며, 스프린트 목표를 달성하는 데 방해가 되는 요소에 대응할 수 있다.

진솔한 피드백이 오가야 하는 리뷰

스프린트 리뷰는 매 스프린트 사이에 진행하며 주로 데모와 프로젝트 리뷰 등 두 부분으로 나뉜다. 데모 시간에는 모든 팀원이 이번 스프린트에서 자신이 담당했던 업무를 다른 팀원들에게 보여준다. 그 후에는 목표에 대비해 현재 스프린트를 평가한다. 스프린트가 성공적이었다면 목표를 달성하고 완료한 스토리 비율도 높을 것이다.

재평가와 조정을 위한 회고

회고 회의에서는 지난번 회고 이후로 잘 해낸 일과 잘 해내지 못한 일에 대한 함께 이야기한다. 이 회의는 주로 공유, 우선순위 결정, 문제 해결 등 세 가지 단계로 진행한다.

중장기 계획을 위한 로드맵 수립

2주 단위 스프린트는 중소 규모의 업무를 수행하기 좋은 방법이지만 규모가 더 큰 프로젝트는 더 세밀한 계획이 필요하다. 개발자는 고객과 약속한 출시일을 지켜야 하고 비즈니스는 어떤 팀에 엔지니어가 더 필요할지를 알아야 하며 대규모 기술 프로젝트는 더 작은 크기로 나누고 계획하고 조율해야 한다.

지금 진행하는 프로젝트도 당장의 마일스톤보다 3개월 정도로 목표를 잡는게 좋을 것 같다.

결론

애자일에 대한 가장 중요한 점은 꼭 개발이 아니더라도 변경이 잦은 반복성이 있는 모든 것들에 적용할 수 있다는 점이다. 또한 권장하는 방법들을 모두 사용하기보다 핵심적인 부분들을 적용하고 나머지는 팀에 맞게 조정하는 것이 중요하다.

논의사항

  • 애자일이 이상적인 방법이라고 불리는 이유가 뭘까요?

13장 관리자, 팀장, 상사와 함께 일하기

한마음 한뜻으로 공동의 목표를 향해

관리자나 팀장과 원할한 관계를 구축하면 경력이나 스트레스 감소, 안정적인 소프트웨어 출시에도 도움이 된다.

관리자들이 하는 일

팀장들은 늘 회의 중인 것처럼 보이지만 정확히는 무슨 일을 하는지는 명확하지 않다. 엔지니어링 관리자인 팀장은 직원을 보살피고 제품과 절차도 관리하며, 팀을 구축하고 엔지니어를 코칭하고 성장을 도우며 원할한 대인관계를 유지할 수 있도록 관리한다.

또한, 제품 개발을 계획하고 조율하기도 한다. 제품 개발의 기술적 관점에 무게를 두기도 하지만 훌륭한 엔지니어링 관리자가 코드를 직접 작성하는 일은 거의 없다. 마지막으로 팀장은 팀이 효율적으로 일할 수 있도록 팀의 절차를 살핀다. 팀장은 더 높은 직급의 임원이나 디렉터, 다른 팀장 그리고 자신의 팀들과 협업을 통해 이 모든 것을 관리한다.

성공적인 업무 수행과 평가를 위한 절차를 마련하자

팀장들은 팀과 개인들이 원활하게 업무를 수행할 수 있는 절차를 만든다. 가장 보편적인 팀 위주 절차 프레임워크인 애자일 방법론은 앞서 12장에서 다뤘다.

일대일 회의

팀장은 매주 또는 격주로 팀원들과 일대일 회의를 계획할 것이다. 일대일 회의는 팀원과 팀장이 중요한 주제를 논의하고 큰 계획에 대한 우려사항을 해결하며 생산적이고 장기적인 관계를 구축하기 위한 시간이다. 일대일 회의는 이미 널리 알려진 기법이지만 그저 상태 점검이나 문제 해결 세션으로 전락하는 경우도 많다.

따라서 일대일 회의를 할 때는 안건을 정해두고 발언을 많이 해야 한다. 회의를 시작하기 전에 팀장에게 회의 안건을 간략하게 요약해 공유하자. 지난 회의의 안건과 대화 기록은 문서로 남기자. 팀장과 일대일 회의 전후에 이 문서를 공유하고 업데이트해야 한다.

즉, 일대일 회의의 안건은 팀장이 아닌 팀원이 정해야 하며, 일대일 회의는 단순한 업무 보고가 아니다. 이 두 가지를 지키는 것만으로 낭비되는 수많은 시간을 줄일 수 있다.

PPP 회의

PPP 회의는 업무 보고 형식으로 이뤄진다. 업무 보고란 그저 업무 시간을 추적하는 것이 아닌 팀장을 도와 문제점을 찾고 배경지식을 필요로 하는 부분을 파악하며, 팀원에게 적임자를 연결해줄 기회를 찾게 하기 위함이다. 업무 보고를 하다 보면 일대일 회의 주제가 드러나기도 하고 업무를 어떻게 해왔는지, 지금은 어떻게 하고 있는지, 방해물이 있느지를 파악하는데 도움이 된다.

이름에서 알 수 있듯이 PPP는 P로 시작하는 3가지 섹션(진척상황 progress, 계획Plan, 문제Problem)으로 구성된다. 각 섹션에는 3~5가지 요점이 있으며 각 요점은 1~3개 문장으로 간단하게 요약해야 한다.

공유해야 할 사항을 PPP로 정리하여 팀장을 통해 정제하고 팀원들에게 공유한다면 좋은 방법이 될 것 같다.

OKR

OKR프레임워크는 회사가 성공을 위한 목표를 정의하고 측정하는 방법이다. OKR 프레임워크는 회사, 팀, 개인이 모두 목표를 정의하고 각 목표에 대한 측정 지표를 추가한다. 각 목표에는 3~5가지 핵심 결과가 있으며 이것이 지표가 되어 목표를 완수하기 위한 진척 정도를 표현한다.

성과 평가

팀장들은 보통 1년에 한두 번, 정기적으로 공식적인 성과 평가를 수행한다. 직책과 보상의 조정 역시 평가 기간에 이뤄진다. 평가는 보통 다음과 같은 도구나 템플릿을 이용해 수행한다.

  • 올해 수행한 업무는 무엇인가?
  • 올해 잘한 일은 무엇인가?
  • 올해 좀 더 개선할 수 있었던 부분이 있는가?
  • 경력을 어떻게 개발하고 싶은가? 향후 3~5년 후 본인의 모습은 어떨 것이라고 생각하는가?

직원들이 스스로 평가하고 그 후에 팀장이 그에 대한 피드백을 제공한다.

팀장이나 상사도 여러분의 관리가 필요하다

관리자나 팀장들이 더 높은 임원이나 디렉터를 관리하는 것처럼 팀원도 팀장을 돕거나 팀장이 돕도록 관리해야 한다. 팀장에게 피드백을 제공함으로써 팀원들도 팀장을 도울 수 있다.

팀장의 피드백이 적을 경우 적극 요청하자

성과 평가와 다면평가는 종합적인 피드백을 제공해주지만 자주 수행하는 것도 아니어서 거기에만 의존할 수는 없다. 따라서 여러분의 업무 수행 능력을 신속하게 조정할 수 있는 정기적인 피드백이 필요하다.

팀장이 알아서 피드백을 항상 제공하진 않을 테므로 필요하다면 직접 요청해야 한다. 일대일 회의는 피드백을 받기 좋은 시간이다. 따라서 회의 전에 미리 질문을 보내 회의 때 구체적인 피드백을 받을 수 있도록 하자.

팀장도 여러분의 피드백을 원한다

좋은 팀장은 팀으로부터 피드백을 받길 원한다. 팀장은 어떤 부분이 잘 동작하고 어떤 부분이 잘 동작하지 않는지 알아야 한다. 팀의 모든 개개인은 각자의 관점을 가지고 있다. 피드백을 잘 제공한다면 사각지대를 없앨 수 있다.

피드백의 주제는 무엇이든 될 수 있다. 팀, 회사, 행동, 프로젝트, 기술적 계획, 심지어 인사 정책을 언급해도 된다. 문제를 제기하되 문제에만 집중하자. 긍정적인 피드백은 그 자체로도 가치가 있다. 팀장 입장에서는 어떤 변화가 긍정적인 효과를 가져왔는지 알기가 어려우며, 팀장의 업무는 단위 테스트로 확인할 수 있는 것이 아니기 때문이다.

여러분의 목표에 대해 팀장과 허심탄회하게 논의하자

여러분이 경력을 어떻게 개발해나가고 싶은지 팀장이 늘 알아줄 것이라는 기대는 하지 말자. 원하는 목표와 바람은 팀장에게 분명하게 표현하고 이를 달성할 수 있도록 도움을 요청하자. 이런 대화는 공식적인 리뷰시간에 하는 것이 좋다.

다 시도해봤는데도 안 된다면

모든 직원과 팀장 관계는 저마다 독특하므로 보편적인 조언을 주기란 어렵다. 각각의 상황은 회사, 팀, 관리자, 직원에 따라 모두 다르다. 우리가 확실하게 말해줄 수 있는 것은 일이 잘 풀리지 않는 것 같다는 생각이 들면 주도적으로 행동해야 한다는 점이다.

관계와 업무는 부침을 겪는다. 잠깐 상황이 어려워진다고 해서 뭔가 극단적인 조치를 취할 필요가 없다. 하지만 좌절감이나 불만, 스트레스가 지속된다면 목소리를 낼 필요가 있다.

결론

회사랑 다른 성격인 작은 프로젝트 팀이긴 하지만 팀장을 맡고 있어서 생각해볼만한, 적용해볼만한 거리가 많은 챕터인 것 같다. 마찬가지로 환경과 상황이 다 다르다보니 유연하게 적용해봐야 할 것 같다.

논의사항

  • 여러분들은 팀 관리자가 반드시 잘 해야만 하는 일이 뭐라고 생각하시나요?

14장 경력 관리에 대한 조언

경력 관리는 빠를수록 좋다

소프트웨어 엔지니어로서 성장하는 것은 오랜 시간이 걸리는 일이다. 엔지니어로서 자리를 잡았다면 남은 것은 평생 공부와 리더십, 경영, 창업등이 남아있을 것이다. 어떤 길을 선택하든 끊임없이 성장해야 한다.

시니어 엔지니어, 그리고 더 높은 곳을 향해

진로 단계는 직책의 계층 구조와 각 직책에 대한 기대치를 잘 설명한다. 회사에서의 승진이나 진로는 이 단계별 직책으로 이루어진다. 회사마다 다르지만 연공서열에 따라 대체로 2번 정도 역할이 크게 바뀐다. 한 번은 주니어 엔지니어나 소프트웨어 엔지니어에서 시니어 엔지니어가 될 때, 그리고 또 한 번은 시니어 엔지니어에서 스태프 엔지니어나 프린시펄 엔지니어가 될 때다.

진로에 대한 조언

시니어 엔지니어나 스태프 엔지니어가 되기까지는 시간과 끈기가 필요하지만, 경력을 쌓으면서 더 많은 책임을 맡음으로써 스스로의 성장을 이뤄갈 수 있다. T자형 인재를 지향하고 엔지니어링 프로그램에 참여하며 승진 절차에도 관여하고 이직은 되도록 자주 하지 말며 스스로 페이스를 조절해 나가면 된다.

T자형 인재가 되자

소프트웨어 엔지니어링에는 프론트엔드, 백엔드, 운영, 데이터 웨어하우싱, 머신러닝 등 다양한 분야의 전문 분야가 있다. T자형 엔지니어란 이런 분야 중 적어도 한 가지에 전문성을 가지면서도 대부분의 분야에서 효율적으로 일을 해낼 수 있는 엔지니어를 말한다.

제너럴리스트면서 전문가

우선은 기초를 잘 닦자. 그러다 보면 관련된 다른 분야에 대한 경험도 쌓으면서 자신이 어떤 분야에 집중할 수 있는지를 알게 된다. 데이터 과학, 운영, 프론트엔드, 등 다른 팀이 참여하는 프로젝트를 찾아보자.

좋은 팀이란 모름지기 T자형 인재가 잘 혼합돼 있는 팀이다. 제품 개발팀은 대부분이 다방면에 경험이 많은 반면, 인프라스트럭처럼 특정 분야에 전문성이 높은 인재가 많다.

개발자를 위한 다양한 프로그램에 참여하자

학습과 개발, 공유 문화를 구축하기 위한 엔지니어링 프로그램을 갖춘 회사들이 많다. 채용, 면접, 브라운백, 컨퍼런스, 밋업, 스터디 모임, 오픈 소스 프로젝트, 인터십이나 멘토링 프로그램 등 모두 참여할 수 있는 기회는 많다.

승진을 원한다면 이렇게 하자

승진을 원한다면 스스로 승진을 조율해 나아가야 한다. 승진 절차를 이해한 뒤 눈에 띄는 중요한 업무를 담당해야 하며, 스스로 승진할 자격이 됐다고 생각하면 목소리를 내야 한다.

승진을 하기 위해서는 어떤 평가를 받고 있으며 승진 절차는 어떻게 되는지 확인해야 한다. 평가 조건과 승진 절차를 이해했다면 자체 평가를 진행하고 다른 사람에게 피드백을 받아보자. 진로 단계의 각 분류에 따라 담당했던 업무를 간략히 기록하자. 그리고 좀 더 개선할 부분도 찾아보자. 관리자, 팀장, 동료, 멘토로부터 피드백을 받아보자.

이직은 신중하게

이직을 하면 스킬을 더 개발하고 인맥도 늘릴 수 있지만 그렇다고 너무 자주 회사를 옮겨다니면 여러분의 성장에 방해가 되며 채용 관리자 입장에서도 좋게 보이지 않을 것이다. 확실한 이유가 없다면 이직은 삼기자.

다만 번아웃을 경계하라

소프트웨어 분야라고 해서 스트레스가 없는 것은 아니다. 업무도 바쁘고 경쟁도 치열하며 기술은 빠르게 발전하고 늘 새로운 것을 학습해야 한다. 아마 소프트웨어보다 빠르게 바뀌는 분야는 없을 것이다. 큰 부담을 느끼는 신입 엔지니어는 더 노력하고 장시간 일하는 방식으로 대응하기도 하지만 그러다 보면 번아웃의 희생양이 될 뿐이다. 잠깐 쉬면서 너무 일을 많이 하지 않도록 스스로 조절해야 한다.

결론

선배 개발자가 자신의 개발 인생에서 중요하다고 생각되는 점을 각 챕터로 묶어서 잘 정리해놓은 책이다. 책에서 말하듯이 정말 다양한 분야로 소프트웨어가 적용되고 발전하듯이 해당되지 않는 분야가 있을 수도 있지만, 개발자라면 결국 겪어야 하는 다양한 문제점들을 잘 말해주는 것 같다.

논의사항

  • 혹시 북클럽 말고도 도움이 될 다른 프로그램이 뭐가 있을까요?

댓글남기기