[초보자들] S69 4번째 – Conditional Statement(if문, switch문)

4번째 스터디가 진행되었습니다. 오늘 배운 내용은 다들 아실(?) Conditional Statement!
보통은 조건문이라고들 하는데요, 정확한 번역은 아닙니다. Conditional Statement는 조건을 따지기보단 주어진 값의 Condition 즉, 상태를 판단하는 문이기 때문이죠. 그래서 판단문, 상태문 정도로 표현할 수 있겠습니다.(이 글에서는 판단문이라고 할게요~!)

판단문이라하면 5분만에 배우고 몇페이지로 슝~ 건너뛰는 그런 기초 of 기초, 쉬움 of 쉬움이라고 생각하시겠지만, 이번 스터디 시간에는 if문과 switch문만으로 스터디 2시간을 꽉꽉 채워도 모자랐습니다. 그만큼 깊고 심오한 내용이 있었던 판단문!

오늘의 후기는 평소 제가 가볍고 단편적으로 봤던 판단문의 진짜 의미와 if문, switch문을 올바르게 쓰는 방법에 대해 중점적으로 나눠보겠습니다.

흐름을 제어한다! Flow와 제어문

우리는 스터디 맨 처음시간에 문과 식에 대해서 배웠습니다.
판단문은 제어문(Control Statement)의 종류이고, 제어문은 문의 종류 중 하나입니다.
그럼 도대체 뭘 제어하는가?!? 바로 Flow를 제어합니다.

그럼 또 Flow는 무엇인가?

잠깐 컴퓨터가 동작하는 방식을 생각해 봅시다.
지난 후기에서도 말했지만, 컴퓨터라는 것은 결국 “입력된 데이터를 가지고 어떤 처리를 해서 결과를 반환해주는 기계”입니다.
컴퓨터는 입력되거나 생성된 데이터를 기억하고 계산할 수 있는 장치로 구성되어 있습니다. 그래서 데이터를 입력 받으면 그것을 어떻게 처리하겠다는 내용과 함께 메모리에 차례대로 얹히고, 메모리에 얹혀진 내용을 하나씩 연산 장치가 계산하고 결과를 반환 받기를 반복하는데, 이것을 컴퓨터의 처리 흐름(Flow)이라고 합니다.

우리는 이 Flow를 코드로 만들어 낼 수 있습니다.
보통의 프로그램은 위에서 아래로 ‘순차적’인 흐름대로 진행되는데, 가끔은 그  순차적인 흐름을 바꿔야할 때가 있습니다. 흐름을 제어할 때가 필요한 것이죠. 그 때 제어문을 사용합니다.

제어문은 크게 판단문과 반복문으로 나눕니다.
판단문은 값을 기준으로 분기점을 만들고, 이것을 기준으로 어떤 흐름으로 갈 지 정할 수 있는 문입니다.
반복문은 특정 부분이 계속해서 반복되도록 흐름을 반복하는 문이구요.

JavaScript에서는 판단문을 만들기 위해 if, switch 키워드를 이용합니다.

각각을 자세히 살펴봅시다.

if문 제대로 쓰기

if문의 형식은 다음과 같습니다.

if(식) 문 

중요한 것은 판단을 내리는 부분에는 값 또는 한가지 값으로 수렴하는 식이 와야한다는 것입니다. 왜냐하면 값으로 분기점을 판단하기 때문이죠.

문이 올 자리에는 말 그대로 문이 올 수 있습니다.
아시다시피 문에는 단문, 중문, 식문, 빈문 등 이 있는데 이 모든게 문 자리에 올 수 있습니다. (문과 식 다 기억하시죠?!?)

많은 책에서 문의 자리를 설명할 때, 문장이 1개 오면 {}가 없어도 되지만 2개 이상이면 {}으로 묶어 줘야 한다고들 합니다.
예전에는 이 말을 들으면 ‘아 그렇구나. 그런 법칙이 있군! ‘싶었는데, 이제는 ‘아, 문의 자리니까 단문, 중문 등이 올 수 있네. 문이 1개면 단문이니까 {}가 없는거고, 중문은 원래 {}를 가지고 있으니까 {}가 있는것이 당연한거군!’ 이렇게 보입니다. 앞에서 배워둔 내용을 잘 배워놓으니 예전엔 ‘이거 왜이래?-_-?하면서 여러가지 경우의 수처럼 외웠던 것들이 다르게 보이네요?

if else문도 많이 사용하는데요, 형식은 다음과 같습니다.

if(식) 문
else 문

if문과 if else문, 언뜻 비슷해 보이지만 둘이 의미하는 것은 완전히 다릅니다.

