[Javascript] while문을 이용한 반복과 재귀를 이용한 반복문의 차이

우아한 테크코스의 프리코스 1주차를 통해 많은 것들을 배웠는데요,, 코드 리뷰에서 다른 분들의 조언과 좋은 코드들을 보며 많은 생각을 하게 되었습니다. 😊

1주차 문제에선 “사용자의 입력을 받아 재시작할지 종료할지를 결정”하는 기능이 있었습니다. 코드 리뷰를 통해 많은 분들의 코드에서 재시작 로직을 while 문으로 작성한 것이 많이 보였습니다.

let is_retry = true;

while(is_retry){
	gameStart(); //게임을 시작한다

	const USER_INPUT = await readUserInput(); // 재시작 여부를 묻는다
	if(USER_INPUT === "2") is_retry = false; //입력이 2일경우 게임을 종료한다.
}

위와 같은 형식으로 while문을 채택한 것을 많이 봤습니다.

저는 이 부분을 재귀함수로 풀었는데요

async function game(){
	gameStart(); //게임을 시작한다

	const USER_INPUT = await readUserInput(); // 재시작 여부를 묻는다
	if(USER_INPUT === "1") await game(); //1을 누르면 재시작을 한다
}

await game();

위와 같은 방식을 사용하였습니다.

많은 분들이 while을 사용한 것을 보고, 분명 이유가 있을 것이라 생각이 들었는데요, 이 둘의 차이를 명학하게 안다면, 어떤 상황에서 while을 사용할 것인지 재귀를 사용할 것인지 알 수 있을거라 생각하여 글을 적어 봅니다!

그렇다면 while루프와 재귀 루프의 차이점에는 어떤게 있을까요?

❓성능 차이?

둘 다 같은 반복문이라 생각되지만, 명확한 차이가 있습니다. 바로 ‘스택’ 입니다.

자바스크립트 에서는 스택을 통해 함수 호출 및 실행 컨텍스트를 관리합니다!

(실행 컨텍스트는 변수,스코프 등을 제어하는 개념이라고 생각하시면 됩니다!)

함수를 실행하면 그 함수에 해당하는 실행 컨텍스트가 생성됩니다. 그 실행 컨텍스트는 스택에 쌓이게 되고, 함수의 순서를 결정하게 되는 셈이죠.

while문과 재귀는 여기서 큰 차이점이 있습니다.

4번의 반복을 하는 while과 재귀 예시를 만들어 보겠습니다.

let is_retry = true;

while(is_retry){
	gameStart(); //게임을 시작한다

	const USER_INPUT = await readUserInput(); // 재시작 여부를 묻는다
	if(USER_INPUT === "2") is_retry = false; //입력이 2일경우 게임을 종료한다.
}

이 로직에서 while의 반복이 4번이라면 코드는 다음과 같습니다.

gameStart(); //1.
const USER_INPUT = await readUserInput(); //2.
if(USER_INPUT === "2") is_retry = false; //3.

gameStart(); //4.
const USER_INPUT = await readUserInput(); 
if(USER_INPUT === "2") is_retry = false;

gameStart(); 
const USER_INPUT = await readUserInput(); 
if(USER_INPUT === "2") is_retry = false;

gameStart(); 
const USER_INPUT = await readUserInput(); 
if(USER_INPUT === "2") is_retry = false;

이렇게 실행되면 스택에는 하나의 실행 컨텍스트만 쌓이게 됩니다.

왜냐햐면 1번 함수가 끝나고 2번함수가 실행되고, 2번 함수가 끝나고 3번함수가 실행되고 … 이런 식으로 다음 함수가 진행되며 실행 컨텍스트는 스택에서 완전히 pop한 상태에서 다음 실행 컨텍스트가 스택에 담기게 되는 것이죠!

그렇다면 재귀는 어떨까요? 4회 반복하는 재귀를 풀어쓰면 다음과 같습니다.

