개요 - 프로그래머라면 당연히 알아야 할 기초 지식에 대해 #2

이전 게시글에 이어, 나머지 부분이었던 Language ElementSync Flow에 대해서도 정리를 해 보도록 하겠습니다. 잘 몰라서 부연설명 하지 못한 부분도 있고, 잘못 표현된 부분이 있을 수 있습니다. 혹시 잘못된 부분이 보인다면 꼭 수정할 수 있도록 알려주시면 감사하겠습니다!

Language Element

Language Element의 구성 주체는 최소 Statement(문), Expression(식), Identifier(식별자) 3가지가 포함되며, 언어학자의 견해에 따라 Comments(주석)까지 포함시키기도 합니다.

  1. Statement (문) : 자바스크립트 엔진이 어떻게 해석해야할지 알려주는 힌트로써, 실행되고 나면 메모리에서 깨끗이 사라집니다. 자바스크립트에서 if, while, for, throw 문들은 변수에 ‘할당’할 수 없습니다. 문이란 엔진이 실행될 때 힌트를 제공할 뿐, 이는 실제 메모리에는 남지 않으며 연산으로만 남게 된다고 합니다. 메모리에 올라가지 않는다는 것은 저장되지 않는다는 것이며, 이는 값이될 수 없음을 의미하기도 합니다.
    • 공문 - ‘;;;;;’ 와 같이 표현할 수 있는 비어있는 문을 의미합니다. 이 자체만으로는 문법 오류가 아닙니다. 인간들이 워낙 실수를 많이하여 공문을 인정해주기로 한 것이라고 합니다.
    • 식문 - 하나의 식은 하나의 문이 됩니다. 3 + 5 와 같은 연산식도 세미콜론을 찍어 문으로 만들어 엔터를 쳐도 됩니다. 3;2;5; 와 같은 표현도 아무런 문법 오류가 발생하지 않습니다. 자바에서는 이와 같은 표현은 문법 오류로 lint time을 넘길 수 없습니다.
    • 제어문 - 코드의 흐름을 제어할 때 사용하는 문으로, if, swith, for, do-while 등이 있습니다.
    • 선언문 - var, let, const와 같이 변수를 선언할 때 사용하는 문입니다.
    • 단문 - 제어문 뒤 문을 표현하는 곳에 중괄호 없이 표현할 수 있습니다. 줄바꿈 문자를 만나면 해당 제어문은 그 문의 발동 조건이 만족될 때 다음의 단문만 실행시킵니다.
    • 중문 - 중괄호({})를 사용해서 복수개의 단문을 내부에 표현할 수 있도록 하는 문입니다.

  2. Expression (식) : 표현식이라는 표현은 잘못된 표현이라고 합니다. 그냥 으로 표현해야합니다. 식이란, 최종적으로 하나에 값으로 수용되는 것을 의미합니다. 1+5는 다항연산식인데, 이는 6으로 수용됩니다. 식은 항상 단과 값으로 수용됩니다. 하나의 값이 될 수 있는 것을 식이라고 표현합니다. 값식, 연산식, 호출식이 있습니다. 식은 결국 값으로 평가되는데, 이를 변수에 담지 않으면 즉시 메모리에서 휘발됩니다. 이처럼 휘발되면 재활용 할 수도 없고, 그저 무의미한 값의 표현이 될 뿐입니다.

  3. Identifier (식별자)
    • 변수 - 메모리 주소 또는 값 그리고 타입(데이터와 타입)을 가지고 있는 존재를 의미합니다. 여러번 할당할 수 있습니다.
    • 상수 - 변수와는 다르게, 한 번 선언 및 할당된 이후에는 절대 다시 할당할 수 없는, 매우 지조있는(-_-) 존재를 의미합니다. 상수를 사용한다는것은 변화 가능성을 줄인다는 것과도 맥이 닿아있는데, 이처럼 변화할 가능성을 배제하면 복잡성을 줄일 수 있게 됩니다. 모든 변수를 상수화 하는 것이 변화가능성을 낮출 수 있는 방법 중 하나입니다. 이를 통해 복잡성을 정복하고, 수정 가능성을 확보할 수 있게 된다고 합니다.
    • 기본형(primitive type) - 언어에서 객체나 메소드가 아닌 데이터를 의미하며, 변경 불가능한 값(immutable value)을 의미합니다. 이 값은 변경되지 않습니다. 변수에 할당되었던 값이 아닌 다른 값으로 재할당되면, 기존 값과의 연결이 끊어지게 되는 것이지, 기존에 할당되어있단 primitive value가 변경되는 개념의 것이 아닙니다. 아래 처럼 변수에 변수를 할당할 때 primative value가 들어있던 경우에는 그 값을 복사하여 새 변수에 할당하게 됩니다. 이는 다음의 코드를 통해 증명할 수 있습니다.
      let a = 10;
      let b = a; // b에는 10의 값이 들어있다.
      a = 20; //만약 b가 a의 메모리주소를 참조한다면, b도 20으로 변해야한다.
      console.log(b); // 10이 출력된다. 값은 '복사'된다.
      
    • 참조형(reference type) - 기본형이 아닌 나머지 데이터의 메모리의 주소를 의미합니다. 데이터와 관련하여 메모리에서 어떻게 처리되는지 더 보고싶으신 분들은 이 링크의 게시물을 통해 확인하실 수 있습니다.

  4. Comments (주석) : 엔진에게는 해석되지 않고 사람에게만 필요한 그 주석(註釋)입니다. ‘글 뜻 풀 주’에, ‘풀 석’으로 이루어진 단어인데요. 낱말이나 문장의 뜻을 쉽게 풀이한 글을 의미하는 단어입니다. (-_-)


