PHpullh

STATIC LEARNING LIBRARY

C++ 학습 예제

200개 예제를 개별 HTML 문서로 제공합니다. 검색엔진과 공유 링크에서 각 주제를 바로 열 수 있습니다.

Hello World 출력

main 안에서 println! 하나로 Hello, World 1! 문자열을 표준 출력에 내보냅니다. 새 프로젝트가 실제로 빌드되고 실행되는지 확인하는 첫 단계로 쓰입니다. println!은 함수가 아니라 매크로여서 포맷 문자열과 인자 개수가 컴파일 시점에 검사되고, …

변수 값을 같이 출력

env::var("APP_ENV")로 환경 변수를 조회하고, 없으면 local을 대신 써서 출력합니다. 실행 환경에 따라 동작을 나누는 프로그램의 가장 앞자리에 오는 코드입니다. 반환값이 Result라는 점이 핵심이며, unwrap_or_else는 실패했을 때만 클로저…

함수로 인사 만들기

함수를 정의해 인사를 만들지는 않고, 리터럴 하나를 그대로 출력하는 코드입니다. 로직을 넣기 전에 실행 경로만 확인하고 싶을 때 이 정도면 됩니다. 큰따옴표로 적은 문자열은 String이 아니라 프로그램 바이너리에 박혀 있는 &'static str이라, 힙 할당이 전혀…

배열을 순회하며 인사

배열 순회는 없고, 환경 변수 조회 결과 또는 기본값 local을 한 줄 출력합니다. 배포 단계 이름을 로그 맨 앞에 남기는 용도로 자주 보이는 패턴입니다. env::var는 변수가 없을 때뿐 아니라 값이 올바른 UTF-8이 아닐 때도 오류를 내며, 두 경우는 VarE…

명령행 인자 읽기

명령행 인자를 읽는 코드는 들어 있지 않고 고정 문자열만 화면에 나옵니다. 인자 처리를 붙이기 전 단계의 뼈대라고 보면 됩니다. 실제로 인자를 받으려면 std::env::args()를 순회해야 하는데, 첫 항목은 사용자가 준 값이 아니라 실행 파일 경로라는 점을 놓치기…

템플릿 문자열 인사

템플릿 문법 대신 환경 변수 값을 읽어 그대로 찍는 코드가 들어 있습니다. 설정 값이 제대로 주입됐는지 눈으로 확인할 때 이 한 줄이면 충분합니다. {} 자리에는 Display를 구현한 값만 들어갈 수 있고, 구조체처럼 Debug만 있는 타입은 {:?}로 적어야 합니다…

유니코드 메시지 출력

유니코드를 다루는 부분 없이 ASCII 문자열 한 줄이 출력됩니다. 다국어 메시지를 넣더라도 코드 모양은 이것과 달라지지 않습니다. 러스트 소스 파일과 str 타입 모두 UTF-8로 고정되어 있어 한글이나 이모지를 그대로 적어도 되지만, len()은 글자 수가 아니라 …

환경 변수 읽기

제목 그대로 APP_ENV를 읽되 값이 없으면 local로 떨어지는 안전한 조회입니다. 컨테이너나 CI처럼 설정이 환경 변수로 주입되는 곳에서 그대로 쓰입니다. 환경 변수는 프로세스가 시작할 때 받은 복사본이라 나중에 셸에서 바꿔도 반영되지 않고, 실행 중 변경은 스레…

컬렉션 요약 출력

컬렉션을 요약하지는 않고 짧은 문자열 한 줄을 출력하는 것이 전부입니다. 출력 자체가 잘 나오는지 확인하는 자리에 놓기 좋습니다. 표준 출력은 줄 단위로 비워지고 호출마다 잠금을 잡기 때문에, 반복문에서 대량으로 찍어야 한다면 stdout().lock()을 한 번 잡고…

버전 정보 출력

버전 문자열이 아니라 환경 이름을 읽어 출력하는 코드가 실려 있습니다. 어떤 설정으로 떠 있는지 기동 로그에 남기는 용도에 맞습니다. 기본값을 unwrap_or("local".to_string())로 주면 성공했을 때도 문자열을 미리 만들어 버리므로, 여기처럼 unwr…

숫자 변수 선언

let count = 1은 타입 표기 없이 기본 정수형 i32로 추론되고, 이어지는 if가 값을 만들어 label에 넣습니다. Rust에서 if는 문이 아니라 식이라 두 갈래가 같은 타입을 내놓아야 하며 여기서는 둘 다 &str입니다. 변수는 기본이 불변이라 값을 바꾸…

문자열 상수 선언

제목은 문자열 상수지만 코드는 정수 벡터를 만들어 sort로 오름차순 정렬한 뒤 {:?}로 찍습니다. 정렬이 제자리에서 일어나기 때문에 let mut가 필요하고, 반환값이 없어 결과를 다른 변수에 받을 수 없습니다. 진짜 상수는 const NAME: &str처럼 타입을…

불리언 플래그 만들기

count > 5라는 비교 결과를 곧바로 if에 넣어 문자열 두 개 중 하나를 고릅니다. count가 3이라 작은 쪽이 나옵니다. 플래그로 쓰려면 비교식을 bool 변수에 담으면 되고, Rust는 정수나 빈 문자열을 참·거짓으로 자동 해석하지 않아 조건 자리에는 오직 …

문자열 길이 구하기

문자열 길이와는 무관하게 vec![3, 1, 4, 4]를 정렬해 중복된 4가 나란히 붙은 결과를 출력합니다. sort는 안정 정렬이라 같은 값끼리 원래 순서가 유지되며, 그 대가로 임시 버퍼를 씁니다. 순서 보존이 필요 없다면 sort_unstable이 추가 할당 없이…

문자열 정리하기

count가 정확히 5라서 > 비교가 거짓이 되고 작은 쪽 라벨이 출력됩니다. 경계값이 어느 편에 붙는지 확인할 때 이렇게 값을 바꿔 가며 돌려 보게 됩니다. 문자열을 다듬는 trim은 원본을 건드리지 않고 잘라 낸 &str를 돌려주므로, 결과를 쓰려면 따로 받아 두어…

숫자 합계 계산

합계를 구하는 대신 네 개짜리 벡터를 정렬해 출력합니다. 실제 합은 values.iter().sum::<i32>()로 얻는데, 결과 타입을 적어 주지 않으면 컴파일러가 추론에 실패합니다. 릴리스 빌드에서는 정수 덧셈이 넘쳐도 패닉 없이 값이 뒤집히므로, 큰 값을 더할 …

나머지 연산 확인

count 7과 5를 비교해 큰 쪽 라벨을 고르는 흐름이며 나머지 연산자는 쓰이지 않습니다. Rust의 %는 피제수 부호를 따라가서 -7 % 3이 -1이 되므로, 부호가 섞인 값으로 짝수를 판정할 때는 rem_euclid를 씁니다. 0으로 나누면 컴파일은 통과해도 실행…

if else 분기

분기문 대신 벡터 정렬이 실행되어 1, 3, 4, 8 순서가 출력됩니다. sort는 원소가 Ord를 구현해야 호출할 수 있어서 f64 벡터에는 그대로 쓸 수 없고, sort_by에 partial_cmp를 넘겨야 합니다. NaN 때문에 부동소수점이 전순서를 이루지 못하는…

switch 또는 match 사용

match 대신 if와 else 한 쌍으로 두 갈래를 나눕니다. 같은 판정을 match count { 0..=5 => ..., _ => ... }처럼 범위 패턴으로 옮길 수 있습니다. match는 모든 경우를 덮지 않으면 컴파일이 거부되기 때문에, 값의 종류가 늘어날 …

null 기본값 처리

널 처리와는 상관없이 벡터를 정렬해 찍는 코드입니다. Rust에는 널이 없고 값이 없을 수 있는 자리는 Option으로 표현하며, 기본값은 unwrap_or나 unwrap_or_else로 채웁니다. 앞쪽은 인자를 항상 미리 계산하므로 기본값 만들기가 비싸면 클로저를 받…

삼항 연산자 사용

Rust에는 ?: 문법이 없어서, 이 코드처럼 값을 내놓는 if 식이 삼항 연산자 자리를 대신합니다. 조건에 따라 짧은 문자열 하나를 고르는 흔한 형태입니다. else를 생략하면 전체 타입이 ()가 되어 대입이 막히므로, 식으로 쓸 때는 양쪽 갈래가 반드시 있어야 합니…

for 반복문

반복문 없이 sort 한 번으로 순서를 정리합니다. 직접 for value in &values로 돌면 원소를 빌려 읽기만 하고, &를 빼면 벡터가 통째로 소비되어 뒤에서 다시 쓸 수 없습니다. 정렬은 원소 수에 로그를 곱한 만큼 비교하므로, 최댓값 하나만 필요하다면 i…

while 누적 계산

누적 변수 없이 정수 하나를 임계값과 비교해 라벨을 정합니다. 합을 쌓는 while로 바꾸려면 카운터와 누적값 모두 mut여야 하고, 조건을 갱신하는 줄을 빠뜨리면 그대로 무한 루프가 됩니다. 조건 없이 도는 반복에는 loop를 쓰는데, break에 값을 실어 밖으로 …

break 와 continue

제어 흐름 키워드는 나오지 않고 벡터 정렬 결과만 출력됩니다. break와 continue는 중첩 루프에서 'outer 같은 라벨을 붙여 어느 루프를 벗어날지 지정할 수 있습니다. 정렬된 데이터에서 값을 찾는다면 루프를 처음부터 도는 대신 binary_search로 비…

