69P by xguru 4달전 | favorite | 댓글 6개

계속 발에 총을 쏘고 있다면, 총을 고치세요

  • 팀에서 시스템에 대해 자주 실수하는 부분이 있지만, 그 실수를 줄일 방법에 대해 생각하지 않는 경우가 많음
  • 이런 경우 시스템을 개선하여 실수를 줄이는 것이 중요함
  • 경험:
    • iOS 개발 시 CoreData를 사용할 때, UI 업데이트는 메인 스레드에서만 가능함
    • 구독 콜백이 메인 스레드와 백그라운드 스레드에서 모두 발생하여 문제가 종종 생겼음
    • 기존 팀원들은 이를 인지하고 잘 처리하지만, 신입 팀원의 리뷰에서 종종 언급됨
    • 가끔 실수가 발생하면 충돌 보고서를 보고 DispatchQueue.main.async를 추가해 왔음
    • 이를 해결하기 위해 구독 레이어를 업데이트하여 구독자를 메인 스레드에서 호출하도록 변경함. 딱 10분 걸림.
    • 전체 충돌 클래스와 약간의 정신적 부담을 제거함
  • 누구나 몇 분 동안 생각하면 명백한 문제였을 것임
  • 그러나 이러한 문제를 해결할 자연스러운 시기가 없기 때문에 이상하게 오래 지속됨
    • 즉 이러한 문제는 팀에 오래 있으면 배경으로 사라지기 쉬움
  • 마음가짐의 변화가 필요함
    • 이런 문제가 있을 때 자신과 팀의 삶을 더 쉽게 만들 수 있다는 것을 가끔 상기시켜야 함

품질과 속도 사이의 균형 맞추기

  • 구현 속도와 정확성에 대한 자신감 사이에는 항상 트레이드오프가 존재함
    • 현재 상황에서 버그를 출시하는 것이 얼마나 괜찮은지 자문해 보아야 함
    • 이에 대한 답변이 작업 방식에 영향을 미치지 않는다면, 지나치게 경직된 것임
  • 첫 직장에서 데이터 처리 프로젝트를 할 때는 데이터를 소급하여 재처리할 수 있는 좋은 시스템이 갖추어져 있었음
    • 버그 출시의 영향은 매우 미미했고, 이런 환경에서는 가드레일에 어느 정도 의존하고, 더 빠르게 움직일 수 있음
    • 100% 테스트 커버리지나 광범위한 QA 프로세스는 개발 속도를 늦출 뿐임
  • 두 번째 직장에서는 수천만 명이 사용하는 제품으로 고가치 금융 데이터와 개인 식별 정보를 다루어 버그가 치명적이었음
    • 작은 버그라도 사후 분석이 필요했음
    • 기능을 매우 느린 속도로 출시했지만, 그 해에 버그를 0개 냈다고 생각
  • 대부분의 경우, 두 번째 회사와 같은 상황에 있지는 않음
    • 버그가 치명적이지 않은 상황(예: 99%의 웹 앱)에서는 빠르게 출시하고 버그를 빠르게 수정하는 것이 더 나음
    • 처음부터 완벽한 기능을 출시하는 데 시간을 들이는 것보다 더 발전할 수 있음

톱을 갈고 있는 시간은 거의 항상 가치가 있음

  • 도구를 잘 다루는 것이 중요함
  • 코드를 빠르게 작성하고, 주요 단축키를 알고, 운영체제와 셸에 능숙해야 함
    • 이름 바꾸기, 타입 정의로 이동, 참조 찾기 등을 많이 하게 될 것
    • 에디터의 주요 단축키를 모두 알고, 자신 있고 빠른 타이핑을 할 수 있어야 함
    • 브라우저 개발 도구를 효과적으로 사용하는 것도 중요함
  • 도구를 잘 선택하고 능숙하게 사용하는 것은 큰 장점임
  • 새로운 엔지니어에게서 보이는 가장 큰 그린 플래그 중 하나는 도구 선택과 능숙한 사용에 대한 관심임

어려움을 쉽게 설명할 수 없다면, 그것은 아마도 우발적 복잡성일 것이고, 이 문제는 해결할 가치가 있음

  • 내가 가장 좋아하는 관리자는 구현이 어렵다고 주장할 때마다 계속해서 압박하는 버릇이 있었음
    • 종종 그의 대답은 대개 "X를 Y할 때 보내는 것에 불과한 것 아닌가요?" 또는 "몇 달 전에 했던 Z와 같은 것 아닌가요?"와 같은 식이었음
    • 매우 높은 수준의 반대 의견이었고, 내가 설명하려고 했던 실제 함수와 클래스 수준이 아니었음
  • 관리자가 이렇게 단순화하는 것은 그저 성가신 일이라는 것이 통상적인 견해임
  • 하지만 놀랍게도 높은 비율로, 관리자가 계속 물어볼 때 내가 설명하던 복잡성의 대부분이 우발적 복잡성이라는 것을 깨달았음
  • 그리고 실제로 그것을 먼저 해결함으로써 문제를 관리자가 말하는 것처럼 사소하게 만들 수 있었음
  • 이런 식의 접근은 향후 변경을 더 쉽게 만드는 경향이 있음

