테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

소프트웨어 공학

코드가 오래 살고 고치는 사람이 많아지면 '돌아가는 프로그램'만으로는 부족하다. 요구사항을 적는 법, 설계 원칙과 패턴, 리팩터링과 리뷰, 프로세스와 추정, 기술 부채, 사람과 조직이 코드에 남기는 흔적을 작은 예를 직접 계산한 그림으로 따라간다.

목차 (12)
  1. 소프트웨어 공학이란: 규모와 시간이 만드는 문제
  2. 요구사항: 사용자 스토리와 인수 기준
  3. 설계 원칙: 응집과 결합, SOLID, DRY와 YAGNI
  4. 디자인 패턴: 이름 붙은 해법
  5. 리팩터링: 동작은 그대로, 구조만
  6. 코드 리뷰
  7. 개발 프로세스: 폭포수, 애자일, 스크럼, 칸반
  8. 추정: 불확실성 원뿔
  9. 기술 부채: 이자처럼 불어나는 것
  10. 브룩스의 법칙과 콘웨이의 법칙
  11. 문서와 ADR
  12. 핵심 자료

소프트웨어 공학이란: 규모와 시간이 만드는 문제

혼자 며칠 만에 짜고 버리는 스크립트는 돌아가기만 하면 된다. 그런데 같은 코드를 열 명이 몇 년 동안 고친다면 이야기가 달라진다. 지금 짠 사람은 떠나고, 요구사항은 바뀌고, 의존하던 라이브러리는 판이 오른다. 구글 엔지니어들이 쓴 Software Engineering at Google은 이를 한 문장으로 줄였다. 소프트웨어 공학은 시간에 걸쳐 적분한 프로그래밍이다. 프로그래밍과 갈라지는 지점으로는 시간, 규모(사람과 조직), 그리고 그 둘 때문에 더 무거워지는 트레이드오프를 든다.

같은 책이 소개하는 하이럼의 법칙은 시간이 왜 무서운지 보여 준다. API를 쓰는 사람이 충분히 많으면, 문서에 무엇을 약속했든 관찰할 수 있는 동작은 전부 누군가가 기대게 된다. 오류 메시지의 문구, 결과의 순서, 걸리는 시간까지. 그래서 오래 사는 코드에서는 "바꿔도 되는가"를 판단하는 장치(테스트, 버전 관리, 리뷰, 문서)가 코드 자체만큼 중요하다. 이 노트는 그 장치 가운데 사람과 설계 쪽을 다루고, 도구 쪽은 버전 관리, 소프트웨어 테스트, CI/CD 노트에, 큰 시스템의 구조는 시스템 설계에 맡긴다.

혼자 며칠 짜는 프로그래밍, 여럿이 몇 달 하는 팀 개발, 조직이 몇 년 이상 유지하는 소프트웨어 공학을 왼쪽에서 오른쪽으로 놓고 각각의 목표와 필요한 것을 적은 그림프로그래밍혼자 · 며칠돌아가면 끝—팀 개발여럿 · 몇 달남이 읽고 고친다버전 관리 · 리뷰소프트웨어 공학조직 · 몇 년 이상바뀌어도 버틴다설계 · 테스트 · 문서사람 수(규모)와 코드가 살아 있는 기간(시간)이 함께 늘어난다
사람과 시간이 늘수록 목표가 '돌아간다'에서 '바뀌어도 버틴다'로 옮겨 간다. 오른쪽으로 갈수록 필요한 장치가 늘어난다.

요구사항: 사용자 스토리와 인수 기준

가장 비싼 버그는 코드가 아니라 요구사항에서 나온다. 잘못 이해한 채 만든 기능은 테스트를 모두 통과해도 쓸모가 없다. 요구사항은 크게 기능 요구사항(무엇을 하는가: 장바구니를 저장한다)과 비기능 요구사항(얼마나 잘하는가: 응답 시간, 가용성, 보안, 접근성)으로 나뉜다. 비기능 쪽은 빠뜨리기 쉽고, 나중에 더하기도 어렵다.

애자일 팀은 요구사항을 사용자 스토리로 적는 일이 많다. 가장 흔한 꼴은 "(역할)로서 (기능)을 하고 싶다, (이유) 때문에"다. 이유를 함께 적으면 만드는 사람이 더 나은 방법을 제안할 수 있다. 스토리는 대화를 시작하는 메모일 뿐이고, "다 됐다"를 정하는 것은 인수 기준이다. 인수 기준은 제품 책임자가 완료로 받아들이기 위해 스토리가 해야 할 일을 적은 목록이며, 많은 팀이 전제–행동–결과(Given–When–Then) 꼴로 쓴다. 이렇게 쓴 기준은 그대로 자동화된 인수 테스트가 되기도 한다(테스트 노트).

