프로그래밍 공부는 어떤 순서대로 시작하면 좋을까? (비전공자, 청소년편)

2023. 7. 18. 17:45IT 정보

프로그래밍으로 자신만의 프로그램을 만들어 서비스를 출시하고 싶으신가요?

 

컴퓨터 전공자가 아니라서 할 수 있을지 걱정되신다고요?

 

사실 프로그래밍, 코딩은 누구든지 할 수 있는 분야예요

대표적으로 테슬라 CEO 일론머스크나 페이스북 창업자 주커버그, 마이크로소프트 창업자 빌게이츠도 

모두 어린 나이에 코딩을 공부해서 자신만의 프로그램을 만들고 심지어 팔기까지 했어요

 

어린 머스크는 프로그래밍에도 흥미를 가졌다. 12살 때 프로그래밍 언어를 독학Blastar[3]라는 이름의 게임을 동생과 함께 만들고 이를 게임 잡지에 500달러(현재 가치로 1,200달러)에 판매했다. 또한 모형 로켓 만드는 데도 취미가 있어 가솔린과 각종 화학 약품을 혼합하여 로켓 연료를 만들곤 그걸 자작 로켓에 넣어 시험 발사한 적도 있었다.

출처 : 나무위키

 

프로그래밍, 코딩을 꼭 배울 필요는 없고

그렇다고 12살도 하는 프로그래밍을 너무 어렵게 생각할 필요가 없습니다.

 

여러분이 배우려는 의지만 있다면!! 누구든지 할 수 있다고 생각합니다.

 

 

 

그럼 본론으로 들어가서 개발하는 데 있어 문제없이 기본기가 쌓이려면 어떤 지식을 쌓아놔야 할까요?

- 언어(Python, Java, C 등)

- 자료구조 (List, Tree, Hash 등)

- 알고리즘 (알고리즘의 조건, Sorting, 시간과 공간 복잡도, Dijkstra 등)

- 데이터베이스 (SQL query, Mysql, Oracle 등)

- 운영체제 (Mutex, Semaphore, Process Scheduling 등)

 

어떤 분야를 공부하든 각 분야의 용어를 이해하는 게 중요해요

이 정도가 컴퓨터 공학자들의 용어를 이해하기 위해 들어야 하는 이론 지식입니다.

물론 이 정보를 모두 공부해야만 프로그램을 짤 수 있다는 건 아니에요

 

그럼 어떤 순서대로 이론 공부를 하는 게 좋을까요?

언어 > 자료구조 > 알고리즘 > 데이터베이스 > 운영체제 > (다양한 영역)

이 순서대로 공부해 보면 웬만한 프로그래밍 용어는 이해하게 될 거예요

 

이제 하나씩 파헤쳐 볼까요?

 

 


Chapter 1. 프로그래밍 언어 공부는 어떻게 하면 좋을까? 

대표적인 입문용 언어Python, C언어 두 가지로 나뉩니다.

Python을 먼저 공부하는 것을 추천하시는 분이 계시고, C를 먼저 공부하는 것을 추천하시는 분이 계신데요

아래의 장단점을 비교해 보며 본인에게 맞는 언어로 시작하는 것을 추천드립니다.

 

  Python C
난이도
제어 깊이
오류 빈도
동적/정적 동적 (리스트의 크기가 알아서 늘어남) 정적 (배열의 크기를 늘리려면 조작이 필요함)
실행 속도
추천 대상 나이가 어리다면 파이썬을 추천 모두 확실하게 배우고 싶다면 C언어를 추천 

 

위 내용대로 요약해 보면

스파르타 방식으로 제대로 배우고 싶다면 C언어를, 쉽고 친절하게 배우고 싶다면 Python을 추천합니다!

사실 C언어를 바로 배우면 난이도가 비교적 높아서 오류로 범벅될 수 있습니다.

하지만 C언어를 배울 때의 장점은 프로그래밍에 대한 type, pointer 등의 내부적으로 컴퓨터가 어떻게 처리하는 지를 깊게 공부할 수 있다는 점이에요

그리고 속도가 매우 빠르다는 장점이 있습니다.

특히 처음에 C언어로 처음에 공부하게 되면 다른 언어들은 점점 쉬워지는 느낌을 받게 됩니다.

(C언어로 하나하나 디테일하게 배웠기 때문이죠)

단점은 어려워서 프로그래밍을 포기할 수 도 있어요

(어린 나이라면 개인적으로 Python을 추천합니다.)

 

반면에 Python은 전반적인 프로그래밍의 메커니즘을 빠르게 배울 수가 있습니다!

