뒤로가기
아무도 안 알려주는 코딩의 현실

August 8, 2025

essaycareer

코딩에 대해 글을 쓸 때 대부분 "부트캠프에서 안 가르치는 것"이라는 프레임을 쓴다. 코드를 읽는 시간이 더 길다, 미팅이 많다, 검색을 많이 한다. 다 맞는 말이다. 근데 이건 입사 첫 주에 알게 되는 것들이다. 진짜 아무도 안 알려주는 건 이런 게 아니다.

몇 년 일하면서 깨달은, 정말 누가 미리 말해줬으면 좋았을 것 세 가지를 쓴다. 각각이 하나의 이야기다.

1. 최고의 코드는 삭제한 코드다#

입사 1년차 때 자부심을 갖고 있던 코드가 있었다. 공통 에러 핸들링 유틸리티였다. API 호출에서 발생하는 모든 종류의 에러를 분류하고, 사용자에게 적절한 메시지를 보여주고, 로깅까지 하는 범용 모듈이었다. 두 달에 걸쳐서 만들었다. 엣지 케이스를 하나씩 발견할 때마다 조건문을 추가하고, 새로운 에러 타입이 나오면 분기를 늘렸다. 400줄짜리 파일이 됐다.

뿌듯했다. "이제 어떤 에러가 와도 이 모듈이 처리한다." 코드 리뷰에서도 "꼼꼼하다"는 평을 받았다. 이게 내 첫 번째 "작품"이라고 생각했다.

6개월 뒤, 새로운 시니어가 팀에 합류했다. 코드베이스를 파악하는 과정에서 내 에러 핸들링 모듈을 봤다. "이거 왜 이렇게 복잡해요?"라고 물었다. 나는 각 조건문의 이유를 설명했다. "이 에러는 이런 경우에 발생하고, 이건 또 다른 경우에..." 설명이 15분이 걸렸다.

그 시니어가 한 일은 이랬다. 먼저 실제로 프로덕션에서 발생한 에러 로그를 뽑았다. 3개월치 로그를 보니까, 내 400줄 모듈이 처리하는 에러 타입 중 실제로 발생한 건 3가지뿐이었다. 나머지 12가지는 이론적으로 가능하지만 실제로는 한 번도 발생하지 않은 에러였다. 이론적 가능성에 대비한 코드가 전체의 80%를 차지하고 있었다.

그 시니어는 발생하지 않는 에러 처리를 전부 삭제했다. 400줄이 80줄이 됐다. 코드가 읽기 쉬워졌다. 새로운 팀원이 이해하기 쉬워졌다. 그리고 놀랍게도, 삭제한 뒤에 버그가 줄었다. 복잡한 분기가 줄어드니까 의도치 않은 상호작용이 사라진 거다.

이 경험이 코딩에 대한 내 인식을 완전히 바꿨다. 코드를 쓰는 능력보다 코드를 삭제하는 판단이 더 어렵고 더 가치있다.

한 유명 개발자가 "Goodbye, Clean Code"라는 글에서 비슷한 이야기를 했다. 동료의 반복적인 코드를 깔끔한 추상화로 리팩토링했는데, 그 동료의 매니저가 되돌리라고 했다는 이야기다. 이유는, 그 추상화가 "중복을 제거하는 대가로 요구사항 변경에 대응하는 능력을 희생시켰기" 때문이다. 반복적이지만 수정하기 쉬운 코드가, 깔끔하지만 수정하려면 전체를 이해해야 하는 코드보다 실무에서는 낫다.

주니어 때는 코드를 추가하는 게 기여라고 생각한다. 기능을 만들고, 유틸리티를 만들고, 추상화를 만든다. 경험이 쌓이면 반대가 된다. 코드를 줄이는 게 기여가 된다. 복잡도를 낮추는 게 가치가 된다. 최고의 코드는 존재하지 않는 코드다. 존재하지 않는 코드에는 버그가 없다.

지금도 가끔 PR을 올릴 때 추가한 줄보다 삭제한 줄이 많으면 좀 뿌듯하다. +30, -120 같은 diff를 볼 때 "오늘 좋은 일을 했다"는 느낌이 든다.

2. 코드를 쓰는 게 아니라 결정을 내리는 게 일이다#

