본문 바로가기

비기능 요구와 변경 추적

성능, 사용성, 보안, 복구 같은 품질 요구를 측정할 대상, 환경, 기준, 확인 방법으로 바꾸어 적고, 입력 검사와 인증, 인가를 구분합니다. 요구를 설계, 코드, 테스트와 이어 두는 추적 표를 만들고, 요구가 바뀔 때 영향을 찾아 다시 확인하는 순서를 정합니다.

신청 결과가 맞더라도 30초 뒤에야 화면에 나타난다면, 사용자는 버튼을 여러 번 눌러 같은 신청을 되풀이할 것입니다. 잘못된 아이디를 정확히 거절하더라도 명단 파일을 누구나 열어 볼 수 있다면 개인정보를 지켜야 한다는 요구는 만족하지 못한 것입니다. 이처럼 무엇을 하는지가 아니라 얼마나 잘, 어떤 조건에서 하는지를 정하는 요구를 비기능 요구(non-functional requirement)라고 합니다. 이 글에서는 기능 요구와 수락 조건에 이어 품질에 관한 요구를 확인할 수 있게 적고, 요구가 바뀔 때 무엇을 함께 고쳐야 하는지 찾는 방법을 다룹니다.

품질을 확인할 수 있는 질문으로 바꾼다

"빠르게", "안전하게" 같은 형용사만으로는 요구를 만족했는지 판단할 수 없습니다. 그래서 무엇을 측정할지, 어떤 환경에서 잴지, 기준이 얼마인지, 어떻게 확인할지를 함께 정해야 합니다.

막연한 표현측정할 수 있게 바꾼 초안더 정해야 할 것
빠르게 신청된다정한 기기에서 고정된 입력 100개를 처리하는 시간의 분포를 기록한다허용할 지연, 동시 요청의 수, 측정 횟수
쉽게 쓸 수 있다처음 쓰는 참가자가 오류 안내만 보고 아이디를 고친다참여자, 맡길 과업, 성공의 기준
안전하게 저장된다권한이 없는 사용자는 참가자 명단을 읽지 못한다누구를 어떻게 확인할지, 어떤 위협을 막을지
복구할 수 있다프로그램을 끈 뒤 저장한 상태로 명단을 되살린다잃어도 되는 양, 복구에 걸려도 되는 시간, 백업 주기

이 교재의 아이디 형식 검사는 입력값이 규칙에 맞는지 보는 입력 검사(input validation)입니다. 그 아이디를 쓰는 사람이 정말 min인지 확인하는 인증(authentication)이나, min이 어떤 명단을 볼 수 있는지 정하는 인가(authorization)와는 다른 일입니다. 그래서 프로세스 하나에서 도는 이 교재의 함수가 실제 서버의 보안을 갖추었다고 말할 수는 없습니다. 인증과 인가를 실제 웹 서비스에서 구현하는 방법은 인증과 권한 관리 기초에서 다룹니다.

측정값은 환경과 함께 적는다

빠르기는 실행 환경에 따라 크게 달라지므로, 시간을 한 번 재어 나온 값을 그대로 제품의 응답 시간으로 삼으면 안 됩니다. 시간을 재는 방법부터 보겠습니다. 다음 코드를 timing.mjs로 저장해 실행합니다.

import { performance } from "node:perf_hooks";

function measure(work) {
  const start = performance.now();
  const value = work();
  return { value, elapsedMs: performance.now() - start };
}

const observed = measure(() => Array.from({ length: 100 }, (_, i) => `member-${i}`));
console.log(observed.value.length, observed.value[0], observed.value.at(-1), observed.elapsedMs >= 0);
100 member-0 member-99 true

측정할 작업으로 넘긴 Array.from({ length: 100 }, (_, i) => ...)는 길이가 100인 배열을 만들면서, 자리 번호 i마다 화살표 함수가 돌려준 값을 원소로 채웁니다. 첫 매개변수는 원소의 값인데 여기서는 쓰지 않으므로 관례대로 _라는 이름을 붙였습니다. 백틱으로 감싼 템플릿 리터럴(template literal) 안의 ${i}는 그 자리에 i의 값을 넣으므로, 원소는 member-0부터 member-99까지입니다. at(-1)은 배열의 마지막 원소를 돌려줍니다.