async function game(){
	gameStart();

	const USER_INPUT = await readUserInput();
	if(USER_INPUT === "1") {
		gameStart();

		const USER_INPUT = await readUserInput();
		if(USER_INPUT === "1") {
			gameStart();

			const USER_INPUT = await readUserInput();
			if(USER_INPUT === "1") {
				gameStart();

				const USER_INPUT = await readUserInput();
				if(USER_INPUT === "1") {
				
				};
			};
		};
	};
}

await game();

블록이 중첩되는게 보이시나요? 자바스크립트에서는 함수의 실행이 완전히 끝나기 이전, 실행 컨텍스트를 스택에서 제거(pop)하지 않습니다. 따라서 스택에는 4개의 실행 컨텍스트가 쌓이게 되는 것이죠!

이 상황이 극단적인 상황이 된다면 어떻게 될까요? 예를 들어 수천만번의 반복이 일어났다고 가정해봅시다!

while 문의 경우 실행 컨텍스트가 스택에 쌓이게 되는 것은 단 1개일 것입니다. 하지만 재귀의 경우 실행 횟수만큼의 스택이 쌓이게 되겠죠.

이는 스택 오버플로우가 발생할 수 있습니다. 스택의 용량은 제한되어 있기 때문에, 수많은 스택이 쌓이게 되어 용량을 초과한다면 자칫 프로그램의 오류로 이어질 수 있겠죠?

🤔그렇다면 재귀를 사용할 이유가 없겠네?

재귀가 스택 오버플로우의 위험을 가지고 있다면, 굳이 사용할 필요는 없어보입니다..! 그렇다면 재귀를 사용해야 하는 상황이 있지 않을까요?

제가 생각했을 때 재귀를 사용하기 좋은 상황은 피보나치 수열과 같은 알고리즘 상황이라고 생각됩니다

function fibonacci(n) {
  if (n <= 0) {
    return 0;
  }

	if (n === 1) {
    return 1;
  }

  let fibNMinus1 = 1;
  let fibNMinus2 = 0;
  let fibN = 0;
  let i = 2;

  while (i <= n) {
    fibN = fibNMinus1 + fibNMinus2;
    fibNMinus2 = fibNMinus1;
    fibNMinus1 = fibN;
    i++;
  }

  return fibN;
}

위 코드는 피보나치 수열을 while문으로 구현했을 때 입니다.

function fibonacci(n) {
  if (n <= 0) {
    return 0;
  }

	if (n === 1) {
    return 1;
  }

  return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
  
}

위 코드는 재귀를 사용하여 피보나치 수열을 구현하였습니다

while과 재귀를 사용한 코드를 보았는데요. 확연히 차이가 나죠? 재귀를 사용한 코드가 훨씬 가독성이 뛰어나다는 것을 느끼셨을겁니다.

이렇듯 while문과 재귀에는 정답이 없습니다. 굳이 둘 중 하나를 선택해서 사용해야 하는 상황은 그때그때 다르다는 것이죠!!!

😀While 문을 사용할 때의 주의할 점

이번 코드 리뷰에서 많은 분들이 while(true)를 사용하는 것을 보았습니다!

이 부분은 문제라고 생각되는데요, 많은 서적에서도 while문 조건에 true 값을 넣는 것을 경계하라고 합니다.

while(true){
	gameStart(); //게임을 시작한다

	const USER_INPUT = await readUserInput(); // 재시작 여부를 묻는다
	if(USER_INPUT === "2") break; //입력이 2일경우 게임을 종료한다.
}

왜 이렇게 사용하면 안될까요?

일단 while(true) 자체가 무한 루프를 의미합니다. 내부 블록의 코드가 조금만 복잡해지더라도 무한 루프에 빠질 가능성이 높다는 것이죠. 이는 유지 보수에 큰 문제점을 일으킨다는 것입니다!

그러니 while 문의 조건에는 명확한 조건을 입력해주는게 좋은 습관이라고 생각됩니다!

❗결론..!

while 문과 재귀에 대해서 알아봤어요. 이를 통해서 저는 굳이 재귀를 사용해야 하는 상황이 아니라면 while 문을 사용하는 게 정답이라는 주관적인 결론에 도달했습니다. 여러분들은 어떠신가요?