Jungseob's Note
포스트
Refactoring English의 효과적인 소프트웨어 설계 문서 작성 가이드

좋은 설계 문서는 비싼 실수를 먼저 다룬다 — 목적·범위·운영·결정 기록

Michael Lynch의 설계 문서 작성법을 잘못된 결정의 비용이라는 기준으로 정리한다. 범위와 인터페이스, 운영·보안 조건을 합의하고 미해결 문제와 결정 이유를 남기는 방법을 다룬다.

좋은 설계 문서는 비싼 실수를 먼저 다룬다 — 목적·범위·운영·결정 기록

TL;DR

  • 설계 문서에 넣을 결정은 잘못됐을 때의 비용으로 고른다. 언어나 저장소처럼 바꾸기 어려운 선택은 깊이 검토하되 쉽게 수정할 UI 세부까지 미리 확정할 필요는 없다. 문서의 분량과 투자도 프로젝트의 위험·협업 규모에 맞춘다.
  • 첫 페이지에서 목적과 배경을 이해할 수 있어야 한다. 목표는 사용자나 조직에 생길 변화로 쓰고 비목표로 범위의 오해를 막는다. 시나리오와 수정 가능한 다이어그램은 작성자만 알고 있는 시스템의 모습을 독자와 공유한다.
  • 구현 구조와 함께 운영 조건을 설계한다. SLO와 감시·알림, 인터페이스와 의존성, 보안·개인정보·법적 제약을 연결해야 한다. 로그에 남길 내용과 접근·보존 정책도 구현 뒤로 미루지 않는다.
  • 미해결 문제에는 선택지와 바로 다음 행동을 적는다. 결정이 나면 이유와 토론을 보존하고 해결된 문제로 옮긴다. 설계 문서의 목적은 모든 생각을 기록하는 데 있지 않고 중요한 선택을 검토하고 실행할 수 있게 만드는 데 있다.

되돌리기 어려운 선택부터 문서에 담는다

큰 코드베이스를 만든 뒤 구현 언어가 잘못됐음을 깨닫는 일과, 화면의 목록을 한 번에 보여줄지 더 보기 버튼으로 나눌지 바꾸는 일은 비용이 다르다. Michael Lynch는 이 차이로 설계 문서에 넣을 내용을 고른다. 언어와 저장소처럼 되돌리기 어려운 결정은 구현 전에 논의할 가치가 크지만 몇 시간 안에 수정할 수 있는 표시 방식까지 상세히 검토하면 문서가 구현 작업을 대신하게 된다. 핵심 질문은 그 결정을 틀렸을 때 무엇을 잃는가다.[1]

문서가 필요한 정도도 프로젝트마다 다르다. 여러 사람이 함께 구현하거나 팀 사이의 협력이 필요하고 목표가 모호하거나 오랫동안 운영할 시스템이라면 사전 합의의 가치가 커진다. 보안이나 법적 문제처럼 설계 단계에서 예방할 큰 위험도 중요한 기준이다. 반대로 모든 작업에 긴 문서를 요구하는 보편 규칙은 없으며 저자는 상황에 따라 한 페이지로 충분하거나 문서 작성에 투자하지 않는 편이 맞을 수도 있다고 본다.[1]

첫 페이지에서 목적과 범위를 이해하게 한다

문서는 작성자의 설명을 먼저 듣지 않은 사람도 이해할 수 있어야 한다. 짧고 구별하기 쉬운 제목을 붙이고 작성자와 작성일, 공식 문서 주소, 승인자와 승인 시점을 남긴다. 목적은 이해관계자가 이해할 수 있는 한 문장으로 쓰며 배경에서는 왜 이 일을 시작했고 무엇이 문제이며 이전에는 어떤 해결을 시도했는지 설명한다. 관련 설계나 테스트 계획도 연결해 독자가 필요한 맥락을 찾게 한다.[1]

목표는 구현할 부품보다 구현 후 달라질 상황으로 표현한다. 원문의 설명용 캐시 프로젝트 RecencyBank에서는 캐시를 만든다는 기술 선택과 사용자 응답성 개선·데이터베이스 부하 감소라는 효과를 구분한다. 비목표는 독자가 당연히 포함된다고 생각할 수 있는 일을 명시적으로 제외한다. 예를 들어 특정 웹 앱의 성능 개선과 범용 캐시 플랫폼 개발을 구별하면, 검토가 다른 프로젝트로 커지는 일을 막을 수 있다.[1]