자바스크립트에서의 if문은 변수에 할당할 수 없습니다. 하지만 ruby언어에서는 변수에 할당할 수 있습니다. 우리가 사용하는 프로그래밍 언어는 인간들과 일상에서 소통하기 위해 사용하는 자연어(Natural Language)가 아닌 인공어(Artificial Language)입니다. 이처럼 인간이 인위적으로 만든 언어는 가장 먼저 primitive를 정하고 그것이 아닌 나머지에 대해 reference로 정한 뒤(폰노이만 머신에서는 메모리 구조상으로도 어쩔 수 없이 이러한 형태를 취해야 한다고 합니다), 위에서 말하고 있는 Language Elements 등에 대한 세부 내용들을 직접 결정하게 됩니다. 루비 언어를 만든 사람은 if의 개념을 문으로 보지 않고 식으로 보았기 때문에 변수에 할당할 수 있고 값이 될 수도 있도록 설계했다고 합니다.

이러한 Language Element를 포함한 모든 개념들은 ABC언어로부터 왔습니다. Algol을 시작으로 이를 개조하여 B를 만들고, 여기서 사용된 변수에 타입 같은 것들이 없어서 개조된 것이 현재도 많이 사용되고 있는 C 언어라고 합니다. 이처럼 모든 기초 문법들을 ABC를 기반으로 한 대부분의 언어들은 개념이 비슷비슷해서 언어 하나를 할 수 있게 되면 다른 언어도 대충은 할 수 있게 되는 것이 이 때문이라고 합니다. 다만, Haskell같은 ABC 언어를 기반으로 하지 않고 만들어진 언어의 경우는 배우기가 매우 힘든데, 이처럼 우리가 익숙한 다른 언어와 같은 기반이 아니기 때문입니다.

우리가 알고있는 규칙은 상식이 아닙니다. 일반적이라고 착각하고 있는 것 또한 착각일 수 있습니다. 우리가 주로 사용하는 언어에서, if가 문이기 때문에 다른 언어에서도 문이여야 한다는 것은 그저 잘못된 고정관념에 지나지 않은 것 처럼 말입니다.

자바에서의 함수는 문입니다. 메모리 서브루틴에 람다가 작성되어있을지언정, 실제 그 자체는 문으로 취급된다고 합니다. 하지만 자바스크립트에서는 왜 함수가 값으로 취급될까요? 자바스크립트를 10일만에 만든 Brendan Eich(브랜던 아이크)가 함수가 값으로 취급되던 Scheme이라는 언어로부터 영향을 받아 이를 자바스크립트에 적용시켰기 때문이라고 합니다. 이처럼 언어별로 공통된 특징이 있다고 해도 그것이 항상 진리인 것 처럼 생각한다면 큰 오산입니다!


Sync Flow

메모리에 있는 명령과 값을 꺼내서 cpu에 옮긴 뒤 연산하고 그 결과를 메모리에 넣는것을 끊임없이 반복하는 것이 바로 노이만 머신의 원리인데요, 컴퓨터에 실제 적재되는 프로그램을 보면 operand와 instruction으로 이뤄져 있음을 알 수 있습니다.