부트캠프에서는 "어떻게 구현하느냐"가 전부였다. 이 기능을 React로 어떻게 만들 것인가. 상태 관리는 어떻게 할 것인가. API 호출은 어떤 패턴으로 할 것인가. 전부 "어떻게"에 대한 질문이다.

회사에 와보니 "어떻게"보다 "무엇을"과 "왜"가 더 중요했다. 그리고 그 "무엇을"과 "왜"를 정하는 게 일의 대부분이었다.

구체적인 예를 들어보겠다. 어느 날 기획자가 말했다. "사용자 프로필 페이지에 활동 내역을 보여주는 기능을 추가해주세요." 부트캠프식 사고라면 바로 컴포넌트를 설계하고 코드를 쓰기 시작할 거다. 그런데 실무에서는 코드를 쓰기 전에 결정할 게 산더미다.

활동 내역의 범위는 뭔가? 최근 7일? 30일? 전체? 데이터를 클라이언트에서 페이지네이션할 것인가, 서버에서 할 것인가? 활동 타입별로 필터를 넣을 것인가? 실시간 업데이트가 필요한가? 로딩 상태는 스켈레톤인가 스피너인가? 에러 시 리트라이 버튼을 보여줄 것인가? 빈 상태의 문구는? 모바일에서 어떻게 보일 것인가?

이 결정들 하나하나가 구현 복잡도와 일정에 영향을 미친다. "전체 활동 내역 + 클라이언트 페이지네이션 + 실시간 업데이트"를 고르면 2주짜리 작업이 된다. "최근 30일 + 서버 페이지네이션 + 정적"을 고르면 3일짜리 작업이 된다. 어떤 선택이 맞는지는 사용자의 실제 니즈, 서버 리소스, 팀의 현재 작업량, 스프린트 남은 기간을 종합해서 판단해야 한다.

코드를 쓰는 건 이 결정들이 끝나고 나서의 일이다. 그리고 솔직히 결정이 끝나면 구현 자체는 비교적 직선적이다. "서버 페이지네이션으로 최근 30일 활동을 10개씩 보여준다"가 정해지면, 그 다음은 기계적이라고 할 수 있다. 물론 구현 중에도 소소한 결정이 있지만, 큰 방향이 정해진 뒤의 결정과 큰 방향 자체를 정하는 결정은 무게가 다르다.

경력이 올라갈수록 이 비율이 더 극단적으로 변한다. 시니어 개발자가 하루에 코드를 쓰는 시간이 전체 업무의 30%도 안 되는 경우가 많다. 나머지는 결정을 내리는 데 쓴다. 기술 스택 결정, 아키텍처 결정, 트레이드오프 판단, 일정 대비 범위 조정. 한 테크 블로거가 쓴 것처럼, 시니어 엔지니어의 가치는 코드를 빨리 쓰는 데 있는 게 아니라 올바른 결정을 내리는 데 있다.

이걸 일찍 알았으면 좋았을 것 같다. "코드를 잘 쓰는 사람"이 되려고만 했는데, 실제로 필요했던 건 "좋은 결정을 내리는 사람"이 되는 거였다. 코드는 결정의 결과물일 뿐이다.

3. 코드베이스는 팀이 내린 모든 타협의 기록이다#

입사 첫 달에 레거시 코드를 보면서 "이걸 누가 이렇게 짠 거야"라고 속으로 생각한 적이 수없이 많다. 네이밍이 이상하고, 구조가 뒤틀려 있고, 같은 로직이 세 군데에 복사되어 있다. "처음부터 제대로 짰으면 이럴 일이 없었을 텐데."

2년이 지나고 나서야 그 코드를 다시 보는 시야가 생겼다. 네이밍이 이상한 이유가 있었다. 그 코드가 쓰여진 시점에는 비즈니스 로직이 지금과 달랐다. "user"라고 불리던 게 나중에 "member"와 "guest"로 분리됐는데, 코드의 변수명은 아직 "user"인 거다. 그 당시에는 정확한 네이밍이었다. 비즈니스가 바뀌면서 이름과 실체가 어긋난 거다.