사용자 경험과 시스템의 경계를 함께 보여준다

시나리오는 기능 이름을 실제 행동으로 바꿔 보여준다. 보고서를 URL로 공유하는 기능이라면 누가 보고서를 만들고 어떤 메뉴를 선택하며 링크를 받은 사람이 무엇을 어떤 권한으로 보는지까지 설명한다. 이런 흐름이 있어야 검토자가 같은 완성 모습을 떠올릴 수 있다. 익숙하지 않은 내부 용어도 가능한 한 본문에서 풀고 필요한 용어집을 보조 수단으로 두면 문서 여기저기를 오가는 부담이 줄어든다.[1]

다이어그램에는 데이터의 이동과 구성 요소의 관계, 외부 의존성과 하위 시스템, 통신 경계를 드러낸다. 작성자의 머릿속에 이미 있는 그림을 검토자에게 전달하는 수단이므로 보기 좋게 그리는 일보다 수정할 수 있게 남기는 일이 중요하다. 화이트보드 사진만 붙이면 변경할 때마다 다시 그려야 하지만 Excalidraw나 draw.io의 원본, Mermaid 같은 다이어그램 코드를 함께 두면 팀이 계속 고칠 수 있다. 그림을 재현할 수 있는 원본 링크도 문서의 일부다.[1]

성능 목표와 장애를 알아차리는 방법을 구분한다

빠르게 동작한다는 표현만으로는 구현 완료를 판단하기 어렵다. SLO는 가용성·지연시간·처리 규모를 측정할 수 있는 조건으로 바꾸며 목표와 측정 지표를 구현 전에 합의하게 한다. RecencyBank의 설명용 목표는 사용자 요청의 중앙값 지연시간 200ms 이하와 데이터베이스 쿼리의 중앙값 지연시간 80ms 이하다. 이 값들은 글의 예제이지 모든 서비스가 따라야 할 성능 기준은 아니다.[1]

모니터링과 알림은 그 목표를 운영에서 어떻게 관찰하고 문제를 누구에게 알릴지 정한다. 서비스 중단이나 급격한 지연 증가, 인증 실패와 시스템 오류를 발견할 수 있어야 하며 조직이 성장하면 수동 확인을 자동 감시로 바꾼다. 원문의 알림 예시는 사용자 요청의 95백분위 지연시간이 3초 이상일 때 온콜 담당자를 호출하는 식이다. 중앙값 목표와 꼬리 지연의 알림 조건은 서로 다른 지표이므로 같은 숫자로 뭉뚱그리지 않는다.[1]

인터페이스와 의존성에는 변경의 영향을 드러낸다

시스템이 다른 주체와 만나는 부분은 구현 전에 검토할 가치가 크다. UI는 핵심 상호작용을 보여줄 정도로 그리되 세밀한 배치에 빠지지 않고 소프트웨어 인터페이스는 API·CLI의 의미와 파일 형식을 설명한다. 원문의 Go 예제는 서버가 구체적인 PostgresDB 타입에 직접 의존하던 구조를 Store 인터페이스로 바꾼다. 캐시가 그 인터페이스를 구현해 데이터베이스를 감싸면 서버 쪽 변경과 캐시의 책임을 분리해서 논의할 수 있다.[1]

제약과 의존성은 이런 선택의 배경을 제공한다. 어떤 하드웨어에서 실행해야 하는지, 예산과 고객 조건은 무엇인지, 사용할 언어와 라이브러리, 영속 데이터를 둘 저장소는 어디인지 적는다. 이때 모든 패키지를 같은 깊이로 설명할 필요는 없다. 나중에 언어나 저장 백엔드를 바꾸는 비용과 표준 인터페이스를 쓰는 메일 발송 업체를 교체하는 비용을 구별해야 검토 시간을 중요한 선택에 쓸 수 있다.[1]

보안·개인정보·법적 조건과 로그도 설계의 일부다