performance.now()는 밀리초 단위의 정밀한 시각을 돌려주므로, 작업 전후의 차이가 걸린 시간입니다. measure는 넘겨받은 작업을 실행하고, 그 결과와 걸린 시간을 함께 돌려줍니다. 출력은 만든 아이디가 100개이고 처음과 끝이 member-0, member-99라는 것, 그리고 걸린 시간이 0 이상이라는 것만 확인합니다. 걸린 시간 자체는 컴퓨터와 실행할 때마다 달라지므로 출력에 넣지 않았고, 이 배열 만들기가 실제 신청 서비스의 성능을 보여 주는 것도 아닙니다. 실제 성능 요구는 측정 환경, 부하, 여러 번 잰 결과의 통계와 함께 품질 요구와 평가 모델에서 정합니다.

더 깊이: 시간을 잴 때 Date.now()를 쓰지 않는 이유

Date.now()도 밀리초를 돌려주지만, 이것은 시스템의 벽시계(wall clock) 시각이라 걸린 시간을 재는 데는 맞지 않습니다. 웹의 시간 측정을 정한 W3C의 High Resolution Time 명세는 Date.now()의 값이 시계의 오차와 시스템 시계의 조정에 영향을 받으므로, 두 번 부른 값의 차이가 양수일 수도, 음수나 0일 수도 있다고 적습니다. 반면 performance.now()가 쓰는 단조 시계(monotonic clock)는 줄어들지 않아 시스템 시계를 맞추어도 바뀌지 않으므로 측정에 알맞습니다. Node.js 24의 문서에 따르면 performance.now()는 현재 node 프로세스가 시작된 때를 0으로 하는 고해상도 밀리초 값입니다.

더 세밀한 값이 필요하면 process.hrtime.bigint()로 나노초 단위의 정수를 얻습니다. Node.js 문서는 이 시간이 과거의 임의 시점을 기준으로 하고 하루의 시각과 상관이 없어 시계의 흐름 오차(clock drift)를 받지 않는다고 설명하며, 배열을 돌려주던 예전의 process.hrtime()은 레거시로 분류합니다. 브라우저에서는 사정이 조금 다릅니다. 명세는 타이밍 공격을 막으려고 performance.now()의 해상도를 기본으로 100마이크로초나 그보다 거칠게 하도록 정하므로, 아주 짧은 작업은 한 번 재서는 차이가 드러나지 않습니다.

요구와 산출물을 이어 두는 추적 표

요구가 설계, 코드, 테스트, 변경 기록과 이어져 있어서 어느 쪽에서든 다른 쪽을 찾아갈 수 있는 성질을 추적성(traceability)이라고 합니다. 요구마다 번호를 붙여 두면, 요구가 바뀔 때 영향을 받는 코드와 테스트를 번호로 찾을 수 있습니다.

요구맡은 코드확인하는 사례바뀔 때 함께 볼 것
R3 중복을 먼저 거절register의 검사 순서정원이 찬 상태에서 같은 아이디가 다시 신청거절 안내 문구, 수락 조건
R6 마감 뒤 취소cancel, closeSession취소한 뒤에도 상태가 CLOSED상태 모델, 운영 정책
Q1 저장 상태의 일관성restoreSession중복 아이디가 든 저장 자료의 복원 거절저장 구조, 복구 절차

표에는 문서의 버전, 바꾼 이유, 승인한 사람, 확인한 결과도 함께 남깁니다. 승인자를 정한다고 작은 오타까지 회의를 열자는 뜻은 아니지만, 정원이나 취소처럼 사용자에게 보이는 정책은 누가 확정하는지 알아야 합니다.

요구를 바꾸자는 요청을 받으면, 요청을 기록하고, 영향을 분석하고, 할지 말지 결정하고, 코드와 문서를 함께 고치고, 다시 확인하는 순서로 진행합니다. 코드만 먼저 바꾸면 요구 문서와 화면 안내가 서로 다른 버전을 가리키게 됩니다. 여러 파일을 같은 버전으로 묶어 관리하는 방법은 형상과 변경 기준선에서 다룹니다.

