2025-08-24 13:32
-
표현식은 값, 변수, 연산자, 함수 호출 등을 조합하여 궁극적으로 하나의 값으로 평가되는 코드 조각입니다.
-
프로그램의 모든 계산과 결정은 표현식을 통해 이루어지며, 문장(Statement)과 구분되는 핵심 개념입니다.
-
특히 정규표현식은 문자열의 특정 패턴을 찾아내고 조작하는 데 사용되는 매우 강력하고 전문화된 표현식입니다.
표현식 완벽 핸드북 프로그래밍의 기초부터 정규표현식까지
우리가 사용하는 모든 소프트웨어, 웹사이트, 앱의 가장 깊은 곳에는 무엇이 있을까요? 바로 ‘표현식(Expression)‘입니다. 표현식은 프로그램을 살아 움직이게 만드는 최소 단위의 연산이자, 컴퓨터와 소통하는 가장 기본적인 언어입니다. 많은 개발자가 코드를 작성하면서 무의식적으로 표현식을 사용하지만, 그 본질과 작동 원리를 깊이 이해하면 코드의 품질과 사고의 깊이가 달라집니다.
이 핸드북은 표현식이라는 개념을 탄생 배경부터 구조, 실제 사용법, 그리고 가장 강력한 형태인 ‘정규표현식’까지 남김없이 파헤쳐 봅니다. 이제 프로그래밍의 심장부로 함께 떠나보시죠.
1부: 프로그래밍의 기본 단위, 표현식
1. 표현식은 왜 만들어졌을까?
컴퓨터의 본질은 계산입니다. 초창기 컴퓨터 과학자들은 복잡한 계산을 기계어로 직접 지시해야 했습니다. 하지만 이는 매우 비효율적이고 어려웠습니다. 사람에게 친숙한 수학 공식처럼, 2 + 3
이나 사용자_나이 > 19
같은 형태로 계산을 지시하고, 컴퓨터가 그 결과를 알아서 만들어내게 할 방법이 필요했습니다.
이러한 요구에서 ‘표현식’이 탄생했습니다. 표현식의 핵심 철학은 **“평가(Evaluation)를 통해 하나의 값(Value)을 만들어내는 것”**입니다. 우리가 “사과 2개와 오렌지 3개를 더하면?”이라고 물으면 “총 5개의 과일”이라는 하나의 답이 나오는 것처럼, 표현식은 컴퓨터에게 질문을 던지고 하나의 명확한 답을 얻어내는 과정입니다.
2. 표현식이란 무엇인가?
표현식(Expression): 하나 이상의 값, 변수, 연산자(Operator), 함수 호출(Function Call)의 조합으로, 인터프리터나 컴파일러에 의해 평가되어 궁극적으로 하나의 값을 생성하는 코드 구문.
예를 들어보겠습니다.
-
42
: 숫자 리터럴(값) 그 자체로,42
라는 값을 생성하는 가장 단순한 표현식입니다. -
x + y
: 변수x
와y
의 값을 더해 새로운 값을 생성하는 표현식입니다. -
sum(10, 20)
:sum
함수를 호출하고 그 반환값(return value)으로 평가되는 표현식입니다. -
a > b
:a
가b
보다 큰지 비교하여true
또는false
라는 불리언(Boolean) 값을 생성하는 표현식입니다.
3. 표현식 vs 문장: 무엇이 다른가?
표현식과常常 헷갈리는 개념이 바로 **문장(Statement)**입니다. 둘의 차이를 이해하는 것은 매우 중요합니다.
구분 | 표현식 (Expression) | 문장 (Statement) |
---|---|---|
핵심 목적 | 하나의 값으로 평가되는 것 | 특정 동작을 수행하는 것 |
결과 | 반드시 값을 반환한다 | 값을 반환하지 않을 수 있다 |
비유 | 수학의 수식 (3 + 4 ) | 영어의 명령문 (Go home. ) |
예시 | a + b , x === 10 , getUserName() | let x = 10; , if (x > 5) { ... } , for (let i=0;...) |
let myVar = 5 + 10;
이라는 코드를 분석해볼까요?
-
5 + 10
은15
라는 값으로 평가되는 표현식입니다. -
let myVar = 15;
전체는myVar
라는 변수를 선언하고15
를 할당하는 동작을 수행하는 문장입니다.
즉, 문장은 표현식을 포함할 수 있습니다. 문장은 프로그램의 흐름을 제어하는 더 큰 단위의 ‘명령’이고, 표현식은 그 명령에 필요한 값을 계산하는 ‘재료’와 같습니다.
4. 표현식의 구성 요소
표현식은 다음과 같은 재료들로 구성됩니다.
-
리터럴(Literals): 소스 코드에 고정된 값 그 자체입니다. (예:
100
,"Hello"
,true
) -
변수(Variables): 값을 저장하고 있는 식별자입니다. (예:
userAge
,productName
) -
연산자(Operators): 값을 조작하고 결합하는 기호입니다.
-
산술 연산자:
+
,-
,*
,/
,%
-
할당 연산자: =,
+=
,-=
-
비교 연산자:
==, !=, '===',>, <
-
논리 연산자:
&&
(AND),||
(OR),!
(NOT)
-
-
함수 호출(Function Calls): 특정 작업을 수행하고 결과를 반환하는 코드 블록을 실행합니다. (예:
Math.random()
,parseInt("123")
)
2부: 문자열 세계의 지배자, 정규표현식
지금까지 설명한 것이 일반적인 프로그래밍 표현식이라면, 이제부터는 그중 가장 특수하고 강력한 **정규표현식(Regular Expression, 줄여서 Regex 또는 RegExp)**에 대해 알아보겠습니다.
1. 정규표현식은 왜 만들어졌을까?
1950년대 수학자 스티븐 클레이니가 신경망을 수학적으로 기술하기 위해 처음 고안한 개념에서 출발했습니다. 이것이 컴퓨터 과학으로 넘어와, 1968년 유닉스(Unix)의 아버지 중 한 명인 켄 톰슨이 텍스트 편집기 qed
에 이 기능을 구현하면서 프로그래머들에게 알려지기 시작했습니다.
그가 해결하려던 문제는 간단했습니다. “수많은 텍스트 데이터 속에서 특정 규칙(패턴)을 가진 문자열을 어떻게 효율적으로 찾고, 바꾸고, 추출할 것인가?”
예를 들어, “모든 이메일 주소를 찾아라”, “주민등록번호 형식에 맞는 문자열만 골라내라”, “HTML 태그를 전부 제거하라” 같은 복잡한 요구사항을 간단한 if
와 for
문으로 처리하기는 매우 어렵습니다. 정규표현식은 이런 ‘문자열 패턴’을 기술하는 표준화된 언어를 제공하기 위해 만들어졌습니다.
2. 정규표현식의 핵심 구조
정규표현식은 일반 문자와 **메타문자(Metacharacters)**라는 특수 문자의 조합으로 패턴을 정의합니다.
종류 | 메타문자 | 설명 | 예시 |
---|---|---|---|
기본 문자 | . | 줄바꿈 문자를 제외한 모든 문자와 일치 | a.c 는 abc , aac , a1c 등과 일치 |
| | OR 연산. 여러 패턴 중 하나와 일치 | cat|dog 는 cat 또는 dog 와 일치 | |
^ | 문자열의 시작과 일치 | ^start 는 start of line 과는 일치하지만 the start 와는 불일치 | |
$ | 문자열의 끝과 일치 | end$ 는 the end 와는 일치하지만 end of line 과는 불일치 | |
수량자 | * | 앞선 문자가 0번 이상 반복 | ca*t 는 ct , cat , caaat 등과 일치 |
+ | 앞선 문자가 1번 이상 반복 | ca+t 는 cat , caaat 등과 일치 (ct는 불일치) | |
? | 앞선 문자가 0번 또는 1번 등장 | colou?r 는 color 와 colour 모두 일치 | |
{n,m} | 앞선 문자가 n번 이상 m번 이하 반복 | a{2,4} 는 aa , aaa , aaaa 와 일치 | |
그룹과 범위 | () | 그룹으로 묶어 연산자 적용, 캡처 | (ab)+ 는 ab , abab 등과 일치 |
[] | 대괄호 안의 문자 중 하나와 일치 | [abc] 는 a , b , c 중 하나와 일치 | |
[^] | 대괄호 안의 문자를 제외한 문자와 일치 | [^0-9] 는 숫자가 아닌 문자와 일치 | |
문자 클래스 | \d | 모든 숫자와 일치. [0-9] 와 동일 | |
\w | 영문, 숫자, 밑줄(_)과 일치. [A-Za-z0-9_] 와 동일 | ||
\s | 공백, 탭, 줄바꿈 등 공백 문자와 일치 |
3. 실전 정규표현식 사용법
정규표현식은 대부분의 프로그래밍 언어에서 기본으로 지원합니다. JavaScript와 Python을 예로 들어보겠습니다.
예제 1: 이메일 주소 유효성 검사
가장 흔한 사용 사례입니다. “알파벳/숫자로 시작하고, 중간에 ’@‘가 있으며, 그 뒤에 도메인 주소와 ’.’이 있고, 마지막으로 최상위 도메인이 온다”는 패턴을 만들어야 합니다.
패턴: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
-
^
: 문자열 시작 -
[\w-\.]+
: 영문/숫자/밑줄(\w), 하이픈(-), 점(.)이 1번 이상(+) 반복 -
@
: ’@’ 문자 -
([\w-]+\.)+
: (영문/숫자/하이픈 1번 이상 + 점) 그룹이 1번 이상 반복 -
[\w-]{2,4}
: 영문/숫자/하이픈이 2~4글자 -
$
: 문자열 끝
JavaScript 코드:
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
const email1 = "test.user@example.com";
const email2 = "invalid-email@";
console.log(emailRegex.test(email1)); // true
console.log(emailRegex.test(email2)); // false
Python 코드:
import re
email_regex = re.compile(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
email1 = "test.user@example.com"
email2 = "invalid-email@"
print(bool(email_regex.match(email1))) # True
print(bool(email_regex.match(email2))) # False
예제 2: 로그 파일에서 특정 정보 추출하기
다음과 같은 로그가 쌓이고 있을 때, ERROR
레벨의 메시지만 추출하고 싶다고 가정해 봅시다.
[INFO] 2023-10-27 User logged in
[ERROR] 2023-10-27 Database connection failed
[DEBUG] 2023-10-27 Caching data
[ERROR] 2023-10-27 Payment processing error
패턴: /^\[ERROR\].*$/gm
-
^\[ERROR\]
:[ERROR]
로 시작하는 줄.[
와]
는 메타문자이므로\
로 이스케이프. -
.*
: 모든 문자(.
)가 0번 이상(*
) 반복 (즉, 나머지 모든 내용) -
$
: 줄의 끝 -
플래그:
-
g
(Global): 일치하는 모든 것을 찾습니다. 첫 번째 일치에서 멈추지 않습니다. -
m
(Multiline):^
와$
가 전체 문자열의 시작/끝이 아닌, 각 줄(line)의 시작/끝에 대응하도록 합니다.
-
JavaScript 코드:
const logs = `[INFO] 2023-10-27 User logged in
[ERROR] 2023-10-27 Database connection failed
[DEBUG] 2023-10-27 Caching data
[ERROR] 2023-10-27 Payment processing error`;
const errorRegex = /^\[ERROR\].*$/gm;
const errorLogs = logs.match(errorRegex);
console.log(errorLogs);
// 출력:
// [
// "[ERROR] 2023-10-27 Database connection failed",
// "[ERROR] 2023-10-27 Payment processing error"
// ]
3부: 더 깊이 알아보기
1. 컴퓨터는 표현식을 어떻게 이해할까?
a = 10 * (5 + 3)
이라는 표현식을 컴퓨터는 어떻게 계산할까요? 그냥 왼쪽부터 순서대로 계산하지 않습니다.
-
파싱(Parsing): 코드를 어휘 단위(토큰)로 쪼갭니다. (
a
, =,10
,*
,(
,5
,+
,3
,)
) -
추상 구문 트리(AST) 생성: 토큰들의 관계와 연산자 우선순위를 바탕으로 트리 구조를 만듭니다.
-
가장 먼저 계산될
(5 + 3)
이 트리의 가장 아래쪽에 위치합니다. -
그다음
10 *
연산이 그 위를 감쌉니다. -
최종적으로 = 할당 연산이 트리의 최상단에 위치합니다.
-
-
평가(Evaluation): 생성된 트리를 아래에서부터 위로 올라가며 계산합니다.
-
5 + 3
을 평가하여8
을 얻습니다. -
10 * 8
을 평가하여80
을 얻습니다. -
a
에80
을 할당합니다.
-
이처럼 컴퓨터는 **연산자 우선순위(Operator Precedence)**와 **결합 규칙(Associativity)**에 따라 표현식을 체계적으로 분석하고 평가합니다.
2. 탐욕적 vs 게으른 수량자
정규표현식에서 *
나 +
같은 수량자는 기본적으로 **탐욕적(Greedy)**으로 동작합니다. 이는 가능한 한 가장 긴 문자열과 일치하려고 시도하는 성질입니다.
예를 들어, <h1>Title</h1>
이라는 문자열에서 <.+>
패턴을 사용하면 결과는 무엇일까요? <h1>
만 선택될 것 같지만, 결과는 <h1>Title</h1>
전체입니다. .
이 모든 문자와 일치하고 +
가 최대한 많이 반복되려 하기 때문에, 마지막 >
를 만날 때까지 모든 문자를 삼켜버립니다.
이를 방지하려면 게으른(Lazy) 수량자를 사용합니다. 수량자 뒤에 ?
를 붙이면 됩니다. <.+?>
패턴을 사용하면, >
를 만나는 즉시 일치를 멈추므로 <h1>
만 정확히 찾아냅니다.
마치며
표현식은 프로그래밍 언어의 문법을 구성하는 가장 기본적인 벽돌과 같습니다. 이 벽돌이 어떻게 생겼고, 어떻게 서로 결합하여 값을 만들어내는지 깊이 이해하는 것은 견고한 코드를 작성하는 첫걸음입니다.
특히 정규표현식은 처음에는 외계어처럼 보일 수 있지만, 한번 손에 익으면 텍스트를 다루는 모든 작업의 효율을 극적으로 높여주는 ‘슈퍼파워’가 됩니다. 이메일 검증, 데이터 파싱, 코드 리팩토링 등 그 활용 분야는 무궁무진합니다.
이 핸드북이 여러분의 코드에 대한 이해를 한 단계 끌어올리는 계기가 되기를 바랍니다. 이제 직접 코드를 열고, 다양한 표현식을 만들어보고, 정규표현식 테스트 사이트(Regex101 등)에서 여러 패턴을 실험해보세요. 직접 부딪히고 경험하는 것만큼 좋은 학습은 없습니다.