배열 생성과 순회

배열 대신 정수 변수 하나와 조건식이 전부입니다. Rust의 배열 [i32; 4]는 길이가 타입에 박혀 있어 컴파일 시점에 크기가 정해지고, 길이가 변하는 자료는 Vec을 씁니다. 범위를 벗어난 인덱스 접근은 실행 중 패닉이므로, 실패를 값으로 받고 싶으면 get이 돌…

맵 또는 객체 생성

맵을 만들지 않고 벡터를 정렬합니다. 키와 값의 짝은 HashMap이 맡는데, 삽입 순서를 보존하지 않아 순회 순서가 실행마다 달라질 수 있습니다. 순서가 필요하면 BTreeMap이 키 순으로 정렬해 주지만 조회 비용이 상수 시간 대신 키 개수의 로그에 비례합니다.

정렬 기초

제목은 정렬이지만 코드는 값 하나를 기준과 비교해 문자열을 고르는 조건식입니다. 비교 자체가 정렬의 재료라서, sort_by에 넘기는 클로저도 이런 비교를 Ordering으로 돌려주는 함수입니다. 그 클로저가 일관되지 않은 순서를 반환하면 결과가 뒤죽박죽이 되거나 패닉…

튜플 또는 페어 사용

튜플 대신 단일 정수 벡터를 정렬해 출력합니다. 페어를 정렬하면 (i32, &str)처럼 튜플이 사전식으로 비교되어 첫 요소가 같을 때만 두 번째를 봅니다. 튜플은 필드에 이름이 없어 .0, .1로 접근하는데, 원소가 서너 개를 넘어가면 구조체로 옮기는 편이 읽기 쉽습…

타입 변환

변환 없이 i32 값 하나를 그대로 비교합니다. Rust는 정수끼리도 자동 변환을 해 주지 않아 as나 try_into로 명시해야 합니다. as는 값이 목표 타입에 안 들어가면 조용히 잘라 내지만 try_into는 Result로 실패를 알려 주므로, 범위를 장담할 수 …

날짜 문자열 만들기

날짜와는 무관하게 벡터를 정렬해 {:?}로 출력합니다. 표준 라이브러리에는 달력 개념이 없어 SystemTime은 기준 시각으로부터의 경과만 주고, 사람이 읽는 날짜 형식은 별도 크레이트가 필요합니다. 시간 간격을 잴 목적이라면 시스템 시계가 바뀌어도 역행하지 않는 I…

기본 함수 선언

add는 매개변수 두 개와 반환값에 모두 number를 붙인 가장 기본적인 함수 선언입니다. 인자 타입을 적어 두면 호출부에서 문자열을 넘겼을 때 실행 전에 잡힙니다. 반환 타입은 추론으로도 채워지지만, 명시해 두면 구현을 잘못 고쳤을 때 오류가 호출부가 아닌 함수 안…

두 값을 더하는 함수

나머지 매개변수 ...values로 받은 숫자들을 reduce로 접어 총합을 냅니다. 인자 개수가 정해지지 않은 유틸리티를 만들 때 배열을 따로 감싸지 않아도 되는 방식입니다. values는 언제나 number[]이며 인자를 하나도 안 넘기면 빈 배열이 되어, 초깃값 …

기본값 매개변수

makeCounter가 지역 변수 current를 감싼 함수를 돌려주고, 반환된 next를 두 번 부르면 1과 2가 나옵니다. 외부에 노출하지 않을 상태를 함수 안에 가둘 때 쓰는 구조입니다. console.log(next(), next())는 인자를 왼쪽부터 평가하므…

가변 인자 합계

제네릭 first<T>가 배열의 0번 원소를 꺼내고, 문자열 배열을 넘겼으므로 결과 타입은 string | undefined로 좁혀집니다. 어떤 타입의 목록이든 첫 값을 안전하게 꺼내야 할 때 이 서명이 적합합니다. 반환 타입에 undefined를 넣어 둔 덕분에 빈 …

객체 또는 구조체 반환

객체를 만들어 돌려주는 대신 두 수의 합이라는 원시값 하나만 반환합니다. 반환할 정보가 여럿으로 늘어나면 이 자리에 객체 리터럴이 들어가고 타입도 함께 커집니다. 여러 값을 담은 객체를 반환할 때는 인터페이스를 따로 선언해 두어야 호출부에서 필드 이름 오타가 걸러집니다…

람다 함수

람다 대신 function 선언을 쓰되, 내부 reduce 콜백은 화살표 함수로 작성한 혼합 형태입니다. 짧은 콜백은 화살표로, 이름이 필요한 최상위 함수는 선언문으로 두는 조합이 읽기 편합니다. 두 문법의 실질적 차이는 길이가 아니라 this 처리와 호이스팅 여부에 …

클로저 카운터

카운터 상태가 makeCounter 호출마다 새로 만들어지며, next는 그 하나를 계속 증가시킵니다. 인스턴스마다 독립된 번호가 필요할 때 클래스 없이 해결하는 방법입니다. makeCounter()를 한 번 더 호출하면 완전히 다른 current가 생겨 번호가 1부터…

재귀 팩토리얼

재귀 대신, 배열에서 첫 원소를 한 번 꺼내는 제네릭 함수가 정의돼 있습니다. 타입 매개변수 T가 인자에서 string으로 추론되어 별도 지정 없이 호출됩니다. 인덱스 접근은 범위를 벗어나도 예외 없이 undefined를 주므로, 기본 설정에서는 반환 타입에 이를 직접…

고차 함수 map

map에 넘길 만한 형태이긴 하지만 여기서는 add(9, 2)를 직접 호출해 결과만 찍습니다. 고차 함수에 넘길 콜백도 먼저 이렇게 단독으로 검증해 두면 문제 범위를 좁히기 쉽습니다. add를 그대로 map에 넘기면 두 번째 인자로 인덱스가 들어가 의도와 다른 값이 나…

조건 함수 predicate

참·거짓을 돌려주는 술어가 아니라 숫자 여러 개를 합산하는 함수입니다. 조건 검사도 같은 골격을 쓰되 reduce 대신 every나 some으로 바꾸면 됩니다. 누적 콜백의 total 타입은 초깃값에서 추론되므로 0 대신 null을 넣으면 이후 덧셈에서 타입 오류가 납…

정렬용 비교 함수

정렬 비교자와는 별개로, 호출할 때마다 값이 1씩 커지는 클로저를 만들어 두 번 실행합니다. 정렬 과정에서 비교 횟수를 세는 계측기를 붙일 때 이런 카운터가 그대로 쓰입니다. ++current는 증가 후 값을 돌려주므로 첫 호출이 0이 아니라 1을 반환합니다.

문자열 파서

문자열을 해석하는 코드는 없고, 배열의 첫 원소를 꺼내는 제네릭 함수만 있습니다. 파서를 만들 때도 토큰 목록의 맨 앞을 들여다보는 이런 함수가 기본 재료가 됩니다. 여기서 T를 any로 바꾸면 반환값의 타입 정보가 사라져 뒤따르는 자동완성과 검사까지 무력해집니다.

함수 팩토리

팩토리 대신 두 수를 더해 값을 반환하는 평범한 함수가 정의돼 있습니다. 설정을 미리 받아 함수를 만들어 내는 구조로 확장하려면 이 함수를 감싸는 바깥 함수가 필요합니다. 함수 선언문은 호이스팅되어 정의보다 위에서도 호출되지만, const에 담은 함수 표현식은 그렇지 …

메모이제이션

캐시 없이 매번 배열 전체를 순회해 합을 다시 계산합니다. 결과를 재사용하려면 인자를 키로 삼는 저장소를 함수 바깥에 두어야 합니다. 나머지 매개변수는 호출마다 새 배열을 만들기 때문에, 배열 자체를 키로 쓰면 값이 같아도 참조가 달라 캐시가 절대 맞지 않습니다.

커링

커링 형태는 아니지만, 함수가 함수를 반환한다는 같은 뼈대를 makeCounter가 보여 줍니다. 인자를 하나씩 나눠 받는 커링도 결국 이렇게 안쪽 함수를 돌려주는 구조입니다. 반환 타입을 적지 않았어도 TypeScript가 () => number로 추론하므로 next…

compose 함수

합성 함수는 없고, 배열의 첫 값을 꺼내는 제네릭 함수 하나가 전부입니다. 여러 함수를 이어 붙이는 compose를 만들 때도 각 조각은 이처럼 작은 단일 책임 함수여야 합니다. 제네릭 함수를 합성에 넘기면 타입 매개변수가 그 자리에서 고정되므로, 호출마다 다른 타입으…

제네릭 함수

제네릭이 아니라 number로 고정된 덧셈 함수이며, add(17, 2)의 결과를 출력합니다. 다루는 타입이 하나뿐이라면 굳이 타입 매개변수를 도입할 이유가 없습니다. 이 함수를 <T>로 일반화하면 + 연산이 임의의 T에는 허용되지 않아 제약을 따로 걸어야 합니다.

콜백으로 완료 알림

완료 콜백은 등장하지 않고, 인자로 받은 숫자들을 즉시 합산해 동기적으로 반환합니다. 결과를 기다릴 필요가 없는 계산은 콜백이나 Promise로 감싸지 않는 편이 단순합니다. 안쪽 reduce 콜백은 값을 돌려주기만 할 뿐 완료를 알리는 콜백과는 역할이 전혀 다릅니다.

reducer 함수