if문은 (식)이 거짓이 아니면 문을 실행한다는 뜻이고(=실행을 안하는 경우도 있다는 것)
if else문은 (식)이 거짓이 아니라면 else 앞의 문을 실행하고 거짓이면 else 뒤의 문이 실행된다는 뜻입니다.
즉, if문은 식(=값)에 따라 문의 내용이 실행여부가 ‘선택적(Optional)’이지만, if else는 문 중에 하나는 ‘무조건적(Mandatory)’으로 실행이 되어야 한다는 뜻입니다.

그림으로 표현하자면 다음과 같은 느낌일까요?

그래서 Optional인지 Mandatory인지 의도에 맞춰 if문, if else문을 사용해야합니다.

하지만 웬만하면 if를 사용하지 않은 것을 추천합니다. 왜냐하면 ‘옵션’이라는 것은 경우의 수를 만들어 내는데 경우의 수가 많아질 수록 오류 발생의 가능성도 높아지니까요. 예를 들면 다음과 같이..?

var weather;
var temperature = 18;
if(temperature 〈 5) weather = 'freezing';
if(temperature 〈 12) weather = 'chilly';
if(temperature 〉 18) weather = 'good';
if(temperature 〉 22) weather = 'hot';
console.log(weather);

온도에 따라 날씨의 상태를 if로 섬세하게 나눈 것 같지만… 결과는 undefined 입니다. 전부 if문으로 판단식을 전개하느라 많은 경우의 수가 생겼고 경우의 수가 커버하지 못한 예외가 발생한 것이죠.
이처럼 경우의 수가 많아지게 만드는 if문 패턴은 되도록 지양하도록 합니다.

그래도 여기까진 쉬운 것 같죠..?
그래서 저도 되게 가볍게 생각하고 훅훅 넘어갔습니다. 하지만 과연…? else if를 살펴봅시다.

여러가지 상태를 판단 할 때 쓰는 if else if else문의 형식은 다음과 같습니다.

if(식) 문 
else if (식) 문 
else 문

식에 따라 각각의 문이 실행되고, 그 식에 해당하지 않는 것은 else 이하 문의 내용이 실행될 것 같죠?
네. 맞습니다. 그런데 else if를 제대로 이해하지 않으면 실제 코드가 예상과 달리 작동할 수 있습니다.
다음 예를 봅시다.

if(a > 8) console.log(a+ '는8보다 큼!');
else if (a > 10) console.log(a + '는 10보다 큼');
else console.log(a + '는 아무것도 아니야');

다음과 같은 판단문에서 a가 11이라면 어떤 결과가 나올까요?
코드만 봤을 때 경우의 수는 a > 8, a > 10, 기타 경우 3개인 것 같고, a가 포함되는 가장 큰 영역은 a > 10이니까 ‘a는 10보다 큼’ 이런게 나올 것 같지 않으신가요..?

하지만 결과는 ‘a는 8보다 큼’입니다.

왜냐하면 else if는 사실

if (식) 문 
else {
  if (식) 문
  else 문
}

이니까요.
즉, 먼저 앞의 if else를 처리하고 그 다음에 else 이하 즉, 그 다음의 if else를 처리하는 방식으로 동작합니다.
따라서 위의 예에서 a가 첫 if 식에서 true가 되므로 (a > 8)의 경우의 문이 실행되는 것입니다.

이처럼 if else문은 식에 따라 일정한 영역이 나뉘게 되는데, else if처럼 여러 경우의 수가 생기면 미처 고려하지 못한 부분이 생길 수도 있습니다. (예를 들면 우리 교재의 학점 예제(p96)에서 4.5 이상의 점수에 대해 커버하지 못한 것 처럼…^_ㅠ) 따라서, else if에서는 경우의 수를 커버하지 못하거나 꼬이는 영역이 생기지 않도록 하는 것이 매우 중요합니다.

이를 위해 할 수 있는 것은…
1. if, else와 else if의 식이 각각 명확하게 “===”으로 나눠지는 경우에만 사용하거나,
2. 그런 경우를 아예 만들지 않거나(=쓰지 않거나^0^)
3. 어쩔 수 없이 써야하는 경우에는 다음의 예처럼 else if 보다는 if else문 안에 if else문을 쓰는 “중첩 if 문”을 쓰는 방법
들이 있습니다.

[예] else if를 중첩 if문으로 변형한 예

var apple = 10;
var method;
//-------else if를 이용----------
if(apple > 20){
  method = '트럭';
}else if(apple > 10){
  method = '카트';
}else{
  method = '내 손';
}
console.log('사과 ' + apple + '개를 옮기려면 ' + method +'로 옮겨야겠어');