정리

  • 비기능 요구는 무엇을 하는지가 아니라 얼마나 잘, 어떤 조건에서 하는지를 정하며, 측정할 대상, 환경, 기준, 확인 방법을 함께 적어야 합니다.
  • 입력 검사는 값이 규칙에 맞는지, 인증은 사용자가 누구인지, 인가는 무엇을 할 수 있는지를 확인하는 서로 다른 일입니다.
  • 시간을 잰 값은 환경과 측정 방법에 따라 달라지므로, 한 번 잰 값을 제품의 성능으로 삼지 않습니다.
  • 추적성은 요구와 설계, 코드, 테스트, 변경 기록을 이어 두는 성질이며, 요구 번호로 영향을 받는 곳을 찾습니다.
  • 요구를 바꿀 때는 기록, 영향 분석, 결정, 코드와 문서의 수정, 다시 확인하는 순서를 따릅니다.

연습 문제

1. 모임이 끝나면 지워 주세요

"모임이 끝나면 참가자 아이디를 지워 주세요"라는 요청은 기능 요구인가요, 비기능 요구인가요? 지우는 동작과 보관에 관한 조건을 나누고, 영향을 받는 산출물 세 가지를 적으세요.

해설 보기

모임이 끝난 뒤 아이디를 지우는 동작은 기능 요구로 적을 수 있고, 얼마 동안 보관하는지와 누가 접근할 수 있는지는 품질과 운영에 관한 제약입니다. 언제를 "끝났다"고 볼지, 주 파일만 지울지 로그와 백업까지 지울지, 지운 뒤 복구 요청과 충돌하지 않는지를 확인해야 합니다. 영향을 받는 산출물로는 데이터 모델, 삭제를 확인하는 테스트, 백업 운영 절차를 들 수 있습니다. 어느 분류에 넣을지보다, 확인할 사람과 방법을 빠뜨리지 않는 것이 중요합니다.

2. 측정할 수 있는 성능 요구

"신청이 빨라야 한다"를 측정할 수 있는 요구로 다시 적어 보세요.

해설 보기

예를 들어 "진행자의 노트북에서 모임 하나에 신청 1000건을 차례로 처리할 때, 한 건의 처리 시간을 1000번 모두 기록하고, 그 가운데 95%가 10 ms 이하여야 한다"처럼 적을 수 있습니다. 측정 기기, 부하의 크기와 모양, 결과를 요약하는 방법인 95번째 백분위, 기준값 10 ms가 모두 들어 있어 만족했는지 판단할 수 있습니다. 평균만 적으면 가끔 아주 느린 신청을 놓치므로, 백분위나 최댓값을 함께 보는 경우가 많습니다. 숫자는 사용자의 불편에서 정해야 하며, 이 예의 10 ms는 설명을 위한 값입니다.

3. 요구를 바꿀 때 함께 볼 곳

R3을 "정원이 차면 이미 신청한 사람에게도 FULL을 돌려준다"로 바꾸려 합니다. 추적 표를 보고 함께 고치거나 다시 확인해야 할 것을 적으세요.

해설 보기

추적 표의 R3 행을 보면 register의 검사 순서를 바꾸어야 하고, 정원이 찬 상태에서 같은 아이디가 다시 신청하는 수락 사례의 기대 결과를 ALREADY_REGISTERED에서 FULL로 고쳐야 합니다. 거절 안내 문구도 이미 신청한 사람에게 "자리가 없습니다"라고 보여도 되는지 진행자와 확인해야 합니다. 거절할 때 명단이 바뀌지 않는다는 조건은 그대로이므로, 그 테스트는 다시 실행해 통과하는지 확인합니다. 표에 R3이 이어져 있지 않았다면 이 가운데 무엇을 빠뜨렸는지 알기 어려웠을 것입니다.

추적 표에 이름이 적혀 있다고 실제로 확인된 것은 아니므로, 확인한 결과와 아직 확인하지 않은 범위를 함께 적어 두세요. 다음 장의 유스케이스와 사용자 흐름에서는 사용자가 목표를 이루기까지 시스템과 주고받는 흐름을 그립니다.