리듀서와 비슷하게 이전 값을 바탕으로 다음 값을 만들지만, 상태를 인자로 받지 않고 클로저 안에 감춰 둡니다. 액션 로그가 필요 없는 지역 카운터라면 이 방식이 훨씬 가볍습니다. 반대로 상태가 함수 밖에서 보이지 않기 때문에 스냅숏을 찍거나 되돌리는 동작은 만들 수 없…

파이프라인 함수

파이프라인 대신 배열의 첫 원소를 반환하는 함수 하나가 정의되고, 문자열 배열에 대해 호출됩니다. 여러 단계를 잇는 흐름에서도 각 단계는 이렇게 입력 하나에 출력 하나인 모양이 좋습니다. values[0]은 인덱스 검사를 켜지 않는 한 타입상 T로 취급되므로, 반환 타…

기본 클래스 또는 구조체

생성자 매개변수에 public을 붙여 name과 level 필드를 별도 선언 없이 만들고, summary()가 둘을 콜론으로 이어 붙인 문자열을 돌려줍니다. 데이터와 그 데이터를 다루는 동작을 한 덩어리로 묶을 때 쓰는 형태입니다. 이 매개변수 프로퍼티 문법은 Type…

생성자와 초기화

Connection은 mode만 받는 생성자를 두고, static local()이 "local"을 채운 인스턴스를 만들어 반환합니다. 초기화 조합이 몇 가지로 정해져 있을 때 이름 붙은 진입점을 제공하는 방식입니다. 정적 메서드는 인스턴스가 아니라 클래스 객체에 붙기 …

인스턴스 메서드

Logger에는 인자를 그대로 출력하는 log가 있고, Service는 생성자로 받은 로거를 run() 안에서 호출합니다. 호출 대상 객체를 밖에서 넣어 주므로 로거 구현을 바꿔 끼우기 쉬워집니다. 인스턴스 메서드는 프로토타입에 한 번만 만들어져 인스턴스마다 복제되지 …

정적 메서드

여기서는 클래스 대신 Readonly<{ name: string; tags: string[] }>로 Profile 타입을 정의하고 객체 리터럴 하나를 그 타입으로 고정했습니다. 값 자체를 바꾸지 못하게 표시해 두고 싶을 때 쓰는 최소 형태입니다. Readonly는 한 …

상속 또는 확장

제목과 달리 코드에는 extends가 없고, name과 level을 갖는 User 클래스 하나가 summary()로 요약 문자열을 만들 뿐입니다. 계층이 한 단계면 이렇게 단일 클래스로 두는 편이 읽기 쉽습니다. 실제로 상속을 넣는 순간 파생 클래스 생성자는 this를…

인터페이스 또는 trait

interface 선언은 등장하지 않고, mode 필드와 정적 팩터리 local()을 가진 구체 클래스만 있습니다. 규약이 하나뿐일 때는 이 정도로 충분합니다. TypeScript는 구조적 타입 시스템이라 필드와 메서드 모양만 맞으면 implements를 적지 않아도 …

캡슐화

private logger로 선언해 Service 바깥에서는 로거 필드에 접근할 수 없고, 노출되는 통로는 run() 하나뿐입니다. 내부 협력 객체를 감추고 공개 표면을 좁힐 때 이렇게 씁니다. 다만 private는 컴파일 시점 검사일 뿐이라 트랜스파일된 JS에서는 o…

합성으로 기능 묶기

타입 조합만으로 name과 tags를 한 형태로 묶은 뒤 Readonly를 씌워 Profile을 만들었습니다. 상속 계층을 세우지 않고 필요한 조각을 붙여 형태를 정의하고 싶을 때 적합합니다. 타입 별칭은 컴파일 후 완전히 사라지므로 런타임에는 Profile이라는 이름…

팩터리 메서드

코드에는 팩터리 함수가 없고 new User("Ada", 9)로 직접 인스턴스를 만든 뒤 summary() 결과를 출력합니다. 생성 규칙이 단순하면 굳이 감싸지 않는 편이 낫습니다. 팩터리를 도입해 이득을 보는 시점은 생성 시 검증이나 분기가 붙거나, 반환 타입을 구체…

열거형 상태 표현

상태 값 mode는 enum이 아니라 그냥 string으로 선언되어 있고, local()이 그중 하나를 미리 채워 줍니다. 값이 몇 개로 한정된 상태를 표현할 때 흔히 마주치는 출발점입니다. string을 "local" | "remote" 같은 리터럴 유니온으로 좁히면…

불변 데이터 객체

불변 데이터 대신 서비스 조립이 보입니다. Service는 로거 참조를 생성자에서 한 번 받아 필드에 저장하고 이후 run()에서 재사용합니다. 참조를 생성 시점에 고정하면 실행 도중 대상이 바뀌지 않아 추적이 쉬워집니다. 필드에 readonly를 붙여도 막히는 것은 …

빌더 패턴

단계별로 값을 채워 가는 빌더 대신, 완성된 객체 리터럴을 Readonly 타입에 한 번에 대입합니다. 필드 수가 적고 모두 필수라면 이 직접 생성이 가장 짧습니다. 빌더가 필요해지는 지점은 선택 필드가 많아 생성 도중 부분 상태를 허용해야 할 때인데, Readonly…

다형성

출력되는 것은 User 인스턴스의 summary() 호출 결과 하나이고, 오버라이드하는 하위 타입은 없습니다. 동작이 한 종류일 때의 기본형입니다. TypeScript에서는 summary(): string이라는 모양만 같으면 상속 관계가 없는 별개 클래스도 같은 자리에…

의존성 주입

local()은 "local"이라는 값을 메서드 안에 직접 적어 넣고 인스턴스를 만듭니다. 설정이 고정된 개발용 진입점으로는 편리한 방식입니다. 반대로 이 문자열을 호출자가 넘기도록 바꾸면 테스트에서 다른 모드를 주입할 수 있는데, 지금 형태는 값이 메서드 본문에 묶여…

값 객체 비교

비교 코드는 없고, Logger 인스턴스를 Service에 넘겨 run()을 호출하는 흐름만 있습니다. 협력 객체를 다룰 때는 동일성보다 동작이 관심사입니다. 참고로 클래스 인스턴스는 ===가 참조를 비교하므로, 필드 값이 완전히 같은 두 객체도 서로 다른 것으로 판정…

저장소 객체

저장소 대신 조회용 데이터 형태가 나옵니다. Profile은 Readonly로 감싼 객체 타입이고 profile 상수가 그 타입을 따릅니다. 읽기 전용으로 넘겨 줄 결과 모델을 정의할 때 이런 형태를 씁니다. const는 변수 재대입만 막고 속성 변경은 막지 않기 때문…

서비스 객체

User는 상태를 들고 있으면서 summary()로 자기 데이터를 가공합니다. 상태 없는 서비스 객체와 달리, 데이터에 가까운 로직은 이렇게 모델 안에 두는 편이 호출부가 단순해집니다. 반대로 외부 I/O가 섞이기 시작하면 모델에서 떼어 내야 테스트에서 대역으로 바꿔 …

DTO 매핑

매핑 함수 없이, mode 하나만 가진 Connection을 local()로 생성합니다. 전달 대상 필드가 하나라면 별도 DTO 없이 이렇게 두어도 충분합니다. 다만 클래스 인스턴스를 그대로 JSON.stringify에 넘기면 메서드는 빠지고 필드만 남으므로, 응답 모…

변경 불가능 업데이트

값을 갱신하는 부분은 없고, 생성자에서 받은 로거를 그대로 유지한 채 run()이 호출됩니다. 상태 변경 없이 동작만 위임하는 구조입니다. 객체를 갱신할 때 흔히 쓰는 스프레드 복사는 한 겹만 새로 만들기 때문에, 중첩된 객체나 배열은 원본과 같은 참조를 계속 공유한다…

확장 스타일 동작

확장 메서드를 붙이는 대신, Readonly 유틸리티 타입으로 기존 객체 형태를 감싸 새 타입 Profile을 파생시켰습니다. 원본 정의를 건드리지 않고 성질만 바꾸고 싶을 때의 방법입니다. TypeScript에는 다른 언어의 확장 메서드가 없어서, 동작을 더하려면 별…

map 변환

items.map이 각 요소에 2를 곱한 새 배열을 만들어 출력합니다. 원본은 그대로 남아 있어서 변환 결과만 따로 쓰고 싶을 때 적합합니다. map은 항상 원본과 같은 길이의 배열을 돌려주므로, 개수를 줄이려면 filter를 앞뒤에 붙여야 합니다.

filter 필터링

문자열 배열을 Set에 넣었다가 스프레드로 다시 배열로 꺼내 중복된 값을 하나로 만듭니다. 필터링보다는 중복 제거에 가까운 코드입니다. Set은 === 기준으로 비교해서, 내용이 같은 객체라도 참조가 다르면 둘 다 남습니다.

reduce 합계

reduce가 누산 객체를 돌면서 team별 배열에 점수를 밀어 넣어 팀마다 점수 목록을 만듭니다. 합계가 아니라 그룹핑에 쓰인 형태입니다. ??= 덕분에 키가 없을 때만 빈 배열이 생기고, 누산기에 타입을 주지 않으면 인덱스 접근에서 오류가 납니다.

첫 요소 찾기

1부터 5까지의 배열을 slice로 두 개씩 잘라 조각 목록을 만듭니다. 첫 요소를 찾는 대신 구간을 나누는 코드입니다. 마지막 조각은 남은 개수만큼만 채워지므로, 크기가 항상 같다고 가정한 후속 코드는 깨집니다.

Set 중복 제거