type이나 pointer에 대해 오류가 잘 발생하지 않아 조금 덜 배울 수는 있지만, 그거는 차근차근히 공부하면서 배울 수 있고 

다른 사람들이 작성해 놓은 다양한 라이브러리들을 활용하여 매우 적은 줄로 자신만의 프로그램을 만들어 볼 수 있어요! 

 


위의 두 가지 언어가 아니라도 사실 프로그래밍 언어라면 비슷비슷하니 하나를 배우면 다른 언어는 금방 터득하게 돼요.

잘 선택해서 공부해 보세요!

 


Chapter 2. 자료구조에서는 어떻게 공부해야 할까? (언어공부를 하고 읽어보세요!)

우선 자료구조는 왜 배워야 하는가? 를 생각하게 되는데요

그냥 데이터를 리스트에만 담으면 되는 거 아니야?라고 생각할 수도 있지만

만약 데이터가 1억 개라면?

1억 개의 데이터를 정렬해야 한다면?

이미 정렬되어 있는 1억 개의 데이터정렬을 유지하며 1개의 데이터를 삽입해야 한다면?

 

단순히 상황에 맞는 자료구조를 사용하지 않고 그때마다 데이터를 변환하게 되면

사용자가 간단한 정보만 요청했는데, 10초 이상도 걸릴 수가 있어요 (작성한 request API가 timeout 되기도 합니다.)

 

하지만! 자료구조를 잘 활용하면 이런 문제가 해결됩니다.

만약 데이터 한 개를 조회하는데 0.01초가 걸린다고 가정했을 때, 1억 개를 모두 조회하려면 100만 초 (1억 번 x 0.01초 = 100만 초)가 걸리겠죠?

 

반면에 자료구조중에 트리 자료구조인 '이진트리'를 활용하면,

log2(100000000+1) = 26.575425 = 약 27번 조회

27번 x 0.01초 = 최대 0.27초 걸립니다.

1억건의 데이터를 조회하는데 100만 초가 0.27초가 되어버리도록 데이터를 저장할 수 있는 장치가 자료구조예요!

 

대표적으로 [-5, 0, 5, -3, 2, 7, -1] 를 이진트리로 저장한다면 아래의 트리 그림처럼 변형하여 저장해요.

 

그리고 이렇게 이진트리로 저장한 데이터는 중위순회라는 방식으로 조회하게 되면, 데이터가 모두 정렬됩니다!

그럼 이진트리라는 자료구조를 유지하기만 한다면? 데이터가 추가되어도 모두 정렬되어 있는 것과 비슷하게 됩니다.

 

이외에도

Linked List (연결리스트), Stack (스택), Queue (큐), Priority Queue (우선순위 큐), Hash Table (해시 테이블), Red Black Tree (레드블랙트리), Heap Tree (힙트리)

등등 다양한 자료구조가 있습니다.

 

이 정도 배우셨으면 일반적인 자료구조의 핵심 용어들은 익히셨을 거예요 

위에 적힌 내용들을 상황에 맞게 그때그때 알맞게 활용한다면

엄청나게 응답이 빠른 시스템을 구축하실 수 있을 거예요!

 

 

단, 연결리스트를 공부할 때는 C언어로 공부하는 게 제대로 배웁니다

C언어는 정적인 언어라 포인터를 사용하여 연결을 구현해 볼 수 있고, 

파이썬은 동적인 언어라 Linked List처럼 기본적으로 작동하기 때문에 그냥 지나쳐버릴 수 있어요!

 

 


Chapter 3. 알고리즘에서는 어떻게 공부해야 할까? 

알고리즘을 공부하게 되면 코드를 작성할 때, 시간 복잡도, 공간 복잡도라는 개념이 머릿속에 각인되어 어떤 소스코드를 작성할 때 

시간복잡도와 공간복잡도를 고려하며 작성하게 되는 훈련이 됩니다.

이 알고리즘을 공부하고 이전에 작성했던 코드를 다시 보았을 때 더욱 효율적인 코드가 머릿속에 떠오르게 될 텐데요

사실 앞의 자료구조와 알고리즘은 보통 함께 배우게 됩니다.

왜냐하면 알고리즘을 알려면 자료구조의 내용을 언급할 수밖에 없거든요

대표적인 예로 앞의 이진트리 또한 시간복잡도를 계산할 수가 있는데요

알고리즘을 모르시는 분이 앞의 이진트리가 1억 건의 데이터에서 하나의 샘플을 검색할 때 27번 연산하여 0.27초가 걸린다고 했을 때 감이 안오셨을 수도 있어요

 

