프로그래밍이란 무엇인가

프로그래밍이란 무엇인가

토픽: Programming 레슨: 1 of 16 선수 지식: 없음 목표: 프로그래밍이 무엇인지, 문제 해결 과정, 계산적 사고, 컴퓨터가 프로그램을 실행하는 방법을 이해합니다.


프로그래밍이란 무엇인가?

프로그래밍은 컴퓨터에게 작업을 수행하도록 지시하는 예술이자 과학입니다. 핵심은 문제 해결입니다 — 현실 세계의 문제를 가져와 논리적 단계로 나누고, 컴퓨터가 실행할 수 있는 언어로 그 단계를 표현하는 것입니다.

프로그래밍 vs 코딩

용어가 종종 혼용되지만, 미묘하지만 중요한 차이가 있습니다:

  • 코딩(Coding): 특정 프로그래밍 언어로 코드를 작성하는 행위 — 문법, 키워드, 메커니즘
  • 프로그래밍(Programming): 문제 이해, 솔루션 설계, 코드 작성, 테스팅, 디버깅, 시간에 걸쳐 소프트웨어를 유지보수하는 것을 포함하는 더 넓은 개념

프로그래머는 단순히 코드를 작성하는 것이 아니라: - 문제를 분석합니다 - 알고리즘을 설계합니다 - 적절한 자료구조를 선택합니다 - 유지보수 가능하고 읽기 쉬운 코드를 작성합니다 - 테스트하고 디버그합니다 - 다른 사람들과 협업합니다 - 작업을 문서화합니다 - 기존 코드를 리팩토링하고 개선합니다

비유: 코딩이 프로그래밍에서 차지하는 위치는 타이핑이 소설 쓰기에서 차지하는 위치와 같습니다. 타이핑은 필요한 기술이지만, 글쓰기는 창의성, 구조, 캐릭터 개발, 플롯, 수정을 포함합니다.


계산적 사고

계산적 사고(Computational Thinking)는 복잡한 문제를 관리 가능한 부분으로 나누는 문제 해결 방법론입니다. 네 가지 핵심 구성 요소로 이루어져 있습니다:

1. 분해(Decomposition)

큰 문제를 더 작고 관리 가능한 하위 문제로 나눕니다.

예시: 여행 계획 - 큰 문제: "일본 여행 계획하기" - 분해된 문제: - 목적지 조사 - 항공편 예약 - 호텔 예약 - 일일 일정 계획 - 예산 편성 - 기본 회화 배우기

2. 패턴 인식(Pattern Recognition)

문제를 더 효율적으로 해결하는 데 도움이 되는 유사성, 패턴, 경향을 식별합니다.

예시: 모든 로그인 시스템에 다음이 필요하다는 것을 알게 됩니다: - 사용자명/비밀번호 입력 - 검증 - 암호화 - 세션 관리 - 비밀번호 재설정 메커니즘

하나를 만들고 나면 이 패턴을 인식하고 솔루션을 재사용할 수 있습니다.

3. 추상화(Abstraction)

관련 없는 세부 사항을 무시하고 필수적인 특징에 집중합니다. 추상화는 복잡성을 관리하는 데 도움이 됩니다.

예시: 자동차를 운전할 때 추상화를 사용합니다: - "핸들을 돌린다" — 타이어로의 기계적 연결을 생각하지 않습니다 - "브레이크를 밟는다" — 유압 시스템을 고려하지 않습니다 - 인터페이스(핸들, 페달)와 상호작용하며 구현(엔진, 변속기, 브레이크)을 이해할 필요가 없습니다

프로그래밍에서: - sort(array) 함수 사용 — 퀵정렬인지, 병합정렬인지, 힙정렬인지 알 필요가 없습니다 - database.save(user) 호출 — 내부 SQL, 연결 풀링, 트랜잭션이 추상화되어 있습니다

4. 알고리즘적 사고(Algorithmic Thinking)

문제를 해결하기 위한 단계별 지시사항(알고리즘)을 설계합니다. 알고리즘은 다음과 같아야 합니다: - 정확함(Precise): 각 단계가 명확하게 정의됨 - 명확함(Unambiguous): 해석의 여지가 없음 - 유한함(Finite): 결국 종료되어야 함 - 일반적(General): 하나의 경우가 아닌 입력 범위에 대해 작동함

예시: 차 만들기 알고리즘

1. 주전자에  채우기
2. 주전자 켜기
3. 물이 끓지 않는 동안:
   - 기다리기
4. 컵에 티백 넣기
5. 끓는 물을 컵에 붓기
6. 3-5 기다리기
7. 티백 제거
8. 원하면 우유/설탕 추가
9. 완료

문제 해결 과정