버그를 한 층 더 깊이 해결하려고 노력하기

  • 버그를 표면적으로 해결하는 대신, 근본적인 원인을 찾아 해결하는 것이 중요함
  • 대시보드에 현재 로그인한 사용자의 상태에서 가져온 User 객체를 다루는 React 컴포넌트가 있을때
    • Sentry에서 렌더링 중에 usernull이었다는 버그 리포트가 나옴
      • if (!user) return null을 빠르게 추가하거나
    • 또는 조금 더 조사해 보면, 로그아웃 함수가 두 개의 별개 상태 업데이트를 수행한다는 것을 알 수 있음
      • 첫 번째는 사용자를 null로 설정하고, 두 번째는 홈페이지로 리디렉션
    • 두 가지의 순서를 바꾸면, 이제 어떤 컴포넌트도 이 버그를 다시는 겪지 않을 것
    • 대시보드 내에서는 사용자 객체가 절대 null이 아니기 때문
  • 첫 번째 유형의 버그 수정을 계속하면 엉망이 되지만,
    두 번째 유형의 버그 수정을 계속하면, 깨끗한 시스템과 불변성에 대한 깊은 이해를 갖게 될 것

버그 조사를 위해 히스토리를 파고드는 것의 가치를 과소평가하지 말 것

  • 나는 println과 디버거와 같은 일반적인 도구를 사용하여 이상한 문제를 디버깅하는 데 꽤 능숙했음
  • 그래서 버그의 히스토리를 파악하기 위해 git을 많이 보지 않았는데, 하지만 어떤 버그의 경우에는 이것이 매우 중요함
  • 최근 서버에서 메모리가 지속적으로 누수되는 것 같았고, OOM에 의해 종료되고 다시 시작되는 문제가 있었음
    • 어떤 가능성 있는 원인도 배제되었고, 로컬에서 재현할 수 없었음
    • 눈을 가리고 다트를 던지는 것 같은 느낌이었음
    • 커밋 히스토리를 살펴보니 Play Store 결제 지원을 추가한 후에 발생하기 시작했음
    • 단지 몇 개의 HTTP 요청일 뿐이라 백만 년이 지나도 찾아보지 않았을 곳임
    • 첫 번째 액세스 토큰이 만료된 후 액세스 토큰을 가져오는 무한 루프에 빠진 것으로 밝혀짐
    • 각 요청은 메모리에 1kB 정도만 추가했을 수 있지만, 여러 스레드에서 10ms마다 재시도하면 빠르게 누적됨
    • 보통 이런 일은 스택 오버플로를 야기했겠지만, Rust에서 비동기 재귀를 사용하고 있었기에 스택 오버플로가 발생하지 않았음
    • 이는 절대 떠오르지 않았을 것이지만, 문제를 일으킨 것이 분명한 특정 코드를 살펴보게 되면서 갑자기 이론이 떠오름
  • 이런 접근을 언제 해야 할지에 대한 규칙은 없음
    • 직관에 기반한 것으로, 버그 보고서에 대한 다른 종류의 "어라?"가 이런 종류의 조사를 촉발함
    • 시간이 지나면서 직관을 발전시킬 수 있지만, 때로는 매우 귀중하다는 것을 아는 것으로 충분함
  • 문제가 적합한 경우 git bisect를 시도해 볼 것
    • 잘못된 것으로 아는 커밋 하나와 좋은 것으로 아는 커밋 하나가 있는 경우

나쁜 코드는 피드백을 주지만, 완벽한 코드는 그렇지 않음. 나쁜 코드를 작성하는 쪽으로 오류를 범할 것

  • 끔찍한 코드를 작성하는 것은 정말 쉬움
  • 하지만 모든 모범 사례를 절대적으로 따르는 코드를 작성하는 것도 정말 쉬움
    • 단위, 통합, fuzz, 돌연변이 테스트를 모두 거쳐야 하는데, 스타트업은 그 전에 돈이 바닥날 것
  • 프로그래밍의 많은 부분은 균형을 찾는 것
  • 빨리 코드를 작성하는 쪽으로 오류를 범하면...
    • 가끔은 나쁜 기술 부채로 인해 곤란을 겪게 될 것임
    • "데이터 처리에 대해 훌륭한 테스트를 추가해야 한다"는 것을 배울 것임
      • 나중에 수정하는 것은 종종 불가능하기 때문
    • "테이블 설계를 정말 잘 생각해봐야 한다"는 것도 배울 것임
      • 다운타임 없이 변경하는 것은 매우 어려울 수 있기 때문
  • 완벽한 코드를 작성하는 쪽으로 오류를 범하면...
    • 아무런 피드백을 받지 못함
    • 모든 것이 보편적으로 오래 걸림
    • 어디에 시간을 제대로 쓰고 있는지, 어디에서 시간을 낭비하고 있는지 모름
    • 피드백 메커니즘은 학습에 필수적이지만, 그런 것을 얻지 못하고 있음
  • "나쁜" 코드의 의미 명확히 하기
    • "해시맵 생성 구문을 기억할 수 없어서 내부 루프를 두 번 사용했다"는 의미가 아님
    • 다음과 같은 의미임:
      • 특정 상태를 표현할 수 없게 하기 위해 데이터 수집을 재작성하는 대신, 몇 가지 핵심 검사점에서 불변성에 대한 어설션을 몇 개 추가함
      • 서버 모델이 작성할 DTO와 정확히 동일하므로 그냥 직렬화함. 모든 상용구를 작성하는 대신 필요에 따라 나중에 DTO를 작성할 수 있음
      • 이 컴포넌트들은 사소하고 버그가 있어도 큰 문제가 없으므로 테스트 작성을 건너뜀