여기서는 간단하게 어떻게 1억건의 데이터에서 0.27초 만에 내가 원하는 데이터 조회가 가능한지 설명할 거예요


만약 가지고 있는 데이터가 [-5, 0, 5, -3, 2, 7, -1]이라는 7개의 데이터가 있다고 가정해 볼게요

이 데이터들 중에서 '-1'이라는 데이터를 찾아내려면 총 몇 번을 비교해야 하는지 계산해 볼까요?

 

아래는 위처럼 데이터를 찾아낼 때의 파이썬 코드예요

data = [-5, 0, 5, -3, 2, 7, -1]

def find_element(element, data):
    for i, e in enumerate(data):
        if e == element :
        	return i
    return None
#value = -1 # 찾고 싶은 값
#result = find_element(value, data)
#if result is not None:
#	print(f"{value}은/는 data[{result}] 에 있어요!")
#else :
#	print(f"{value}을/를 찾을 수 없어요")

 

하지만! 만약에 데이터가 이미 정렬이 되어있었다면? 살짝 다른 트릭을 줄 수 있습니다.

이미 데이터가 정렬이 되어있었다면, 하나씩 순차적으로 조회하는 것이 아니라 크기가 중간인 (중앙값) 원소부터 비교하게 되면

본인이 찾고자 하는 값 보다 크면 더 큰 값의 위치들만, 더 작으면 더 작은 값의 위치들만 비교할 수 있습니다.

 

이렇게 찾아내면 단 3번 만에 원하는 값을 찾아낼 수 있어요!

찾고자 하는 값 : -1

 

(UP & DOWN 게임으로 이기기 위한 유명한 예시예요)

 

 


중앙 값을 선택합니다.
-1은 0보다 작으니
[-5, -3, -1] 에서 중앙값 -3을 비교합니다.

0보다 작고, -3보다 큰 (-3 < x < 0)
-1을 비교하고 끝이납니다.

 

이 알고리즘을 Binary Search 알고리즘이라고 합니다.

(이진 탐색(Binary Search)이란 데이터가 정렬되어 있는 리스트/배열에서 원하는 값을 찾아내기 위한 알고리즘입니다.)

data = [ -5, -3, -1, 0, 2, 5, 7 ]
def binary_search(data, element, start, end):
    if start > end:
        return None
    mid = (start + end) // 2
    if data[mid] == element:
        return mid
    elif data[mid] > element:
        return binary_search(data, element, start, mid-1)
    else:
        return binary_search(data, element, mid+1, end)

#value = -1 # 찾고 싶은 값
#size = len(data)
#result = binary_search(data, value, 0, size-1)
#if result is not None:
#	print(f"{value}은/는 data[{result}] 에 있어요!")
#else :
#	print(f"{value}을/를 찾을 수 없어요")

 

그렇다면 데이터가 사전에 정렬이 되어 있었다면 데이터를 검색하기가 더욱 편하겠구나!라고 생각할 수 있겠죠?

 

이런 원리를 활용한 이론이 바로 자료구조입니다. 

결국 자료구조와 알고리즘은 서로 활용하게 되는 관계라 보통 강의에서 함께 가르칩니다. 

 

마치 사전에 정렬되어 있던 것처럼 데이터를 저장해 둘 때, 바로바로 빠르게 검색, 정렬, 최댓값 또는 최솟값 등

필요로 하는 값을 빠르게 꺼내오기 위한 저장 장치로 활용되거든요

 

그리고 Binary Search 방법을 활용한 자료구조가 Binary Search Tree (BST) 에요

 

 

이런 자료구조를 트리 (Tree) 자료구조로 불리며,

Binary Search Tree (이하 BST라고 하겠습니다.)에서는 Root 노드부터 찾고자 하는 값과 비교하여 크면 오른쪽 자식노드, 작으면 왼쪽 자식노드를 비교하며 원하는 값이 나왔을 때 값을 조회합니다.

 

그럼 이 BST는 데이터의 개수가 1억 개일 때, 총 몇 회 비교하게 되는지 계산해 볼까요?

트리에서의 최대 비교 횟수는 곧 트리의 높이와 같습니다.

 

즉 데이터가 1억 개일 때, 트리의 높이를 알면 원하는 값을 찾기 위한 최대 비교 횟수를 알아낼 수 있다는 것을 의미합니다.

그럼 데이터가 7개일 때, 트리의 높이를 계산하려면?

 

각 노드의 개수가 데이터의 개수가 될 거고

7 = 20+21+22 = 23-1이 됩니다.

 