전문 프로그래머는 체계적인 과정을 따릅니다:

1. 문제 이해

  • 입력은 무엇인가?
  • 예상 출력은 무엇인가?
  • 제약 조건은 무엇인가?
  • 엣지 케이스는 무엇인가?

예시: "리스트에서 가장 큰 숫자를 찾는 프로그램 작성" - 입력: 숫자 리스트 (비어있을 수 있나? 음수?) - 출력: 가장 큰 숫자 (리스트가 비어있으면?) - 제약 조건: 시간? 메모리? 리스트 크기?

2. 솔루션 계획

  • 문제 분해하기 (분해)
  • 알고리즘 설계
  • 자료구조 선택
  • 여러 접근법 고려

가장 큰 숫자 찾기 접근법 예시: - 접근법 1: 리스트 정렬, 마지막 요소 가져오기 - 접근법 2: 리스트를 반복하며 지금까지 본 최댓값 추적 - 어느 것이 더 나은가? (접근법 2: O(n) vs O(n log n), 메모리 적게 사용)

3. 솔루션 구현

알고리즘을 코드로 번역합니다. 여러 언어로 동일한 솔루션:

Python:

def find_largest(numbers):
    if not numbers:
        return None

    largest = numbers[0]
    for num in numbers:
        if num > largest:
            largest = num
    return largest

# Usage
print(find_largest([3, 7, 2, 9, 1]))  # Output: 9

JavaScript:

function findLargest(numbers) {
    if (numbers.length === 0) {
        return null;
    }

    let largest = numbers[0];
    for (let num of numbers) {
        if (num > largest) {
            largest = num;
        }
    }
    return largest;
}

// Usage
console.log(findLargest([3, 7, 2, 9, 1]));  // Output: 9

Java:

public class Main {
    public static Integer findLargest(int[] numbers) {
        if (numbers.length == 0) {
            return null;
        }

        int largest = numbers[0];
        for (int num : numbers) {
            if (num > largest) {
                largest = num;
            }
        }
        return largest;
    }

    public static void main(String[] args) {
        int[] numbers = {3, 7, 2, 9, 1};
        System.out.println(findLargest(numbers));  // Output: 9
    }
}

C++:

#include <iostream>
#include <vector>
#include <optional>

std::optional<int> findLargest(const std::vector<int>& numbers) {
    if (numbers.empty()) {
        return std::nullopt;
    }

    int largest = numbers[0];
    for (int num : numbers) {
        if (num > largest) {
            largest = num;
        }
    }
    return largest;
}

int main() {
    std::vector<int> numbers = {3, 7, 2, 9, 1};
    auto result = findLargest(numbers);
    if (result) {
        std::cout << *result << std::endl;  // Output: 9
    }
    return 0;
}

주목: 알고리즘은 모든 언어에서 동일합니다 — 문법만 다릅니다.

4. 솔루션 테스트

  • 정상 입력으로 테스트
  • 엣지 케이스 테스트 (빈 리스트, 단일 요소, 음수)
  • 경계 조건 테스트
# Test cases
assert find_largest([3, 7, 2, 9, 1]) == 9
assert find_largest([5]) == 5
assert find_largest([-3, -7, -2]) == -2
assert find_largest([]) is None

5. 개선 및 최적화

  • 코드가 읽기 쉬운가?
  • 더 효율적으로 만들 수 있는가?
  • 유지보수 가능한가?

개선 (내장 함수 사용):

def find_largest(numbers):
    return max(numbers) if numbers else None

더 간결하고, 테스트된 라이브러리 코드를 활용하며, 명확합니다.


추상화 수준

프로그램은 하드웨어부터 고수준 프레임워크까지 여러 수준에 존재합니다:

1. 기계어(Machine Code) (최저 수준)

CPU가 직접 실행하는 바이너리 명령:

10110000 01100001  // Move 'a' into register AL

2. 어셈블리 언어(Assembly Language)

기계 명령을 위한 사람이 읽을 수 있는 니모닉:

MOV AL, 61h  ; Move 'a' into register AL

3. 고수준 언어(High-Level Languages)

인간 언어에 가까운 추상화된 문법:

letter = 'a'

4. 프레임워크와 라이브러리(Frameworks and Libraries) (최고 수준)

일반적인 작업을 위한 미리 구축된 솔루션:

# Django web framework
from django.http import HttpResponse

def hello(request):
    return HttpResponse("Hello, world!")

추상화의 힘: TCP/IP, HTTP 헤더, 소켓 프로그래밍, 어셈블리를 이해하지 않고도 웹 애플리케이션을 구축할 수 있습니다. 각 계층은 아래 계층의 복잡성을 추상화합니다.


프로그래머의 역할