요소마다 2를 곱한 배열을 출력합니다. 중복 제거가 주제라면 new Set(items)로 감싸는 단계가 더 필요합니다. Set 조회는 배열의 includes와 달리 요소 수가 늘어도 비용이 거의 일정해서, 반복 검사에서 차이가 커집니다.

단어 수 세기

같은 단어가 섞인 배열에서 유일한 값만 남겨 세 개를 출력합니다. 개수를 세려면 여기서 멈추지 말고 단어별 카운터를 담을 Map이나 객체가 필요합니다. Set은 존재 여부만 알려 줄 뿐 몇 번 나왔는지는 기억하지 않습니다.

스택 구현

행 목록을 팀 키로 묶어 점수 배열을 만드는 reduce입니다. 스택은 아니지만, 각 키의 배열이 push로 커진다는 점은 적재 동작과 같습니다. 배열을 스택으로 쓸 때는 push와 pop을 짝지어 쓰고, 빈 배열의 pop은 undefined를 돌려줍니다.

큐 구현

두 칸씩 잘라 조각 배열을 만드는 반복문입니다. 큐를 배열로 흉내 내면 shift로 앞을 꺼내게 되는데, 이 연산은 나머지 요소를 모두 당기므로 길이가 커질수록 비싸집니다. 앞뒤 삽입과 삭제가 잦다면 인덱스를 옮기는 방식이 낫습니다.

2차원 배열 합계

네 개짜리 배열의 각 값을 두 배로 만듭니다. 2차원 배열이라면 바깥을 돌면서 안쪽 행 합계를 다시 더해야 합니다. flat()으로 한 겹 펼친 뒤 한 번만 더하는 방법도 있는데, 기본 깊이가 1이라 더 깊게 중첩됐다면 인자를 지정해야 합니다.

groupBy 구현

중복 문자열을 걷어내 고유 목록을 만듭니다. 그룹핑이라면 값을 버리는 대신 키마다 모아야 하므로 Map이나 누산 객체가 필요합니다. 최신 런타임에는 Object.groupBy가 있지만, 반환 객체의 값은 undefined가 될 수 있는 타입으로 나옵니다.

flatten 펼치기

reduce로 팀별 점수 배열을 만드는 코드라, 결과는 펼쳐지는 대신 오히려 중첩됩니다. 이렇게 만든 값들을 한 줄로 늘어놓을 때 flat이나 flatMap을 씁니다. flatMap은 변환과 한 겹 펼치기를 함께 처리해 중간 배열을 남기지 않습니다.

zip 묶기

인덱스를 2씩 올리며 배열을 두 칸짜리 조각으로 나눕니다. 두 배열을 짝짓는 zip은 대신 하나의 인덱스로 양쪽을 동시에 읽는 형태가 됩니다. 길이가 다르면 짧은 쪽에 맞춰야 하며, 그러지 않으면 없는 인덱스에서 undefined가 섞여 들어옵니다.

chunk 나누기

각 요소를 두 배로 바꾼 새 배열을 출력합니다. 실제로 나누는 코드는 없고, 나누려면 slice로 구간을 잘라 담는 반복문이 필요합니다. slice는 끝 인덱스가 길이를 넘어가도 예외 없이 남은 만큼만 돌려주어 경계 검사를 따로 하지 않아도 됩니다.

key 기준 정렬

단어 배열에서 중복을 없앤 결과를 보여 줍니다. 키 기준 정렬을 붙이려면 여기에 비교 함수를 가진 sort가 이어져야 합니다. Set은 삽입 순서를 유지할 뿐 정렬해 주지는 않으므로, 순서가 필요하면 배열로 꺼낸 뒤 직접 정렬합니다.

빈도표 만들기

행들을 팀별로 묶어 점수 배열을 만듭니다. 빈도표라면 배열에 담는 대신 숫자를 1씩 올리는 형태로 바꾸면 됩니다. 누산 객체를 {}로 시작하면 toString 같은 상속 키와 이름이 겹칠 수 있어, Object.create(null)이나 Map이 더 안전합니다.

대소문자 무시 중복 제거

숫자 배열을 두 개씩 나누는 코드라 대소문자 비교와는 관계가 없습니다. 대소문자를 무시하고 걸러내려면 toLowerCase()로 정규화한 값을 Set에 넣고, 출력용 원본은 따로 보관합니다. 일부 언어에서는 소문자 변환 결과가 로케일에 따라 달라집니다.

sliding window

각 값을 두 배로 만드는 변환 한 줄입니다. 슬라이딩 윈도우는 인덱스마다 고정 길이 구간을 보는 방식이라 slice(i, i + k)를 도는 반복문이 더해져야 합니다. 매 칸마다 합을 새로 구하면 낭비이므로, 빠지는 값을 빼고 들어오는 값을 더하는 편이 낫습니다.

pair 목록을 맵으로 변환

문자열 중복을 제거해 배열로 되돌립니다. 짝 목록을 맵으로 바꿀 때는 new Map(pairs)처럼 키와 값 두 칸짜리 배열을 그대로 넘기면 됩니다. 같은 키가 두 번 나오면 나중 값이 앞 값을 덮어쓰므로 충돌 처리를 미리 정해 두어야 합니다.

reverse 복사

그룹핑 reduce가 원본 rows를 건드리지 않고 새 객체만 만듭니다. 순서를 뒤집을 때도 같은 태도가 필요한데, reverse()는 원본 배열 자체를 뒤집어 놓습니다. 복사본만 뒤집으려면 [...arr].reverse()나 toReversed()를 씁니다.

partition 나누기

반복문이 배열을 두 칸 단위로 잘라 담습니다. 조건에 따라 둘로 가르는 분할은 조각 대신 참과 거짓 두 배열에 나눠 넣는 형태가 됩니다. filter를 두 번 부르면 같은 배열을 두 번 훑게 되어, 한 번의 reduce로 처리하면 순회 횟수를 줄일 수 있습니다.

순수 함수

입력만으로 결과가 정해지는 addTax가 가격에 1.1을 곱해 10에 대한 값을 냅니다. 외부 상태를 건드리지 않아 테스트와 캐싱이 쉬운 형태입니다. 다만 부동소수점 곱셈이라 결과가 11이 아니라 11.000000000000002로 찍힙니다.

불변 업데이트

state를 그대로 두고 전개 연산자로 새 객체를 만들어 page를 올리고 tags에 항목을 더합니다. 이전 값과 새 값을 참조로 비교하는 화면 갱신 로직에서 이 방식이 필요합니다. 전개는 한 겹만 복사하므로 배열을 따로 펼치지 않으면 참조가 공유됩니다.

Maybe 스타일 처리

함수 두 개를 배열에 담고 reduce로 차례대로 적용해 3을 8로 바꿉니다. 값이 있을 수도 없을 수도 있는 흐름을 단계로 쪼갤 때 이 구조가 뼈대가 됩니다. 초깃값을 빼면 첫 원소인 함수 자체가 시작 값이 되어 엉뚱한 결과나 오류로 이어집니다.

Result 스타일 매핑

제너레이터 numbers가 세 값을 순서대로 내놓고, 전개 구문이 이를 모두 소비해 배열 [1, 2, 4]를 만듭니다. 성공과 실패를 순서대로 흘려보내는 구조를 흉내 낼 때 쓸 수 있습니다. 전개하는 순간 지연 계산이 사라져 끝없는 제너레이터에는 쓸 수 없습니다.

fold 합계

합계를 접어 나가는 흐름의 재료로 삼을 만한 addTax에 50을 넣어 세금이 붙은 값을 얻습니다. 여러 금액을 하나로 합칠 때 이런 단항 변환 함수를 앞단에 둡니다. 세율 1.1이 본문에 박혀 있어 값이 바뀌면 흩어진 곳을 모두 고쳐야 합니다.

compose

두 단계 변경을 객체 리터럴 하나로 합성해 next를 만듭니다. 변환을 이어 붙이는 구조에서는 각 단계가 새 객체를 돌려주므로 중간 결과를 그대로 로그로 남길 수 있습니다. 대신 단계마다 객체가 새로 생겨 큰 구조를 자주 갱신하면 복사 비용이 쌓입니다.

partial 적용

고정된 단계 목록에 시작 값 7만 바꿔 넣어 16을 얻습니다. 인자 일부를 미리 묶어 두는 부분 적용과 같은 발상으로, 달라지는 값만 나중에 넘기는 구성입니다. 단계 순서가 결과를 바꾸므로 배열의 나열 순서 자체가 사양의 일부입니다.

curried 함수

function*로 정의된 함수는 호출해도 몸통이 실행되지 않고 이터레이터만 돌려주며, 값은 소비 시점에 하나씩 만들어집니다. 인자를 나눠 받는 커링처럼 실행을 미루는 성질이 핵심입니다. 이미 소진한 이터레이터를 다시 펼치면 빈 배열이 나옵니다.

pipeline

파이프라인의 한 칸으로 쓸 만한 addTax를 90에 적용합니다. 인자 하나에 결과 하나인 단항 함수라서 map이나 합성 함수에 그대로 넘길 수 있습니다. 반대로 인자가 둘 이상인 함수는 미리 부분 적용해 단항으로 줄여야 이어 붙일 수 있습니다.

lazy iterator

느긋한 계산과 달리 이 코드는 곧바로 새 상태 객체를 만들어 출력합니다. 갱신 단위마다 스냅숏을 남기고 싶을 때 쓰는 형태입니다. state가 const여도 재할당만 막힐 뿐 속성 변경은 가능해서, 진짜 고정이 필요하면 Object.freeze가 따로 있어야 합니다.

