PH pullh
입문 블로그 / CS는 언제부터 얼마나 공부해야 할까
프로젝트와 성장 5분 Beginner

CS는 언제부터 얼마나 공부해야 할까

자료구조, 네트워크, 운영체제 같은 CS 지식은 중요하지만, 초보자는 순서와 깊이를 조절할 필요가 있다.

CS 타이밍 커버 이미지

CS를 공부해야 한다는 말은 많이 듣지만, 막상 처음 시작하는 사람은 어디서부터 얼마나 봐야 하는지 감이 잘 안 잡힙니다. 너무 일찍 깊게 들어가면 코딩 경험이 부족해서 개념이 추상어로만 남고, 전혀 안 보면 나중에 왜 이렇게 동작하는지를 설명할 언어가 없어집니다.

그래서 이 글은 "CS를 언제 공부하나요"를 "CS가 없어서 막히는 순간이 어떻게 생겼나요"로 바꿔서 봅니다. 증상을 알아볼 수 있으면 순서는 저절로 정해집니다.

쓰임이 보일 때 개념이 붙습니다

웹 요청을 만들고 있는 중이라면 네트워크 기초가 잘 붙고, 리스트를 뒤지는 코드를 반복해서 쓰고 있다면 자료구조가 잘 붙습니다. 지금 손에 든 문제와 연결된 개념은 도구로 기억되고, 연결되지 않은 개념은 용어로만 기억됩니다. 용어로만 기억된 것은 몇 주 뒤에 거의 남지 않습니다.

이 말을 "필요할 때만 찾아보면 된다"로 줄이면 절반만 맞습니다. 필요하다는 것을 알아채려면 최소한의 지도가 있어야 하기 때문입니다. 이름조차 들어 본 적 없는 개념은 검색어로도 떠오르지 않습니다. 그래서 실제로 필요한 것은 얕고 넓은 목차 한 번, 그리고 막힐 때마다 그 목차에서 한 칸을 깊게 파는 반복입니다.

CS가 없어서 막히는 순간들

아래 증상들은 문법을 아무리 잘 알아도 풀리지 않습니다. 각각이 특정 CS 주제와 짝을 이룹니다.

데이터가 늘자 갑자기 느려집니다. 항목 백 개일 때 잘 돌던 코드가 십만 개에서 멈춘 것처럼 보입니다. 코드는 그대로인데 입력만 커졌다면, 그건 버그가 아니라 알고리즘의 성질입니다. 복잡도라는 말은 이 상황을 설명하기 위해 있습니다.

목록에서 찾는 코드가 반복해서 등장합니다. 반복문 안에서 또 목록을 뒤지고 있다면, 자료구조 선택이 문제일 가능성이 큽니다. 무엇을 자주 하느냐에 따라 리스트가 맞을 수도, 집합이나 딕셔너리가 맞을 수도 있습니다.

동시에 들어온 두 요청이 같은 값을 고쳐서 숫자가 이상해집니다. 혼자 테스트할 때는 절대 재현되지 않다가, 사용자가 생기면 가끔 나타납니다. 동시성과 경쟁 상태를 모르면 이 버그는 "가끔 이상한 버그"로 남습니다.

한글이 깨져서 이상한 기호로 보입니다. 파일을 읽을 때, 다른 시스템에서 데이터를 받을 때 자주 생깁니다. 문자 인코딩이 무엇이고 바이트와 문자가 어떻게 다른지를 한 번 이해하면 이 부류의 문제는 대부분 같은 방식으로 풀립니다.

소수점 계산 결과가 예상과 다릅니다. 더한 값이 미세하게 어긋나거나, 같아야 할 두 값의 비교가 거짓이 됩니다. 부동소수점이 어떻게 저장되는지를 알면 왜 그런지, 그리고 돈 계산에는 왜 다른 방법을 쓰는지가 이해됩니다.

네트워크가 가끔 끊깁니다. 열 번 중 아홉 번은 잘 되고 한 번은 실패합니다. 네트워크는 원래 실패할 수 있다는 전제, 재시도와 타임아웃 같은 이야기가 여기서 필요해집니다.

