본문 바로가기

실행 환경과 작은 프로그램

Node.js를 설치해 독서 모임의 신청을 받는 작은 프로그램을 실행하고, 객체·배열·함수·조건문이 결과를 어떻게 정하는지 따라갑니다. 조건을 검사하는 순서가 사용자에게 보이는 정책을 바꾸는 이유와, 이 첫 프로그램이 아직 해결하지 못한 문제를 짚습니다.

독서 모임 진행자는 종이 명단에 두 자리까지만 신청을 받습니다. 그런데 같은 사람이 두 번 신청하면 자리 하나가 낭비되고, 정원이 찼는데도 이름을 더 적으면 누가 참가하는지 헷갈립니다. 이 교재는 이 작은 신청 규칙을 프로그램으로 옮기는 데서 시작해, 요구를 정하고, 설계하고, 테스트하고, 바꾸어 가는 소프트웨어 개발의 과정을 하나의 예로 따라갑니다. 이 글에서는 프로그램을 실행할 준비를 하고, 첫 프로그램이 어떤 조건으로 결과를 정하는지 살펴봅니다.

실행할 준비

예제는 JavaScript로 작성하고 Node.js로 실행합니다. Node.js는 웹 브라우저 밖에서 JavaScript를 실행하는 프로그램이며, 이 교재는 Node.js 24 이상에 들어 있는 내장 모듈만 쓰고 외부 패키지는 설치하지 않습니다. Node.js 공식 다운로드 페이지에서 운영체제에 맞는 설치 파일을 받아 설치한 뒤, 새 터미널을 열고 버전을 확인합니다.

node --version

v24.21.0처럼 버전이 나오면 준비가 끝난 것입니다. node를 찾을 수 없다는 오류가 나오면 설치가 끝났는지 확인하고 터미널을 새로 엽니다. 설치하는 동안 이미 열려 있던 터미널은 새 설정을 모르기 때문입니다.

작업할 곳에 reading-lab 폴더를 만들고, 텍스트 편집기로 그 안에 start.mjs 파일을 만듭니다. .mjs는 JavaScript 모듈 파일의 확장자인데, 편집기에 따라 start.mjs.txt로 저장되는 경우가 있으므로 실제 파일 이름을 확인해야 합니다. 터미널에서 reading-lab 폴더로 이동한 뒤 node start.mjs로 실행합니다.

값과 규칙을 함께 읽기

다음 내용을 start.mjs에 그대로 저장합니다. 정원이 2인 모임 하나를 meeting에 두고, 신청 한 번을 처리하는 함수 join이 이미 신청한 사람인지와 정원이 찼는지를 차례로 확인합니다. 아래쪽 반복은 min, hana, min, jun의 신청 네 번을 순서대로 넣으므로, 실행 결과에서는 신청마다 어떤 결과를 받는지와 마지막에 남은 명단을 보면 됩니다.

const meeting = { title: "함께 읽는 첫 책", capacity: 2, participants: [] };

function join(memberId) {
  if (meeting.participants.includes(memberId)) return "ALREADY_REGISTERED";
  if (meeting.participants.length >= meeting.capacity) return "FULL";
  meeting.participants.push(memberId);
  return "REGISTERED";
}

for (const memberId of ["min", "hana", "min", "jun"]) {
  console.log(memberId, join(memberId));
}
console.log(meeting.participants);
min REGISTERED
hana REGISTERED
min ALREADY_REGISTERED
jun FULL
[ 'min', 'hana' ]

meeting은 모임에 관한 값들을 이름과 함께 묶은 객체(object)입니다. capacity는 정원이고, participants는 참가자의 아이디를 차례로 담는 배열(array)입니다. const는 meeting이라는 이름에 다른 값을 다시 대입하지 못하게 할 뿐, 그 안의 배열을 바꾸는 것까지 막지는 않으므로, push로 배열 끝에 아이디를 더할 수 있습니다. 객체와 배열을 만들고 읽는 방법은 객체와 배열 기초에서, const로 선언한 객체의 내용을 바꿀 수 있는 이유는 let, const와 블록 스코프에서 더 자세히 다룹니다.

