본문 바로가기

Front-end/JavaScript

러닝 자바스크립트 4장 : 제어문

 4.1. 제어문의 기초 

 

순서도는 제어문을 시각적으로 나타내는 방법이다. 이 장에서 사용하는 예제는 19세기 당시 유행했던 주사위 게임 크라운 앤 앵커를 하는 과정에 대한 시뮬레이션을 만들 것이다. 

 

게임은 평평한 면 위에 여섯개의 사각형이 있고, 각 사각형에는 크라운, 앵커, 하트, 클럽, 스페이드, 다이아몬드를 나태는 그림이 있다. 게임을 하는 선원은 그 사격형에 마음대로 돈을 걸 수가 있다. 돈을 건 다음에는 6면체 주사위 세개를 굴린다. 주사위가 사각형 번호에 일치하는 숫자에 멈추면, 선원은 거기 건만큼의 돈을 딴다. 

 

다음은 선원이 돈을 거는 방법과 주사위를 굴렸을 때 따는 돈을 정리한 표이다.

거는 돈 주사위 결과 따는 돈
크라운에 5 펜스 크라운, 크라운, 크라운 15 펜스
크라운에 5 펜스 크라운, 크라운, 앵커 10 펜스
크라운에 5 펜스 크라운, 하트, 스페이드 5 펜스
크라운에 5 펜스 하트, 앵커, 스페이드 0
크라운에 3 펜스, 스페이드에 2 펜스 크라운, 크라운, 크라운 9 펜스
크라운에 3 펜스, 스페이드에 2 펜스 크라운, 스페이드, 앵커 5 펜스
모든 사각형에 1 펜스씩 어떤 주사위가 나오든 3 펜스

이 게임은 제어문의 용도를 쉽게 알 수 있는 예시가 된다. 

 

시작 조건과 끝내는 조건부터 만든다. 이 게임에 사용할 수 있는 자본금은 50펜스이고, 자본금을 2배로 불리거나, 아예 모든 자본금을 잃을 때까지 게임을 계속 하는 것이 기본 조건이다.

 

우리는 게임 과정을 다음과 같은 단계로 나눌 수가 있다.

  • 게임에 돈을 거는 단계
  • 주사위를 굴리는 단계
  • 딴 돈을 가져오는 단계

이에 따라 순서도를 그리면 다음과 같다.

순서도에서 마름모 모양은 예/아니오인 결정이며, 사각형은 행동이다. 시작과 끝은 원으로 나타낸다.

 

우리는 이제 각 단계에 대한 순서도를 각각 그릴 것이다. 순서도의 각 행동은 다음과 같은 동작만 할 수 있도록 제한할 것이다. 

  • 변수 할당: funds = 50, bets = {}, hand = []
  • m 이상 n 이하의 무작위 정수 : rand(1, 6) (보조 함수)
  • 하트, 크라운 등을 결정할 무작위 문자열 randFace() (보조 함수)
  • 객체 프로퍼티 할당: bets["heart"] = 5, bets[randFace()] = 5
  • 배열에 요소 추가: hand.push(randFace())
  • 간단한 사칙 연산: funds -  totalBet, funds + winnings
  • 증가: roll++

순서도의 결정 부분(마름모)은 다음과 같이 제한한다.

  • 숫자 비교: funds > 0, funds < 100
  • 일치 비교: totalBet === 7
  • 논리 연산자: funds > 0 && funds < 100

이 장 전체에서 참 같은 값과 거짓 같은 값이라는 용어를 계속 사용하게 될 텐데, 이것을 지금으로서는 각각 true와 false라고 생각하면 된다.

 

이제 사용할 수 있는 범위를 제한했으니, 그에 맞게 다시 그린 순서도는 다음과 같다.


 4.1.1 while 루프 

 

마침내 순서도에는 첫 번째 제어문인 while 루프가 나타났다. while 루프는 조건을 만족하는 동안 코드를 계속 반복한다. 순서도에서 루프문의 조건은 funds > 1 && funds < 100이다. 

