유스케이스와 사용자 흐름
모임 신청을 액터의 목표, 사전 조건과 사후 조건, 정상 흐름과 대안 흐름으로 적는 유스케이스를 씁니다. UML 유스케이스 그림의 연관, include, extend가 무엇을 나타내는지 정리하고, 흐름의 결과를 core.mjs로 실행해 확인합니다.
모임 신청 화면에 버튼을 하나 붙였다고 해서 신청 기능이 완성되지는 않습니다. 사용자는 자리가 남았는지, 버튼을 누른 뒤 정말 참가자가 되었는지 알고 싶어 하고, 이미 신청했거나 마지막 자리가 찼을 때도 무슨 일이 일어났는지 알아야 합니다. 이 글에서는 기능의 이름 대신 사용자의 목표와 시스템의 응답으로 신청을 설명하는 유스케이스를 쓰고, 정상적인 흐름과 거절되는 흐름을 작은 실행으로 확인합니다. 화면의 모양이나 저장 방식은 아직 정하지 않습니다.
버튼이 아니라 목표에서 시작한다
유스케이스(use case)는 시스템 바깥의 누군가가 시스템과 행동과 응답을 주고받으며 하나의 목표를 이루는 과정입니다. 이때 시스템과 상호작용하는 바깥의 역할을 액터(actor)라고 하는데, 특정한 사람이 아니라 "참가하려는 회원", "모임을 관리하는 진행자"와 같은 역할을 가리킵니다. 알림을 보내 주는 다른 시스템처럼 사람이 아닌 것도 액터가 될 수 있습니다.
"신청 버튼 누르기"는 화면에서의 동작이고, "모임에 참가자로 등록되기"는 사용자의 목표입니다. 버튼이 없어도 명령줄에서 같은 목표를 이룰 수 있고, 반대로 버튼 하나가 여러 상황을 처리하기도 하므로 화면의 버튼 수와 유스케이스의 수는 같지 않습니다. 또 유스케이스 하나가 함수 하나와 꼭 맞아떨어져야 하는 것도 아닙니다.
유스케이스를 쓸 때는 이번에 만들 시스템이 어디까지인지도 함께 정합니다. 이 실습의 시스템은 회원 아이디와 모임의 상태를 받아 신청을 처리하는 데까지이며, 로그인 화면, 메일 발송, 결제는 시스템 바깥에 둡니다. "회원 아이디를 받는다"는 말은 그 아이디가 정말 그 회원의 것인지 확인했다는 뜻이 아니므로, 범위를 적어 두어야 만들지 않은 기능을 만들었다고 오해하지 않습니다.
정상 흐름과 끝난 뒤의 상태를 함께 쓴다
모임 신청 유스케이스는 다음과 같이 정리할 수 있습니다. 유스케이스가 시작되기 전에 성립해야 하는 조건을 사전 조건(precondition), 끝난 뒤에 성립해야 하는 조건을 사후 조건(postcondition)이라고 합니다.
| 항목 | 모임 신청 |
|---|---|
| 목표와 주 액터 | 회원이 열린 모임에 참가자로 등록된다 |
| 사전 조건 | 회원 아이디와 모임이 주어져 있다 |
| 시작하는 계기 | 회원이 신청을 요청한다 |
| 성공했을 때의 사후 조건 | 그 회원의 아이디가 명단에 한 번 들어 있다 |
| 거절되었을 때의 사후 조건 | 명단은 바뀌지 않고, 회원은 거절된 이유를 안다 |
사전 조건이 늘 성립한다고 가정해 검사하는 코드를 빼서는 안 됩니다. 유스케이스에 적은 흐름은 이해를 돕는 대표적인 경로일 뿐이고, 실제로는 조건을 어기는 요청도 들어오기 때문입니다. 거절되었을 때의 사후 조건을 적어 두면 "거절 안내는 나왔는데 명단에는 들어갔다" 같은 결함도 찾을 수 있습니다.
유스케이스 안에서 실제로 일어날 수 있는 한 가지 경로를 시나리오(scenario)라고 합니다. 자리가 하나 남은 모임에 처음 신청한 사람이 등록되는 경로와, 이미 신청한 사람이 다시 요청하는 경로는 같은 목표 안의 서로 다른 시나리오입니다. 아래 그림은 아직 신청하지 않은 유효한 아이디로 열린 모임에 신청하는 시나리오를 요청, 정책 확인, 결과 확인의 세 단계로 따라갑니다.
사용자의 목표를 확인 가능한 결과로 연결한다
아직 참가하지 않은 유효 ID로 열린 모임에 신청한다. 해당 ID가 명단에 한 번 존재한다. 각 단계의 값과 전환 이유를 비교합니다.
SOFTWARE ENGINEERING · CH3-1
사용자의 목표를 확인 가능한 결과로 연결한다
버튼이나 함수의 개수보다 목표·응답·끝난 상태를 먼저 정합니다.
회원의 참가 목표에서 신청 요청, 시스템의 정책 확인, 결과와 명단 확인으로 이어지는 정상 흐름이며 대안 흐름은 본문 표에 명시합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 신청 목표와 요청부터 결과 확인까지 진행합니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 신청 목표와 요청 아직 참가하지 않은 유효 ID로 열린 모임에 신청한다. |
|
|
| 정책 확인 ID·모집·중복·정원 조건을 확인한다. |
|
|
| 결과 확인 해당 ID가 명단에 한 번 존재한다. |
|
|
사용자의 목표를 확인 가능한 결과로 연결한다
아직 참가하지 않은 유효 ID로 열린 모임에 신청한다. 해당 ID가 명단에 한 번 존재한다. 각 단계의 값과 전환 이유를 비교합니다.
SOFTWARE ENGINEERING · CH3-1
사용자의 목표를 확인 가능한 결과로 연결한다
버튼이나 함수의 개수보다 목표·응답·끝난 상태를 먼저 정합니다.
회원의 참가 목표에서 신청 요청, 시스템의 정책 확인, 결과와 명단 확인으로 이어지는 정상 흐름이며 대안 흐름은 본문 표에 명시합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 신청 목표와 요청부터 결과 확인까지 진행합니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 신청 목표와 요청 아직 참가하지 않은 유효 ID로 열린 모임에 신청한다. |
|
|
| 정책 확인 ID·모집·중복·정원 조건을 확인한다. |
|
|
| 결과 확인 해당 ID가 명단에 한 번 존재한다. |
|
|
거절되는 흐름도 적는다
모든 확인을 통과하는 기본 경로를 정상 흐름(main flow)이라 하고, 어느 단계에서 갈라져 다른 결과로 끝나는 경로를 대안 흐름(alternative flow)이라고 합니다. 대안 흐름은 "오류가 나면 실패한다"로 끝내지 않고, 어느 단계에서 갈라지는지와 끝난 뒤에 무엇이 그대로 남는지를 적습니다. 모임 신청의 흐름을 단계별로 쓰면 다음과 같습니다.
정상 흐름
1. 회원이 모임에 신청을 요청한다.
2. 시스템이 아이디의 형식을 확인한다.
3. 시스템이 모임이 열려 있는지 확인한다.
4. 시스템이 아직 신청하지 않은 아이디인지 확인한다.
5. 시스템이 남은 자리가 있는지 확인한다.
6. 시스템이 명단에 아이디를 더하고 REGISTERED를 알린다.
대안 흐름
2a. 형식이 잘못되었으면 INVALID_MEMBER를 알리고, 명단을 그대로 둔 채 끝난다.
3a. 모임이 마감되었으면 CLOSED를 알리고, 명단을 그대로 둔 채 끝난다.
4a. 이미 신청한 아이디이면 ALREADY_REGISTERED를 알리고, 명단을 그대로 둔 채 끝난다.
5a. 자리가 없으면 FULL을 알리고, 명단을 그대로 둔 채 끝난다.대안 흐름의 2a는 정상 흐름의 2단계에서 갈라진다는 뜻입니다. 단계의 순서는 기능 요구와 수락 조건의 R1부터 R4까지의 우선순위와 같으므로, 정원이 찬 열린 모임에 이미 신청한 회원은 4a에서 먼저 갈라져 ALREADY_REGISTERED를 받습니다. 같은 회원이 마감된 모임에 다시 신청하면 3a에서 먼저 갈라져 CLOSED를 받습니다. 어느 순서가 옳다고 정해진 것은 아니지만, 한번 정한 순서는 유스케이스, 코드, 테스트가 모두 따라야 합니다.
UML 유스케이스 그림과 관계
UML(Unified Modeling Language)의 유스케이스 그림은 액터를 사람 모양으로, 유스케이스를 타원으로 그리고, 시스템이 맡는 범위를 사각형인 시스템 경계(system boundary)로 둘러쌉니다. 액터와 유스케이스를 잇는 연관(association) 선은 둘이 상호작용한다는 뜻일 뿐, 일이 일어나는 시간 순서를 나타내지 않습니다. 그림만 보아서는 중복 신청의 결과나 거절된 뒤의 명단을 알 수 없으므로, 그림은 전체를 훑어보는 지도로 쓰고 흐름은 앞에서처럼 문장으로 적습니다.
유스케이스 사이에는 두 가지 관계를 그릴 수 있습니다. 포함(include)은 여러 유스케이스가 반드시 거치는 공통 행동을 따로 떼어 낼 때 쓰고, 확장(extend)은 어떤 조건에서만 기본 유스케이스에 행동을 덧붙일 때 씁니다. 화살표는 포함하는 쪽에서 포함되는 쪽으로, 확장하는 쪽에서 확장되는 기본 유스케이스 쪽으로 그립니다. 이름이 비슷하지만 클래스 상속을 나타내는 JavaScript의 extends와는 관계가 없습니다.
예를 들어 신청과 취소가 모두 같은 아이디 확인을 반드시 거친다면, 그 확인을 별도의 유스케이스로 떼어 두 유스케이스가 포함하게 그릴 수 있습니다. 반면 신청이 거절되는 경우는 신청 유스케이스 안의 대안 흐름으로 충분하므로, 코드의 if마다 새 유스케이스나 확장 관계를 만들 필요는 없습니다. 관계는 그것이 답해 주는 질문이 있을 때만 그리며, 지금의 실습에서는 회원과 진행자의 목표를 문장으로 구별하는 것으로 충분합니다.
흐름의 결과를 실행으로 확인한다
정상 흐름과 대안 흐름이 실제로 그렇게 동작하는지는 기능 요구와 수락 조건에서 만든 core.mjs로 확인할 수 있습니다. 다음 코드를 reading-lab/examples/ch3-1.mjs로 저장하고, reading-lab 폴더에서 node examples/ch3-1.mjs로 실행합니다.
import { createSession, register } from "../core.mjs";
const start = createSession({ id: "book-01", title: "함께 읽는 첫 책", capacity: 1 });
const first = register(start, "member-a");
const duplicate = register(first.state, "member-a");
const full = register(first.state, "member-b");
console.log(first.code, first.state.participants.join(","));
console.log(duplicate.code, full.code);
console.log("처음 상태의 인원:", start.participants.length);REGISTERED member-a
ALREADY_REGISTERED FULL
처음 상태의 인원: 0정원이 1인 모임에 member-a가 정상 흐름으로 등록된 뒤, 같은 아이디는 4a에서, 새 아이디 member-b는 5a에서 거절되었습니다. 두 번째와 세 번째 호출에 start가 아니라 first.state를 넘긴 것은, 앞의 신청이 끝난 상태에서 다음 요청이 들어온다는 유스케이스의 전제를 코드에 옮긴 것입니다. 마지막 줄은 신청이 처음 상태를 고치지 않고 새 상태를 만들었다는 것을 보여 줍니다.
아이디를 받을 때 Member-a의 대문자를 소문자로 바꾸거나 앞뒤 공백을 지워서 받아 주지 않고, 형식이 다르면 그대로 INVALID_MEMBER로 거절하는 것도 이 교재가 정한 정책입니다. 입력을 고쳐서 받아 주면 사용자가 입력한 것과 다른 아이디가 명단에 들어갈 수 있으므로, 그렇게 하려면 요구로 따로 정해야 합니다.
자주 하는 오해
"화면에서 이미 신청한 회원에게는 신청 버튼을 숨겼으니 중복 신청을 막았다"는 설명은 충분하지 않습니다. 화면을 오래 열어 둔 회원이 예전 상태의 버튼을 누르거나 같은 요청을 다시 보낼 수 있으므로, 시스템이 직접 중복을 검사해야 합니다. 화면의 안내는 사용자를 돕는 일이고, 신청 규칙의 검사는 시스템이 맡는 일로 서로 따로 갖추어야 합니다.
또 이 실행은 한 프로세스에서 요청을 하나씩 차례로 처리한 것입니다. 두 요청이 동시에 마지막 자리를 읽고 각자 저장하는 문제는 다루지 않았으며, 이 문제는 동시 저장과 복구에서 다시 봅니다. 여기서 정리한 정상 흐름과 대안 흐름은 그때 저장 방식과 동시 처리를 검사하는 기준이 됩니다.
정리
- 유스케이스는 액터가 시스템과 행동과 응답을 주고받으며 목표를 이루는 과정이며, 액터는 사람이 아니라 역할입니다.
- 유스케이스는 버튼이나 함수가 아니라 사용자의 목표를 단위로 나누고, 시스템이 맡는 범위를 함께 적습니다.
- 사전 조건과 사후 조건, 특히 거절되었을 때의 사후 조건을 적어 두면 결함을 찾기 쉽습니다.
- 대안 흐름은 정상 흐름의 어느 단계에서 갈라지는지와 무엇이 그대로 남는지를 적고, 단계의 순서는 요구의 우선순위와 같아야 합니다.
- UML 유스케이스 그림의 연관 선은 상호작용을 나타낼 뿐 시간 순서가 아니며, include와 extend는 답해야 할 질문이 있을 때만 씁니다.
연습 문제
1. 카메라 대여 유스케이스
사진 동아리는 카메라를 회원에게 빌려줍니다. 카메라 한 대는 한 번에 한 회원만 빌릴 수 있고, 수리 중인 카메라는 빌릴 수 없으며, 이미 그 카메라를 빌린 회원이 다시 요청하면 "이미 대여 중"을 알립니다. 수리 여부를 가장 먼저 확인하기로 했습니다. 주 액터, 시작하는 계기, 성공했을 때와 거절되었을 때의 사후 조건을 쓰고, 정상 흐름 하나와 대안 흐름 세 가지를 적으세요.
해설 보기
주 액터는 카메라를 빌리려는 회원이고, 시작하는 계기는 특정 카메라의 대여 요청입니다. 성공하면 그 카메라의 대여자가 요청한 회원이 되고, 거절되면 카메라의 상태와 기존 대여자가 그대로 남아야 합니다. 정상 흐름은 수리 중이 아니고 대여자가 없는 카메라를 요청해 대여자가 되는 경로입니다. 대안 흐름은 수리 중이라 거절되는 경우, 같은 회원이 이미 빌려 "이미 대여 중"을 알리는 경우, 다른 회원이 빌리고 있어 거절되는 경우입니다.
수리 여부를 가장 먼저 확인하므로, 수리 중인데 기록상 같은 회원이 대여자로 남아 있는 카메라에는 "이미 대여 중"이 아니라 수리 중이라는 결과를 냅니다. 이 순서를 적어 두지 않으면 개발자마다 다른 우선순위로 구현할 수 있습니다.
2. 유스케이스의 이름
카메라 대여에서 "카메라 고르기"와 "카메라 빌리기" 가운데 유스케이스의 이름으로 더 알맞은 것은 무엇인가요? 이유를 설명하세요.
해설 보기
"카메라 빌리기"가 더 알맞습니다. 회원이 얻으려는 결과는 카메라를 빌리는 것이고, 카메라를 고르는 일은 그 목표를 이루는 과정의 한 단계이기 때문입니다. "카메라 고르기"를 별도의 유스케이스로 두면, 고르기만 하고 빌리지 못한 경우가 목표를 이룬 것인지 판단하기 어렵습니다. 유스케이스의 이름은 보통 액터가 이루려는 목표를 "무엇을 하다"의 형태로 짓습니다.
3. 처음 상태만 넘기면
본문 예제에서 두 번째와 세 번째 호출에 first.state 대신 start를 넘기면 둘째 줄의 출력은 어떻게 바뀌나요? 그 결과가 유스케이스의 무엇을 놓친 것인지 설명하세요.
해설 보기
둘째 줄은 REGISTERED REGISTERED가 됩니다. 두 호출 모두 아무도 신청하지 않은 처음 상태를 보므로 같은 아이디의 재신청도, 정원이 1인 모임의 두 번째 신청도 받아들여집니다. 이 코드는 앞의 신청이 끝난 상태에서 다음 요청이 들어온다는 전제를 빠뜨린 것이라, 4a와 5a의 대안 흐름을 한 번도 시험하지 못합니다. 흐름을 확인하는 예제는 각 요청이 어떤 상태를 보고 처리되는지까지 유스케이스와 맞추어야 합니다.
유스케이스로 사용자와 시스템이 주고받는 흐름을 정했으니, 다음 글 상태와 데이터 모델에서는 status와 참가자 명단이 어떤 뜻을 가지며 어떤 사건에서 바뀌는지를 상태 모델과 데이터 모델로 정리합니다.
원자료와 더 읽기
장원사이버평생교육원 「소프트웨어공학」 PDF 물리 105–127쪽의 주제를 바탕으로 설명, 도표, 실습을 새로 구성했습니다. 유스케이스 그림의 표기는 OMG UML 2.5.1 공식 명세에서 확인할 수 있습니다.