제어와 반복, 함수와 재귀, 에러

프로그램의 논리적인 흐름을 제어하는 제어문과 반복문, 함수와 재귀에 대해서 알아봅니다.
제어문, 반복문, 재귀, 함수, 에러


제어문

반복문과 조건문반복문과 조건문

프로그램은 기본적으로 소스코드의 첫째 줄부터 마지막 줄까지 차례대로 실행되지만 때로는 실행 순서를 조절해야 할 필요가 있습니다. 어느 조건(Boolean)을 만족하는 경우에만 코드를 실행 (조건문)해야 하는 경우도 있고, 어느 조건(Boolean)을 만족하는 경우에 특정 코드를 반복 (반복문)해야 하는 경우도 있습니다.

조건문

조건이라 함은 계산했을 때 Boolean이 되는 식이나 변수, 값을 의미합니다.

IF-ELSE 문

if (조건) {

} else if (조건) {

} else if (조건) {

} else {

}

x라는 숫자가 담긴 변수가 1~10 사이 일때, 10~20 사이일 때 20~ 일때로 분기

x = 5;
if (1 <= x || x < 10) {
    // x가 1~10
} else if (10 <= x || x < 20) {
    // x는 10~20
} else {
    // x는 20 이상 또는 1 미만
}

SWITCH 문

switch () {
    case1:
        // 값1인 경우
    break;
    case2:
        // 값2인 경우
    break;
    default:
        // 값1도 값2도 아닌 경우
}
x = "a";
switch(x) {
    case "a":
        // ...
    break;
    case "b":
        // ...
    break;
    default:
        // ...
}

반복문

특정 작업을 반복적으로 처리를 해야할 일을 처리할때 FOR문 WHILE문을 사용합니다. 배열의 각 요소들에 대해서 같은 처리를 반복하는 경우를 생각해 볼 수 있습니다.

FOR문

// 어떠한 반복적인 코드를
arr = [1,2,3,4,5, ..., 100];
arr[0] += 10;
arr[1] += 20;
arr[2] += 30;
arr[3] += 40;
arr[4] += 50;
...
arr[99] += 1000;

// 위 코드를 for문으로 표현하면
for(i=0; i < arr.length; i++) {
    arr[i] += (i+1)*10;
}

// for문의 구조
for(초기화; 반복 조건; 반복 후) {
    // ..

    if (...)
        break; // 루프를 완전히 종료

  if (...)
    continue; // 이번 루프는 여기서 종료하고 다시 루프 조건을 확인하고 진행  
}

FOR문을 이용해서 구구단을 출력해보기

for(i=1; i < 10; i++){
    for(j=1; j < 10; j++){
        console.log(i+"*"+j+"="+i*j);
    }
}

FOR문을 이용해서 구구단중 1, 3, 5, 6, 7, 9단만 출력해보기

for(i=1; i < 10; i++){
    if(i%2==0) continue;
    for(j=1; j < 10; j++){
        console.log(i+"*"+j+"="+i*j);
    }
}

WHILE 문

WHILE문은 FOR문과 달리 루프 조건만을 명시합니다. 하지만 WHILE문은 FOR문은 쓰이기에 따라서 똑 같은 일을 할 수 있습니다. 상황에 맞게 편리한 문법을 쓰면 됩니다.

i = 0;
while (i < 10) {
   // 마찬가지로 break; continue; 사용 가능
   // ...

   i++;
}

WHILE문을 이용해서 구구단 중 2, 5, 8 단만 출력해보기

i = 1;
while(i < 10){
    if(i==2 || i==5 || i==8){
        j=1;
        while(j < 10){
            console.log(i+"*"+j+"="+i*j);
            j++;
        }
    }
    i++;
}

함수

함수(Function)는 초기 값(인자)의 변화를 주면서 특정한 코드를 반복하는 경우에 사용합니다. 함수를 사용하면 코드의 재사용성을 높힐 수 있습니다. 나아가서 함수는 말이되는 코드, 짜임새있는 프로그램의 구조를 구성하는 기초가 됩니다. 함수를 사용하지 않는다면 특정 기능이 필요한 곳마다 같은 코드를 반복해서 작성해야 합니다.

이 때의 문제점은…

  • 기능이 필요 할 때마다 전체 코드를 매번 작성해야 하는 수고
  • 기능의 공통적인 부분에 변화가 필요할 때 모든 코드를 수정해야 하는 수고
  • 코드의 기능이 눈에 쉽게 읽히지 않음

함수는 초기 값(인자)들을 받아서 특정 코드를 수행하고 결과 값을 리턴(반환)해줍니다. 이때 리턴 값을 명시하지 않으면 그 값은 undefined가 됩니다.

함수 정의하기

sum = function (a, b){ // 함수를 정의하고 sum이라는 이름을 붙힘
    return a + b;
};
result = sum(1, 3); // 함수를 실행 할 땐 함수명(인자들...)

이름있는 함수 정의하기

function sum(a, b){ // 이름을 붙히면서 정의하기
   return a + b;
}
result = sum(1, 3);

함수 실행하기

함수를 정의하는 것과 함수를 실행하는 것은 다릅니다. 실행하기 위해서 함수 이름 뒤에 인자 묶음 (...) 을 붙힙니다.