함수 join은 신청 한 번을 처리합니다. 매개변수 memberId는 그 호출의 입력이고, return은 결과를 돌려주면서 그 호출을 곧바로 끝냅니다. includes는 배열에 그 아이디가 이미 있는지, length는 지금 몇 명인지를 알려 주며, >=는 정원과 같거나 많을 때 모두 거절하게 합니다. 마지막의 for...of 반복은 신청 목록의 아이디를 하나씩 join에 넘기고 결과를 출력합니다. 배열을 출력한 마지막 줄의 따옴표나 띄어쓰기 모양은 Node.js 버전에 따라 조금 다를 수 있지만, 값과 순서는 같습니다.

결과에서 눈여겨볼 것은 셋째 줄입니다. min이 두 번째로 신청할 때는 이미 정원 두 자리가 다 찼지만, 결과는 FULL이 아니라 ALREADY_REGISTERED입니다. 중복을 확인하는 조건이 먼저 참이 되어 그 자리에서 반환했기 때문에, 정원을 확인하는 조건까지 가지 않았습니다.

입력과 현재 상태가 다음 결과를 결정한다

이미 만석인 모임에 min이 다시 신청한다. 거절 과정에서 명단을 변경하지 않는다. 각 단계의 값과 전환 이유를 비교합니다.

SOFTWARE ENGINEERING · READING CLUB

입력과 현재 상태가 다음 결과를 결정한다

정원은 같아도 기존 참가자와 새 참가자에게 적용되는 조건이 다릅니다.

입력과 현재 상태가 다음 결과를 결정한다입력 + 현재 상태: 입력 min · 정원 2, 참가자 min, hana → 먼저 만족한 규칙: 이미 참가했는가? → 참, 이번 호출의 정원 검사 생략 → 결과 + 다음 상태: ALREADY_REGISTERED, 참가자 min, hana 유지입력 + 현재 상태입력 min · 정원 2참가자 min, hana검사먼저 만족한 규칙이미 참가했는가? → 참이번 호출의 정원 검사 생략반환결과 + 다음 상태ALREADY_REGISTERED참가자 min, hana 유지
입력과 현재 상태가 다음 결과를 결정한다입력 + 현재 상태: 입력 min · 정원 2, 참가자 min, hana → 먼저 만족한 규칙: 이미 참가했는가? → 참, 이번 호출의 정원 검사 생략 → 결과 + 다음 상태: ALREADY_REGISTERED, 참가자 min, hana 유지
입력 + 현재 상태입력 min · 정원 2참가자 min, hana
검사검사입력 + 현재 상태 · 입력 min · 정원 2 · 참가자 min, hana → 먼저 만족한 규칙 · 이미 참가했는가? → 참 · 이번 호출의 정원 검사 생략
먼저 만족한 규칙이미 참가했는가? → 참이번 호출의 정원 검사 생략
반환반환먼저 만족한 규칙 · 이미 참가했는가? → 참 · 이번 호출의 정원 검사 생략 → 결과 + 다음 상태 · ALREADY_REGISTERED · 참가자 min, hana 유지
결과 + 다음 상태ALREADY_REGISTERED참가자 min, hana 유지

실패 결과와 명단 보존을 함께 읽습니다. 검사 순서를 바꾸면 상태는 같아도 사용자에게 안내하는 이유가 달라집니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 재신청 입력부터 결과와 상태까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
재신청 입력

이미 만석인 모임에 min이 다시 신청한다.

  • 신청·현재 상태 1: capacity2
  • 신청·현재 상태 2: participants [min,hana]
  • 신청·현재 상태 3: memberId min
  • 호출 시작
중복 판정

첫 조건이 참이라 바로 반환한다.

  • 신청·현재 상태 1: includes(min)=true
  • 신청·현재 상태 2: 정원 검사 생략
  • ALREADY_REGISTERED 선택
결과와 상태

거절 과정에서 명단을 변경하지 않는다.

  • 신청·현재 상태 1: ALREADY_REGISTERED
  • 신청·현재 상태 2: participants [min,hana]
  • 명단 유지하며 호출 종료

