수학을 하려면 가장 먼저 숫자를 배우고, 그 숫자로 덧셈·뺄셈·곱셈·나눗셈을 익힙니다. 그리고 사칙연산을 모르면 계산기를 손에 쥐여 줘도 소용없습니다. 어떤 수를 어떤 순서로 넣어야 하는지, 즉 식을 세우는 일은 계산기가 대신해 주지 않기 때문입니다.
개발도 마찬가지입니다. 기초, 그리고 그 기초를 활용하는 방법을 익히고 스스로에게 물어봐야 합니다.
웹 백엔드라면, 실무에서 더 자주 발목을 잡는 것은 오히려 언어 밖의 기초입니다.
다만 IT는 다른 분야보다도 이 점이 더 두드러집니다. 방대한 지식 저장소인 인터넷과 상시 연결되어 있기 때문에, 모든 것을 100% 암기하는 것보다 본인에게 필요한 내용을 기록해 두고, 필요한 순간에 빠르게 찾아 활용할 수 있는가가 핵심입니다.
과거의 IT나 다른 학문은 본인의 머릿속에서 찾아 대응하는 능력이 중요했습니다. 하지만 요즘은 키워드, 연관성, 문맥의 흐름, 과거 검색 이력 같은 일련의 검색 메커니즘을 통해 AI가 아주 빠르게 찾아 줍니다.
그렇다고 "아무것도 안 외워도 된다"는 뜻은 아닙니다. 경계선은 대략 이렇습니다.
판단 기준은 "이걸 모르면 문제 해결의 방향 자체를 못 잡는가"입니다. 방향을 잡는 지식은 머리에, 세부 구현은 도서관에 둡니다.
AI가 빠르게 찾아 주는 것과, 그 답이 옳은지 아는 것은 다릅니다.
그리고 남의 글을 북마크만 해 두는 것으로는 부족합니다. 나중에 검색해도, 그때의 내가 이해하지 못하면 못 씁니다. 그래서:
초보와 주니어의 차이 — 초보는 "돌아가는 코드"를 만듭니다. 주니어는 "왜 이렇게 돌아가는지, 언제 깨지는지 설명할 수 있는 코드"를 만듭니다. 그 차이는 검색 속도가 아니라 기초의 깊이에서 나옵니다.
코드부터 작성해도 됩니다. 실제로 작은 기능은 그게 더 빠릅니다. 하지만 요구사항이 조금이라도 복잡해지면, 흐름을 글로 먼저 고정하고 → 사람과 맞춘 뒤 → 코드를 여는 순서가 결국 시간을 아낍니다.
요구사항은 "주문 기능을 만들어 주세요" 같은 한 문장으로 옵니다. 이 문장에는 결정되지 않은 것들이 잔뜩 숨어 있습니다. 재고가 모자라면 주문을 거절할지, 가능한 만큼만 담을지. 같은 상품에 동시에 주문이 들어오면 재고를 어떻게 지킬지. 결제가 실패하면 이미 차감한 재고를 되돌릴지 말지. 이 빈칸을 코드로 채우기 전에, 글로 먼저 채워 보는 것이 이 루프의 목적입니다.
① 벤치마킹한다.같은 문제를 이미 푼 서비스가 반드시 있습니다. 쇼핑몰이라면 네이버·쿠팡·배민이 품절, 중복 결제, 주문 취소, 부분 환불을 어떻게 처리하는지 직접 써 보면 됩니다. 혼자 상상으로 요구사항을 채우면 반드시 빠뜨리는 부분이, 남이 이미 겪고 해결해 둔 흔적에는 남아 있습니다.
② 의사코드(pseudocode)를 초안으로 적는다.한 메서드 안에, 데이터와 동작이 흐르는 순서를 주석으로만 나열합니다. 아직 코드는 한 줄도 쓰지 않습니다.
public OrderResponse placeOrder(OrderRequest req) {
// 1. 요청 검증 — 수량 > 0, 상품 존재
// 2. 재고 확인 및 차감 (동시성: 원자 감소 or 비관적 락)
// 3. 주문 저장
// 4. 결제 요청 — 실패 시 재고 롤백
// 5. 주문 확정 이벤트 발행
// 6. 응답 DTO 변환
}이 여섯 줄을 적는 순간, "4번에서 결제가 실패하면 2번을 되돌려야 하네"라는 사실이 눈에 보입니다. 코드를 다 짜고 나서 발견하면 구조를 뜯어고쳐야 하지만, 지금은 주석 한 줄 고치면 됩니다.
③ 요구사항 담당자·책임자와 논의한다.②에서 내가 임의로 채운 빈칸(재고 부족 시 거절인지 부분 주문인지, 롤백 범위가 어디까지인지)을 들고 가서 "이렇게 이해했는데 맞나요?"를 확인합니다. 이 대화가 없으면 "돌아가지만 요구와 다른 코드"를 만들게 됩니다.
④ 의사코드를 다시 쓴다.논의에서 나온 내용을 반영해 흐름을 고칩니다. 흐름이 크게 바뀌면 ②~④를 다시 돕니다. 보통 두세 번이면 수렴합니다.
왜 이 순서가 빠른가.요구를 오해한 채 코드를 완성하면, 폐기되는 건 코드만이 아닙니다. 그 위에 쌓은 테스트 코드, 받은 코드 리뷰, 작성한 API 문서, QA가 확인한 시나리오가 전부 함께 버려집니다. 뒤로 갈수록 한 번의 방향 수정에 딸려 나가는 작업이 기하급수적으로 늘어납니다. 의사코드 다섯 줄을 고치는 비용과, 배포 하루 전에 "설계가 틀렸다"를 되돌리는 비용은 비교가 안 됩니다.
// 재고 확인 및 차감이라는 주석이 decreaseStock(productId, quantity)라는 메서드 이름으로 올라가면, 주석 없이도 흐름이 읽힙니다. 주석은 코드와 따로 놀다가 낡아버리지만, 이름은 코드와 함께 갑니다. 가장 좋은 주석은 필요 없어진 주석입니다.
placeOrder를 열었을 때 여섯 줄짜리 흐름만 보여야 합니다.
public OrderResponse placeOrder(OrderRequest req) {
validate(req); // 1
stockService.decrease(req.items()); // 2
Order order = orderRepository.save(Order.of(req)); // 3
paymentService.pay(order); // 4 (실패 시 내부에서 롤백)
eventPublisher.publish(new OrderPlaced(order)); // 5
return OrderResponse.from(order); // 6
}이렇게 하면 이 메서드를 읽는 사람은 "무슨 일이 어떤 순서로 일어나는가"를 3초 만에 파악합니다. 세부가 궁금하면 그때 해당 메서드로 들어갑니다.
각 주석은 그대로 테스트 목록이 된다."정상 주문이면 201과 주문번호", "재고 부족이면 409", "결제 실패면 재고가 원상복구되는지" — 흐름을 여섯 줄로 적어 두면 무엇을 테스트해야 하는지 고민할 필요가 없습니다.
돌아가는 코드가 나왔으면, 팀의 코드 컨벤션에 맞춰 다듬습니다. 여기서 흔히 하는 실수는 배운 것을 한꺼번에 다 적용하려는 것입니다. MVC, DDD, TDD, GoF 디자인 패턴을 한 번에 들이대면 코드가 오히려 읽기 어려워집니다. 지금 필요한 만큼만 가져옵니다.
MVC 계층 분리가 막아 주는 것을 구체적으로 보면 이렇습니다. 컨트롤러에 비즈니스 로직을 두면, 그 로직을 배치 작업이나 다른 API에서 재사용할 수 없습니다. HTTP 요청 없이는 테스트할 수도 없습니다. 서비스로 내려두면 재사용도 되고, 순수 자바 객체로 단위 테스트가 가능해집니다.
리팩토링은 "무조건 추상화 = 좋음"이 아닙니다. 추상화에는 비용이 있습니다. 파일이 늘고, 코드를 따라가려면 여러 곳을 오가야 하고, 새로 온 사람이 이해하는 데 시간이 더 걸립니다. 이 비용보다 얻는 게 클 때만 추상화합니다.
① 과소 — 분리가 필요한데 안 한 상태if (method.equals("CARD")) { /* 카드 결제 50줄 */ }
else if (method.equals("KAKAO")) { /* 카카오페이 40줄 */ }
else if (method.equals("TOSS")) { /* 토스 45줄 */ }한 메서드가 150줄입니다. 결제사를 하나 추가하려면 이 거대한 메서드를 열어 else if를 끼워 넣어야 하고, 그 순간 기존 카드·카카오 로직까지 회귀 테스트 대상이 됩니다. 변형이 계속 늘어나는데 한 덩어리로 묶여 있는 것이 과소의 신호입니다.
PaymentGateway gateway = gateways.get(method); // CARD/KAKAO/TOSS 각각의 구현체
gateway.pay(order);결제사는 자주 늘어나고(변형 증가), 각각 독립적으로 테스트하고 싶고(격리 필요), 사용자가 고른 값에 따라 런타임에 결정됩니다(교체 시점이 실행 중). 세 조건이 모두 맞으니 전략·팩토리 패턴이 정확히 이 문제를 위해 존재하는 도구입니다. 새 결제사는 구현체 하나를 추가하고 등록만 하면 됩니다.
③ 과잉 — 분리가 짐이 되는 상태// 할인이 지금 "전 상품 10% 정률" 하나뿐인데
interface DiscountPolicy { Money calc(Order o); }
class FixedRateDiscountPolicy implements DiscountPolicy { ... }
class DiscountPolicyFactory { ... }할인 정책이 하나뿐이고 반년 동안 바뀐 적이 없습니다. 코드를 읽는 사람은 "정책이 여러 개인가?" 하고 세 파일을 다 열어 본 뒤에야 "아, 하나뿐이구나"를 알게 됩니다. order.total().multiply(0.9) 한 줄이면 될 일을 세 배로 부풀린 것입니다. 미래에 생길지도 모르는 요구를 위해 지금의 가독성을 지불한 것이 과잉입니다. 두 번째 정책이 실제로 생기는 날 추출해도 5분이면 됩니다.
오른쪽 칸이 우세하면, 지금은 그냥 if가 정답입니다. 참는 것도 실력입니다.
미리 완벽한 구조를 설계하려는 시도는 대부분 빗나갑니다. 아직 코드를 안 써봤으니 어디가 복잡해질지, 어디서 변형이 생길지 정확히 알 수 없기 때문입니다. 순서는 이렇게 잡습니다. 먼저 돌아가게 만든다 → 코드가 눈앞에 있으니 어디가 뭉쳐 있고 어디가 반복되는지 보인다 → 그때 추출한다.
의사코드 주석이 "흐름을 보이게 만드는" 도구이고, 2-4의 판단표가 "지금이 추출할 때인지" 알려 주는 신호입니다.
초보와 주니어의 차이 — 초보는 요구를 받으면 바로 코드를 엽니다. 주니어는 유사 사례를 보고, 흐름을 적고, 담당자와 맞춘 뒤 코드를 엽니다. 그리고 리팩토링을 시작할 줄 아는 것만큼, 어디서 멈출지를 압니다.
고백하자면, 필자도 기능 단위로 커밋을 쪼개는 습관이 완전히 자리 잡지는 못했습니다. 개발에 몰입하다 보면 여러 변경이 한 덩어리로 쌓여 있곤 합니다. 그래도 최소한 이 두 가지는 지키려 합니다.
커밋을 잘게 남기는 진짜 이유는 "기록을 위한 기록"이 아니라, 나중에 문제가 생겼을 때 Git이 해 줄 수 있는 일이 늘어나기 때문입니다.
① 정확한 되돌리기 (git revert)
요구사항이 바뀌어 특정 기능만 빼야 할 때, 그 기능이 독립된 커밋이면 git revert <해당 커밋> 한 줄로 끝납니다. 여러 기능이 한 커밋에 섞여 있으면 손으로 코드를 골라내야 하고, 그 과정에서 새 버그가 들어옵니다.
② 원인 추적 (git bisect)
"결제 성공률이 어제부터 떨어졌다"처럼 언제부터 깨졌는지 모를 때, git bisect는 커밋 히스토리를 이진 탐색해서 문제가 처음 생긴 커밋을 찾아 줍니다. 커밋 10개면 3~4번 만에 범인이 나옵니다. 단, 이게 작동하려면 각 커밋이 "그 시점에 빌드·동작하는 상태"여야 합니다. 커밋이 feat: 결제 기능 전체 하나뿐이면 bisect가 가리키는 것도 그 커밋 하나뿐이라, 아무것도 좁히지 못합니다.
리뷰어는 "논리 단위"로 코드를 읽습니다. 검증 로직 추가 → 재고 차감 → 결제 연동으로 나뉜 PR은 각 커밋을 순서대로 따라가며 이해할 수 있지만, 800줄이 한 커밋에 뭉쳐 있으면 리뷰어는 대충 훑고 approve를 누릅니다.
Git의 효과를 최대로 끌어내는 방식은 이렇습니다.
feature/order-payment처럼 하나의 브랜치가 하나의 작업 항목을 담습니다.주문 기능을 개발하던 중, 리뷰에서 "포인트 적립은 이번 스코프에서 빼기로 했다"는 결정이 나왔다고 합시다.
뭉친 커밋의 경우a1b2c3d feat: 주문 기능 구현 (검증 + 재고 + 결제 + 포인트 적립, 620줄)선택지는 두 가지입니다. 커밋을 통째로 되돌려 검증·재고·결제까지 다 날리고 다시 짜거나, 620줄에서 포인트 관련 코드만 눈으로 찾아 손으로 지우거나. 후자는 거의 항상 뭔가를 빠뜨립니다.
나눈 커밋의 경우e5f6a7b feat: 주문 요청 검증
b8c9d0e feat: 재고 확인 및 차감
f1a2b3c feat: 결제 게이트웨이 연동
c4d5e6f feat: 포인트 적립 ← 이 커밋만 되돌리면 됨git revert c4d5e6f끝입니다. 포인트 적립 코드와 그 테스트가 정확히 사라지고, 나머지는 그대로 남습니다. "이 커밋만 되돌리면 이 기능이 사라진다"가 성립하도록 커밋을 나누는 것이 핵심입니다.
나누는 기준은 하나입니다. 한 커밋 = 하나의 이유로 되돌릴 수 있는 단위.
bisect가 의미를 가집니다.
반대로 너무 잘게 쪼개도 문제입니다. import 추가, 변수명 오타 수정, 괄호 위치 같은 커밋이 흩어져 있으면 히스토리가 노이즈로 덮입니다. 개발 중에 남긴 지저분한 WIP 커밋은 머지 전에 git rebase -i로 squash·reword해서 논리 단위로 정리한 뒤 올립니다. 로컬에서는 마음껏 어질러도 되지만, 공유되는 히스토리는 깔끔해야 합니다.AI에게 기능을 통째로 요청하면 200~300줄이 한 번에 나옵니다. 이걸 그대로 한 커밋으로 올리면, 몇 주 뒤 그 코드에서 버그가 났을 때 "내가 이걸 왜 이렇게 짰지?"에 답할 수 없습니다. AI 출력도 내가 읽고 이해한 조각으로 나눠서, 조각별로 커밋 메시지를 직접 씁니다. 커밋을 쪼개는 행위 자체가 "이 코드를 내가 이해했는가"를 점검하는 절차가 됩니다.
초보와 주니어의 차이 — 초보는 하루가 끝날 때
update한 줄로 커밋합니다. 주니어는 각 커밋이 하나의 완결된 변경이 되도록 나누고, 공유하기 전에 히스토리를 정리합니다. 그래서 몇 달 뒤에도 "이 기능은 이 커밋"이라고 짚을 수 있습니다.
코드 실력만큼 중요한 것이 어떻게 일하는가입니다. 혼자 코드를 짤 때는 보이지 않다가, 팀에 들어가는 순간 실력의 절반을 차지하기 시작합니다.
보고를 미루는 이유는 대개 정직합니다. 무서워서, 혼나기 싫어서, 어떻게든 혼자 해결해서 감추고 싶어서.
그렇게 감추기로 했다면, 그 일에 대한 책임을 100% 혼자 지겠다는 선택을 한 것입니다. 반대로 상사와 보고 체계가 존재하는 이유는:
언제 보고하나. "나쁜 소식일수록 빨리"입니다. 좋은 소식은 늦게 전해도 좋은 소식이지만, 나쁜 소식은 늦을수록 수습 비용이 커집니다.
어떻게 보고하나. 감정과 변명을 빼고 네 가지로 정리합니다.
예를 들어 발주 실수를 발견하자마자 이 형식으로 5분 안에 알린 덕에 큰 손해를 막았다는 이야기들이 종종 도는데, 그 5분을 만드는 것이 바로 "감추지 않는 습관"입니다.
일뿐 아니라 모든 활동에 목표가 있어야 합니다. 취미도, 휴식도 마찬가지입니다. "오늘 휴식의 목표는 일 생각을 안 하는 것" 같은 것도 목표입니다. 목표를 정하지 않으면, 그 시간이 끝났을 때 성취감도, 남는 것도 없습니다.
일에서는 목표가 곧 결과물의 형태를 결정합니다. "A 프로젝트 PPT를 만들어 달라"는 요청을 받으면, 만들기 전에 물어야 합니다.
개발도 똑같습니다. "이 기능 만들어 줘"라는 요청에도 목표 확인이 필요합니다. 빠르게 검증할 MVP인가, 실서비스에 나갈 완성품인가. 예상 트래픽은 얼마인가. 마감은 언제인가. 이 답에 따라 설계의 깊이, 예외 처리 범위, 테스트 수준이 전부 바뀝니다.
확인 질문 세 개: ① 누가 이걸 쓰는가 ② 이걸로 무슨 결정·행동을 하는가 ③ "잘 됐다"의 기준이 무엇인가.
내 작업을 어떻게 진행할지 막막할 때는, 다른 사람이 어떻게 일하는지 보는 것이 가장 빠릅니다. 특히 동료들의 일하는 방식과 코드 컨벤션을 관찰합니다. 흐름이 너무 다르면, 한두 번에 끝날 일이 반복·중첩되며 작업 전체가 엉킵니다.
예를 들어 팀은 "PR을 작게 쪼개 하루 안에 리뷰"하는데 나만 3일치를 한 PR로 올리면, 리뷰가 밀리고, 그 사이 main이 바뀌어 충돌이 나고, 충돌을 풀다 또 리뷰가 밀립니다. 코드 스타일뿐 아니라 PR 크기, 리뷰 응답 속도, 커밋 메시지, 브랜치 네이밍, 회의에서의 공유 방식까지 팀에 맞춥니다.
큰 틀은 이렇습니다.
계획을 세운다
→ 우선순위를 정한다
→ 우선순위 순으로 작업한다
↳ 중간에 우선순위가 바뀌면 갱신하고 공유 (짧은 단위로)
→ 마무리 시 최종 공유왜 공유가 이득인가. 남이 내 진행 상황을 알면, 관련 작업을 병렬로 도울 수 있고, 내가 방향을 잘못 잡았을 때 일찍 잡아 줍니다. 침묵하다가 다 만든 뒤 "이거 아닌데요"를 듣는 것보다, 중간에 30초짜리 공유를 여러 번 하는 편이 훨씬 쌉니다.
영역이 완전히 분리되어 있어도 공유는 필요합니다. 인수인계를 하거나 급히 지원 요청을 받았을 때, 처음 보는 것보다는 몇 번 들어 본 쪽이 훨씬 낫기 때문입니다.
위의 것들도 중요하지만, 내가 어디까지 할 수 있고, 무엇은 못할 것 같고, 무엇을 배우면 할 수 있는지 스스로 경계를 그어야 합니다. 그러지 않으면:
"못한다"를 말하는 법. 그냥 거절이 아니라 조건을 붙입니다. "이 부분은 제가 X를 학습해야 하고 대략 Y일 걸립니다. 그 사이 Z를 대신 진행할 수 있습니다." 이러면 상대는 일정을 조정하거나, 다른 사람을 붙이거나, 학습 시간을 인정할 수 있습니다.
받아와서 해결한 경우는 성장의 발판이 됩니다. 다만 그 성장이 가로인지 세로인지 꼭 확인하세요.
초반에는 가로로 넓히는 것이 도움이 됩니다. 하지만 깊은 문제를 다뤄야 하는 시점부터는 세로 방향의 전문성이 반드시 필요합니다. 가로 폭이 넓으면서 세로로 깊은 축이 하나 있는 형태(T자형)를 목표로 잡으면, 어떤 일을 받아왔을 때 그것이 나를 T자로 키우는지, 그냥 가로로만 늘리는지 판단할 수 있습니다.
초보와 주니어의 차이 — 초보는 시킨 일을 시킨 대로, 혼자, 다 끝난 뒤에 보고합니다. 주니어는 목표를 먼저 확인하고, 팀의 흐름에 맞춰 짧게 공유하며, 나쁜 소식을 빨리 보고하고, 자기 영역의 경계를 안다고 말할 수 있습니다.
정리하면 순서는 이렇습니다.
댓글
GitHub 계정으로 의견이나 질문을 남길 수 있습니다.