즉 7 = 2^3-1 이면, 데이터의 개수가 7일 때, 비교 횟수가 3이라는 관계가 성립됩니다.

 따라서 식을 세우면,

N = 2h-1이며, h = log2(N+1) 이 됩니다. 따라서 1억건의 데이터는 총 log2(1억+1) = 26.575425 회 비교하게 되며, 데이터 하나를 비교하는데 0.01초가 걸린다고 가정한다면, 약 0.27초 가량 걸리는 것 입니다.

 

이런 식으로 데이터가 N개 들어왔을 때, N개를 기준으로 몇 번을 계산하는 지를 가늠하는 것을 시간복잡도라고 합니다.

비슷하게 메모리 사용량을 가늠하는 것을 공간복잡도라고 합니다.

 

설명을 보시면, logN과 N은 엄청난 연산량의 차이를 만들거든요

 

알고리즘 강의를 들으시면, 이런 식으로 다양한 컴퓨터 과학의 방법론들을 배우실 수 있습니다.

예를 들면, 무작위로 섞여있는 데이터를 정렬하기 위해 다양한 정렬방법을 공부하고 각각의 정렬방법들의 시간복잡도, 공간복잡도를 비교해 보며 실무에서 각 상황에 맞게 알고리즘을 사용할 수 있게 됩니다. 

 

 

그리고 만약 프로그래머로 취업을 하고 싶으시다면, 알고리즘을 공부하고 나서부터는 

꾸준히 주기적으로 코딩테스트로 알고리즘 문제를 1~2 문제씩 풀어보는 것을 추천드립니다.

몇몇 기업들은 코딩테스트를 통과해야 취업할 수 있거든요

 


Chapter 4. 데이터베이스에서는 어떻게 공부해야 할까? 

데이터베이스(RDBMS)는 서비스를 운영하면서

사용자가 접속을 종료해도 이전에 접속했던 기록을 저장하고 다음번에 접속했을 때도 기록이 유지되도록 하고 싶을 때 사용하게 되는데요

사용자의 기록을 담아 두는 곳을 데이터베이스라고 합니다.

 

데이터 베이스는 Mysql, Oracle, MongoDB.. 등 다양하게 있지만

대표적으로 많이 사용되며 무료인 Mysql 을 사용하여 공부하게 됩니다.

 

대표적으로 데이터베이스에서 사용자가 로그인 했을 때, 사용자의 정보를 가져오려면 어떻게 해야할까요? 

 

select users.username, users.email from users where user_id = '사용자의 아이디';

위의 문장을 Mysql 에 입력하면 사용자의 username 이라는 값과 email 이라는 값이 가져와져요. 

위의 문장의 문법을 구조적 질의 언어 (SQL) 이라고 부릅니다!

 

이 과정에서는 주로 어떻게 데이터를 삽입하고 저장하고 삭제하고 조회하고 정렬하는 지를 SQL 쿼리를 작성해 보며 공부해야 합니다.

특히 복잡한 관계의 데이터를 조회하는 것을 위주로 연습해 보고 쿼리를 작성해 보세요!

 

아래의 쿼리 예약어들과 subquery, view table은 모두 사용해 보도록 해요!

select, from, where, join, left join, group by, having, order by..

 

그리고!! 특히 처음에 혼자서 서비스를 운영할 때, 인젝션 공격을 당할 수 있는데요.

SQL 인젝션 공격을 모르시는 분들은 아래의 링크 글을 꼭 읽어보세요!

SQL 인젝션 공격

 

 


Chapter 5. 운영체제에서는 어떻게 공부해야 할까?  (앞의 공부들을 마치고 읽어보세요!)

자 이제 드디어 마지막 Chapter입니다!

 

운영체제에는 멀티 프로세싱, 멀티 스레드라는 개념을 배우게 되는데요

멀티 프로세싱을 공부하게 되면, 한 번에 여러 프로세스 또는 스레드가 실행되어 속도를 x N 배로 늘려버릴 수 있는 필살기를 배우게 됩니다. 

하지만 동시에 여러 프로세스나 쓰레드가 함께 실행되면 다양한 문제가 발생합니다.

대표적으로 공유 자원에 접근할 때! 문제가 발생하는데요

 

코드로 한번 볼까요?

import threading
import queue
lock = threading.Lock()
q = queue.Queue()


# 쓰레드가 동일한 자원에 접근하는 작업 : do_work
def do_work(t_param):
    for k in t_param:
        print(k, end = '') 
        # 사실 파이썬은 print문이 자체적으로 block 됩니다.
        # 그래서 예제로는 1문자씩 print 하게 되었습니다. 
    print('\n') 
    