이 목록의 공통점은 "문법을 더 안다고 풀리지 않는다"는 것입니다. 반대로 말하면, 이런 증상을 만나기 전에 해당 개념을 미리 파는 것은 순서가 어긋난 공부입니다. 증상이 먼저 오고 개념이 뒤따를 때 학습이 가장 싸게 끝납니다. 그리고 한 번 겪은 증상은 대체로 다시 만나기 때문에, 그때 들인 시간은 계속 돌려받습니다.

같은 일을 두 가지 자료구조로

복잡도 이야기는 글로 읽으면 잘 안 와닿지만, 직접 재어 보면 한 번에 이해됩니다. 아래 코드를 그대로 실행해 보되, 결과 숫자는 각자의 컴퓨터에서 나온 값을 보면 됩니다.

MEASURE IT YOURSELF

import random, time

n = 200_000
data = [random.randint(0, 10_000_000) for _ in range(n)]
targets = [random.randint(0, 10_000_000) for _ in range(2_000)]

as_list = data
as_set = set(data)

start = time.perf_counter()
hits_list = sum(1 for t in targets if t in as_list)
list_time = time.perf_counter() - start

start = time.perf_counter()
hits_set = sum(1 for t in targets if t in as_set)
set_time = time.perf_counter() - start

print(hits_list, hits_set)
print(f"list: {list_time:.4f}s")
print(f"set:  {set_time:.4f}s")

n을 2만, 20만, 200만으로 바꿔 가며 두 시간이 각각 어떻게 변하는지 비교해 보세요.

여기서 볼 것은 두 값의 절대 크기가 아니라 변화의 모양입니다. n을 열 배로 키우면 리스트 쪽 시간은 대체로 같이 커지는 반면, 집합 쪽은 거의 그대로입니다. 리스트에서 in은 앞에서부터 하나씩 비교하고, 집합과 딕셔너리는 값을 해시로 바꿔 자리를 바로 찾기 때문입니다.

이걸 한 번 눈으로 보고 나면 "리스트 안에서 in을 반복문 안에 넣지 말라"는 조언이 규칙이 아니라 당연한 이야기로 바뀝니다. 세 자료구조가 각각 어떤 상황에 맞는지는 배열, 리스트, 딕셔너리를 처음 배울 때 헷갈리는 지점에서 더 자세히 다룹니다.

계산기와 다른 결과가 나오는 이유

FLOATING POINT

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
>>> from decimal import Decimal
>>> Decimal("0.1") + Decimal("0.2") == Decimal("0.3")
True

파이썬 대화형 셸에 그대로 쳐 보면 재현됩니다. 다른 언어에서도 대부분 같은 결과가 나옵니다.

이건 파이썬의 버그가 아닙니다. 0.1을 2진수로 정확히 표현할 수 없어서 아주 가까운 값으로 저장되고, 그 오차가 더하기에서 드러난 것입니다. 원리를 한 번 알고 나면 대응도 명확해집니다. 금액처럼 정확해야 하는 값은 정수(원 단위)나 Decimal로 다루고, 실수 비교는 등호 대신 허용 오차를 두고 합니다.

이런 종류의 지식은 외워서 쓰는 게 아니라 한 번 겪고 이름을 알아 두는 것으로 충분합니다. 이름을 알면 다음에 비슷한 증상이 나왔을 때 검색이 됩니다.

막힌 증상에서 찾아볼 주제로

순서를 미리 정해 놓기보다, 아래 짝을 붙여 두고 해당하는 증상이 생겼을 때 그 주제를 하루쯤 파는 방식을 권합니다.

  1. 입력이 커지자 느려짐 → 시간 복잡도, 반복문 안의 반복문 찾기
  2. 찾기·중복 제거가 잦음 → 배열·해시 테이블·집합의 차이
  3. 순서와 우선순위가 필요함 → 정렬, 큐와 스택
  4. 값이 가끔 이상해짐 → 동시성, 경쟁 상태, 원자적 갱신
  5. 문자가 깨짐 → 유니코드와 UTF-8, 바이트와 문자열의 구분
  6. 숫자가 미세하게 안 맞음 → 부동소수점 표현, 정수·고정소수점 대안
  7. 가끔 실패하는 호출 → 타임아웃, 재시도, 멱등성
  8. 메모리가 계속 늘어남 → 참조와 수명, 가비지 컬렉션의 대략적 동작