프로그래머는 도구로 코드를 사용하는 문제 해결자입니다. 주요 책임:

  1. 요구사항 이해: 어떤 문제를 해결하는가? 누구를 위한가?
  2. 솔루션 설계: 아키텍처, 알고리즘, 자료구조
  3. 코드 작성: 설계를 작동하는 소프트웨어로 변환
  4. 테스팅: 정확성과 신뢰성 보장
  5. 디버깅: 오류 찾기 및 수정
  6. 유지보수: 업데이트, 리팩토링, 기존 코드 개선
  7. 협업: 다른 개발자, 디자이너, 이해관계자와 작업
  8. 문서화: 미래의 개발자(미래의 자신 포함)가 이해할 수 있게 코드 작성
  9. 학습: 기술은 빠르게 변화하므로 지속적인 학습이 필수

프로그래밍은 의사소통입니다: - 컴퓨터와의 의사소통: 정확하고 명확한 지시 - 다른 개발자와의 의사소통: 명확하고 유지보수 가능하며 문서화된 코드 - 이해관계자와의 의사소통: 요구사항 이해, 기대치 설정


컴퓨터가 프로그램을 실행하는 방법

실행 모델을 이해하면 성능과 동작에 대해 추론하는 데 도움이 됩니다.

컴파일(Compilation)

소스 코드컴파일러기계어실행

언어: C, C++, Rust, Go

과정: 1. 소스 코드 작성: hello.c 2. 컴파일: gcc hello.c -o hello 3. 실행: ./hello

장점: - 빠른 실행 (최적화된 기계어) - 컴파일 시점에 오류 포착 - 컴파일러에 대한 런타임 의존성 없음

단점: - 느린 개발 사이클 (재컴파일 필요) - 플랫폼별 바이너리 (Windows/Mac/Linux용으로 재컴파일 필요)

인터프리테이션(Interpretation)

소스 코드인터프리터실행 (라인별)

언어: Python, Ruby, JavaScript (초기 버전)

과정: 1. 소스 코드 작성: hello.py 2. 실행: python hello.py

장점: - 빠른 개발 사이클 (컴파일 단계 없음) - 플랫폼 독립적 (인터프리터만 필요) - 동적 기능 (eval, 런타임 코드 생성)

단점: - 느린 실행 (최적화 없음) - 런타임에 오류 발견 - 인터프리터 설치 필요

JIT(Just-In-Time) 컴파일

소스 코드바이트코드JIT 컴파일러기계어실행

언어: Java, C#, JavaScript (V8 같은 최신 엔진)

과정 (Java 예시): 1. 작성: Hello.java 2. 바이트코드로 컴파일: javac Hello.javaHello.class 3. 실행: java Hello (JVM이 즉석에서 바이트코드를 기계어로 컴파일)

장점: - 플랫폼 독립적 바이트코드 - 런타임 최적화 (실제 사용 패턴 기반) - 순수 인터프리테이션보다 빠름

단점: - 워밍업 시간 (초기 실행 느림) - 더 많은 메모리 오버헤드


소프트웨어 개발 생명주기(SDLC)

코드 작성은 한 단계일 뿐입니다. 전문적인 소프트웨어 개발은 다음을 포함합니다:

  1. 계획: 무엇을 구축하는가? 왜?
  2. 분석: 요구사항 수집, 타당성 연구
  3. 설계: 아키텍처, 데이터베이스 스키마, UI/UX 목업
  4. 구현: 코드 작성
  5. 테스팅: 단위 테스트, 통합 테스트, 사용자 수용 테스트
  6. 배포: 프로덕션 릴리스
  7. 유지보수: 버그 수정, 업데이트, 새 기능

이것은 종종 반복적입니다: Agile, Scrum, Kanban 방법론은 계획 → 구축 → 테스트 → 릴리스의 반복 사이클을 포함합니다.


프로그래밍의 간략한 역사

Ada Lovelace (1843)

  • 기계 처리를 위한 최초의 알고리즘 작성
  • Charles Babbage의 Analytical Engine에 대한 노트
  • 최초의 프로그래머로 간주

Alan Turing (1936)

  • 튜링 머신: 계산의 이론적 기초
  • 튜링 테스트: 인공지능 정의
  • 제2차 세계대전의 암호 해독가

초기 언어 (1950년대-1960년대)

  • FORTRAN (1957): Formula Translation, 과학 계산용
  • COBOL (1959): 비즈니스 애플리케이션
  • LISP (1958): 인공지능, 함수형 프로그래밍

구조적 프로그래밍 (1970년대)

  • C (1972): 시스템 프로그래밍, 거의 모든 현대 언어에 영향
  • Pascal (1970): 프로그래밍 교육, 구조적 설계