왼쪽은 회원으로서 다른 기기에서 이어 사려고 장바구니를 저장하고 싶다는 사용자 스토리 카드, 오른쪽은 전제·행동·결과로 적은 인수 기준과 덧붙인 규칙 한 줄사용자 스토리누가회원으로서무엇을장바구니를 저장하고 싶다왜다른 기기에서 이어 사려고인수 기준(Given · When · Then)전제로그인한 회원이 상품 2개를 담았다행동다른 기기에서 같은 계정으로 로그인한다결과장바구니에 같은 상품 2개가 보인다+ 품절된 상품은 표시만 하고 수량은 0으로 둔다
스토리 카드는 누가·무엇을·왜만 적고, 경계 조건은 인수 기준이 정한다. '품절이면?' 같은 질문이 기준 한 줄로 남아야 나중에 다투지 않는다.

설계 원칙: 응집과 결합, SOLID, DRY와 YAGNI

설계 원칙의 대부분은 두 낱말로 줄어든다. 응집도는 한 모듈 안의 것들이 얼마나 같은 목적을 위해 모여 있는지, 결합도는 모듈끼리 얼마나 서로의 속사정을 아는지다. 응집은 높이고 결합은 낮추면, 한 가지 변경이 한 곳만 건드린다. 결합을 낮추는 대표 수단은 모듈 사이를 좁은 인터페이스로만 잇고 안쪽 자료 구조를 숨기는 것(정보 은닉)이다.

높은 결합: 주문·결제·재고·알림 모듈 넷이 서로 모두 이어진 그림. 낮은 결합·높은 응집: 주문 묶음과 결제 묶음이 안쪽에 관련 기능을 모으고 인터페이스 선 하나로만 이어진 그림높은 결합주문결제재고알림하나를 고치면 나머지 셋을 확인해야 한다낮은 결합 · 높은 응집주문장바구니재고 확인주문 기록결제카드 승인환불영수증경계는 pay(주문) 하나 — 안쪽은 마음대로 바꾼다
왼쪽은 모듈 넷이 서로의 내부를 직접 만진다. 오른쪽은 관련된 기능끼리 묶고(응집), 묶음 사이는 함수 하나로만 잇는다(결합). 결제 안쪽을 통째로 바꿔도 주문은 모른다.

객체 지향 설계에서 자주 인용하는 SOLID는 로버트 C. 마틴이 2000년 논문에서 정리한 원칙들에 마이클 페더스가 머리글자를 붙인 것이다. 각각 한 줄 예를 들면 다음과 같다.

  • 단일 책임(SRP): 클래스가 바뀔 이유는 하나여야 한다. 보고서를 계산하는 코드와 PDF로 찍는 코드를 나누면, 글꼴이 바뀌어도 계산 코드는 건드리지 않는다.
  • 개방–폐쇄(OCP): 기존 코드를 고치지 않고 확장으로 새 동작을 더한다. 결제 수단이 늘 때마다 if를 늘리는 대신 새 결제 클래스를 하나 더한다.
  • 리스코프 치환(LSP): 자식 타입은 부모 타입이 쓰이는 어디에 넣어도 프로그램이 깨지지 않아야 한다. 정사각형이 직사각형을 상속했는데 너비만 바꾸면 높이도 바뀐다면, 직사각형을 기대한 코드가 틀린 넓이를 얻는다.
  • 인터페이스 분리(ISP): 쓰지 않는 메서드에 기대게 하지 않는다. 읽기만 하는 화면에 save()까지 있는 거대한 저장소 인터페이스를 넘기지 말고 읽기용 인터페이스를 따로 둔다.
  • 의존성 역전(DIP): 상위 정책이 구체적인 하위 구현이 아닌 추상에 기댄다. 주문 서비스는 "알림 보내기" 인터페이스만 알고, 메일인지 문자인지는 바깥에서 꽂아 준다. 테스트에서 가짜를 꽂기 쉬운 것도 이 덕분이다.