이 목록을 위에서부터 순서대로 다 볼 필요는 없습니다. 지금 겪고 있는 줄부터 보면 됩니다. 학습 순서를 좀 더 큰 그림에서 잡고 싶다면 로드맵을 참고하고, 파이썬에서 성능을 실제로 재고 개선하는 방법은 파이썬 성능 가이드에 정리되어 있습니다.

양쪽 극단이 다 틀립니다

흔한 실수 하나는 "CS를 다 떼고 나서 개발을 시작하자"는 계획입니다. 이 순서의 문제는 의욕이 아니라 구조입니다. 만들어 본 것이 없으면 배운 개념이 걸릴 데가 없습니다. 해시 테이블을 배워도 내가 느리게 짰던 코드가 없으니 어디에 쓰는 물건인지 감이 안 오고, 트랜잭션을 배워도 데이터가 깨져 본 적이 없으니 왜 그렇게 복잡한지 납득이 안 됩니다. 그래서 반년쯤 뒤에 남는 것은 목차뿐입니다.

비슷하게, 알고리즘 문제를 잘 푸는 능력과 제품을 만드는 능력은 겹치되 같지는 않습니다. 문제 풀이는 요구사항이 이미 정확하게 주어져 있고 정답이 하나입니다. 실제로 무언가를 만들 때는 요구사항을 스스로 정하고, 실패하는 경우를 처리하고, 남이 읽을 코드를 남기고, 나중에 바꿀 수 있게 해 두는 일이 시간의 대부분을 차지합니다. 한쪽을 잘한다고 다른 쪽이 저절로 되지는 않습니다.

그렇다고 반대 극단, 즉 "실무에서는 CS 필요 없다"도 맞지 않습니다. 위에서 본 증상들은 프레임워크가 대신 막아 주지 않습니다. 느려진 코드는 결국 누군가 복잡도를 이해하고 고쳐야 하고, 깨진 인코딩은 문자 표현을 아는 사람이 30분이면 잡을 것을 모르는 사람은 며칠 헤맵니다. CS를 아느냐 모르냐가 갈리는 지점은 평소가 아니라 이런 순간입니다.

현실적인 위치는 그 사이입니다. 만들면서 배우되, 막히는 증상을 그냥 넘기지 않고 그때마다 이름을 찾아 두는 것. 그게 쌓이면 몇 년 뒤에는 목차가 아니라 경험으로 연결된 지도가 남습니다.

알아두면 좋은 점

개념 하나를 봤으면 그날 안에 열 줄짜리 코드로 확인해 보는 습관이 남는 양을 크게 바꿉니다. 해시가 왜 빠른지 읽었다면 위의 측정 코드를 직접 돌려 보고, 부동소수점을 읽었다면 셸에 몇 줄 쳐 봅니다. 읽기만 한 개념은 잊히고, 한 번이라도 실행한 개념은 남습니다.

자주 하는 실수

성능이 의심될 때 재어 보지 않고 짐작으로 코드를 고칩니다. 실제 병목은 대개 예상과 다른 곳에 있어서, 측정 없이 고치면 읽기만 어려워지고 속도는 그대로인 코드가 남습니다. 먼저 재고, 가장 큰 항목 하나만 고칩니다.

이번 주에 해 볼 것

  • 최근 한 달 안에 겪은 "이상했던 순간" 하나를 떠올려 적습니다.
  • 위 짝 목록에서 그에 해당하는 주제 하나를 고릅니다.
  • 그 주제를 두 시간 이내로만 읽고, 바로 열 줄짜리 예제로 확인합니다.
  • 확인한 내용을 자기 코드의 한 군데에 적용해 봅니다.
  • 이번 주에는 여기까지만 하고 다음 주제로 넘어가지 않습니다.

CS는 코딩을 대신하는 공부가 아니라, 이미 쓰고 있는 도구가 왜 그렇게 동작하는지 설명해 주는 공부입니다. 그래서 만든 것이 많을수록 같은 시간에 더 많이 남습니다.

Next Read

독학, 부트캠프, 멘토링 중 무엇이 나에게 맞을까

정답은 하나가 아니라 현재 상태에 따라 달라진다. 각 방식이 어떤 사람에게 더 잘 맞는지 초보자 관점에서 정리했다.