패턴 분기

각 단계가 조건 분기 없이 값을 그대로 변환하며 11에서 24까지 나아갑니다. 분기를 넣고 싶다면 단계 함수 안에서 판단해 다른 값을 돌려주면 됩니다. reduce는 중간에 멈출 수 없어, 조기 종료가 필요한 분기에는 for...of가 맞습니다.

reducer 상태 누적

yield가 실행을 멈췄다 재개하는 덕분에 함수 안의 지역 변수가 호출 사이에도 살아남습니다. 리듀서처럼 상태를 이어 가는 계산을 짤 때 이 성질을 씁니다. 다만 여기서는 결과를 배열로 모으므로 세 값이 한꺼번에 메모리에 올라옵니다.

all predicate

모든 원소가 조건을 만족하는지 보는 대신, addTax가 130을 받아 10퍼센트가 더해진 값을 돌려줍니다. 화살표 함수 본문이 표현식 하나라 return 없이 값이 나갑니다. 이런 함수는 자기 this를 갖지 않아 객체 메서드로 붙일 때 주의해야 합니다.

flatMap

tags 배열을 펼쳐 새 항목을 덧붙이고 나머지 속성은 복사합니다. 평탄화가 필요할 만큼 중첩이 깊어지면 flatMap이나 헬퍼를 끌어오지만, 여기서는 한 단계 갱신이라 전개만으로 충분합니다. 키가 겹치면 뒤에 쓴 값이 앞의 값을 덮습니다.

함수 값을 반환하는 함수

배열에 담긴 익명 함수들이 값 하나를 넘겨받아 다음 함수로 전달하고, 마지막에 32가 남습니다. 함수를 값처럼 담고 꺼내 쓸 수 있어야 이런 목록이 성립합니다. 화살표 함수는 arguments 객체를 만들지 않아 선언한 매개변수로만 값을 받습니다.

비동기 함수 시작

async function run()이 await Promise.resolve(1)로 값을 꺼내 출력하고, 반환 타입은 Promise<void>로 적었습니다. 비동기 함수의 골격을 잡을 때 이 형태에서 출발합니다. await은 이미 이행된 값이라도 마이크로태스크 한 턴…

순차 작업 처리

이미 이행된 두 프로미스를 Promise.all에 넣고 결과 배열을 then으로 받습니다. 순서를 유지한 채 여러 작업 결과를 한 번에 모을 때 씁니다. 하나라도 거부되면 나머지 결과를 버리고 즉시 거부되므로, 실패까지 모두 보고 싶다면 Promise.allSettle…

병렬 작업 처리

AbortController를 만들자마자 abort()를 호출하고 signal.aborted가 참으로 바뀐 것을 확인합니다. 취소 가능한 작업의 신호 전달 구조를 실험할 때 이 정도면 충분합니다. 신호는 한 번 중단되면 되돌릴 수 없어, 재시도할 때는 컨트롤러를 새로 …

timeout 처리

setTimeout으로 10밀리초 뒤에 한 줄을 출력하도록 예약합니다. 시간 제한을 붙이려면 이 타이머와 실제 작업을 Promise.race로 겨루게 만드는 식으로 확장합니다. 지연 시간은 최소 대기 시간일 뿐이라 이벤트 루프가 바쁘면 10ms보다 늦게 호출됩니다.

retry 로직

재시도 자리에 놓인 코드는 await 한 번으로 값 5를 받아 출력하는 최소 형태입니다. 실제 재시도는 run 본문을 try로 감싸고 루프를 도는 방식으로 붙입니다. 실패마다 곧바로 다시 부르면 상대 서버를 더 밀어붙이므로 대기 간격을 늘려 가는 편이 안전합니다.

스트림 또는 채널 소비

스트림을 다루는 대신, 문자열 두 개를 담은 프로미스 배열을 한꺼번에 기다려 결과를 찍습니다. 조각을 모두 모은 뒤 한 번에 처리하는 소비 방식에 해당합니다. 도착하는 대로 처리해야 한다면 for await...of로 비동기 이터러블을 도는 쪽이 메모리에 유리합니다.

producer consumer

생산자와 소비자를 잇는 큐 대신, 컨트롤러 하나를 만들어 곧바로 중단하고 그 상태를 읽습니다. 소비 쪽을 언제 멈출지 알려 주는 공유 신호를 어떻게 표현하는지 보여 줍니다. signal은 abort 이벤트도 발생시키므로, 대기 중인 소비자는 리스너로 깨울 수 있습니다.

취소 신호

여기서는 타이머 하나만 걸어 두고 10ms 뒤 한 줄을 출력합니다. 취소를 붙이려면 setTimeout이 돌려주는 핸들을 보관했다가 clearTimeout에 넘겨야 합니다. Node에서는 살아 있는 타이머가 프로세스 종료를 붙잡으므로, 정리하지 않으면 프로그램이 늦게 …

비동기 에러 처리

await 한 줄이 성공 경로만 밟는 코드라 에러 처리는 아직 어디에도 없습니다. 실제로는 await을 try/catch로 감싸야 거부된 프로미스를 잡을 수 있습니다. 현대 설정에서 catch가 받는 값의 타입은 unknown이라, message를 읽기 전에 좁혀야 합…

배치 처리

두 개짜리 배열을 통째로 Promise.all에 넘겨 한 배치로 처리합니다. 항목이 수천 개일 때 이 방식을 그대로 쓰면 동시 요청이 한꺼번에 터지므로, 배열을 잘라 배치마다 나눠 호출합니다. 결과는 입력 순서에 맞춰 튜플로 추론되어 인덱스별 타입이 유지됩니다.

debounce

디바운스 대신 놓인 코드는 컨트롤러를 즉시 중단해 aborted가 참임을 보여 줍니다. 입력이 이어질 때 앞선 요청을 버리는 디바운스 구현에서 이 취소 신호가 그대로 쓰입니다. 취소된 fetch는 AbortError로 거부되므로, 일반 실패와 구분해 무시해야 합니다.

throttle

10ms 타이머 한 개가 전부이고 콜백은 한 번만 실행됩니다. 스로틀은 여기에 마지막 실행 시각을 기억하는 변수를 더해 정해진 간격 안의 호출을 건너뛰는 식으로 만듭니다. 타이머 콜백이 화살표 함수라 바깥 this를 그대로 쓰고, 일반 함수로 바꾸면 this가 달라집니…

파일 읽기 작업

파일 입출력 대신 Promise.resolve(13)을 기다려 값을 출력하는 자리표시 코드입니다. Node에서 실제로 읽을 때는 node:fs/promises의 readFile을 같은 자리에 넣습니다. 인코딩을 넘기지 않으면 반환 타입이 string이 아니라 Buffe…

요청 작업 감싸기

요청 두 개를 흉내 낸 프로미스를 묶어 결과를 한 번에 받습니다. HTTP 호출을 감싸는 얇은 함수를 만들 때 이런 병렬 호출 지점이 생깁니다. fetch는 404나 500에도 거부되지 않으므로, 래퍼 안에서 res.ok를 직접 확인해 예외로 바꿔야 합니다.

스케줄 작업

스케줄러 대신 컨트롤러를 만들어 바로 취소한 뒤 플래그를 읽습니다. 주기 작업을 돌릴 때는 이런 신호를 반복 조건에 걸어 다음 회차 진입을 막습니다. AbortSignal.timeout()을 쓰면 일정 시간 뒤 자동으로 중단되는 신호를 직접 타이머를 만들지 않고 얻습니…

try catch 기본

throw로 던진 RuntimeException을 같은 타입의 catch가 받아 메시지를 꺼냅니다. 잡을 타입을 좁게 적으면 예상한 실패만 처리하고 나머지는 위로 흘려보낼 수 있습니다. 내장 예외 대부분은 Exception을 상속하지만 TypeError 같은 Error…

사용자 정의 오류

requireName은 trim()으로 공백뿐인 이름을 걸러 내고, 비었으면 Error를 던집니다. 잘못된 값이 더 깊이 흘러가기 전에 경계에서 막는 방식입니다. 오류 종류를 구분하려면 Error를 상속한 클래스를 만들고 name을 지정해 instanceof로 판별합니…

입력 검증

parseNumber는 문자열을 Number()로 변환하고 Number.isNaN이면 null을 돌려줍니다. 반환 타입이 number | null로 추론되어 호출부가 실패를 무시하기 어렵습니다. 다만 빈 문자열은 0으로 변환되어 이 검사를 통과하므로 앞에서 따로 걸러야…

안전한 숫자 파싱

assert는 조건이 거짓일 때만 메시지를 담아 던지고, 호출부는 참을 넘겨 그냥 통과합니다. 안전한 파싱보다는 전제 조건 확인에 가깝습니다. condition에 타입이 없어 암묵적 any이며, asserts condition 시그니처를 붙여야 호출 이후 타입이 좁혀집…

안전한 나눗셈

JSON.parse가 실패하도록 만들어 catch가 어떻게 동작하는지 보여 줍니다. 나눗셈은 사정이 달라서, 자바스크립트에서 1 / 0은 예외 대신 Infinity를, 0 / 0은 NaN을 냅니다. 따라서 이런 try가 아니라 분모를 미리 확인하는 검사가 필요합니다.

finally 정리

이름이 비었는지 확인하고 아니면 그대로 돌려주는 함수이며, 정리 구문은 아직 없습니다. 파일 핸들이나 잠금처럼 반드시 놓아야 하는 자원이 있다면 finally에서 반환합니다. finally 안에서 값을 return하면 앞서 던진 오류가 삼켜지므로 피해야 합니다.