let funds = 50; // 시작 조건

while (funds > 1 && funds < 100) {
    // 돈을 건다.

    // 주사위를 굴린다.

    // 그림을 맞추면 돈을 가져온다.
}

 4.1.2 블록 문 

 

블록 문(block statement)은 엄밀히 말해 제어문이 아니지만 제어문과 함께 쓰이며, 복합문 compound statement이라고도 불린다. 블록 문은 문  여러개를 중괄호로 묶은 것이며 자바스크립트는 이들을 하나의 단위로 취급한다. 제어문 없이 블록 문만 써도 되나 그 자체로는 별 의가 없다.

{
    // 블록문을 시작한다.
    console.log("statement 1");
    console.log("statement 2");
    // 블록문을 끝낸다.
}

console.log("stateent 3");

처음의 두 console.log는 블록 안에 있으며, 이 블록문은 유효한 문법이나 별 의미는 없다.

 

블록문은 제어문과 함께 쓰일 때 유용해진다. 예를 들어 while 문에서 실행하는 루프는 블록 문 전체를 실행하는 루프는 블록문 전체를 실행한 후 조건을 다시 테스트하여 다시 블록문을 실행할 지를 검사한다.

 

다음 예제에서 나타나는 while 루프는 끝이 있다. 블로문을 실행할 때마다 funds는 1씩 늘어나서 언젠가 100이 되어 루프를 벗어나게 될 것이기 때문이다.

let funds = 50;

while (funds > 1 && funds < 100) {
    funds = funds + 2;
    funds = funds - 1;
}

제어문에는 블록을 쓰는 것이 일반적이나, 꼭 그래야만 하는 것은 아니다. 100이 될 때까지 계속 2를 더하기만 한다면 블록 문을 쓰지 않아도 된다.

let funds = 50;
while (funds > 1 && funds < 100)
  funds = funds + 2;

  4.1.3 공백 

 

대부분의 경우, 자바스크립트는 줄바꿈 문자를 포함해서, 추가 공백을 모두 하나의 공백으로 취급한다. 그러나 공백을 잘 활용해서 열을 맞춰야만 개발자가 보기에 두 문 사이에 어떤 연관이 있는 지를 한 눈에 확인하기가 쉽다. 다음 코드의 두 스타일은 모두 같은 작업을 수행하고, 관계가 뚜렷이 보여 자주 사용된다.

while(funds < 1 && funds < 100) funds = funds + 2;
while(funds < 1 && fudns < 100) { funds =  funds + 2; }

프로그래머 중에서는 제어문 바디를 항상 블록 안에 써서 일관성을 유지하고 의미를 명료하게 표현해야 한다고 주장하는 사람도 있기는 하나, 너무 부주의한 들여쓰기를 하는 것도 그렇게 좋은 습관은 아니다.

 

다음 예제는 while 루프의 바디에서 2개 문을 실행하는 것처럼 보인다.

while (funds > 1 && funds < 100)
  funds = funds + 2;
  funds = funds - 1; 

그러나 사실 자바스크립트는 위 예제를 갖고 다음과 같이 두개의 문 중 위의 하나의 문만을 while 루프 바디로 해석한다.

while (funds > 1 && funds < 100)
  funds = funds + 2;

funds = funds - 1;

문 하나만 쓸 때에는 블록을 생략하는 것이 좋지만, 들여쓰기는 항상 의미가 명확하게 드러나도록 작성해야 한다. 문법적으로는 문제가 없지만 왠만하면 다른 사람들에게 환영받지 못할 스타일들은 쓰지 않는 것이 좋다. 예시로는 같은 if문 안에서 블록문과 블록 없는 문을 쓰는 사례 등이 있다.

// 좋지 않은 예시 1
if(funds > 1) {
    console.log("There's money left");
    console.log("That means keep playing");
} else 
    console.log("I'm broke! Time to quit.");
    
