[JS] 레이블과 흐름제어

Jiheon Kim·2023년 9월 24일
0

Javascript

목록 보기
3/6
post-thumbnail

🤖 자바스크립트 엔진처럼 사고 하기

내가 자바스크립트 엔진이라고 상상해 보자.
브라우저가 HTML 문서를 파싱하여 각각의 DOM 엘리먼트로 변환하는 것처럼 자바스크립트 엔진은 JS로 작성된 단순 텍스트 파일을 파싱하고 메모리에 적재한 후 실행할 것이다.

💡 자바스크립트 언어를 이루고 있는 구성 요소들

1) 문(Statement)

다양한 작업을 수행하는 실행 단위을 나타낸다
예를 들어, 변수 선언, 조건문, 반복문, 함수 호출 등이 문의 예시이다

2) 식(Expression)

식은 값을 생성하고 평가하는 하나의 코드 조각을 나타낸다
쉽게 말해 값을 표현하는 방법들은 전부 식으로, 값 자체도 식이며 변수 할당, 연산, 함수 호출 등을 포함한다

🔎문과 식의 구분은 변수에 할당할 수 있는지로 판단하면 쉽다
식은 값을 반환하고 결과가 메모리에 저장되기 때문
하지만 모든 언어가 이런 것은 아니다 대표적으로 Ruby는 for문과 if문이 변수에 할당이 가능하다

3) 식별자(Identifier)

식별자는 변수, 함수, 객체 속성 등의 이름을 나타낸다
코드에서 값을 참조하는 데 사용되며, 변수 이름이나 함수 이름이 대표적인 예이다

이러한 요소는 JavaScript 프로그래밍에서 중요한 역할을 하는데
문은 코드의 실행 흐름을 제어하고 조작하며, 식은 값의 계산과 데이터 조작에 사용되고, 식별자는 데이터를 저장하고 참조하는 데 필요한 이름을 제공한다.

💡 흐름제어와 레코드

1) 흐름 (Flow)

프로그램이란 메모리에 적재 되어있는 명령어의 순차적인 실행이며
여기서 흐름(Flow)이란 메모리에 적재된 명령어들이 순차적으로 실행되는 과정을 나타낸다

2) 흐름제어 (Flow control)

순차적인 명령어의 실행은 기본적으로 멈출 수 없어서 모든 명령어가 끝날 때까지 계속 실행되는데 이러한 flow에 관여하는 것이 flow control(흐름 제어)이다
일반적으로 흐름제어를 하지 않으면 flow는 일자로 쭉 흘러가지만, 흐름제어를 하게 되면 flow를 선택(if문) 하거나 flow를 반복(for문)할 수 있는 권한을 갖게 되는 것이다

3) 레코드(Record)

자바스크립트 엔진은 우리가 작성한 JS파일을 파싱하고 모든 문을 레코드 단위로 파싱하는데 이 레코드에는 문의 유형, 실행되어야 할 코드, 변수 및 함수 선언, 조건문 블록 등과 같은 정보가 포함되며 엔진 내부에서 코드 실행을 추적하고 제어하기 위한 데이터 구조를 나타낸다

레코드는 단순히 문(Statement)에 대한 정보를 저장하는 자바스크립트 엔진의 내부 데이터 구조로 파싱과 해석의 단위를 가리키는 약간 추상적인 개념으로 느껴진다.

💡 레이블 (Label)

loop: // loop라는 레이블 정의
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outerLoop; // loop 레이블로 점프하는 break 문
        }
    }
}

🔎 다중 for문에서 탈출 하려면 레이블을 써야 한다

레이블은 과거에 프로그래밍 언어가 if문과 for문과 같은 제어문을 도입하기 전에 직접 원하는 곳으로 코드를 이동시키던 흔적으로 가장 원초적인 흐름제어 기법이며 자바스크립트에서 유일하게 흐름제어를 직접 하는 기법이다

현대의 레이블과 과거의 goto문은 약간의 차이가 있는데
직접 레이블을 지정해서 이동할 수 있는 goto문과 달리 레이블은 break 와 continue를 통해 아래로만 흐르기 때문에 레이블이 위에 있으면 위로 갈 순 없다.

🤔 레이블 주석

이러한 레이블을 마치 주석처럼 사용하는 경우도 있다고 한다
왜냐하면 주석 같은 경우 코드의 뒷줄에 달면 코드가 끝나는 길이가 다르기 때문에 주석이 중구난방인 경우가 있는데 이런 경우에 읽기 쉽게 하려고 앞 주석을 사용하기 위해 레이블을 쓰는 경우도 있었다고 한다.

variable: var a =3;
block: {
  //...
}

💡 레이블 특징

1) 런타임 이전 평가