오류 다시 던지기

이 함수는 던지는 대신 실패를 null로 표현합니다. 다시 던지기는 오류를 기록만 하고 처리는 상위에 맡길 때 필요하며, catch 블록 끝에서 같은 값을 throw하면 됩니다. 새 Error로 감쌀 때 cause를 지정하면 원래 오류를 잃지 않습니다.

도메인 오류 표현

조건과 메시지를 받아 위반했을 때만 던지는 최소 검사기입니다. 도메인 규칙마다 문구를 다르게 넣어 어디서 어긋났는지 남길 수 있습니다. 다만 문자열 메시지로만 구분하면 호출부에서 분기하기 어려워, 코드 필드나 별도 클래스를 가진 오류 타입으로 나누는 편이 낫습니다.

fallback 값 사용

파싱이 실패하면 메시지를 출력하는 데서 끝나는데, 대체값을 쓰려면 catch 안에서 기본 객체를 돌려주도록 바꿉니다. 설정 파일이나 캐시를 읽을 때 흔한 형태입니다. JSON.parse의 반환 타입은 any라, 결과를 검증 없이 쓰면 타입 검사가 사실상 무력해집니다.

assertion

requireName이 조건을 확인하고 어기면 즉시 중단하므로, 반환된 값은 항상 내용이 있는 문자열입니다. 내부 불변식을 지켜야 하는 지점에 둡니다. 그래도 타입은 여전히 string이라, 검증을 거쳤다는 사실까지 타입에 담으려면 브랜드 타입이나 타입 서술어가 필요합…

Result 반환

성공하면 숫자를, 실패하면 null을 돌려주어 예외 없이 결과를 표현합니다. 실패 이유까지 전하려면 { ok: true, value }와 { ok: false, error }를 묶은 판별 유니온으로 확장합니다. ok를 확인하는 순간 타입이 좁혀져 잘못된 필드 접근이 막…

다단계 검증

assert 하나로 단계마다 조건을 확인하는 구조이고, 예시는 통과하는 경우만 보여 줍니다. 여러 규칙을 이어 붙이면 첫 위반에서 곧바로 멈춥니다. 폼처럼 모든 오류를 한 번에 보여 줘야 하는 화면에서는 던지는 대신 위반 목록을 모으는 방식이 맞습니다.

로깅 래퍼

catch가 잡은 값이 Error인지 확인한 뒤 메시지만 뽑아 기록합니다. 로깅 래퍼의 뼈대가 바로 이 변환 부분입니다. 자바스크립트는 문자열이나 숫자 등 무엇이든 던질 수 있어서, String(error)로 넘어가는 대비가 실제로 쓸모 있습니다.

recover 흐름

값이 규칙에 맞지 않으면 던지고, 맞으면 그대로 흘려보냅니다. 복구 흐름은 이 호출을 감싸는 쪽에서 오류를 잡아 기본 이름으로 대체하는 형태가 됩니다. 어디서 복구할지 정하지 않고 모든 층에서 잡으면, 문제가 조용히 감춰져 원인을 찾기 어려워집니다.

입력 가드

문자열을 숫자로 바꾸고 변환 실패를 null로 걸러 내는 가드 함수입니다. 쿼리 문자열이나 폼 값처럼 바깥에서 온 문자열이 들어오는 입구에 둡니다. 타입은 컴파일 뒤 사라지므로 응답을 number로 선언했다고 해서 실제 값이 숫자라는 보장은 없습니다.

텍스트 파일 읽기

node:fs의 readFileSync로 package.json을 UTF-8 문자열로 읽은 뒤 앞 20자만 잘라 출력합니다. 프로그램 시작 시점에 작은 설정 파일을 한 번 읽는 정도라면 이 형태로 충분합니다. 세 번째 인자인 인코딩을 빼면 반환 타입이 string이 아…

텍스트 파일 쓰기

writeFileSync가 sample-2.txt에 "hello\n" 한 줄을 UTF-8로 기록합니다. 빌드 산출물이나 테스트 픽스처처럼 작고 한 번에 끝나는 쓰기에 적합한 방식입니다. 기본 플래그가 w라서 같은 이름의 파일이 있으면 내용을 이어붙이는 대신 통째로 잘라…

텍스트 파일 추가 쓰기

path.join이 logs, 2026, app-3.log 세 조각을 플랫폼 구분자로 이어 하나의 상대 경로 문자열을 만듭니다. 날짜별 로그처럼 경로 일부가 실행 중에 정해질 때 문자열 덧셈 대신 씁니다. join은 경로 문자열만 계산할 뿐 디렉터리를 만들지도, 존재 …

JSON 저장

process.env.NODE_ENV와 process.argv.slice(2)를 객체로 묶어 한 번에 출력하며, 환경 변수가 없으면 development로 대체합니다. 실행 환경과 전달된 인자를 동시에 확인해야 하는 CLI 진입점에서 쓰는 형태입니다. ??는 undef…

CSV 파싱

여기서는 파일을 읽어 앞 20자만 확인할 뿐, 쉼표 분리나 헤더 해석 같은 파싱 단계는 전혀 하지 않습니다. CSV를 본격적으로 다루기 전에 구분자와 첫 줄 모양을 눈으로 훑는 단계에 어울립니다. slice는 UTF-16 코드 단위로 세기 때문에 이모지나 결합 문자가 …

환경 변수 읽기

제목 그대로 APP_ENV를 읽되 값이 없으면 local로 떨어지는 안전한 조회입니다. 컨테이너나 CI처럼 설정이 환경 변수로 주입되는 곳에서 그대로 쓰입니다. 환경 변수는 프로세스가 시작할 때 받은 복사본이라 나중에 셸에서 바꿔도 반영되지 않고, 실행 중 변경은 스레…

명령행 인자

인자 세 개를 받아 logs/2026/app-7.log 형태의 경로 문자열을 조립해 찍습니다. 명령행 인자로 받은 이름을 디렉터리 이름과 합쳐야 할 때 이 방식이 안전합니다. 윈도우에서는 결과 구분자가 역슬래시로 나오므로, 출력 문자열을 / 기준으로 다시 쪼개는 코드를…

경로 합치기

환경 변수와 인자 배열을 담은 객체 하나를 만들어 콘솔에 넘깁니다. 스크립트가 어떤 값을 받고 시작했는지 로그로 남겨 두면 재현이 쉬워집니다. argv의 0번은 node 실행 파일, 1번은 스크립트 경로이므로 사용자가 준 값만 보려면 slice(2)가 필요합니다.

디렉터리 목록

고정된 파일 하나를 동기로 열어 내용 일부를 출력할 뿐, 디렉터리를 순회하는 코드는 들어 있지 않습니다. 목록 처리 로직을 붙이기 전에 대상이 실제로 읽히는지 확인하는 단계로 보면 됩니다. readFileSync는 완료될 때까지 이벤트 루프를 세우므로 요청 처리 경로에…

임시 파일

sample-10.txt라는 이름을 그대로 써서 파일을 생성합니다. 임시 산출물을 남길 때도 경로가 어디에 잡히는지 알고 써야 합니다. 상대 경로는 소스 파일 위치가 아니라 process.cwd()를 기준으로 해석되므로, 실행 디렉터리가 바뀌면 파일이 엉뚱한 곳에 생깁…

존재 여부 확인

경로 조각을 이어 붙여 문자열을 만들 뿐 존재 여부 검사는 하지 않는 코드입니다. 확인 로직을 붙이려면 이렇게 만든 경로를 그다음 단계로 넘기게 됩니다. path.join은 중간의 ..를 정리해 주지만 결과는 여전히 상대 경로이며, 절대 경로가 필요하면 path.res…

표준 입력 읽기

표준 입력을 읽는 대신 process.env와 process.argv에서 값을 꺼내 출력합니다. 파이프로 데이터를 받지 않아도 되는 간단한 도구라면 인자만으로 입력을 대신할 수 있습니다. TypeScript에서 process를 쓰려면 @types/node가 설치되어 있…

설정 로드

설정 파일 자리에 package.json을 넣어 동기로 읽고 앞 20자를 확인합니다. 애플리케이션이 뜨기 전 한 번만 읽는 설정 로딩은 동기 호출이 오히려 코드가 단순합니다. 다만 읽어 온 값은 string일 뿐이라, JSON.parse를 거쳐도 반환 타입은 any여서…

직렬화

이 코드가 파일에 넘기는 값은 이미 완성된 문자열 "hello\n"입니다. 객체 상태를 저장하려면 이 자리에 직렬화 결과를 넣어야 합니다. writeFileSync는 문자열이나 Buffer만 받으므로 객체를 그대로 주면 의도치 않게 [object Object]가 기록될…

로그 파일 회전 흉내

연도 디렉터리와 app-15.log를 합쳐 회전된 로그 파일이 놓일 자리를 계산합니다. 날짜나 순번으로 파일명을 나누는 규칙은 이렇게 경로 조립부터 분리해 두면 다루기 쉽습니다. 경로만 만들었을 뿐이라 실제로 쓰기 전에는 상위 디렉터리를 mkdirSync로 recurs…

설정 객체

이름을 키로, 할인 계산 람다를 값으로 담은 사전을 만들고 percent 항목에 100을 넣어 90을 출력합니다. 규칙 이름이 설정 파일이나 DB에서 문자열로 들어올 때 이 표를 그대로 씁니다. (int) 캐스팅은 반올림이 아니라 소수부를 버리므로 금액 계산에는 dec…