1 + 2를 보면, 1과 2는 operand이며, +는 instruction입니다. 메모리 상에 이것들을 들고와서 레지스터에 operand를 적재하고 accumulator에 연산된 결과를 누적시킨 결과인 3을 옮기라는 명령이 있으면 레지스터 C로부터 그 결과를 옮기게 됩니다. 실제 기계 코드를 보면 더하기를 한 뒤, 레지스터 C에 있는 값을 메모리 몇번째로 옮기라는 mov 명령어가 따라오게 됩니다. 이 레지스터 C가 다시 덮어씌우기 전에 이 값을 저장해야 합니다. 재활용 하고 싶으면, 이 값을 저장해야합니다. 노이만 머신의 프로그램 실행 전략을 이러한 명령들을 시작부터 끝까지 한방에 쭉 쉼 없이 실행하는 것입니다. 노이만 머신이 메모리에 적재되어있는 프로그램을 순서대로 소비하는 과정을 Flow라고 표현합니다.

Flow는 메모리에 적재되어있는대로 그냥 쭉 한번에 실행된 후 종료됩니다. 이 사이에 절대 관여할 수 없게 되어있습니다. 이처럼 관여할 수 없이 한번에 적재되어있는 메모리에 명령을 소비하는 과정을 요즘 용어로 동기화 과정이라고 표현합니다. 이런 과정을 어길 수 있는 것은 비동기성이라고 표현합니다. 동기화 명령이란, 적재된 프로그램이 한 번에 소비되고, 그 사이에 CPU를 중단시키거나 프로그램을 중단시킬 수 없는 상황을 의미합니다. 메모리에 적재된 프로그램이 끊임없이 순차적으로 CPU에게 소비당하고 있는 상황을 동기화 명령으로 표현합니다.

우리는 프로그램이 왼쪽에서 오른쪽으로, 위에서 아래로 순서대로 실행될 것이라는 믿음을 가지고 있습니다. 이러한 규칙은 서양에서부터 왔습니다. 아래 훈민정음 해례본이 이런 규칙에 맞게 작성되어있지 않음을 알 수 있듯, 원래부터 그렇게 사용되진 않았습니다. (양키센스!)

훈민정음 해례본

(⬆️ 오른쪽에서 왼쪽, 위에서 아래로 읽어야 하는 훈민정음 해례본 이미지, 위키피디아)

이처럼 모든것들이 우리에게 당연하게 받아들여지고 있는, 누군가에게 배우고 있는 것들이 항상 진리가 아닐 수 있음에 대해서는 생각해 봐야 하는 일입니다.

이와같은 Sync Flow 규칙을 위배하는, 오른쪽에서 왼쪽으로 유일하게 흐르는 것이 있습니다. 바로 할당(Assignment, =) 연산자 입니다. 왜 Sync Flow를 어기고 이것에 한해서만 반대로 했을까요?

우리가 a = 3 이렇게 할당하는 것 대신, 3 = a 로 할당해도 되지 않을까요? 물론 문제될 것은 전혀 없을 것입니다. 하지만 이 인공어를 만든 사람들은 모두 ‘수학자’였고, 수학적 기호에 따르면 할당은 오른쪽에서 왼쪽으로 하게 돼 있었기 때문에 이들이 만든 언어들은 수학적 할당을 사용하게 되었다고 합니다.

아울러, 과연 우리에게 덧셈 연산자가 필요할까요? 3 + 5 대신 plus(3, 5)로 표현해도 아무런 문제가 없지 않을까요? 언어 차원에서 반드시 산술 연산자가 필요할까요? 만든 사람들에게 편한 것을 사용했기 때문에 우리가 산술 연산자를 사용하게 된 것임을 알 수 있었습니다. 3+5/2%7|6 이와 같은 표현을 쉽게 이해할 수 있을까요? 3 + 5 / 2 <- 이와같은 수학적 표현을, plus(3, div(5, 2)) 이렇게 표현한다면 더욱더 의도가 잘 드러나고, 누구나 똑같이 해석할 수 있게 됩니다. 3 + 5 / 2 이 수학식에 * 3을 추가하게 된다면, 이것은 컴퓨터와 수학이 서로 다르게 해석을 하게 됩니다. 사소한 수식이라도 연산자가 개입되는 것은 모두 괄호로 그 연산자 우선순위를 외워 쓰지 않을 수 있도록 명확한 코드로 누구도 오해하지 않을 수 있도록 표현하는 습관을 들여야합니다. 복잡성을 최대한 낮추는 것이 수정하기 더 좋은건 두 말할 것도 없겠죠.