입력과 현재 상태가 다음 결과를 결정한다

이미 만석인 모임에 min이 다시 신청한다. 거절 과정에서 명단을 변경하지 않는다. 각 단계의 값과 전환 이유를 비교합니다.

SOFTWARE ENGINEERING · READING CLUB

입력과 현재 상태가 다음 결과를 결정한다

정원은 같아도 기존 참가자와 새 참가자에게 적용되는 조건이 다릅니다.

입력과 현재 상태가 다음 결과를 결정한다입력 + 현재 상태: 입력 min · 정원 2, 참가자 min, hana → 먼저 만족한 규칙: 이미 참가했는가? → 참, 이번 호출의 정원 검사 생략 → 결과 + 다음 상태: ALREADY_REGISTERED, 참가자 min, hana 유지입력 + 현재 상태입력 min · 정원 2참가자 min, hana검사먼저 만족한 규칙이미 참가했는가? → 참이번 호출의 정원 검사 생략반환결과 + 다음 상태ALREADY_REGISTERED참가자 min, hana 유지
입력과 현재 상태가 다음 결과를 결정한다입력 + 현재 상태: 입력 min · 정원 2, 참가자 min, hana → 먼저 만족한 규칙: 이미 참가했는가? → 참, 이번 호출의 정원 검사 생략 → 결과 + 다음 상태: ALREADY_REGISTERED, 참가자 min, hana 유지
입력 + 현재 상태입력 min · 정원 2참가자 min, hana
검사검사입력 + 현재 상태 · 입력 min · 정원 2 · 참가자 min, hana → 먼저 만족한 규칙 · 이미 참가했는가? → 참 · 이번 호출의 정원 검사 생략
먼저 만족한 규칙이미 참가했는가? → 참이번 호출의 정원 검사 생략
반환반환먼저 만족한 규칙 · 이미 참가했는가? → 참 · 이번 호출의 정원 검사 생략 → 결과 + 다음 상태 · ALREADY_REGISTERED · 참가자 min, hana 유지
결과 + 다음 상태ALREADY_REGISTERED참가자 min, hana 유지

실패 결과와 명단 보존을 함께 읽습니다. 검사 순서를 바꾸면 상태는 같아도 사용자에게 안내하는 이유가 달라집니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 재신청 입력부터 결과와 상태까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
재신청 입력

이미 만석인 모임에 min이 다시 신청한다.

  • 신청·현재 상태 1: capacity2
  • 신청·현재 상태 2: participants [min,hana]
  • 신청·현재 상태 3: memberId min
  • 호출 시작
중복 판정

첫 조건이 참이라 바로 반환한다.

  • 신청·현재 상태 1: includes(min)=true
  • 신청·현재 상태 2: 정원 검사 생략
  • ALREADY_REGISTERED 선택
결과와 상태

거절 과정에서 명단을 변경하지 않는다.

  • 신청·현재 상태 1: ALREADY_REGISTERED
  • 신청·현재 상태 2: participants [min,hana]
  • 명단 유지하며 호출 종료

그림처럼 결과는 입력인 아이디와 지금의 명단 상태가 함께 정하고, 여러 조건 가운데 먼저 참이 된 것이 결과를 정합니다. 조건의 순서는 코드의 세부 사항처럼 보이지만, 이미 신청한 사람에게 "이미 신청했습니다"라고 알릴지 "자리가 없습니다"라고 알릴지를 정하므로 사용자가 보는 정책이 됩니다. 그래서 조건의 순서를 바꾸는 것은 코드를 다듬는 일이 아니라 정책을 바꾸는 일로 보고 검토해야 합니다.

첫 실험과 이 프로그램의 한계

capacity를 3으로 바꾸고 신청 목록 끝에 "ara"를 더해 다시 실행해 보세요. 실행하기 전에 결과를 먼저 적어 보는 것이 중요합니다. 예상을 먼저 적어 두어야, 실행 결과가 우연히 그렇게 나온 것인지 규칙대로 나온 것인지 구별할 수 있습니다. 이번에는 jun까지 등록되고 ara가 FULL을 받아, 명단은 min, hana, jun이 됩니다.