자바스크립트의 문(Statement)은 런타임에서 에러가 나지만 레이블 문에 구문 오류가 있을 경우 런타임 이전에 파싱 단계에서 발견되어 프로그램이 실행되기 전에 에러가 나게 된다
이러한 특징은 자바스크립트의 구문 오류를 런타임 오류보다 이른 시점에 발견하고 수정할 수 있도록 도와주기 때문에 파싱 단계에서 오류가 발생하고 해당 코드는 실행되지 않고 좀 더 안전하다는 특징이 있다

2) 점프 되는 위치

레이블의 특징중에 하나는 break label을 했을 때 해당 레이블의 이름으로 가는 게 아니라 해당 블록의 끝으로 간다는 재밌는 특징이 있다.

label: 1️⃣
for (let i =0; i < 5; i++) { 
   if(i === 3) break label
    // 점프하면 1번에서 다시 반복문을 실행하는게 아닌 해당 블록의 끝인 2번으로 간다
} 2️⃣ 

3) Auto Label

for문이나 while문은 레이블 이름 없이 break만 했는데 어디로 가야 할지 어떻게 알 수 있을까?

for (let i =0; i < 5; i++) { 
   if(i === 3) break
}

label: 
for (let i =0; i < 5; i++) { 
   if(i === 3) break label
}

이는 암묵적으로 자바스크립트 엔진이 임의의 레이블을 만들어서 break labelName 이런 식으로 자동으로 삽입해 준 것이다. 내가 직접 for문에 명시적으로 레이블 이름을 줘도 되지만 레이블을 생략할 경우 레이블을 명시적으로 삽입하여 동작한다
그래서 for, while, switch문에서 자동으로 auto Label 기능으로 인해 레이블처럼 동작하여 break가 돌아갈 곳을 알고 있게 된다.


💡 if문과 else if

흐름제어 기법의 또 다른 종류인 if문이다
if문은 간단하지만 실수하기 쉬운 몇 가지를 정리해 보려고 한다

사실 else if라는 문법은 특별한 키워드가 아니라 else문에 새로운 if문이 있는 것과 동일하다 즉, else뒤에 새로운 문이 올 수 있고 그렇기 때문에 else switch던 else for, else while 또한 이상할 게 전혀 없는 것

if(true){
    //...
}else switch(type){
    //...
}

if(true){
    //...
}else while(true){
    //...
}

또한 else if 라는 것은 병렬조건에 대한 분기를 만드는 것이 아닌 1차 조건을 분기한 이후에 2차 분기를 만드는 것이다
즉, 중첩된 조건문을 제어하는 것인데 마치 else if라는 키워드가 있는 것마냥 사용하고 있다는 것. 이러한 else if는 마치 switch문 처럼 병렬적으로 조건을 평가하는 것 마냥 착각하기 쉬운데 else if가 사실은 중첩된 분기이다.

if(condition1){

} else if(condition2){
  // 이 부분은 조건식1이 거짓면서 조건식2를 만족해야만 들어올 수 있다
}

내가 평가해야 하는 식들이 동등할 땐 switch이지만 그게아닌 중첩되어서 평가해야 하는 경우라면 else if를 써야한다
따라서 else if는 실수하기 쉽고 오해하기 쉽기 때문에 중첩된 조건의 경우 if문의 중첩으로 코드를 작성하는게 좀 더 안전하고 직관적이다

💡 Optional, Mandatory

Optional(선택적)은 조건이 참일 때 어떤 동작을 수행하는 것을 의미하며
Mandatory(필수적)는 조건이 참이든 거짓이든 상관없이 어떤 동작을 반드시 수행해야 함을 의미한다

Optional과 Mandatory 한 동작을 구분하는 것은 코드의 의도를 명확하게 표현하고, 코드의 가독성을 높이며, 버그 발생 가능성을 줄이는 데 의미가 있다.

if (condition1) {
   // Optional action
}

if (condition2) {  
    // Mandatory action
} else { 
    // Mandatory action
}

Mandatory한 동작으로 시작한 코드 블록은 Mandatory한 동작으로 끝나야 하며, Optional한 동작으로 시작한 코드 블록은 Optional한 동작으로 끝나게 하면 코드의 일관성이 유지되고 예기치 않은 버그를 방지할 수 있다. 만약 Mandatory한 동작으로 시작해 optional한 동작으로 끝나거나 그 반대의 경우 코드의 의도를 이해하기 어려워질 수 있고, 논리적인 오류나 버그가 발생할 수 있다.

if (condition1) {
    // Mandatory action
} else {
    if (condition2) {
        // Optional action
    }
	// ??? 
}

첫 번째 condition에서 truthy와 falsy에 관계없이 어떠한 동작을 수행하고 있지만
두 번째 condition에서 Optional 하게 동작하고 있기 때문에 두 번째 condition 또한 falsy라면 아무것도 동작하지 않는다
이는 의도와 다를 수 있는 잠재적인 버그를 포함할 수 있기 때문에 이러한 패턴을 피하는게 좋은데 menatory한 동작이라면 menatory로 끝나야 하고 optional이 개입한 동작이라면 optional로 끝나야 하는게 좋다.