두 원칙이 더 있다. DRY(Don't Repeat Yourself)는 같은 지식을 한 곳에만 두라는 뜻이다. 겉모습이 같은 코드를 무조건 합치라는 말이 아니라, 함께 바뀌어야 하는 규칙(예: 무료 배송 기준 금액)이 두 곳에 흩어져 하나만 고쳐지는 일을 막으라는 것이다. 우연히 닮았을 뿐 따로 바뀔 코드를 억지로 합치면 오히려 결합이 생긴다. YAGNI(You Aren't Gonna Need It)는 지금 필요하지 않은 일반화·설정·확장점을 미리 만들지 말라는 익스트림 프로그래밍의 경고다. 예측은 대개 틀리고, 쓰이지 않는 추상은 읽는 사람에게 짐만 된다. 원칙끼리 부딪칠 때는 "다음 변경이 몇 군데를 건드리게 되는가"를 기준으로 고른다.

디자인 패턴: 이름 붙은 해법

디자인 패턴은 자주 만나는 설계 문제와 그 해법의 뼈대에 이름을 붙인 것이다. 네 저자(GoF, Gang of Four)의 책 『Design Patterns』가 널리 퍼뜨렸고, 지금도 만드는 패턴(생성), 엮는 패턴(구조), 주고받는 패턴(행위)으로 나눈다. 패턴의 가치는 코드 조각보다 어휘에 있다. "여기는 옵저버로 하자"라는 한마디가 설계 대화를 짧게 만든다. 자주 쓰는 넷을 보자.

  • 전략(행위): 바뀌는 규칙을 인터페이스 뒤로 빼고 실행 중에 갈아 끼운다. 할인 정책, 정렬 기준, 압축 방식. 함수를 값으로 넘길 수 있는 언어에서는 함수 하나로 끝나기도 한다.
  • 옵저버(행위): 상태가 바뀌면 구독자 목록에 알린다. 주문 완료가 메일·재고·통계를 직접 부르지 않으니 구독자를 더해도 주문 코드는 그대로다. UI 프레임워크의 이벤트, 메시지 큐의 발행–구독이 같은 생각이다.
  • 어댑터(구조): 모양이 맞지 않는 인터페이스를 감싸 우리 쪽 인터페이스로 바꾼다. 외부 SDK를 어댑터 뒤에 두면 SDK를 바꿀 때 어댑터만 다시 짜면 된다.
  • 팩토리(생성): 어떤 구체 클래스를 만들지 정하는 일을 한곳에 모은다. 호출하는 쪽은 "png 저장기"를 달라고만 하고, 생성자 인자나 설정은 팩토리가 안다.
디자인 패턴 넷을 네 칸에 그린 그림. 전략은 결제가 할인 규칙 인터페이스를 통해 회원 할인·쿠폰·할인 없음 가운데 하나를 쓰고, 옵저버는 주문 완료가 메일·재고·통계 구독자에게 알리며, 어댑터는 우리 코드의 send 호출을 외부 SDK의 다른 함수로 바꾸고, 팩토리는 형식 이름을 받아 알맞은 저장기를 만든다전략규칙을 바깥에서 갈아 끼운다옵저버바뀌면 구독자에게 알린다어댑터맞지 않는 모양을 감싸 맞춘다팩토리무엇을 만들지 한곳에서 정한다결제할인 규칙회원 할인쿠폰할인 없음주문 완료메일 발송재고 차감통계우리 코드send()어댑터외부 SDKpostMsg()형식 이름"png"팩토리PngWriterJpegWriter
네 패턴의 뼈대. 공통점은 바뀌는 부분(할인 규칙, 구독자, 외부 SDK, 구체 클래스)을 한쪽으로 몰아 나머지가 모르게 하는 것이다.

패턴은 목표가 아니다. 문제가 생기기 전에 패턴부터 넣으면 YAGNI를 어긴 간접 층만 늘어난다. 보통은 코드가 아파질 때(같은 switch가 세 군데에 생길 때) 리팩터링으로 패턴 쪽으로 옮겨 간다.

패턴의 유래(건축의 패턴 언어)와 GoF 23가지 전체, 각 패턴의 구조 그림과 예시 코드는 디자인 패턴 노트에 있다.

리팩터링: 동작은 그대로, 구조만

마틴 파울러는 리팩터링을 "겉으로 보이는 동작을 바꾸지 않고, 이해하기 쉽고 고치기 싸게 만들려고 소프트웨어의 내부 구조를 바꾸는 것"이라 정의한다. 핵심은 작은 걸음이다. 이름 하나 바꾸고 테스트, 함수 하나 떼어 내고 테스트. 걸음이 작으면 무엇이 깨뜨렸는지 바로 알고, 언제든 멈출 수 있다. 그래서 리팩터링의 전제는 믿을 만한 테스트다(테스트 노트).

