사용자·문제·범위
요청 속의 해결 수단과 실제 문제를 나누고, 이해관계자마다 다른 관점과 문제·목표·수단의 차이를 정리합니다. 이번 버전에서 다룰 범위와 제약, 타당성을 정하고, 모임을 만들 때 받아들일 값을 코드로 확인합니다.
진행자가 "모임 앱에 AI 추천과 결제 기능을 넣어 주세요"라고 요청했습니다. 이 말을 그대로 기능 목록에 옮기기 전에, 그 기능으로 어떤 불편을 해결하려는지 먼저 물어야 합니다. 이야기를 나누어 보면 추천보다 중복 신청과 스프레드시트로 정원을 세는 일이 더 급한 문제일 수도 있습니다. 이 글에서는 누구의 어떤 문제를 해결할지, 이번에 무엇을 하고 무엇을 하지 않을지를 이유와 함께 정합니다.
문제, 목표, 해결 수단을 나눈다
시스템의 결과에 영향을 받는 사람을 이해관계자(stakeholder)라고 합니다. 참가자는 신청이 되었는지 알아야 하고, 진행자는 명단을 믿을 수 있어야 하며, 운영자는 문제가 생긴 뒤 데이터를 되살려야 하고, 비용을 승인하는 사람은 개발에 드는 시간을 중요하게 봅니다. 그래서 요청한 사람 한 명의 말이 모든 이해관계자의 관점을 대신하지는 않습니다.
요청을 정리할 때는 지금의 불편을 말하는 문제 진술(problem statement), 나아졌는지 판단할 기준인 목표(goal), 목표를 이루는 방법의 후보인 해결 수단을 나누어 적습니다. "AI 추천을 넣어 주세요"는 해결 수단이고, 그 밑에 있는 문제와 목표를 찾아야 다른 수단과 비교할 수 있습니다.
| 구분 | 독서 모임의 예 |
|---|---|
| 지금의 문제 | 같은 아이디가 명단에 두 번 들어가 정원을 잘못 센다 |
| 목표 | 한 모임에 같은 아이디가 두 번 세어지지 않는다 |
| 해결 수단의 후보 | 종이 명단 대조, 스프레드시트 규칙, 작은 신청 프로그램 |
요구를 모을 때는 "편리하게 해 드릴까요?"처럼 막연하게 묻지 말고, 최근에 중복 신청을 실제로 어떻게 처리했는지처럼 지난 사례를 묻습니다. 기존 신청 양식 같은 문서를 살펴보고, 진행자가 명단을 고치는 순서를 직접 관찰하는 방법도 있습니다. 빈 아이디, 취소, 마감 뒤의 신청 같은 예외적인 경우를 정상적인 신청과 함께 모아야, 나중에 규칙을 정할 때 빠뜨리지 않습니다. 이해관계자들의 말이 서로 어긋나면 담당자와 함께 우선순위를 다시 정합니다.
이번 버전의 범위와 제약
이번 작업에 넣을 기능을 범위(scope)라 하고, 반드시 지켜야 할 조건을 제약(constraint)이라고 합니다. 첫 버전의 범위는 모임 하나, 아이디로 참가자 구별, 정원 확인, 신청과 취소와 마감으로 정합니다. 결제, 실명 확인, 메일 발송, 여러 서버에서의 동시 신청은 이번 범위에서 뺍니다. 뺐다는 것은 앞으로도 필요 없다는 뜻이 아니라, 이번 버전에서 다루거나 확인하지 않는다는 뜻입니다.
제약으로는 교육용 가상 아이디만 쓰고, 외부 서비스 없이 Node.js 프로세스 하나로 실행한다는 조건을 둡니다. 그래서 이 프로그램이 실제 서비스의 인증, 동시 접속, 개인정보 보관 문제를 해결했다고 말할 수는 없습니다. 범위와 제약을 먼저 적어 두면, 나중에 무엇을 확인했고 무엇을 확인하지 않았는지 분명히 말할 수 있습니다.
타당성(feasibility)은 계획한 방법이 기술적으로 가능한지, 드는 비용에 비해 효과가 있는지, 실제 업무에 들여올 수 있는지를 따지는 판단입니다. 예를 들어 외부 결제가 꼭 필요하다면, 기술보다 결제 업체와의 계약이나 환불 같은 운영 문제가 먼저 걸림돌이 될 수 있습니다. 작은 프로그램부터 만들어 확인하는 이 교재의 선택은 모임의 규모와 학습 목표에 맞춘 판단입니다.
모임을 만들 때 받을 값
모임을 만들 때 무엇을 입력받고 어떤 값을 거절할지도 이 단계에서 정합니다. 다음 코드를 create-check.mjs로 저장해 실행해 보세요.
function createSession({ id, title, capacity }) {
if (typeof id !== "string" || !id.trim() || typeof title !== "string" || !title.trim()) {
throw new TypeError("id and title must be non-empty strings");
}
if (!Number.isInteger(capacity) || capacity < 1 || capacity > 100) {
throw new RangeError("capacity must be an integer from 1 to 100");
}
return { id, title, capacity, status: "OPEN", participants: [] };
}
console.log(createSession({ id: "book-1", title: "첫 책", capacity: 2 }));
for (const input of [
{ id: "book-2", title: "둘째 책", capacity: 0 },
{ id: "book-3", title: "셋째 책", capacity: 1.5 },
{ id: " ", title: "넷째 책", capacity: 2 },
]) {
try {
createSession(input);
} catch (error) {
console.log(error.name, error.message);
}
}{
id: 'book-1',
title: '첫 책',
capacity: 2,
status: 'OPEN',
participants: []
}
RangeError capacity must be an integer from 1 to 100
RangeError capacity must be an integer from 1 to 100
TypeError id and title must be non-empty strings함수의 매개변수 { id, title, capacity }는 넘겨받은 객체에서 같은 이름의 값을 꺼내는 구조 분해(destructuring)이며, 자세한 문법은 구조 분해 할당과 스프레드 연산자에서 다룹니다. typeof는 값의 종류를 문자열로 알려 주고, trim()은 앞뒤 공백을 지운 문자열을 돌려주므로, 공백만 있는 아이디도 빈 값으로 거절됩니다. ||는 조건 가운데 하나라도 참이면 전체가 참이라서, 하나라도 잘못되면 throw로 오류를 던져 모임을 만들지 않습니다. try와 catch는 던져진 오류를 받아 이름과 메시지를 출력합니다.
정원 0과 1.5는 Number.isInteger와 범위 검사에 걸려 RangeError를, 공백 아이디는 TypeError를 받았습니다. 정원의 상한 100은 이 교재가 정한 규칙일 뿐, 모든 신청 시스템에 공통인 값은 아닙니다. 정상적인 모임은 상태가 OPEN이고 참가자가 없는 처음 상태로 만들어지며, 첫 줄처럼 객체가 길면 Node.js가 여러 줄로 나누어 출력합니다. 이후 글에서는 이 처음 상태를 직접 고치지 않고, 함수를 통해 새 상태를 얻습니다.
정리
- 이해관계자는 시스템의 결과에 영향을 받는 사람이며, 요청한 사람 한 명의 말이 모든 관점을 대신하지 않습니다.
- 요청에서 문제 진술, 목표, 해결 수단을 나누어야 다른 해결 수단과 비교할 수 있습니다.
- 요구는 지난 사례를 묻고, 문서를 살피고, 일하는 모습을 관찰해 모으며, 예외적인 경우를 함께 모읍니다.
- 범위는 이번에 넣을 기능, 제약은 지켜야 할 조건이고, 범위에서 뺀 것은 이번에 확인하지 않은 것입니다.
- 타당성은 기술, 비용과 효과, 업무에 들여올 수 있는지를 함께 따지는 판단입니다.
연습 문제
1. 요청이 서로 어긋날 때
참가자는 마감 뒤에도 취소하고 싶어 하고, 진행자는 마감한 명단을 확정하고 싶어 합니다. 이 문제를 "취소 버튼 추가"로만 적으면 무엇을 놓치나요? 더 물어볼 질문 두 가지와, 가능한 범위 결정을 제안하세요.
해설 보기
취소 버튼은 해결 수단일 뿐이라, 마감이 새 신청만 막는 것인지 명단을 고치는 일 전체를 막는 것인지를 놓칩니다. "마감 뒤 취소로 빈 자리를 다른 사람이 신청할 수 있나요?"와 "확정 명단의 기록은 어디에 왜 필요한가요?"를 물어볼 수 있습니다. 이 교재는 마감 뒤에도 취소는 받되 모임을 다시 열지는 않는 정책을 고르고, 출석이나 감사를 위한 이력 보관은 따로 다룰 요구로 남깁니다. 다른 결정도 가능하지만, 그때는 이유와 함께 확인할 테스트가 달라집니다.
2. 문제, 목표, 수단 나누기
"진행자가 매번 명단을 손으로 세느라 30분씩 걸린다", "명단 확인에 걸리는 시간을 5분 이하로 줄인다", "참가자 수를 자동으로 세는 화면을 만든다"는 각각 문제, 목표, 해결 수단 가운데 무엇인가요?
해설 보기
첫째는 지금의 불편을 말하므로 문제 진술이고, 둘째는 나아졌는지 판단할 기준을 숫자로 정했으므로 목표입니다. 셋째는 목표를 이룰 방법의 하나이므로 해결 수단입니다. 목표를 먼저 정해 두면, 자동으로 세는 화면 말고도 신청 양식을 바꾸는 방법 같은 다른 수단이 같은 목표를 더 싸게 이루는지 비교할 수 있습니다. 수단만 적어 두면 그것이 정말 문제를 해결했는지 판단할 기준이 없습니다.
3. 거절되는 입력
본문의 createSession에 { id: "book-5", title: "다섯째 책", capacity: "2" }와 { id: "book-6", title: "여섯째 책", capacity: 100 }을 넣으면 각각 어떻게 되나요?
해설 보기
첫째는 정원이 숫자가 아니라 문자열 "2"라서 Number.isInteger("2")가 거짓이 되므로 RangeError를 받습니다. 화면에서 입력받은 값은 문자열인 경우가 많으므로, 숫자로 바꾸는 일을 누가 맡을지도 정해 두어야 합니다. 둘째는 정원 100이 허용 범위의 끝값이라 거절되지 않고 모임이 만들어집니다. 범위의 끝값과 그 바로 바깥 값은 실수가 잦은 곳이라, 테스트할 때 꼭 넣어 보아야 하며 이 내용은 동치 분할과 경계값에서 다룹니다.
요청이 많다는 이유만으로 그 기능의 가치가 높다고 보지 말고, 그 기능이 어떤 문제와 목표에 닿아 있는지 먼저 적어 보세요. 다음 글 기능 요구와 수락 조건에서는 이번에 정한 정책을, 결과로 확인할 수 있는 조건으로 바꿉니다.