"Optional" 및 "Mandatory"한 동작을 명확하게 구분함으로써 코드의 일관성을 유지하고, 코드의 동작을 예측 가능하게 만들 수 있다


💡 스위치(switch)

사실 스위치문의 case:와 defalut: 문법 또한 레이블이다

스위치문은 특별한 레이블(case, default)을 만들 수 있는 공간을 만들어 주는 문법이다 스위치문 안에서 다른 레이블은 만들면 Syntax 에러가 난다

스위치 문은 순차적으로 case 레이블에 해당하는 조건을 검사하고, 조건이 일치하는 case 레이블이 실행된다. 따라서 case 레이블은 실행 흐름을 특정 지점으로 이동시키는 역할을 하고 이러한 레이블은 실행 중에 조건을 평가하여 동적으로 분기되는 특수한 형태의 레이블이라고 볼 수 있다. 또한 스위치문은 for문 while문과 마찬가지로 auto label이 적용된다

🍃fall through

스위치문에서 각각의 case 레이블 아래에 break 문을 사용하지 않을 때
fall through 현상이 발생한다.
각 case 레이블 아래에 break를 사용하여 해당 case가 실행된 후 스위치 문을 빠져나가게끔 해야하는데 break 문을 생략하면 조건이 참인 case가 실행된 후 그다음 case 레이블도 실행되며, 그러면 여러 case 레이블이 연속적으로 실행되는 결과가 나오는 것

생각해 보면 당연한 결과이다 각각의 case가 if문처럼 분기되는게 아니라
case가 단순 레이블이기 때문에 break가 없을 때 아래로 내려오는게 당연한것

💡 런타임 스위치

결국 스위치문도 레이블을 만드는 것인데 단지 레이블을 자바스크립트 엔진이 파싱하는 시점에 평가되는 기존의 정적인 레이블을 만드는 것이 아닌 case 안에 있는 표현식을 동적으로 해석할 수 있는 특수한 레이블을 만드는 것이다.

즉, 런타임에 switch문 에서 값을 평가하여 그 평가된 값과 case에 있는 값이 일치하는 레이블로 보내주는 특수한 레이블을 만드는 것
런타임에 평가되는 조금 특별한 레이블이지만 결국 다른 레이블과 동일하다

자바는 컴파일 할때 이미 값을 평가하고 런타임에서는 이미 계산된 정적인 값을 사용하는 반면 자바스크립트와 파이썬의 switch문은 런타임에 해석한다
따라서 실행 중에 값이 변경될 수 있는데 이렇게 런타임에서 평가되기 때문에 switch 문의 분기 조건은 실행 시에 결정된다

이러한 런타임에 평가되는 동작 방식 때문에 자바스크립트의 스위치문은 2가지 방식으로 사용이 가능하다

1) 첫 번째 패턴

어떠한 값이 있고 그 값에 대해서 일어나는 변화를 내가 알고 있을 때 사용하는 일반적인 스위치문의 패턴

switch (type) {
  case "number":
    console.log("숫자");
    break;
  case "string":
    console.log("문자");
    break;
}

2) 두 번째 패턴

조건을 고정하고 case 안에 있는 표현식의 성립 여부로 조건을 검사하는 패턴

switch (true) {
  case value < 0:
    console.log("음수");
    break;
  case value > 0:
    console.log("양수");
    break;
  default:
    console.log("제로");
}

자바와 같은 정적 타입 언어에서는 switch 문의 case 조건은 컴파일 시점에 상수로 평가되어야 하는 제약이 있는반면 자바스크립트는 case문의 표현식이 런타임에서 평가되기 때문에 이러한 패턴을 사용할 수 있는데 이 패턴은 switch(true)을 통해서 값을 고정해 두고 case의 표현식 평가로 조건을 검사하는 패턴이다.

switch(true){
	case f1(): break;
	case f2(): break;
	case f3(): break;
	// ... 
}

만약에 이러한 switch문이 있다면 f1()함수부터 시작하여 위에서부터 순차적으로 처음으로 true가 반환될 때까지 case 레이블에 해당하는 함수들을 실행한다

연속적인 유효성 검사에 대해서 if/else문 보다 좀더 코드가 깔끔해지는 경향이있다
즉, switch 문을 사용하여 여러 함수 호출을 순차적으로 비교하면서 첫 번째로 조건이 참인 함수를 실행하는 것을 보여주는 것

개인적으로는 재밌는 패턴이라고 생각하지만 스위치문의 조건을 런타임에 동적으로 평가할 수 있기 때문에 사용할 수 있는 패턴일 뿐 그리 선호되는 패턴은 아닌 것 같다.

마무리

개인적으로 쉬운 내용이라고 생각했지만, 생각보다 디테일한 부분이 많았고
레이블에 대해서 좀 더 깊이 생각해 보고 나니까 for문, while문, switch문이 평소와 다르게 느껴졌다. 꽤나 흥미로운 주제였던 것 같다.

profile
누군가는 해야하잖아

0개의 댓글