sum = function(a,b){
    return a+b;
};
sum(1,2); // 3
sum(1,2,3); // 3
sum(1); // ???
sum(); // ???
sum2 = sum;
sum2(4,5); // 9

구구단을 x단 부터 y단까지 출력하는 함수를 정의

function print99(x, y) {
    for(var i=x; i < y+1; i++){
        for(var j=1; j < 10; j++){
            console.log(i+"*"+j+"="+i*j);
        }
    }
}
print99(3, 6);

재귀

재귀(Recursion) 함수는== 함수 내부에서 자기 자신을 다시 호출하는 함수==를 말합니다. 재귀 함수를 사용하면 반복문과 조건문만으로는 표현하기 어려운 작업을 구현 할 수 있습니다.

팩토리얼 함수의 호출팩토리얼 함수의 호출

fac(n) = n*fac(n-1)을 만족하는 팩토리얼 함수를 구현

function fac(n){
    if(n==0 || n==1) // 종료 조건
        return 1;
    else
        return n * fac(n-1); // 재귀 호출
}

재귀 함수에는 반복적인 호출이 종료되는 조건이 존재해야 합니다. 또한 재귀 호출을 하는 n*fac(n-1) 부분을 작성할 때는 fact(n-1)이 제대로 된 값을 리턴해준다고 가정하고 구현합니다. 호출 구조를 상상해보면서 함수가 계산 범위를 줄여나가면서 종료 조건에 도달하는지 확인합니다.

피보나치 함수의 호출피보나치 함수의 호출

fibo(n) = fibo(n-1)+fibo(n-2)를 만족하는 피보나치 함수를 구현

function fibo(n){
    if(n==1 || n==2) // 종료 조건
        return 1;
    else
        return fibo(n-1) + fibo(n-2); // 재귀 호출
}

재귀함수를 사용하면 수학적인 문제를 쉽게 해결할 수 있습니다. 예) 하노이의 탑 문제

에러

프로그램의 에러는 크게 두 종류로 볼 수 있습니다. 첫째는 컴파일 타임 에러(Compile time error)로, 흔히 문법적인 오류로 인해 컴파일 자체가 되지 않는 경우입니다. 이러한 에러는 컴파일러가 주는 힌트를 통해 디버깅하면 됩니다.
둘째는 런타임 에러(Runtime error)로, 프로그램 실행 중에 구조적인 또는 논리적인 결함으로 오류가 발생하는 경우입니다. 이때 에러는 프로그램을 중지시킬 수도 있고, 단순히 예상치 않은 동작을 할 수도 있습니다. 이 때는 로직을 일일이 점검해야 하기에 디버깅에 골치가 아플 수 있습니다.
위의 에러들은 의도치 않은 에러지만, 프로그램을 작성하다보면 의도된 에러가 필요 할 수 있습니다.

에러 발생시키기

// x를 y로 나누는 함수
function devide(x, y){
    if (y == 0) {
        var err = new Error("Can't devide by zero.");
        throw err; // 에러를 발생시키면 함수가 바로 종료됨
    }

    return x/y;
}

var a = devide(10, 5); // 2
var b = devide(10, 0); // VM348:6 Uncaught Error: Can't devide by zero.(…)

// 이때 12번 줄에서 발생한 에러는 devide 함수를 종료시키고, 나아가서 12번줄 밑의 프로그램도 중지시킵니다.
console.log('program stopped..'); // 출력 안됨
위처럼 Uncaught Error는 프로그램을 종료시킵니다.

에러 제어하기

// x를 y로 나누는 함수
function devide(x, y){
    if (y == 0) {
        var err = new Error("Can't devide by zero.");
        throw err; // 에러를 발생시키면 함수가 바로 종료됨
    }

    return x/y;
}

try {
    var a = devide(10, 5); // 2
    var b = devide(10, 0); // Error!
} catch(e) {
    console.log(e);
    console.log("Try again..!");
}

// try, catch문으로 제어된 에러는 프로그램을 종료시키지 않습니다.
console.log('program not stopped..');

/**
Error: Can't devide by zero.(…)
Try again..!
program not stopped..
**/

이렇게 에러를 던지고 잡는 제어문을 통해서 프로그램의 확장성을 높힐 수 있습니다. devide 함수에서 0으로 나누는 오류를 임의로 처리하지 않고 throw문을 통해 에러만을 발생시킵니다. 이렇게 ==오류에 대한 처리를 다른 코드에 위임함으로써 devide 함수는 더욱 다양하게 사용 될 수 있습니다.===

의도된 에러는 유용합니다

fs = require('fs'); // 이 구문은 뒤에서 알아봅니다.
try {
  // 존재하지 않는 파일을 읽으려고 함
  data = fs.readFileSync('/invalid/file/path.txt');
  console.log(data);

} catch(err) {
    // 에러 발생시
  console.log(err);

} finally {
    // 에러에 관계 없이 항상 처리
    // catch에서 다시 에러가 발생하더라도 처리됨
}
목차
2. 프로그래밍 연습
3. 웹 프론트엔드
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 05월 23일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.