어디를 고칠지는 코드 냄새가 알려 준다. 켄트 벡이 붙인 이름으로, 대개 더 깊은 문제를 가리키는 겉 신호다. 긴 함수, 거대한 클래스, 여러 곳에 흩어진 같은 조건문, 매개변수 목록이 긴 함수, 다른 객체의 데이터를 더 많이 만지는 함수(기능 욕심), 동작 없이 데이터만 든 클래스, 설명이 필요한 주석 등이 있다. 냄새는 "반드시 고쳐라"가 아니라 "들여다봐라"는 뜻이다.

주문 합계 함수를 다섯 단계에 걸쳐 고치는 코드. 단계마다 코드 전체가 바뀌어 보인다1/5 · 처음: 한 함수가 세 가지 일을 한다function total(order) {let t = 0;for (const i of order.items) t += i.price * i.quantity;if (order.member) t = t * 0.9;if (t < 50000) t += 3000;return t;}2/5 · 이름 바꾸기: t·i가 무엇인지 드러낸다function total(order) {let subtotal = 0;for (const item of order.items)subtotal += item.price * item.quantity;if (order.member) subtotal = subtotal * 0.9;if (subtotal < 50000) subtotal += 3000;return subtotal;}3/5 · 매직 넘버를 이름 있는 상수로const MEMBER_DISCOUNT_RATE = 0.1;const FREE_SHIPPING_THRESHOLD = 50000;const SHIPPING_FEE = 3000;function total(order) {let subtotal = 0;for (const item of order.items)subtotal += item.price * item.quantity;if (order.member) subtotal *= 1 - MEMBER_DISCOUNT_RATE;if (subtotal < FREE_SHIPPING_THRESHOLD) subtotal += SHIPPING_FEE;return subtotal;}4/5 · 함수 추출: 합계·할인을 떼어 낸다const itemsTotal = (items) =>items.reduce((sum, item) => sum + item.price * item.quantity, 0);const applyMemberDiscount = (amount, member) =>member ? amount * (1 - MEMBER_DISCOUNT_RATE) : amount;function total(order) {const subtotal = applyMemberDiscount(itemsTotal(order.items), order.member);if (subtotal < FREE_SHIPPING_THRESHOLD) return subtotal + SHIPPING_FEE;return subtotal;}5/5 · 함수 추출: 배송비 규칙도 이름을 얻는다const shippingFee = (subtotal) =>subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : SHIPPING_FEE;function total(order) {const subtotal = applyMemberDiscount(itemsTotal(order.items), order.member);return subtotal + shippingFee(subtotal);}
1 / 5

1단계 — 처음: 한 함수가 세 가지 일을 한다.

리팩터링은 동작을 그대로 두고 구조만 바꾸는 작은 걸음의 연속이다. 걸음마다 테스트를 돌려, 값이 바뀌면 바로 앞 걸음으로 돌아간다.

위 예에서 최종 함수는 두 줄이지만 줄 수가 목표는 아니다. 할인율과 배송비 기준이 이름을 얻었고, 배송비 규칙이 바뀌면 shippingFee 하나만 고치면 된다. 단계 그림의 각 코드는 노트의 계산 모듈에 그대로 들어 있고, 고치기 전후 함수가 예제 주문에서 같은 값을 내는지 테스트가 확인한다. 편집기의 자동 리팩터링(이름 바꾸기, 함수 추출)은 이런 걸음을 사람 손보다 안전하게 해 준다.

코드 리뷰

코드 리뷰는 다른 사람이 변경을 읽고 합치기 전에 의견을 주는 일이다. 버그를 잡는 효과도 있지만 그보다 큰 몫은 지식이 퍼지는 것이다. 리뷰어는 코드베이스의 다른 부분을 배우고, 작성자는 팀의 관례를 배우며, 한 사람만 아는 코드가 줄어든다. 구글의 리뷰 지침은 기준을 이렇게 둔다. 변경이 완벽하지 않더라도 시스템 전체의 코드 건강을 분명히 낫게 한다면 승인하라. 완벽을 이유로 며칠씩 묵히지 말라는 것이다.

  • 작게 올린다. 수천 줄짜리 변경은 아무도 제대로 읽지 못한다. 리팩터링과 기능 추가를 다른 변경으로 나누면 둘 다 읽기 쉽다.
  • 기계가 볼 것은 기계에게. 서식·린트·테스트는 CI가 먼저 걸러 내고, 사람은 설계, 이름, 빠진 테스트, 예외 경로를 본다.
  • 코드를 말하고 사람을 말하지 않는다. "이 함수는 null을 받으면 어떻게 되나요?"처럼 질문으로, 꼭 고칠 것과 취향(nit)을 구분해 적는다.
  • 빨리 답한다. 리뷰 대기 시간은 작성자의 흐름을 끊고 변경을 크게 만든다.
  • 짝 프로그래밍은 실시간 리뷰다. 두 사람이 한 화면에서 일하며, 키보드를 쥔 드라이버는 지금 쓰는 코드에, 옆의 내비게이터는 쓰는 대로 읽고 큰 방향·빠진 경우·다음 단계를 본다. 역할은 때때로 맞바꾼다(On Pair Programming). 리뷰를 기다리는 시간이 없고 지식이 바로 옮겨 가는 대신, 두 사람의 시간을 함께 쓴다.