객체지향 시대 (1980년대-1990년대)

  • C++ (1985): OOP + 시스템 프로그래밍
  • Java (1995): "한 번 작성, 어디서나 실행"
  • Python (1991): 가독성, 단순성

현대 (2000년대-현재)

  • JavaScript: 브라우저 스크립팅에서 풀스택 개발로 진화
  • Rust (2010): 가비지 컬렉션 없는 메모리 안전성
  • Go (2009): 단순성, 동시성, 효율성
  • Kotlin, Swift: 현대적, 표현력 있는, 안전한
  • 프레임워크의 부상: React, Angular, Django, Rails

의사소통으로서의 프로그래밍

코드는 작성되는 것보다 훨씬 더 자주 읽힙니다. 컴퓨터에 지시하는 것만이 아니라 다음에게 의도를 전달하는 것입니다:

  • 미래의 자신 (6개월 후면 왜 이렇게 작성했는지 잊어버릴 것입니다)
  • 팀원 (코드를 이해하고, 수정하고, 확장해야 하는 사람들)
  • 유지보수 담당자 (버그를 수정하고 기능을 추가할 사람들)

나쁜 코드 (소통이 부족함)

def f(x):
    return [i for i in x if i % 2 == 0]

f는 무엇을 하는가? x는 무엇인가? 무엇을 반환하는가?

좋은 코드 (명확하게 소통함)

def filter_even_numbers(numbers):
    """
    Returns a list of even numbers from the input list.

    Args:
        numbers: List of integers

    Returns:
        List of even integers from the input
    """
    return [num for num in numbers if num % 2 == 0]

이제 즉시 명확합니다: - 목적: 짝수 필터링 - 입력: 숫자 리스트 - 출력: 짝수 리스트 - 구현: 의미 있는 변수명으로 읽기 쉬움


연습 문제

연습 문제 1: 분해

다음 현실 세계 문제를 계산 단계로 나누세요:

문제: 간단한 도서관 관리 시스템 구축

  • 주요 구성 요소는 무엇인가?
  • 어떤 데이터를 저장해야 하는가?
  • 어떤 작업이 필요한가?

연습 문제 2: 추상화

다음을 다양한 추상화 수준에서 설명하세요:

작업: 이메일 보내기

  • 높은 수준의 추상화 (사용자 관점)
  • 중간 수준의 추상화 (애플리케이션 관점)
  • 낮은 수준의 추상화 (네트워크/프로토콜 관점)

연습 문제 3: 알고리즘적 사고

다음에 대한 단계별 알고리즘을 (일반 영어나 의사코드로) 작성하세요:

문제: 단어가 회문(palindrome)인지 판단 (앞뒤로 읽어도 같음)

  • 예시: "racecar" → true, "hello" → false

연습 문제 4: 문제 해결

5단계 문제 해결 과정 적용:

문제: 문장에서 특정 단어가 몇 번 나타나는지 세는 함수 작성

  1. 이해: 입력은? 출력은? 엣지 케이스는?
  2. 계획: 어떤 알고리즘을 사용할 것인가?
  3. 구현: 최소 두 가지 다른 언어로 작성
  4. 테스트: 테스트 케이스 작성
  5. 개선: 더 나아질 수 있는가?

연습 문제 5: 계산적 사고

계산적 사고의 네 가지 구성 요소를 적용:

문제: 대규모 음악 컬렉션 정리

  • 분해: 어떤 하위 문제가 존재하는가?
  • 패턴 인식: 어떤 패턴을 식별할 수 있는가?
  • 추상화: 어떤 필수 기능이 중요한가? 무엇을 무시할 수 있는가?
  • 알고리즘: 컬렉션을 정리하는 단계 개요

연습 문제 6: 코드 의사소통

이 코드를 더 소통이 잘 되도록 리팩토링하세요:

def p(l):
    r = 1
    for i in l:
        r *= i
    return r
  • 의미 있는 이름 사용
  • 문서 추가
  • 엣지 케이스 고려

요약

프로그래밍은: - 계산적 사고를 사용한 문제 해결 - 컴퓨터 및 사람과의 의사소통 - 과정: 이해 → 계획 → 구현 → 테스트 → 개선 - 다층적: 기계어부터 고수준 프레임워크까지 - 실행 모델: 컴파일, 인터프리테이션, JIT - 개발 방법: 분석, 설계, 구현, 테스팅, 유지보수

최고의 프로그래머는 문법을 가장 많이 아는 사람이 아니라 명확하게 생각하고, 체계적으로 문제를 해결하며, 의도를 전달하는 코드를 작성할 수 있는 사람입니다.


다음 단계

다음 레슨: Programming Paradigms →

to navigate between lessons