# 큐에서 받아서 
def worker():
    while True:
        # 큐에서 작업할 아이템이 있다면 가져오기! 그렇지 않으면, 대기 상태가 됩니다.
        t_i = q.get()
        
        # 큐에서 꺼내온 item으로 작업을 실행합니다!
        try:
            i, t_param = t_i
            # 각 쓰레드가 수행할 함수를 실행합니다.
            do_work(t_param)

        except :
            pass
        # 작업이 끝나면 queue에 알립니다. 
        q.task_done()

        
N = 5 # 쓰레드 개수
for i in range(N):
    t = threading.Thread(target=worker)
    t.daemon = True
    t.start()
# 쓰레드 N개를 생성하고, 시작합니다.


for i in range(10):
    t = (i, f'{i}번째 작업용 파라미터')
    q.put(t)
# 큐에 작업을 넣어줍니다.


q.join()

위의 코드를 실행해보면, print 문을 여러개의 각 쓰레드가 동시에 출력하다보니깐, 문자열이 뒤죽박죽으로 섞여있을거에요

아래의 결과처럼요!

[코드 출력 결과]
01234번째 작업용 파라미터

5번째 작업번째 작업용 파라미터

6번째 작업용 파라미터

7번째 작업용 파라미터

8번째 작업용 파라미터

9번째 작업용 파라미터

용번째 작업용 파라미터

 파라미터

번째 작업용 파라미터

번째 작업용 파라미터

 

 

하지만, do_work 함수에서 lock 을 걸어주게 되면 쓰레드가 동일한 자원(출력문)에 접근하려고 할 때

다른 쓰레드가 접근을 못하게 막고 업무가 끝나면 다른 쓰레드가 접근이 가능해집니다.

import threading
import queue
lock = threading.Lock()
q = queue.Queue()


# 쓰레드가 동일한 자원에 접근하는 작업 : do_work
def do_work(t_param):
    with lock: # 출력하기 전에 항상 lock 을 걸기!
        for k in t_param:
            print(k, end = '') 
            # 사실 파이썬은 print문이 자체적으로 block 됩니다.
            # 그래서 예제로는 1문자씩 print 하게 되었습니다. 
        print('\n') 
    

# 큐에서 받아서 
def worker():
    while True:
        # 큐에서 작업할 아이템이 있다면 가져오기! 그렇지 않으면, 대기 상태가 됩니다.
        t_i = q.get()
        
        # 큐에서 꺼내온 item으로 작업을 실행합니다!
        try:
            i, t_param = t_i
            # 각 쓰레드가 수행할 함수를 실행합니다.
            do_work(t_param)
        except :
            pass
        # 작업이 끝나면 queue에 알립니다. 
        q.task_done()

        
N = 5 # 쓰레드 개수
for i in range(N):
    t = threading.Thread(target=worker)
    t.daemon = True
    t.start()
# 쓰레드 N개를 생성하고, 시작합니다.


for i in range(10):
    t = (i, f'{i}번째 작업용 파라미터')
    q.put(t)
# 큐에 작업을 넣어줍니다.


q.join()

결과는 아래처럼 나옵니다.

[코드 출력 결과]
0번째 작업용 파라미터

4번째 작업용 파라미터

5번째 작업용 파라미터

6번째 작업용 파라미터

7번째 작업용 파라미터

8번째 작업용 파라미터

9번째 작업용 파라미터

1번째 작업용 파라미터

3번째 작업용 파라미터

2번째 작업용 파라미터

 

이 코드를 보시면, lock 이라는 코드가 있죠?

이런식으로 하나의 쓰레드가 작업을 하고 있을 때, 다른 쓰레드들이 동일한 작업을 못하도록 block 해놓고 작업을 하게 됩니다.

(이것을 이해하려면 세마포어, 뮤텍스 등의 용어를 이해하셔야해요!)

 

multi-thread 또는 multi-process 에서는 이외에도 다양한 문제가 발생할 수 있는데요

Process Scheduling에서는, 실제 CPU에서 어떻게 스케쥴링하여 프로그램이 실행되는지 (대용량의 게임인 오버워치, LOL 등) 이해해 볼 수 있습니다.

 

관련하여 자세한 내용을 배우고 싶다면? 운영체제를 공부하시면 됩니다.

 

여기까지 공부하셨다면 적어도 전공자가 아니어도 소프트웨어 개발면에서는 컴퓨터 전공자에게 크게 꿀리지 않는 이론과 용어의 이해가 되었을 것이라고 생각됩니다.

 

 

 

 

그럼 20000 즐코하세요!