보안 항목은 어떤 위협을 고려했고 악의적인 입력이 어디로 들어오며 신뢰가 낮은 영역에서 높은 영역으로 넘어가는 경계가 어디인지 설명한다. 위협이 적다고 판단했다면 그 이유도 적어야 검토자가 빠뜨린 위험을 지적할 수 있다. RecencyBank 예제에서는 자체 접근 제어가 없는 캐시가 인터넷의 직접 요청을 받지 않도록 네트워크 연결 범위를 제한한다. 이는 특정 예제의 경계 설계이며 모든 시스템에서 네트워크 격리만으로 보안이 충분하다는 뜻은 아니다.[1]

개인정보 항목은 민감한 데이터의 종류와 보존 기간, 접근 가능한 사람, 전송·저장 중 보호 방법을 다룬다. 법적 검토에서는 규제뿐 아니라 고객 계약이 데이터 복사와 캐싱을 허용하는지, 공개할 코드에 어떤 라이선스를 적용할지도 확인한다. 로그는 장애와 보안 사고 조사에 쓸 사건을 남기되 저장 위치·보존 기간·접근 권한과 기록하지 말아야 할 민감정보까지 함께 정한다. 설계 문서에서 이 조건들을 연결해 놓아야 기능 구현 이후에 운영과 데이터 보호를 따로 끼워 맞추는 일을 줄일 수 있다.[1]

일정은 검토할 수 있는 결과물로 나눈다

마일스톤은 작업 목록을 시간순으로 나열하는 데서 끝나지 않는다. 이해관계자가 직접 보고 의견을 줄 수 있는 결과물이 각 단계에 있어야 한다. 실제 데이터 연결을 모두 구현하기 전에 가짜 데이터를 보여주는 UI부터 검토하면 요구사항을 오해했는지 일찍 알 수 있다. 캐시 예제 역시 고정 데이터로 동작하는 버전에서 실제 데이터 연결, 만료 규칙, 운영 배포로 이어지는 단계로 나뉜다.[1]

저자가 함께 공개한 Little Moments 설계 문서는 이 원칙의 적용 사례다. 가족 사진 공유라는 목적에 맞춰 단일 가족용 서버를 전제로 삼고 다중 가족 지원과 네이티브 앱을 비목표로 둔다. 구현 순서도 먼저 읽기 전용 화면을 만들고 데이터베이스·저장소·인증을 더한 뒤 업로드와 상호작용, 알림으로 확장한다. 이 사례에서 참고할 부분은 제한된 목표와 단계별 결과물을 연결하는 방법이며 그 프로젝트의 보안 선택이나 인프라가 모든 서비스에 적합하다고 검증된 것은 아니다.[2]

미해결 질문을 다음 행동과 결정 기록으로 바꾼다

설계 중 답을 내리지 못한 문제는 숨기지 않고 미해결 항목으로 남긴다. 무엇이 문제인지, 어떤 선택지가 있는지, 해결을 위한 바로 다음 행동이 무엇인지 적어야 한다. 원문의 캐시 메모리 예제는 최적 용량을 찾는 실험에도 개발 시간이 든다는 점을 비교한 뒤, 우선 128GB로 시작할지 기술 리더에게 판단을 요청한다. 이 숫자는 일반적인 권장 사양이 아니라 정보 수집 비용과 잘못된 선택의 비용을 비교하는 예다.[1]

문제가 해결되면 결정과 이유를 요약해 해결된 항목으로 옮기고 기존 토론을 보존한다. 유력했던 대안을 왜 채택하지 않았는지도 짧게 남기면 이후의 독자가 같은 논의를 반복하지 않아도 된다. 반면 스쳐 지나간 아이디어까지 모두 기록하는 것은 과도하다는 것이 저자의 입장이다. 중요한 결정을 남긴 초안을 팀에 공유하고 피드백으로 미해결 문제를 줄여가는 과정까지가 설계 문서의 역할이다.[1]

참고 자료

[1] https://refactoringenglish.com/excerpts/write-an-effective-design-doc — How to Write an Effective Software Design Document — Michael Lynch

[2] https://refactoringenglish.com/excerpts/write-an-effective-design-doc/little-moments-design-doc — Little Moments Design Doc — Michael Lynch

원문 출처는 본문의 Source에서 확인할 수 있습니다.