//-------중첩 if문으로 변경-------
if(apple > 20){
  method = '트럭';
}else{ 
  // 우선 20인 경우 vs 아닌 경우로 확실하게 보임
  if(apple  > 10){
    method = '카트';
  }else{
  method = '내 손';
  }
}
console.log('사과 ' + apple + '개를 옮기려면 ' + method +'로 옮겨야 겠어');

switch문 분해하기

switch문도 판단문의 종류로 식에 따라 여러가지 경우로 나눠 각각의 경우에 해당하는 명령을 실행하는 문입니다.
if else문 또는 if else if else문과 비슷하다는 느낌을 받을 수 있지만, switch의 문법은 좀 독특합니다.

형식

switch문의 형식은 다음과 같습니다.

switch(식){
  case 식: 문 break;
  //여러개의 식에 해당하는건 이렇게 break 없이 case:로 나열
  case 식 :
  case 식: 문 break;
  default: 문 break;
}

switch문은 if문과 달리 {}를 꼭 써줘야 합니다.
그리고 {} 안에 적어도 1개의 case 또는 default를 설정해줘야 합니다. (if문은 {}안에 아무것도 안써도 빈문으로 인정되어 괜찮던 것과는 다르죠..?)

한편, 식은 ‘case’와 ‘:’ 사이에 기술하는데 이를 기대값(Expected Value)라고 합니다.
실제 값과 기대값이 일치하면 해당 case의 문이 실행되고, 어떤 case에도 해당되지 않으면 default에 써진 문이 실행됩니다.

중요한 건 각각의 식이 끝났을 때마다 끝났다는 것을 알려주기 위해 break 키워드를 꼭 써주어야 한다는 점입니다.
왜냐하면 break를 쓰지 않으면 다음 case에 써진 문까지 실행되기 때문이죠!

신기하죠?
처음에 저는 그냥 이게 ‘switch 문에서 정해둔 법칙인가보다…’라고 생각했습니다. 그런데 알고보니 나름의 깊은 이유가 있었던 것..!!
바로 Label문의 원리가 숨어있었습니다!

Label문

Label문은 말 그대로 특정한 부분을 인식하기 위해 붙여진 이름을 말합니다.
예전에 프로그래밍 언어에서 Flow를 특정한 곳으로 점프시킬 때 이동시킬 곳을 인식시키기 위해 이름을 붙인 것에서 비롯되었다고 합니다.
label문은 “label이름:” 이라는 형식으로 만드는데, JavaScript에서 큰 기능이 없기 때문에 아무곳에나 자유롭게 쓸 수 있습니다.

제가 JavaScript에서 label을 처음 본 것은 반복문을 배울 때였는데, 반복 구간을 인식하기 위해 중괄호{}를 쓴 후 label 이름을 붙인 뒤 break 레이블명을 하면 해당 레이블의 반복구간을 해제할 수 있었습니다.

[빵굽는 시간에 따라 손님과 제빵사의 대화를 알려주는 프로그램]

//*label문을 사용하지 않은 경우
var i = 1, j  = 60;
while(i 〈 3){
  while(j 〉 0){
    console.log('제빵사 : ' + j + '분뒤에 ' + i + '개의 빵을 가져가실 수 있습니다.');
    if(j == 30) {
      console.log('손님'+ i +' : 저런 바빠서 먼저 가봐야겠어요.');
      break;
    }else{
      console.log('손님'+ i +': 네 기다리죠.');
    }
    j -= 10;
  }
  console.log('제빵사 : 저런 거의 완성되었는데.... 빵 완성!');
  i++;
  j = 60;
}

//실행결과
제빵사 : 60분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1: 네 기다리죠.
제빵사 : 50분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1: 네 기다리죠.
제빵사 : 40분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1: 네 기다리죠.
제빵사 : 30분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1 : 저런 바빠서 먼저 가봐야겠어요.
제빵사 : 저런 거의 완성되었는데.... 빵 완성!
제빵사 : 60분뒤에 2개의 빵을 가져가실 수 있습니다.
손님2: 네 기다리죠.
제빵사 : 50분뒤에 2개의 빵을 가져가실 수 있습니다.
손님2: 네 기다리죠.
제빵사 : 40분뒤에 2개의 빵을 가져가실 수 있습니다.
손님2: 네 기다리죠.
제빵사 : 30분뒤에 2개의 빵을 가져가실 수 있습니다.
손님2 : 저런 바빠서 먼저 가봐야겠어요.
제빵사 : 저런 거의 완성되었는데.... 빵 완성!