디버깅을 쉽게 만들기

  • 수년에 걸쳐 소프트웨어를 디버깅하기 쉽게 만드는 많은 작은 트릭들을 습득함
    • 디버깅을 쉽게 하기 위한 노력을 하지 않으면, 소프트웨어가 점점 더 복잡해짐에 따라 각 이슈를 디버깅하는 데 엄청난 시간을 소비하게 될 것
    • 변경을 하는 것이 두려워질 것임. 새로운 버그 몇 개를 파악하는 데 일주일이 걸릴 수도 있기 때문
  • 디버깅 시간 중 설정, 재현, 사후 정리에 소요되는 시간을 추적하는 데 주의를 기울일 것
    • 50% 이상이라면, 이번에는 약간 더 오래 걸리더라도 더 쉽게 만드는 방법을 찾아야 함
    • 다른 조건이 동일하다면, 버그 수정은 시간이 지남에 따라 더 쉬워져야 함

팀에서 일할 때는 항상 질문하기

  • "모든 것을 스스로 알아내려고 노력하는 것"부터 "사소한 질문으로 동료를 귀찮게 하는 것"까지 스펙트럼이 있음
    • 대부분의 경력 초반자들은 전자 쪽에 너무 치우쳐 있다고 생각함
  • 항상 코드베이스에 더 오래 있었거나, 기술 X를 훨씬 더 잘 알거나, 제품을 더 잘 알거나, 그냥 더 경험 많은 엔지니어가 주변에 있음
  • 어딘가에서 처음 6개월 동안 일하다 보면 한 시간 넘게 무언가를 알아내는 데 시간을 허비하거나, 몇 분 만에 답을 얻을 수 있는 경우가 많음
  • 질문을 할 것. 질문을 하는 것이 누군가에게 성가신 유일한 경우는 몇 분 안에 스스로 답을 찾을 수 있었다는 것이 분명할 때뿐임

배포 주기가 매우 중요함. 빠르고 자주 배포할 수 있는 방법을 신중히 고민해야 함

  • 스타트업은 Runway가 제한적이고, 프로젝트에는 마감일이 있음
  • 직장을 그만두고 독립할 때, 저축한 돈은 몇 달 동안만 지속될 것임
  • 이상적으로는, 프로젝트 속도가 시간이 지남에 따라 복리로 증가해서, 상상할 수 있는 것보다 빠르게 기능을 배포하게 됨
  • 빠르게 배포하려면 많은 것들이 필요함
    • 버그에 취약하지 않은 시스템
    • 팀 간 빠른 회전 시간(Turnaround)
    • 새로운 기능의 10%를 잘라내는 의지 (엔지니어링 시간의 50%를 차지할 부분)와 그런 부분을 아는 통찰력
    • 새로운 화면/기능/엔드포인트에 조합할 수 있는 일관되고 재사용 가능한 패턴
    • 빠르고 쉬운 배포
    • 속도를 늦추지 않는 프로세스 (불안정한 테스트, 느린 CI, 까다로운 린터, 느린 PR 리뷰, 종교처럼 여기는 JIRA 등)
    • 그 외 수백만 가지
  • 느리게 배포하는 것은 프로덕션을 중단하는 것만큼 사후 분석을 해야 함
    • 우리 업계는 그렇게 운영되지 않지만, 그렇다고 해서 개인적으로 빠른 배포라는 북극성을 따를 수 없는 것은 아님

제 머릿속을 그대로 꺼내놓은 것과 같은 충격을 ㄷㄷ..

"발에 총을 쏜다" = 자승자박
이란 뜻인가요

뭔가 잘못된 코드(고장난 총)로 문제가 생긴다면(발에 쏘기) 총을 고치라는 얘기입니다.

잘 읽었습니다!!

잘 읽었습니다.

개발자는 아니지만 공감이 되는 부분이 많네요