Flow Control Statement란

Sync Flow는 우리가 중간에 개입할 수 없는 흐름을 의미합니다. 하지만 특정 제어문을 통해 이 흐름을 우리가 원하는 의도대로 제어할 수 있기도 합니다. Sync Flow는 시작부터 끝까지 중간에 멈춤 없이 흘러야하는데, 여기서 if, switch, for 등을 이용하여 흐름을 한 방향으로 흐르되 그 흐름을 원하는 대로 제어할 수 있게 됩니다.

이처럼 각각의 문을 조직화 하여 프로그램의 흐름을 제어하는 법을 Flow Control이라고 합니다.

코드로 의도를 노출한다는 것의 의미

변수를 통해 어떤 모델의 특징이나 성질을 단편적으로 표현할 수 있지만, 그것만으로 코드의 전체적인 흐름을 표현하기에는 분명 부족합니다. if문이 존재하는데 왜 switch가 필요한걸까요? while문이 존재하는데 do-while같은게 왜 더 필요할까요?

비슷하지만 다른, 언어별로 주어진 제어문으로 미세한 늬앙스를 담아 우아하게 코드를 표현할 수도 있습니다. 잘 지은 변수 이름과 알고리즘 흐름에 따른 적절한 제어문의 사용 등을 통해 작성된 코드에 작성자의 의도를 담을 수 있다고 합니다. 하지만 저같은 초보 개발자들은 분명 이런 것들에 어려움을 느낍니다. -_-; 이 부분은 다른 고수분들의 코드를 많이 구경하고 그 흐름을 느끼며, 그러한 흐름으로 내 코드를 구현해보며 우아함을 느끼는 경험으로부터 습득할 수 있는 부분이라고 생각될 뿐입니다.

제어문은 특히 자바스크립트 엔진에게 주는 힌트로 활용됩니다. 굉장히 명확해야하며, 규칙이 엄격합니다. 즉, 정해진 문법에 알맞게 구사해야합니다. 특히 메모해 두었던 부분으로, if문을 다음과 같이 사용하게 되는 부분에서 특히 더 생각할 부분이 있었습니다.

// 아래의 if문은 optional합니다. 반드시 충족되지 않아도 됩니다.
if(true){statement}

// 아래의 if문은 mandatory합니다. 반드시 둘 중 하나는 강제됩니다.
if(true){statement} else {statement}

이처럼 if문을 통해 선택적인 부분 또는 필수적인 부분에 대해 의도를 명확히 표현할 수 있게 됩니다. 이 외에도 여러 제어문들을 특정한 컨텍스트 내에서 어떻게 사용하는지에 따라 그 코드를 읽는 사람들에게 어떤 의미를 전달할 수 있는지, 혹은 이러한 코드의 흐름이 관용적으로 어떻게 사용되어온 것인지 등을 느껴가며 코드를 작성하는 훈련을 더 해야겠다는 생각을 하기도 했습니다.

마치며

이런 강의를 늘 무료로 들을 수 있음을 정말 감사하게 생각하고 있습니다!

저는 맹대표님의 강의를 웹 기획자 시절이었던 S62때 부터 듣기 시작했습니다. 하지만 여태껏 노트에 기록하기만 하고 다시 되짚어보는 시간을 제대로 가지지 못해왔는데, 이번 스터디는 그렇게 해 왔던 제 자신에 대해 반성하게 되는 시간이 되어주기도 하는 것 같습니다. 그만큼 레지스터 C에 저장된 결과를 두뇌에 제대로 저장하지 못해 휘발된 것들이 엄청나게 많았구나 하는 것을 느끼기도 했습니다. 어쩌면 그때는 제대로 들었지만 머리에 담을 수 있는 공간이 부족해서 mov를 해도 옮기지 못했던 문제가 발생하던 시절이였을지도 모른다는 생각이 들기도 합니다 ^^;

앞으로 어떻게든 수업에 대한 후기를 강제로라도 남겨야겠다는 생각을 하게 됩니다. 남은 기간도 열심히 듣고 배워 가야겠습니다! 제가 특히 제대로 알지 못하고 표현한 부분들이 많이 있을 수 있는데, 혹시라도 잘못된 부분이 있다면 꼭 수정할 수 있도록 알려주시면 감사하겠습니다.

긴 글 읽어주셔서 감사합니다 :)