전략 패턴

전략 패턴으로 알고리즘을 캡슐화하고 런타임에 교체할 수 있게 합니다. Python에서는 함수를 전략으로 사용할 수 있습니다.

옵저버

사전에 담긴 두 개의 Func<int, int> 중 하나를 키로 꺼내 즉시 호출합니다. 사건이 생겼을 때 등록해 둔 여러 반응을 모두 돌려야 한다면 값 타입을 목록이나 event로 바꿉니다. C#의 event는 구독자가 없으면 델리게이트가 null이라 ?.Invoke로…

데코레이터

원래 조회 앞에 사전 확인 단계를 덧대어 같은 id에는 다시 계산하지 않게 만듭니다. 로깅이나 권한 검사처럼 본체를 건드리지 않고 앞뒤에 동작을 끼울 때 같은 구조를 씁니다. 여러 겹을 쌓으면 감싼 순서가 곧 호출 순서라, 캐시를 로깅보다 바깥에 두면 캐시가 맞은 요청…

어댑터

호출부는 strategies[key](100) 한 줄로 통일되고, 실제 계산 방식은 사전 안 람다가 결정합니다. 시그니처가 제각각인 함수를 하나의 모양으로 맞춰 끼울 때 델리게이트로 감싸면 구현 클래스를 새로 만들지 않아도 됩니다. 없는 키를 넣으면 KeyNotFoun…

커맨드

GetUser라는 이름 하나에 상태인 사전과 실행 절차가 함께 묶여 있습니다. 실행할 일을 객체로 만들어 큐에 넣거나 나중에 되돌려야 한다면 이 묶음을 클래스로 승격시킵니다. 지역 함수가 바깥 변수 cache를 잡으면 컴파일러가 클로저 객체를 힙에 만들어, 그 사전은 …

팩터리 메서드

코드에는 팩터리 함수가 없고 new User("Ada", 9)로 직접 인스턴스를 만든 뒤 summary() 결과를 출력합니다. 생성 규칙이 단순하면 굳이 감싸지 않는 편이 낫습니다. 팩터리를 도입해 이득을 보는 시점은 생성 시 검증이나 분기가 붙거나, 반환 타입을 구체…

빌더 패턴

단계별로 값을 채워 가는 빌더 대신, 완성된 객체 리터럴을 Readonly 타입에 한 번에 대입합니다. 필드 수가 적고 모두 필수라면 이 직접 생성이 가장 짧습니다. 빌더가 필요해지는 지점은 선택 필드가 많아 생성 도중 부분 상태를 허용해야 할 때인데, Readonly…

캐시 래퍼

사전을 조회 결과 보관소로 두고 값이 없을 때만 채운 뒤 돌려줍니다. 같은 입력이 반복되는 계산이나 원격 호출 앞에 두면 호출 횟수가 줄어듭니다. 여기에는 만료도 크기 제한도 없어 키 종류가 계속 늘면 메모리를 그대로 붙잡으므로, 실제 서비스에서는 정리 정책이 있는 M…

retry 유틸

실패 대응 정책도 이 코드처럼 이름과 함수의 짝으로 표현할 수 있습니다. 재시도라면 값을 변환하는 대신 실행할 동작을 인자로 받아 예외를 잡고 다시 부르는 형태가 됩니다. 실패 즉시 그대로 다시 부르면 장애가 난 쪽에 부하가 몰리므로 시도 간격을 점점 늘리는 방식이 기…

미들웨어 체인

조회 앞에 캐시 확인을 끼워 넣은 한 겹짜리 체인입니다. 요청 하나가 인증, 로깅, 캐시를 차례로 지나야 한다면 각 단계가 다음 단계를 인자로 받는 형태로 이어 붙입니다. ASP.NET Core에서는 어떤 단계가 다음을 부르지 않으면 파이프라인이 거기서 끊겨 뒤쪽은 실…

검증기 조합

값 하나를 받아 값을 돌려주는 함수를 표에 모아 둔 구조라, 검증 규칙 여러 개도 같은 방식으로 담을 수 있습니다. 규칙마다 통과 여부와 메시지를 돌려주게 바꾸고 전체를 순회하면 오류를 모아 보고할 수 있습니다. 첫 실패에서 멈추는 방식과 달리 모아 보고하면 사용자가 …

포매터 파이프

GetUser는 id를 받아 문자열을 돌려주는 한 단계짜리 변환입니다. 출력 가공이 여러 단계라면 각 단계를 Func<string, string>으로 만들어 차례로 넘깁니다. 단계마다 새 문자열이 생기므로 단계가 많고 문자열이 길다면 StringBuilder 하나에 이…

페이지네이션

사전에서 항목 하나만 꺼내 쓰는 코드지만, 목록을 잘라 내보내는 일도 같은 방식으로 함수화할 수 있습니다. 페이지 번호와 크기를 받아 Skip과 Take를 적용하는 함수를 표에 담아 정책별로 나눕니다. 데이터베이스 질의에서는 정렬 기준 없이 건너뛰면 페이지마다 순서가 …

데이터 매퍼

id를 키로 받아 대응하는 값을 돌려주는, 식별자와 객체를 잇는 최소 형태입니다. 실제 매퍼는 이 자리에서 결과 행을 객체로 옮겨 담습니다. 지금은 항상 값을 채우지만 실제로는 찾지 못하는 경우가 생기므로, nullable 참조 형식을 켜 두면 그 경로를 컴파일러가 경…

저장소 패턴

동작을 키로 골라 부르는 이 구조는 저장소가 여러 조회 방법을 노출하는 모양과 닮았습니다. 호출부가 저장 매체를 모르게 하려면 이런 진입점 뒤로 질의를 감춥니다. 저장소 인터페이스에 IQueryable을 그대로 노출하면 호출부가 아무 조건이나 붙일 수 있어, 감추려던 …

의존성 주입 컨테이너

키가 없으면 만들어 저장하고 이후에는 같은 것을 돌려주는 흐름은 컨테이너의 싱글턴 해석과 같습니다. 타입별 인스턴스를 한 번만 만들어 재사용해야 할 때 이 구조가 바탕이 됩니다. 요청마다 새로 만들어야 하는 대상을 이렇게 담아 두면, 스코프가 끝나 정리된 객체를 계속 …

템플릿 메서드

전체 흐름은 사전에서 함수를 꺼내 호출하는 것으로 고정되어 있고, 달라지는 계산만 람다가 맡습니다. 절차는 같고 한두 단계만 다른 작업들이 모일 때 이 분리가 들어맞습니다. 추상 클래스로 같은 일을 하면 상속이 하나뿐이라 조합이 막히지만, 델리게이트나 인터페이스는 여러…

파사드

호출부는 GetUser(20)만 알면 되고 사전 확인과 값 생성은 함수 안에 숨어 있습니다. 여러 하위 단계를 한 번의 호출로 감쌀 때 이렇게 좁은 진입점을 만듭니다. 진입점 하나에 계속 기능을 더하면 결국 내부 전부를 아는 타입이 되므로, 감출 범위를 처음에 정해 두…

동등성 단언

1 + 1 <= 1이 거짓이므로 예외 없이 지나가고 True가 출력됩니다. 테스트 프레임워크 없이 조건을 확인할 때는 이렇게 예외를 던지는 방식이 가장 단순합니다. 다만 Exception을 그대로 던지면 기대값과 실제값이 메시지에 남지 않아, 실패했을 때 무엇이 달랐는…

테이블 기반 테스트

Stopwatch를 켜고 비어 있는 루프를 1000번 돈 뒤 경과 밀리초를 출력합니다. 여러 입력을 표로 만들어 돌릴 때 항목별 소요 시간을 이런 식으로 함께 찍어 볼 수 있습니다. 몸통이 빈 루프는 릴리스 빌드에서 통째로 사라질 수 있어 출력은 거의 항상 0이 됩니다…

경계값 테스트

3 + 1이 3보다 크므로 조건이 성립하지 않아 그대로 통과합니다. 경계 근처 값을 넣어 보는 확인은 이렇게 상수를 바꿔 가며 반복합니다. int는 최댓값에서 1을 더하면 예외 없이 음수로 감싸 돌아가므로, 경계 검사에는 극단값도 함께 넣어 봐야 합니다.

시계 목 객체

Stopwatch.StartNew는 고해상도 타이머로 경과 시간만 재고 벽시계 시각과는 무관합니다. 시각에 따라 동작이 갈리는 코드를 시험하려면 현재 시각을 인터페이스로 주입해 원하는 값을 돌려주게 만듭니다. DateTime.Now를 직접 부르면 테스트에서 시각을 고정…

저장소 목 객체

조건이 거짓이라 예외를 건너뛰고 끝나는, 외부 의존이 전혀 없는 확인입니다. 데이터베이스를 쓰는 코드도 저장소를 인터페이스로 받게 만들면 이 정도로 단순한 테스트가 됩니다. 가짜 저장소가 IEnumerable을 돌려주면 대상 코드가 두 번 순회할 때 결과가 달라질 수 …

비동기 테스트

동기 코드의 실행 시간을 재는 형태이고 await는 들어 있지 않습니다. 비동기 메서드를 시험할 때는 테스트 메서드를 async Task로 선언하고 결과를 await해야 합니다. async void로 두면 러너가 완료를 기다리지 못해 안에서 난 예외가 그대로 사라집니다…

예외 테스트