구조가 뒤틀린 이유도 있었다. 원래는 깔끔한 구조였는데, 급한 핫픽스가 들어가면서 꺾였다. "이번 주 금요일까지 이 기능이 나가야 해요"라는 요청 앞에서 리팩토링을 할 여유는 없었다. 임시 코드가 들어갔고, 그 임시 코드 위에 다음 기능이 얹어졌고, 다음 스프린트에서도 "나중에 정리하자"가 반복됐다. 한 번도 "나중에"가 오지 않았다.

같은 로직이 세 군데에 있는 것도 의도적인 경우가 있었다. 세 군데의 로직이 "지금은" 같지만, 비즈니스 요구사항이 다르기 때문에 앞으로 독립적으로 변경될 가능성이 높아서 일부러 분리해둔 거였다. DRY 원칙을 깨는 것처럼 보이지만, 한 개발자가 "The WET Codebase"라는 강연에서 말한 것처럼, 과도한 중복 제거가 오히려 "이해할 수 없는 소프트웨어"를 만든다. 때로는 전략적 복제가 유지보수성을 높인다.

코드베이스를 이렇게 보기 시작하면 관점이 완전히 바뀐다. "왜 이렇게 지저분하지?"가 "어떤 상황에서 이런 결정이 내려졌을까?"로 바뀐다. 그리고 그 질문을 따라가면, 팀의 역사가 보인다.

여기 TODO 주석이 있다. "API v2가 나오면 이 부분 정리할 것." 날짜를 보면 1년 전이다. API v2는 아직 안 나왔다. 이건 백엔드 팀의 로드맵이 바뀌었다는 뜻이다. 저기 주석 없는 복잡한 조건문이 있다. git blame을 해보면 장애 대응 중에 급하게 들어간 커밋이다. 커밋 메시지에 "hotfix: OO 이슈 긴급 수정"이라고 적혀 있다. 그때 장애가 얼마나 급박했는지가 코드에 흔적으로 남아있다.

코드베이스는 기술적 문서가 아니다. 팀이 내린 모든 타협의 기록이다. 마감 압박과 기술적 이상 사이의 타협, 현재의 편의와 미래의 유지보수성 사이의 타협, 개인의 코드 취향과 팀의 일관성 사이의 타협. 이 타협들이 켜켜이 쌓여서 코드베이스라는 지층을 이룬다.

이걸 이해하면 레거시 코드에 대한 태도가 달라진다. "이건 나쁜 코드야"가 아니라 "이건 그때의 최선이었을 거야"가 된다. 그리고 지금 내가 쓰는 코드도 2년 뒤의 누군가에게는 "왜 이렇게 짰지?"라고 보일 거라는 겸손이 생긴다. 나도 지금의 제약 조건 안에서 타협하고 있으니까. 완벽한 코드를 쓰는 게 아니라, 지금 상황에서 가능한 최선의 타협을 하는 거다.

이게 아마 부트캠프에서 가르칠 수 없는 이유다. 타협은 맥락 없이는 이해할 수 없고, 맥락은 시간이 만든다.

세 가지의 교차점#

삭제, 결정, 타협. 이 세 가지는 서로 연결되어 있다.

좋은 결정을 내리려면 삭제할 줄 알아야 한다. "이 기능은 안 만든다"는 결정, "이 코드는 지금 필요 없다"는 판단. 추가하는 것보다 빼는 것이 용기가 필요하다.

타협을 이해하려면 결정의 맥락을 읽을 줄 알아야 한다. 왜 이 코드가 이 모양인지를 아려면, 그때 어떤 결정이 내려졌는지를 추적해야 한다.

삭제를 잘 하려면 타협의 역사를 알아야 한다. 어떤 코드는 삭제해도 되고, 어떤 코드는 남겨야 한다. 그 판단은 코드만 봐서는 안 나온다. 그 코드가 왜 거기 있는지, 어떤 타협의 결과인지를 알아야 한다.

부트캠프에서는 코드를 "쓰는" 법을 가르친다. 현장에서는 코드를 "다루는" 법이 필요하다. 쓰는 것, 읽는 것, 삭제하는 것, 결정하는 것, 타협하는 것. 이 중에서 "쓰는 것"은 가장 작은 부분이다. 그걸 아는 데 꽤 오래 걸렸다.