모든 인디고게임-사설홀덤사이트 개발자에게 필요한 구문과 기능부터 놓치면 안 되는 고차원 개념까지, 자바스크립트를 최대한 활용하기 위한 8가지 방법을 소개한다.
인디고게임-사설홀덤사이트는 놀라울 정도로 안정적이고 유연하고 강력한 언어이며, 기본 상태에서 대부분의 상황에 필요한 모든 기능을 제공한다. 자바스크립트 개발의 성공을 위한 기반은 자바스크립트가 무엇을 제공하는지, 이를 프로그램에서 어떻게 활용할지를 면밀히 파악하는 것이다. 현재 자바스크립트가 제공하는 방대한 툴과 라이브러리를 최대한 활용하고자 하는 개발자를 위한 8가지 핵심 개념을 알아보자.
변수 선언 사용
변수는 프로그래밍 자체만큼 오래됐지만 현대 자바스크립트에서도 여전히 중요한 개념이다. 우선 자바스크립트 프로그래밍에서는 let보다 const를 선호한다는 점을 생각해 보자. 이유가 무엇일까?
const는 상수, 즉 변경되지 않는 변수를 선언한다. const의 불변성은 복잡성을 낮춰주므로 개발자들은 가능한 모든 경우 const를 사용한다. 불변성 변수가 어떻게 동작할지, 프로그램 수명 동안 어떻게 바뀔 수 있는지에 대해 생각할 필요가 없다. const를 사용하면 값을 저장해 두고, 값이 변경되면 어떻게 되는지에 대해 신경 쓰지 않고 어디서든 사용할 수 있다.
불변성은 눈에 잘 띄지 않지만 소프트웨어 설계 전반에 자리 잡은 심오한 개념으로, 특히 함수형 프로그래밍과 리액티브 프로그래밍에서 대규모 시스템의 전체적인 구조를 단순화하는 데 활용된다.
const에 대해 알아야 하는 또 다른 중요한 점은 객체와 컬렉션에서의 동작이다. 여기서 const는 변수에 대한 참조 변경을 방지하지만 변수의 내부 상태 변경은 방지하지 못한다. 이는 자바스크립트 내부 구조에 관한 중요한 사실을 드러낸다. (내부적으로 객체 및 컬렉션 변수는 포인터로, 메모리의 한 위치를 점유한다. const를 사용한다는 것은 이 위치를 변경할 수 없음을 의미한다.)
물론 변수가 이름 그대로 변수여야 하는 경우도 있다. 그럴 때 let을 사용하면 된다. 자바스크립트에는 var 키워드도 있다. let과 var의 차이점을 알면 변수 스코핑를 이해하는 데 도움이 되고, 변수 스코핑을 이해하면 잠시 후 설명할 스코프, 클로저와 같은 고급 개념을 이해하는 데 도움이 된다.
let 선언은 변수가 선언된 블록으로 변수를 제한하는 반면 var는 변수가 포함된 스코프로 변수를 “끌어올린다”. var가 접근하기 쉽지만 그만큼 오류가 발생할 가능성도 높다. 코드에서 var를 발견한다면 가급적 let을 사용해 리팩터링하는 것이 좋다.
컬렉션과 함수형 연산자에 대한 이해
함수형 연산자는 현대 자바스크립트의 가장 강력한 기능 중 하나다. map, flatMap, reduce, forEach와 같은 연산자를 사용하면 깔끔하고 직관적인 구문으로 컬렉션에 대해 반복 작업을 수행할 수 있다. 특히 간단한 작업의 경우 이와 같은 함수형 프로그래밍 구조를 사용하면 코드가 명확하게 읽히며, 반복과 관련된 많은 장황한 요소 없이 필요한 의미를 얻을 수 있다.
프로그램을 만들 때 일반적인 목적은 예를 들어 API로부터 응답을 받아 사용자 입력에 따라 처리하는 등 일종의 비즈니스 기능을 처리하는 데 있다. 이 작업 내에서 루프가 필요하지만 루프는 전체적인 목적을 뒷받침하기 위해 필요한 논리 단편일 뿐이다. 즉, 프로그램에서 너무 많은 공간을 차지해서는 안 된다. 함수형 연산자를 사용하면 전체적인 의미를 거의 흐리지 않으면서 루프를 표현할 수 있다.
예를 들면 다음과 같다.
const albums = [
{ artist: “Keith Jarrett”, album: “The Köln Concert”, genre: “Jazz” },
{ artist: “J.S. Bach”, album: “Brandenburg Concertos”, genre: “Classical” },
{ artist: “The Beatles”, album: “Abbey Road”, genre: “Rock” },
{ artist: “Beastie Boys”, album: “Ill Communication”, genre: “Hip Hop”}];
genreInput = “rock”;
console.log(
albums.filter(album => album.genre.toLowerCase() === genreInput.toLowerCase())
)
위 코드의 전체적인 의도는 장르에 따라 앨범 목록을 필터링하는 것이다. albums 배열의 내장 filter 메서드는 전달된 함수가 적용된 새 컬렉션을 반환한다. (원본 배열을 조작하는 것이 아니라 반환하는 이와 같은 스타일은 불변성이 실제로 작동하는 또 다른 예시다.)
루프 논리는 루프를 둘러싼 의미를 뒷받침하는 간단한 본질로 축소된다. 특히 여러 반복자가 포함된 매우 복잡한 루프, 또는 중괄호로 묶인 코드 블록으로 그 뒷부분을 단순화할 수 있는 매우 큰 루프 본문에서 전통적인 루프가 여전히 중대한 역할을 한다는 점에 주목할 필요가 있다.
promise와 async/await 활용
비동기 프로그래밍은 이름 그대로 여러 동작이 동시에 발생하기 때문에 본질적으로 까다롭다. 이는 이벤트가 뒤섞이는 상황을 고려해야 한다는 의미다. 다행히 자바스크립트에는 이러한 개념과 관련된 강력한 추상화가 있다. promise는 비동기 복잡성을 관리하기 위한 1차 방어선이며, async/await 키워드는 promise 위에 또 다른 계층을 제공한다. async/await를 사용하면 동기식처럼 보이는 구문으로 비동기 작업을 작성할 수 있다.
소프트웨어 개발자는 라이브러리에서 promise 또는 async 함수를 사용하는 경우가 많다. 브라우저에 내장된 fetch 함수, 그리고 노드(Node)와 같은 서버 측 플랫폼이 좋은 예다.
async function getStarWarsPerson(personId) {
const response = await fetch(https://swapi.dev/api/people/${personId}/);
if (response.ok) {
// …
}
여기서 정의하는 함수에는 async가 있고, 사용하는 함수(fetch)에는 await가 붙어 있다. 겉으로는 일반적인 동기 코드처럼 보이지만 이 코드는 fetch가 자체적인 시간에 따라 실행되도록 하고 후속 작업이 그 뒤를 따르도록 한다. 이 시퀀스는 fetch가 진행되는 동안 이벤트 루프가 다른 일을 할 수 있게 해준다(이 예에서는 오류 처리를 생략했지만 후에 다룰 예정).
promise는 이해하기 그다지 어렵지 않지만 promise가 있으면 실제 비동기 작업의 의미 체계를 더 깊게 살펴봐야 한다. 더 복잡해지는 대신 매우 강력해진다. Promise 객체는 비동기 작업을 표현하며, 이 객체의 resolve와 reject 메서드는 결과를 나타낸다. 그러면 클라이언트 코드는 콜백 메서드 then()과 catch()를 사용해 결과를 처리한다.
기억해야 할 점은 자바스크립트가 진정한 의미로 동시적이지는 않다는 사실이다. 비동기 구조를 사용해 병렬 처리를 지원하지만 이벤트 루프는 단 하나이며, 이는 하나의 운영체제 스레드를 나타낸다.
알아두면 좋은 5가지 단축 구문
개발자 경험을 개선하려는 자바스크립트의 의지는 강력한 단축 구문에서 잘 드러난다. 이러한 세련된 연산자는 자바스크립트 프로그래밍에서 가장 일반적이면서 성가신 부분을 몇 번의 키 입력만으로 처리할 수 있게 해준다.
스프레드
스프레드(spread) 연산자(또는 생략 연산자)를 사용하면 배열이나 객체의 개별 요소를 참조할 수 있다.
const originalArray = [1, 2, 3];
const copiedArray = […originalArray];
copiedArray.push(‘foo’); // [1,2,3,’foo’]
스프레드는 객체에도 사용할 수 있다.
const person = { name: “Alice”, age: 30 };
const address = { city: “New York”, country: “USA” };
const fullInfo = { …person, …address };
구조 분해
구조 분해(destructing)는 배열이나 객체의 요소를 개별 요소로 “풀어낼 수 있는” 간결한 방법이다.
onst colors = [“red”, “green”, “blue”];
const [firstColor] = colors;
firstColor === “red”;
const person = { name: “Alice”, age: 30, city: “London” };
const { city } = person;
city === “London”;
이 구문은 여러 상황에 사용되며, 특히 모듈을 가져올 때 자주 사용된다.
const express = require(‘express’);
const { json, urlencoded } = require(‘express’);
구조 분해는 명명된 매개변수와 기본값도 지원한다.
옵셔널 체이닝
옵셔널 체이닝(Optional chaining)은 기존의 수동 null 검사 방식을 간편한 하나의 연산자로 바꿔준다.
const street = user?.profile?.address?.street;
이 점 액세스 체인의 루트나 브랜치 중 하나라도 null이면 null 포인터 예외가 발생하는 것이 아니라 전체가 null이 된다. 즉, 걱정 한 가지를 덜 수 있다.
논리 할당 연산자
논리 할당 연산자(logical assignment)는 and, or, strict null 변형이 있다. 후자는 다음과 같다.
let myString = null;
myString ??= “Foo”;
myString ??= “Bar”;
myString === “Foo”;
myString은 실제로 null(또는 undefined)인 경우에만 변경된다.
null 병합
null 병합(nullish coalescence)은 null일 수 있는 변수와 기본값 중에서 쉽게 선택할 수 있게 해준다.
let productName = null;
let displayName = productName ?? “Unknown Product”;
productName === “Unknown Product”;
이와 같은 편의 기능은 현대 자바스크립트의 특징이다. 적절히 활용하면 더 우아하고 가독성이 좋은 코드를 작성할 수 있다.
스코프와 클로저를 두려워하지 말 것
자바스크립트를 언어로 사용할 때 스코프와 클로저는 필수적인 개념이다. 스코프 개념은 모든 언어의 핵심이다. 스코프는 변수의 가시성 범위, 즉 변수를 선언하면 그 변수를 어디에서 볼 수 있고 사용할 수 있는지를 나타낸다.
클로저는 특수한 상황에서 변수 스코프의 작동 방식이다. 새로운 함수 스코프가 선언되면 해당 함수는 주변 컨텍스트의 변수들을 사용할 수 있게 된다. 어렵게 느껴지는 이름에 겁먹을 필요가 없는 단순한 개념이다. 클로저라는 이름은 주변 스코프가 안쪽 스코프를 “감싸서 닫는다”는 의미일 뿐이다.
클로저에는 강력한 의미가 있다. 클로저를 사용해서 더 큰 컨텍스트에서 중요한 변수를 정의한 다음 이들 변수에 작용하는 일련의 기능 블록을 정의할 수 있다(논리가 강력하게 제한되거나 캡슐화됨). 의사 코드로 표현하면 다음과 같다.
outer context
variable x
function context
do stuff with x
x now reflects changes
The same idea in JS:
function outerFunction() {
let x = 10;
function innerFunction() {
x = 20;
}
innerFunction();
console.log(x); // Outputs 20
}
outerFunction();
이 예에서 innerFunction()은 클로저이며, 부모 스코프(렉시컬 스코프라고도 하며, 클로저가 호출된 스코프가 아닌 선언된 스코프의 변수에 액세스할 수 있음을 의미)의 변수에 액세스한다.
앞서 언급했듯이 함수형 프로그래밍의 원칙 중 하나는 불변성이다. 이는 깔끔한 디자인을 위해 변수 변경을 피한다는 개념이다. 예제에서 x를 수정하는 것은 이 지침에 어긋나지만 변수 액세스는 반드시 필요한 능력이다. 중요한 것은 작동 방식을 이해하는 것이다.
클로저 사용은 map, reduce와 같은 함수형 컬렉션 연산자에서 더욱 중요해진다. 이러한 연산자는 매우 깔끔한 구분으로 작업을 수행할 수 있게 해줄 뿐만 아니라, 선언된 렉시컬 스코프에도 액세스할 수 있다.
매끄러운 오류 처리
예전에 컴퓨팅 오류에 버그라는 이름이 붙은 것은 실제 나방이 회로 속으로 날아들었던 사건 때문이라는 사실을 알고 있는가? 지금은 AI 비서가 확신을 갖고 생성하는 코드가 예측할 수 없는 방식으로 오류를 일으키면서 버그가 발생한다(관련 내용은 잠시 후에 더 자세히 다룸).
프로그래머는 강력한 오류 처리 관행에서 벗어날 수 없다. 다행히 현대 자바스크립트는 오류 처리를 정교하게 개선했다. 오류 처리는 기본적으로 두 가지 형태, 일반적인 동기 코드 오류와 비동기 이벤트 오류로 나뉜다. 오류 객체는 오류 메시지, 원인 객체, 그리고 오류가 발생했을 때의 호출 스택 덤프인 스택 트레이스를 제공한다.
일반적인 오류에 대한 주 메커니즘은 고전적인 try-catch-finally 블록과 그 기원인 throw 키워드다. 비동기 오류는 약간 더 까다롭다. catch 콜백과 promise에 대한 reject 호출을 사용하고 비동기 함수에 대해 catch 블록을 사용하는 구문은 그리 복잡하지는 않지만 비동기 호출을 면밀히 주시하면서 모든 부분이 처리되고 있는지 확인해야 한다.
신중한 오류 처리는 사용자 경험에 큰 영향을 미친다. 실패를 매끄럽게 처리하고 오류를 추적해야 하며, 오류를 그대로 두고 지나가면 안 된다.
제대로 작동하는 프로그래밍 스타일 사용하기
이 방법은 사실 상식의 문제다. 본질적으로, 현명한 자바스크립트 개발자는 프로그래밍 패러다임에 있어 초교파적이다. 자바스크립트는 객체 지향 프로그래밍, 함수형 프로그래밍, 명령형 프로그래밍, 리액티브 프로그래밍 스타일을 제공한다. 개발자라면 그 기회를 최대한 활용하지 않을 이유가 없다. 사용 사례에 따라 하나의 스타일을 중심으로 프로그램을 구축할 수도 있고 여러 스타일을 혼합할 수도 있다.
현대 자바스크립트는 강력한 클래스 지원과 프로토타입 상속을 제공한다. 이는 자바스크립트의 전형적인 특징이다. 즉, 어떤 작업을 수행하는 데는 한 가지 방법만 있는 것은 아니며 특정한 정답이 있는 것도 아니다. 클래스는 객체 지향 영역에서 잘 알려져 있고 대부분의 개발자가 현재 사용하고 있지만, 프로토타입도 알아둘 필요가 있는 유용하고 중요한 개념이다.
자바스크립트는 함수형 프로그래밍의 인기를 높이는 역할도 했다. 자바스크립트 덕분에 함수형 프로그래밍 접근 방식이 알려지면서 다른 언어, 심지어 자바와 같은 강력한 객체 지향 언어도 함수형 프로그래밍의 일부를 핵심 요소로 채택했다. 특히 컬렉션을 다룰 때 함수형 연산자가 꼭 필요한 경우가 종종 있다.
자바스크립트는 RxJS, 시그널(Signals)과 같은 인기 있는 리액티브 툴을 갖춘 강력한 리액티브 언어이기도 하다. 다만 리액티브 프로그래밍은 광범위하고 흥미로운 주제이며, 앵귤러, 리액트와 같은 리액티브 프레임워크와 혼동해서는 안 된다. 이 둘은 서로 다르다.
또한 자바스크립트는 그 이름에서 알 수 있듯이 원래 스크립팅 언어였다는 사실을 잊으면 안 된다. 때로는 고전적인 명령형 스크립트의 디테일 수준이 당면한 작업에 딱 맞은 경우도 있다. 특히 일회성 시스템 유틸리티와 같이 이 방식이 필요한 상황에서는 주저할 필요 없이 사용하면 된다.
자바스크립트를 사용하는 방법은 많다. 하나만 고집하는 것은 본인에게 손해이다.
AI의 도움에 대한 생각
일단 AI 프로그래밍이 놀랍도록 유용하다는 사실은 인정하자. AI 프로그래밍 어시스턴트는 등장한 지 얼마 되지 않았지만 이미 어떤 형태로든 AI 코딩 도움 없이 소프트웨어를 만드는 것은 거의 생각할 수 없는 일이 됐다.
‘거의’라고 말한 이유는 현대 IDE에도 해당되는 이야기이기 때문이다. VS 코드, 인텔리J, 이클립스 등을 사용하지 않고 개발할 사람은 없을 것이라고 생각하겠지만 사실 Posix 명령줄과 Vim 또는 Emacs를 조합해 사용하는 개발자들도 있고, 그 모습을 보면 마우스로 조작하는 시각적 IDE가 오히려 거추장스럽게 느껴진다. 수많은 단축키과 근육 기억, 그리고 시스템에 대한 풍부한 지식의 조화에서 나오는 속도와 효율성은 눈으로 직접 보고도 믿기 어려울 정도다.
개발자는 IDE를 잘 활용하게 된 것과 거의 동일한 방식으로 AI 코딩을 최대한 활용하게 될 것이다. AI의 도움을 받지 않을 때 더 효과적으로 코딩하는 개발자도 있지만, 그건 그 개발자들이 필수 요소를 이미 마스터했기 때문이다. 이들은 크고 복잡한 시스템에서도 가장 가치가 큰 변화를 파악하고 그 변화를 이뤄낼 수 있다.
어떤 툴을 사용하든 기본을 잘 알수록 개발자 자신과 개발자가 만드는 프로그램도 더 좋아진다. 그 기본은 언어와 프레임워크, 플랫폼, 운영체제 전반에서 작용하며 크고 작은 모든 구현에서 코딩의 기반이 된다. 중요한 프로젝트에 대해 이해관계자들과 대화할 때, 그동안 쌓아온 모든 지식과 준비 과정이 비로소 결실을 맺게 되고 그 순간에 가장 큰 보람을 느끼게 된다.
AI가 그 일을 대신 해줄 수는 없지만 AI 툴을 사용해서 프로그래밍을 위한 노력에 힘을 보탤 수 있다.