작은 변경, 자동 검사, 리뷰, 고치기, 합치기 다섯 상자가 차례로 이어지고 고치기에서 리뷰로 돌아가는 고리가 있는 그림다시 보기작은 변경PR 하나자동 검사테스트 · 린트리뷰읽고 묻기고치기답하고 수정합치기main기계가 볼 것은 기계에게사람은 설계 · 이름 · 빠진 테스트를 본다
리뷰의 흐름. 고치기–다시 보기 고리를 몇 번 돈 뒤 합친다. 브랜치와 PR을 다루는 법은 버전 관리 노트에 있다.

브랜치 전략과 풀 리퀘스트의 기계적인 부분은 버전 관리 노트에서 다룬다.

개발 프로세스: 폭포수, 애자일, 스크럼, 칸반

폭포수는 요구사항 → 설계 → 구현 → 테스트 → 운영을 한 번씩 차례로 지나는 방식이다. 흔히 1970년 윈스턴 로이스의 논문을 기원으로 드는데, 정작 로이스는 그 그림을 보여 준 뒤 테스트가 맨 끝에 있어 위험하다고 경고하고 고객 참여·두 번 만들기 같은 보완책을 붙였다. 요구사항이 거의 바뀌지 않고 실패 비용이 매우 큰 분야에서는 단계를 엄격히 나누는 방식이 여전히 쓰이지만, 대부분의 소프트웨어는 만들어 봐야 요구사항이 분명해진다는 문제가 있다.

2001년의 애자일 선언은 네 문장으로 방향을 틀었다. 공정과 도구보다 개인과 상호작용을, 포괄적인 문서보다 작동하는 소프트웨어를, 계약 협상보다 고객과의 협력을, 계획을 따르기보다 변화에 대응하기를 더 중히 여긴다. 오른쪽 것도 가치 있다는 단서가 붙어 있다. 애자일은 방법론이 아니라 가치의 묶음이고, 그 가치를 구체적인 틀로 만든 대표가 스크럼과 칸반이다.

  • 스크럼: 2020년판 스크럼 가이드 기준으로 책임은 제품 책임자·스크럼 마스터·개발자 셋이다. 길이가 한 달 이하로 고정된 스프린트를 되풀이하고, 그 안에 스프린트 계획(한 달 스프린트에 최대 8시간), 매일 15분의 데일리 스크럼, 스프린트 리뷰(최대 4시간), 회고(최대 3시간)가 있다. 산출물은 제품 백로그·스프린트 백로그·증분이며, 각각 제품 목표·스프린트 목표·완료의 정의(Definition of Done)라는 약속이 붙는다.
  • 칸반: 도요타 생산 방식에서 온 생각을 소프트웨어에 옮긴 것이다. 일을 보드의 칸(할 일·진행·검토·완료)에 카드로 보이게 하고, 칸마다 동시에 진행할 수 있는 일의 수(WIP 제한)를 둔다. 칸이 차면 새 일을 밀어 넣지 않고, 빈자리가 생길 때 앞에서 당겨 온다. 막힌 곳이 눈에 보이니 팀이 함께 뚫는다. 정해진 반복 주기 없이 흐름을 관리한다는 점이 스크럼과 다르고, 둘을 섞어 쓰는 팀도 많다.
왼쪽 위는 요구사항·설계·구현·테스트·운영이 계단처럼 한 번씩 내려가는 폭포수, 오른쪽 위는 제품 백로그에서 한 달 이하 스프린트를 돌아 증분을 내는 스크럼, 아래는 할 일·진행·검토·완료 칸에 카드가 흐르고 진행 칸은 최대 2장으로 묶인 칸반 보드폭포수요구사항설계구현테스트운영고객은 끝에서야 결과를 본다스크럼제품백로그스프린트한 달 이하증분매일 15분 데일리 스크럼리뷰 · 회고 → 다음 스프린트칸반빈 자리가 생기면 왼쪽에서 당겨 온다할 일진행 · 최대 2검토완료
세 방식의 모양. 폭포수는 한 번 지나고, 스크럼은 짧은 고리를 반복하며, 칸반은 진행 중인 일의 수를 묶어 흐름을 고르게 한다.