이 첫 프로그램에는 아직 해결하지 못한 문제가 많습니다. 빈 문자열도 아이디로 받아들이고, 프로그램을 끝내면 명단이 사라지며, 여러 사람이 동시에 신청하는 서버도 아닙니다. 이 문제들을 한꺼번에 고치지 않고, 사용자·문제·범위와 기능 요구와 수락 조건에서 어떤 입력을 받고 무엇을 거절할지 먼저 정한 뒤 하나씩 넓혀 갑니다.

정리

  • 예제는 Node.js 24 이상의 내장 모듈만으로 실행하며, 파일 이름과 확장자, 터미널의 현재 폴더를 먼저 확인합니다.
  • 객체는 관련된 값을 이름과 함께 묶고, 배열은 값을 순서대로 담으며, const는 이름의 재대입만 막고 안의 내용 변경은 막지 않습니다.
  • 함수의 return은 결과를 돌려주며 그 호출을 끝내므로, 여러 조건 가운데 먼저 참이 된 것이 결과를 정합니다.
  • 조건을 검사하는 순서는 사용자에게 보이는 정책이므로, 순서를 바꾸는 일은 정책을 바꾸는 일로 검토합니다.
  • 실행 전에 결과를 예상해 적어 두면, 우연히 맞은 결과와 규칙대로 나온 결과를 구별할 수 있습니다.

연습 문제

1. 조건의 순서

정원이 1이고 신청 순서가 min, min, hana라면 세 결과와 최종 명단은 무엇인가요? 두 if의 순서를 바꾸면 어느 결과가 달라지나요?

해설 보기

호출할 때마다 그 직전의 명단을 적어 가며 따라가면 됩니다. 첫 min은 명단이 비어 있으므로 REGISTERED, 둘째 min은 이미 명단에 있으므로 ALREADY_REGISTERED, hana는 정원 1이 찼으므로 FULL이고, 최종 명단은 min 한 명입니다. 정원 검사를 먼저 하면 둘째 min도 FULL을 받습니다. 최종 명단은 같지만 사용자가 받는 안내가 달라지므로, 이 변경은 정책의 변경입니다.

2. const로 막을 수 있는 것

const meeting = { participants: [] }로 만든 뒤 meeting.participants.push("min")과 meeting = {}를 각각 실행하면 어떻게 되나요?

해설 보기

push는 meeting이 가리키는 객체 안의 배열을 바꾸는 것이므로 오류 없이 min이 더해집니다. meeting = {}은 meeting이라는 이름에 다른 객체를 다시 대입하려는 것이라 TypeError: Assignment to constant variable. 오류가 납니다. 그래서 "const로 만들었으니 참가자 명단은 바뀌지 않는다"는 생각은 틀립니다. 내용까지 바꾸지 못하게 하려면 다른 방법이 필요하며, 이 교재의 뒤쪽 core.mjs는 Object.freeze로 상태를 얼려 둡니다.

3. 결과 미리 적기

정원이 3이고 신청 목록이 min, hana, min, jun, ara일 때, 실행하기 전에 다섯 결과와 최종 명단을 적어 보세요.

해설 보기

min과 hana는 REGISTERED, 둘째 min은 ALREADY_REGISTERED, jun은 아직 두 명이라 REGISTERED, ara는 세 명이 찼으므로 FULL입니다. 최종 명단은 min, hana, jun입니다. 정원을 바꾼 프로그램을 실제로 실행하면 이 예상과 같은 다섯 줄이 나옵니다. 예상과 결과가 다르다면, 어느 호출에서 명단이 예상과 달라졌는지 찾아 그 호출의 조건을 다시 따라가 보세요.

결과가 이상하면 호출마다 입력, 그 직전의 명단, 처음으로 참이 된 조건을 한 줄씩 적어 보세요. 다음 글 소프트웨어와 개발 산출물에서는 이 작은 코드 말고도 모임을 운영하는 데 필요한 것들을 살펴봅니다.