// 좋지 않은 예시 2
if(funds > 1) 
    console.log("There's money left");
else {
    console.log("That means keep playing");
    console.log("I'm broke! Time to quit.");
}

 4.1.4 보조 함수 

 

우리는 이 예제에서는 보조 함수 2개가 필요하다. 아직은 이해할 수도 없더라도 일단 다음의 함수를 그대로 가져다 써보자. 일단 첫번째 함수는 m 이상 n 이하의 무작위 정수를 반환한다. 두번째 함수는 여섯가지 도형 중 하나를 무작위 반환한다.

function rand(m, n) {
    return m + Math.floor((n - m + 1)*Math.random());
}

function randface() {
    return ["crown", "anchor", "heart", "spade", "club", "diamond"]
    [rand(0,5)];
}

 4.1.5  if...else문 

 

이제 돈을 거는 행동을 코드로 구현해보자. 이 게임을 실행하는 토마스라는 사람은 랜덤으로 한 판의 게임에서 무작위로 돈을 걸지만, 7 펜스가 우연히 나올 시에만, 토마스가 가진 모든 예산을 하트에 건다. 이 사람의 돈을 거는 행동을 나타내는 순서도는 다음과 같다.

 

중앙의 결정 노드(totalBet === 7)는 if...else 문에 해당한다. while문과 달리 if...else 문 자체에는 반복 기능이 없다. 이 순서도를 자바스크립트로 바꾸면 다음과 같다.

const bets = { crown: 0, anchor: 0, heart: 0 , spade: 0, club: 0, diamond: 0};
let totalBet = rand(1, funds);
if(totalBet === 7) {
    totalBet = funds;
    bets.heart = totalBet;
} else {
    // 그 판에 걸 돈을 분배한다.
}
funds = funds - totalBet;

4.1.6 do...while 루프 

 

우연히 7펜스를 거는 경우 이외에는 무작위로 사각형에 돈을 걸어야 한다. 그런데 이 토마스라는 사람은 오른손에 동전을 왼손으로 동전을 집지만 세지는 않는다. 잡히는 대로 집어 아무 사각형에나 걸기 때문에, 때로는 동전 한 개를 걸 때도 있고, 오른손에 쥔 동전 전부를 걸 때도 있다. 같은 사각형에 여러번 걸 때도 있다. 무작위로 판돈을 나누는 행동을 순서도로 나타내면 다음과 같다.

while 루프와의 차이는 시작하면서 조건을 검사하지 않고, 마지막에 검사한다는 점이다. do...while 루프는 루프 바디를 최소 한번은 실행하려 할 때 사용한다. 그에 반해 while 루프의 조건이 거짓 같은 값으로 시작하면, 루프 바디는 한 번도 실행되지 않는다.

let remaining = totalBet;
do {
    let bet = rand(1, remaining);
    let face = randFace();
    bets[face] = bets[face] + bet;
    remaining = remaining - bet;
} while (remaining > 0);

 4.1.7 for 루프 

 

for 루프는 굉장히 우연하며 while 또는 do...while 루프는 모두 for 루프로 고쳐 쓸 수 있으나, for 루프가 가장 적절한 상황은 어떤 일을 정해진 수만큼 반복하거나, 그 반복적인 일을 지금 몇번째 하는 중인 지 알아야할 때이다. 즉 이번 예제에서 주사위를 세번 굴리는 것도 for문이 가장 적절하다.

for 루프는 세 부분으로 나뉜다. 각 부분은 초기화(roll = 0), 조건(roll < 3), 마지막 표현식(roll++)이다. while 루프도 이처럼 구체화할 수는 있으나, 루프를 제어하는 요소가 한 곳에 있어서 편리하다. 자바스크립트로 바꾸면 다음과 같다.

const hand = [];
for (let roll = 0; roll < 3; roll++) {
    hand.push(randFace());
}

 4.1.8  if 문