하지만 label문을 이용하면 바깥쪽 반복 구간까지 break 할 수 있게 됩니다.

//label을 이용한 반복문
var i = 1, j  = 60;
outer: //label문 지정
while(i 〈 3){
  while(j 〉 0){
    console.log('제빵사 : ' + j + '분뒤에 ' + i + '개의 빵을 가져가실 수 있습니다.');
    if(j == 30) {
      console.log('손님'+ i +' : 저런 바빠서 먼저 가봐야겠어요.');
      break outer; //break에 outer label 명시
    }else{
      console.log('손님'+ i +': 네 기다리죠.');
    }
    j -= 10;
  }
  console.log('제빵사 : 저런 거의 완성되었는데.... 빵 완성!');
  i++;
  j = 60;
}
//결과 (이제 제빵사는 손님을 1명밖에 받지 못하게 되죠^_ㅠ)
제빵사 : 60분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1: 네 기다리죠.
제빵사 : 50분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1: 네 기다리죠.
제빵사 : 40분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1: 네 기다리죠.
제빵사 : 30분뒤에 1개의 빵을 가져가실 수 있습니다.
손님1 : 저런 바빠서 먼저 가봐야겠어요.

이렇게 코드의 특정한 부분을 인식하게 하기 위해 label문을 사용할 수 있습니다.

switch의 label문

switch문에 있는 case, default도 다 label문입니다.
그래서 ‘인식표’처럼의 기능만 하기 때문에 case와 case 사이에 블록을 형성한다든지 변수처럼 따로 값을 저장한다든지의 일은 못합니다. 그래서 case 부분의 끝남을 알려주기 위해 break를 쓰는데 이것을 쓰지 않으면 그 다음에 case가 있어도 문이 계속 실행됩니다.(이를 pass through라고 합니다.)

일반 label문과 달리 switch의 label문의 특이한 점은…
case 뒤에는 특정 값이 나올 수 있고, 이 값이 실제 값과 일치하게 되면 이 곳으로 점프되어 label문 이후의 내용이 실행됩니다. 또 case는 여러번 쓸 수 있구요.
반면 default 같은 경우는 뒤에 아무것도 나올 수 없으며 switch{} 안에서 1번밖에 나올 수 밖에 없습니다.

한편, 많은 책들에서는 default 뒤에 break를 써줄 필요가 없다고들 하는데요 (심지어는 default가 없어도 된다고…) default를 맨 마지막에 쓰기 때문에 안써줘도 된다고 하는 것입니다. (위에서 말했다시피 default, case 안에 마치 {}가 생겨서 자동으로 break가 되는게 아닙니다.) 다음의 예처럼 default 뒤에 break를 안써주면 어떻게 될까요?

var a = 4;
switch(a){
	case 1: console.log('1입니다');break;
	case 2: console.log('2입니다');break;
	default: console.log('뭡니까?');
	case 3: console.log('3입니다');break;
}

답은 ‘뭡니까?”3입니다’가 출력됩니다.
왜냐하면 a가 case의 어떤 것과도 맞지 않으니 흐름이 default로 이동해서 해당하는 문인 console.log(‘뭡니까?’)를 실행했는데, break가 없어 다음 case 3에 해당하는 문인 console.log(‘3입니다’);까지 실행이 되었기 때문이죠.

따라서 default 식에 해당하는 문도 써주도록 하고, default는 맨 마지막에 break와 함께 쓰도록 합니다.
코드는 무엇이든 예외가 없고 명확한 것이 최고이니까요!

switch vs if

그렇다면 언제 switch문을 쓰고 if문을 쓸까요?
경우의 수가 여러개일 때는 switch문을 쓰는 것을 추천합니다. 식에 쓰는 값을 switch가 훨씬 덜 쓸 수 있어 키보드도 덜 치고, 식에 대한 상태 관리도 덜 해도 되기 때문…!

var kim = 0, lee = 0, etc = 0;
var lastname = 'kim';

//if문으로 쓰면 'lastname ==' 이 부분을 매번 써줘야됨. 또 변수의 값이 어떻게 변하는지 계속 상태 관찰을 해야함.
if(lastname == 'kim') kim++;
else if(lastname == 'lee') lee++;
else etc++;

//lastname은 switch문에만 lastname 써두면 됨.
switch(lastname){
  case kim: kim++; break;
  case lee: lee++; break;
  default: etc++;
}
console.log('kim씨인 사람은? ' + kim +'명 입니다.');

판단문의 식에서 변수의 의미

Static vs Runtime