어느 틀이든 본질은 피드백 고리를 짧게 하는 것이다. 자주 보여 주고, 자주 배포하고(CI/CD), 회고로 방식 자체를 고친다. 틀의 의식만 따르고 고리가 길게 남아 있으면 이름만 애자일이다.

추정: 불확실성 원뿔

"얼마나 걸려요?"는 소프트웨어에서 가장 어려운 질문 가운데 하나다. 1950년대 화학 공정 업계에서 나온 생각을 배리 보엠이 1981년 소프트웨어에 옮겼고, 스티브 맥코널이 1997년 불확실성 원뿔이라는 이름을 붙였다. 요구사항을 모으기 전의 첫 추정은 실제 값이 그 1/4에서 4배 사이 어디든 될 수 있을 만큼 불확실하고, 요구사항과 설계가 정해지며 범위가 좁아진다. 중요한 단서는 원뿔이 저절로 좁아지지 않는다는 것이다. 결정을 미루거나 범위가 계속 바뀌면 넓은 채로 남거나 다시 넓어진다.

가로축은 프로젝트 진행, 세로축은 실제 값이 추정의 몇 배인지(로그 눈금). 시작에서 1/4~4배로 넓던 범위가 끝에서 한 점으로 좁아지는 원뿔4배2배추정1/21/4시작(구상)끝(출시)요구사항·설계가 정해질수록 →시작에 "10주"라고 하면실제는 2.5~40주모양은 그림용 근사 — 결정을 미루면 원뿔은 저절로 좁아지지 않는다
불확실성 원뿔(로그 눈금). 시작 시점의 '10주'는 사실 2.5~40주라는 뜻에 가깝다. 중간 모양은 로그 눈금에서 곧게 좁아진다고 둔 그림용 근사다.

그래서 실무 추정은 숫자 하나가 아니라 범위와 그 근거로 말한다. 쓸 만한 방법 몇 가지가 있다. 일을 하루 이틀 크기로 쪼개면 각 조각의 오차가 서로 상쇄된다. 과거에 비슷한 일이 실제로 얼마나 걸렸는지(처리량 기록)로 미래를 예측하면 사람의 낙관을 덜 탄다. 스토리 포인트는 시간 대신 상대적 크기로 견주는 방법인데, 포인트를 사람 평가에 쓰기 시작하면 숫자가 부풀어 쓸모가 없어진다. 마지막으로, 추정은 약속이 아니라 정보다. 범위가 넓으면 가장 불확실한 부분을 먼저 만들어 보는 것이 원뿔을 좁히는 가장 빠른 길이다.

기술 부채: 이자처럼 불어나는 것

워드 커닝햄이 처음 쓴 비유를 마틴 파울러가 정리한 기술 부채는 이렇다. 코드 안의 지저분한 구조를 치우는 비용이 원금, 그 구조 때문에 새 기능을 넣을 때마다 더 드는 노력이 이자다. 파울러의 예로, 깨끗한 코드라면 4일 걸릴 기능이 지저분한 모듈 탓에 6일 걸리면 이자는 2일이다. 치우는 데 5일이 든다면, 기능 하나만 넣을 때는 치우지 않는 편이 낫지만 비슷한 기능이 두 개 더 오면 먼저 치우는 편이 빨라진다.

이자는 그곳을 건드릴 때만 붙는다. 그래서 파울러는 자주 고치는 곳부터 조금씩 갚고, 거의 안 바뀌는 곳은 내버려 두라고 한다. 아래 그림은 갚지 않은 부채에 이자가 다시 붙는다고 가정한 계산이다(지저분한 코드 위에 급하게 덧댄 코드가 또 부채가 되는 상황). 원금 10시간, 스프린트마다 10%로 두면 6스프린트 뒤 17.7시간, 12스프린트 뒤 31.4시간이 된다.

스프린트 0부터 12까지 갚지 않은 기술 부채가 10시간에서 31.4시간으로 커지는 막대그래프. 원금 부분과 불어난 이자 부분을 나눠 칠했다01234567891011121031.4스프린트(갚지 않고 지난 횟수)원금 10시간 · 스프린트마다 10%12스프린트 동안 낸 이자 합 21.4시간원금불어난 이자
갚지 않은 기술 부채의 복리 계산. 숫자는 가상의 예지만 모양이 요점이다 — 초반에는 완만해 무시하기 쉽고, 늦게 갚을수록 한 번에 갚아야 할 몫이 커진다.

부채가 늘 나쁜 것은 아니다. 출시 기한을 맞추려고 알고서 지름길을 택하고 갚을 계획을 적어 두는 것은 대출과 같다. 문제는 모르고 진 부채와, 진 줄도 모르는 사이 쌓이는 부채다. 부채 목록을 백로그에 적고, 스프린트마다 일정 몫을 갚는 데 쓰는 팀이 많다. 파울러는 빨리 내려고 진 부채가 정작 그 급한 기능들을 느리게 만들어 역효과가 나기 쉽다고도 경고한다.

브룩스의 법칙과 콘웨이의 법칙

프레드 브룩스는 1975년 책 『맨먼스 미신』에서 이렇게 썼다. 늦어진 소프트웨어 프로젝트에 사람을 더 넣으면 더 늦어진다. 새 사람은 배우는 동안 기존 팀원의 시간을 쓰고, 일을 더 잘게 나누고 다시 합치는 비용이 생기며, 서로 맞춰야 할 의사소통 경로가 사람 수보다 빨리 는다. 사람이 n명이면 둘씩 짝지은 경로는 n(n − 1)/2개다. 5명이면 10개, 10명이면 45개, 50명이면 1225개다.

왼쪽은 원 위에 놓인 팀원들과 둘씩 잇는 모든 선, 오른쪽은 팀 크기별 의사소통 경로 수 막대. 단계마다 팀 크기가 커진다2명 · 경로 1개3명 · 경로 3개4명 · 경로 6개5명 · 경로 10개6명 · 경로 15개8명 · 경로 28개사람 = 점, 의사소통 = 선팀 크기별 경로 수2명13명34명65명106명158명28
1 / 7

사람 수와 의사소통 경로 수. 단계를 넘기면 한 명씩 늘 때 선이 어떻게 느는지 보인다.

브룩스의 법칙의 한 가지 이유. 새로 온 사람은 기존 팀원 모두와 맞춰야 하고, 일을 나누고 다시 합치는 비용이 사람 수보다 빨리 커진다.

이 법칙에는 단서가 많다. 이미 막바지인 늦은 프로젝트에 해당하는 이야기이고, 처음 일정이 비현실적이었다면 사람보다 일정을 고치는 게 답이다. 잘 나뉜 모듈, 문서·테스트처럼 경계가 분명한 일, 이미 그 코드를 아는 사람이라면 투입 비용이 작다. 스티브 맥코널처럼 이 법칙이 늘 맞지는 않는다고 보는 사람도 있다. 그래도 "사람을 두 배로 하면 두 배 빨라진다"는 기대가 틀리다는 점은 변하지 않는다. 큰 조직이 팀을 작게 나누고 팀 사이를 API로 잇는 것도 경로 수를 줄이려는 것이다.

멜빈 콘웨이가 1968년에 쓴 글의 요지인 콘웨이의 법칙은 반대 방향에서 같은 이야기를 한다. 시스템을 설계하는 조직은 자기 조직의 의사소통 구조를 복사한 설계를 내놓는다. 서로 말하지 않는 팀의 모듈은 서로를 부르지 않고, 한 팀이 맡은 덩어리는 경계가 흐려진다. 그래서 원하는 아키텍처가 있다면 팀 구조를 먼저 그 모양으로 짜는 전략도 쓰인다. 서비스를 어떻게 나눌지는 시스템 설계 노트의 아키텍처 절로 이어진다.

왼쪽 조직도에는 웹 팀과 주문 팀, 주문 팀과 데이터 팀이 서로 이야기하고, 오른쪽 시스템에는 웹 앱과 주문 API, 주문 API와 분석 파이프라인이 같은 모양으로 이어진 그림조직(누가 누구와 말하나)시스템(무엇이 무엇을 부르나)웹 팀웹 앱주문 팀주문 API데이터 팀분석 파이프라인구조가 닮는다웹 팀과 데이터 팀이 말하지 않으면 웹 앱도 분석 쪽을 직접 부르지 않는다
콘웨이의 법칙. 조직도에서 선이 없는 두 팀 사이에는 시스템에서도 선이 생기지 않는다. 마이크로서비스의 경계가 팀 경계와 겹치는 일이 흔한 이유다.

그렇다고 처음부터 서비스를 잘게 나눌 일은 아니다. 마틴 파울러는 성공한 마이크로서비스 이야기는 거의 모두 너무 커진 모놀리스를 쪼갠 데서 시작했고, 처음부터 마이크로서비스로 만든 시스템은 대부분 심각한 어려움을 겪었다는 관찰을 MonolithFirst로 남겼다. 도메인 경계를 모를 때 나눈 선은 대개 틀린 곳에 그어진다. 스타일마다의 득실은 시스템 설계 › 아키텍처 스타일에 있다.

문서와 ADR

애자일 선언이 문서보다 작동하는 소프트웨어를 앞에 두었다고 문서가 필요 없다는 뜻은 아니다. 오래 사는 코드에서 가장 아쉬운 문서는 "무엇을 하는가"(코드가 말해 준다)가 아니라 "왜 이렇게 했는가"다. 왜 이 데이터베이스를 골랐는지, 왜 저 단순한 방법을 버렸는지는 코드에 남지 않고, 결정한 사람이 떠나면 사라진다. 다음 사람은 이유를 모른 채 같은 실수를 되풀이하거나, 멀쩡한 결정을 겁이 나서 못 바꾼다.

아키텍처 결정 기록(ADR)은 이 문제를 겨냥한 가장 가벼운 형식이다. 마이클 나이가드가 2011년 글에서 제안한 형식은 결정 하나를 짧은 문서 하나에 제목·맥락·결정·상태·결과 다섯 칸으로 적고, 코드 저장소 안에 번호를 매겨 쌓는다. 코드와 같은 저장소에 두니 리뷰를 거치고, 코드와 함께 버전이 남는다. 결정이 바뀌면 옛 기록을 지우지 않고 "대체됨"으로 표시한 뒤 새 기록을 쓴다. 이 목록 전체가 프로젝트의 결정 로그가 된다.

왼쪽은 ADR-007 문서 한 장의 제목·상태·맥락·결정·결과, 오른쪽은 docs/adr 폴더에 번호대로 쌓인 기록 셋과 대체된 ADR-003ADR-007 주문 저장소 선택상태채택 · ADR-003을 대체맥락주문과 재고를 한 트랜잭션으로 묶어야 한다결정주문 저장소로 관계형 DB를 쓴다결과조인·제약은 쉬워지고, 수평 확장은 따로 설계docs/adr/ADR-001채택모노레포로 시작ADR-003대체됨주문을 문서 DB에ADR-007채택주문을 관계형 DB에
ADR 한 장과 결정 로그. 결과 칸에는 좋은 점만이 아니라 감수하기로 한 비용도 적는다. 그래야 나중에 그 비용이 커졌을 때 다시 결정할 근거가 된다.

그 밖의 문서도 같은 원칙을 따른다. 처음 온 사람이 빌드·실행까지 가는 길(README), 코드 옆에 두어 함께 바뀌는 문서, 오래된 문서를 지우는 습관. 틀린 문서는 없는 문서보다 해롭다.

핵심 자료

도구 쪽은 버전 관리·테스트·CI/CD 노트, 큰 시스템의 구조는 시스템 설계에 있다.

자료종류요지링크
Software Engineering at Google — 1장무료 책"소프트웨어 공학은 시간에 걸쳐 적분한 프로그래밍"이라는 정의와 시간·규모·트레이드오프, 하이럼의 법칙.abseil.io
Refactoring책 소개마틴 파울러의 리팩터링 정의와 리팩터링 기법 목록.refactoring.com
The Scrum Guide (2020)공식 안내책임 셋, 이벤트 다섯과 시간 상자, 산출물 셋과 각각의 약속을 짧게 정의한다.scrumguides.org
Manifesto for Agile Software Development선언문2001년에 쓴 네 가지 가치 문장. 오른쪽 것도 가치 있지만 왼쪽 것을 더 중히 여긴다는 형식이다.agilemanifesto.org
Technical Debt글원금(치우는 비용)과 이자(그 탓에 매번 더 드는 노력)로 부채를 설명하고, 자주 고치는 곳부터 갚으라고 한다.martinfowler.com
Google Engineering Practices — 코드 리뷰가이드완벽하지 않아도 코드 건강을 분명히 낫게 하면 승인한다는 기준과 리뷰에서 볼 것들.google.github.io
Documenting Architecture Decisions글ADR을 널리 퍼뜨린 2011년 글. 제목·맥락·결정·상태·결과 다섯 칸의 짧은 문서를 저장소에 둔다.Cognitect
Conway's Law원문 소개1968년 Datamation에 실린 "How Do Committees Invent?"의 요지와 원문.melconway.com