조건이 참일 때만 Exception을 던지도록 되어 있는데, 여기서는 참이 되지 않아 아무 일도 일어나지 않습니다. 예외가 나야 정상인 경로를 검증하려면 반대로 호출을 감싸고 예외가 오지 않을 때 실패로 처리합니다. catch (Exception)처럼 넓게 잡으면 의도…

속성 스타일 테스트

고정된 횟수만큼 루프를 돌리고 걸린 시간을 재는 코드입니다. 속성 기반 검증은 이 반복 자리에 무작위 입력을 넣고, 어떤 값이 와도 성립해야 하는 규칙을 확인합니다. 실패한 입력을 다시 재현하려면 난수 시드를 로그에 남겨야 하고, 남기지 않으면 같은 실패를 두 번 볼 …

스냅샷 스타일 테스트

확인이 조건식 하나로 코드에 박혀 있어 기대값이 소스와 함께 관리됩니다. 출력이 길어지면 기대 결과를 파일로 빼 두고 실행 결과와 통째로 비교하는 방식으로 바꿉니다. 스냅샷은 갱신이 쉬운 만큼 잘못된 결과도 그대로 굳을 수 있어, 갱신할 때 차이를 눈으로 확인해야 합니…

통합 테스트 스케치

타이머를 켜고 작업을 돌린 뒤 멈추는 이 흐름이 통합 테스트의 뼈대와 같습니다. 실제로는 루프 자리에 서버 기동과 요청 호출이 들어가고, 끝나면 자원을 정리합니다. 통합 테스트는 병렬로 돌릴 때 같은 포트나 같은 데이터베이스를 두고 부딪히기 쉬워 테스트마다 격리 수단을…

파서 테스트

덧셈 결과를 부등호로 비교해 참이면 통과시키는 구조라, 입력과 기대값이 한 줄에 같이 있습니다. 파서라면 이 자리에 문자열을 넣고 나온 값이 맞는지 비교합니다. 잘못된 입력에 대해 int.Parse는 예외를 던지고 int.TryParse는 false를 돌려주므로, 어느…

컬렉션 테스트

1000회 반복의 경과 시간을 출력하는 코드로 컬렉션 비교는 들어 있지 않습니다. 목록을 검증할 때는 개수만 보지 말고 원소와 순서를 함께 확인해야 합니다. Equals로 두 리스트를 비교하면 내용이 같아도 참조가 달라 항상 거짓이 나오므로 SequenceEqual을 …

문자열 테스트

숫자 비교 한 줄로 통과 여부를 가리는 형태이고 문자열은 등장하지 않습니다. 문자열을 검증할 때는 비교 방식을 명시해 두는 편이 안전합니다. ==는 서수 비교라 정규화 형태가 다른 같은 글자를 서로 다르게 보고, 대소문자를 무시하려면 string.Equals에 비교자를…

매개변수화 테스트

반복 횟수 1000이 코드에 직접 박혀 있어 다른 값으로 돌리려면 소스를 고쳐야 합니다. 입력을 인자로 빼면 같은 본문으로 여러 조건을 돌릴 수 있습니다. xUnit에서는 [Theory]와 [InlineData]가 이 역할을 하는데, 인자는 컴파일 타임 상수여야 하므로…

간단한 벤치마크

이 코드에는 시간 측정이 없고 조건 검사만 있습니다. 성능을 확인하려면 대상 호출을 여러 번 반복해 총시간을 나누고, 측정 전에 몇 번 미리 돌려 JIT 컴파일을 끝내 두어야 합니다. 디버그 빌드에서 잰 수치는 최적화가 꺼져 있어 실제 배포본과 비교할 수 없습니다.

문자열 버퍼링

문자열 조각을 담은 배열을 구분자와 함께 join으로 한 번에 이어 붙입니다. 조각이 많이 쌓일 때는 반복문에서 +=로 붙이는 대신 배열에 모았다가 마지막에 합치는 이 방식이 다루기 편합니다. 다만 요즘 엔진은 문자열 연결을 내부적으로 최적화해서, 조각이 몇 개뿐이면 …

사전 할당

Map에 계산 결과를 저장해 두고 같은 인자가 다시 오면 재귀를 통째로 건너뜁니다. 겹치는 부분 문제가 많은 계산에서 호출 횟수를 크게 줄이는 방법입니다. 캐시는 스스로 비워지지 않으므로 입력 범위가 넓어지면 메모리 사용량이 계속 늘어난다는 대가가 따릅니다.

지연 처리

Set에 태그 세 개를 넣고 특정 항목이 있는지만 물어봅니다. 전체를 순회해 새 결과물을 만들지 않고 필요한 질문에만 답한다는 점에서 계산을 미루는 방향과 맞닿아 있습니다. Set의 조회는 배열 includes와 달리 항목 수가 늘어도 비용이 거의 그대로 유지됩니다.

메모이제이션 캐시

performance.now()로 시작 시각을 찍고, reduce 합산이 끝난 뒤 경과 시간을 결과와 함께 출력합니다. 캐시를 넣기 전후를 비교하려면 이렇게 같은 구간을 재는 코드가 먼저 있어야 합니다. 이 정도로 짧은 작업은 타이머 해상도와 JIT 예열에 묻히므로 한…

조회 맵 사용

배열 요소를 구분자로 이어 하나의 문자열로 만드는 한 줄짜리 코드이며, 맵 조회는 쓰이지 않았습니다. 키로 값을 찾는 작업이라면 배열을 매번 훑는 대신 Map을 미리 만들어 두는 쪽이 유리합니다. 참고로 join은 null과 undefined를 빈 문자열로 바꿔 넣어 …

배치 I/O

재귀 함수가 매번 캐시에 값이 있는지 먼저 확인하고, 없을 때만 계산해 저장합니다. 같은 요청을 반복해서 보내는 대신 결과를 모아 두는 발상은 I/O를 묶을 때도 그대로 통합니다. 다만 이 함수는 동기 재귀라 인자가 커지면 캐시가 채워지기 전에 호출 스택 한계에 먼저 …

불필요한 복사 줄이기

이미 만들어 둔 Set에 조회만 걸 뿐 새로운 컬렉션을 하나도 만들지 않습니다. filter나 전개 연산자를 이어 붙이면 단계마다 배열이 새로 생기므로, 판단만 필요한 자리에서는 이렇게 조회로 끝내는 편이 낫습니다. 참고로 Set은 NaN끼리를 같은 값으로 취급해 중복…

시간 측정

구간 앞뒤로 시각을 재서 배열 합산에 걸린 밀리초를 계산해 찍습니다. 어디가 느린지 감이 없을 때 후보 구간을 이렇게 감싸 숫자부터 확보합니다. Date.now()와 달리 performance.now()는 소수점 아래까지 주고 시스템 시계가 바뀌어도 값이 뒤로 튀지 않…

병렬 처리

배열 세 요소를 이어 붙여 출력하는 동기 코드이며 병렬 실행은 등장하지 않습니다. 여러 비동기 작업을 겹쳐 돌리려면 각 Promise를 모아 Promise.all에 넘겨야 합니다. 자바스크립트는 한 스레드에서 돌기 때문에 이런 CPU 계산 자체는 await를 붙인다고 …

객체 재사용

한 번 만든 cache를 함수 바깥에 두고 호출마다 같은 Map 인스턴스를 계속 씁니다. 호출할 때마다 새 그릇을 만들면 이전 결과가 매번 버려지므로, 재사용할 컨테이너는 이렇게 밖으로 빼 둡니다. 대신 이 캐시는 모듈이 살아 있는 내내 유지되어 테스트 사이에 값이 남…

Set 조회 최적화

문자열 세 개를 담은 Set에서 포함 여부를 확인합니다. 같은 목록을 두고 포함 검사를 여러 번 해야 한다면 배열을 Set으로 한 번 바꿔 두는 것이 이득입니다. 단 변환 자체에도 비용이 들어서, 검사 횟수가 몇 번뿐이면 그냥 includes로 훑는 쪽이 더 빠를 수 …

청크 스트리밍

배열 전체를 reduce로 한 번에 접어 합계를 내고 걸린 시간을 함께 출력합니다. 데이터가 메모리에 다 들어가지 않는 상황이라면 이 통짜 처리를 조각 단위로 나눠 흘려보내야 합니다. 청크로 쪼개면 최대 메모리 사용량은 내려가지만 조각마다 호출 비용이 붙어 총 처리 시…

정렬 비용 비교

정렬은 나오지 않고, 이미 순서가 정해진 배열을 이어 붙이기만 합니다. 출력 순서를 맞추려고 정렬을 넣기 전에 데이터가 이미 정렬된 채로 들어오는지부터 확인할 필요가 있습니다. sort는 비교 함수를 주지 않으면 요소를 문자열로 바꿔 비교하기 때문에 10이 9보다 앞에…

소유권 또는 참조 재사용

캐시에서 꺼낸 값을 복사하지 않고 그대로 반환하므로, 호출부는 저장소 안의 값을 직접 받아 보게 됩니다. 여기서는 값이 숫자라 문제가 없지만 객체를 캐싱하면 같은 참조가 여러 곳에 퍼집니다. 자바스크립트에는 소유권 개념이 없어 누가 수정 권한을 갖는지는 오직 팀 규약으…

페이지네이션 슬라이스

Set을 만들고 특정 항목이 들어 있는지만 묻는 코드로, 잘라 내는 연산은 없습니다. 목록을 페이지로 나눌 때는 slice가 흔하지만 Set은 인덱스 접근을 지원하지 않아 배열로 먼저 바꿔야 합니다. Set은 삽입 순서를 유지하므로 전개해서 배열로 만들면 넣은 순서가 …