컴퓨터에서 프로그램이라는 것은 하드디스크나 다른 보조기억 장치에 저장되어있던 코드들이 메모리에 불러와진 후, 메모리에 나열된 명령의 나열들이 차근차근 처리되어가는 과정을 말합니다. 여기서 중요한 것은 실행여부, 즉 메모리에 불러와졌는가(=적재되었는가) 그렇지 않은가인데 실행 이전의 코드를 ‘static, 정적인 코드’라고 하고, 메모리에 적재된 상태를 runtime, 동적인 코드라고 합니다.
둘의 가장 큰 차이점은 메모리에 실제 값이 있는가로, static은 메모리에 아직 불러와져있지 않기 때문에 실제 값을 갖지 않는 반면, runtime은 메모리에 값을 실제 가지고 있습니다.

판단문에서는 판단을 할 때, static이 아닌 runtime의 값을 판단합니다.
이 때 값을 판단할 수 있는 식을 작성하는데 식 부분에 변수를 사용할 수 있습니다.

변수는 ‘변할 수 있는 값을 담아둔 그릇’을 의미합니다. (실질적으로는 값을 담아둘 수 있는 메모리 주소의 별명.. 이것도 반복의 연속이네요.) 핵심은 ‘변할 수 있다’입니다! 따라서, 판단문의 식에 변수를 쓴다는 것은 코드를 실행할 때 변수의 값이 정해지는 경우(=실행할 때 마다 달라지는 값)에 쓴다는 말입니다

다음 코드를 봅시다.

var a = 1;
var b = 2;
var sum = a + b;

위와 같은 코드가 바로 정적 코드입니다. 즉, 실행되지 않은 상태의 코드죠. 우리는 코드를 보고 a, b, sum에 어떤 값이 들어갈 지 예상할 수는 있습니다. 하지만 실행이 되지 않았기에 a, b, sum이 실제로는 값을 가지지 않고 있죠.

다음은 어떻게 될까요?

var a = 1;
var b = prompt('좋아하는 숫자를 입력해주세요');
var sum = a + parseInt(b);

promt함수는 사용자에게 어떤 값을 입력받을 수 있는 창을 출력하는 함수입니다. 그리고 사용자가 그 창에 값을 입력하면 그 값이 변수 b로 할당되구요.
저 코드는 실제로 실행이 되어 사용자가 입력할 때까지는 b에 어떤 값이 들어올 지 모릅니다.

이처럼 런타임시 결정되는 값을 ‘input’이라고 합니다.
사용자가 입력하는 값은 user input, 서버 시간 처럼 특정 시스템에서 제공하는 값을 system input이라고 하는데, 이처럼 변수는 input을 값으로 가질 때 식에 사용하는 의미가 있습니다.

즉, 다음과 같은 예는 변수를 식에 쓰는 이유가 크게 없다는 것입니다.

[예시] 나의 키가 평균키인지 아닌지 알아보는 프로그램

var myHeight = 154;
var avgHeight = 162;
if(myHeight 〈 avgHeight) {
	console.log('여자 평균키보다 작으시네요.');
} else {
	console.log('여자 평균 키는 되시네요.');
}

이미 나의 키(myHeight)가 154로 고정되어 변수를 쓰는 의미가 없습니다.
다음과 같이 되어야 의미가 있는 것이죠.

var myHeight = parseInt(prompt('당신의 키를 입력해주세요(cm단위)'));
var avgHeight = 162;
if(myHeight 〈 avgHeight) {
	console.log('여자 평균키보다 작으시네요.');
} else {
	console.log('여자 평균 키는 되시네요.');
}

 

결론

if부터 switch문, 단 두개의 문인데 정말 많은 내용이 깊게 다루어졌죠..?
까도 까도 배울 것이 계속 나오는게… 양파같은 매력의 소유자 프로그래밍!
매 시간 느끼지만 정말 쉬운 것, 그냥 넘어가는 것은 하나도 없다는 것. 그리고 어마어마한 프로그래밍의 깊이에 매번 겸손해집니다.

그런 의미에서 동영상 강의를 보며 이번 스터디 내용을 복습해보아요…☆


동영상 강의 보기


summer| bsidesoft 신입사원
디자인을 공부했던 섬머는 개발까지 해버리겠다는 욕심으로 개발자의 세계에 입문하게 되었습니다. 개발왕이 되어 멋진 제품을 만들어내는 꿈을 꾸고 있습니다. 코드, 디자인을 포함한 세상의 모든 아름다운 것들과 미드, 그리고 달리기를 좋아합니다.

%d 블로거가 이것을 좋아합니다: