본문 바로가기
컴퓨터공학/기초

프로그래밍의 기본 : 문장과 함수

by 하이방가루 2022. 8. 5.
728x90
반응형

이 글은 Crash Course의 Computer Science를 보고 정리한 글입니다.

많은 낮은 수준의 세부 사항은 복잡한 프로그램을 만드는 데 장애가 된다. 이러한 낮은 수준의 세부 사항을 추상화하기 위해 프로그래밍 언어는 프로그래머가 계산 문제 해결에 집중하고 하드웨어적인 핵심들은 줄이도록 개발되었다. 

 

우리가 말하는 언어(구어)와 마찬가지로, 프로그래밍 언어에도 문장(statements)이 존재한다. 이 문장들은 개별적인 완전한 생각을 나타낸다. 그리고 다른 단어를 사용하여 의미를 바꿀 수 있다. 그러나 문법적으로 옳지 않게 바꿀 수는 없다. 이러한 언어의 문장 구성 요소와 구조들을 다루는 규칙 세트를 구문이라고 한다. 모든 프로그래밍 언어도 구문(syntax)이 있다. "a = 5"는 프로그래밍 언어로 된 문장이다. 이 문장의 경우, a로 정의된 변수에 5라는 수를 저장한다는 것을 말한다. 변수에 값을 할당하기 때문에 대입문(Assignment statement)이라고 한다.

 

보다 복잡한 것을 표현하기 위해서는 일련의 문장들이 필요하다.

a = 5
b = 10
c = a + b

이 프로그램은 컴퓨터에 변수를 설정하도록 지시한다. 변수 a에 5, 변수 b에 10, 마지막으로 'a'와 'b'를 합하여 그 결과로 15가 나오게 되고 이건 변수 c에 할당된다. a, b, c 대신 apples, pears, fruits가 될 수도 있다. 컴퓨터는 변수 이름이 특이하게 설정되어도 이름이 있는 한 상관하지 않는다. 하지만 이해가 되도록 이름 짓는 것이 좋다. 만약 동료들과 함께 일을 할 때, 동료들이 코드를 이해해야 할 때를 대비해서 말이다.

 

명령어 목록인 프로그램은 요리법과 비슷하다. 물을 끓여 면과 수프를 넣고, 4분 정도 기다렸다가 먹으면 된다와 같이 말이다. 같은 방식으로 프로그램은 첫 번째 문장에서 시작해 한 번에 하나씩 끝에 도달할 때까지 실행된다. 지금까지 두 개의 숫자를 더했다. 대신 비디오 게임을 만들어보자. 물론 전체 게임을 코딩하는 대신에 프로그래밍의 기본 사항을 다루는 코드 토막들을 써보겠다.

 

플레이어가 벌레(버그)를 잡아야 하는 구식 아케이드 게임을 만든다고 상상해 보자. 벌레가 컴퓨터에 들어가서 고장내기 전에 플레이어는 벌레를 잡아야 한다. 모든 레벨에서 버그 수가 증가한다. 대신에 플레이어는 컴퓨터를 고칠 수 있는 여분의 릴레이를 가지고 있다.

 

시작하기 위해, 게임을 실행시키기 위한 중요한 값들을 추적해 나가야 한다. 플레이어의 레벨, 점수, 남아있는 벌레 숫자뿐만 아니라 플레이어의 남은 릴레이 수와 같은 것들이다. 따라서 변수를 초기화(initialize) 해야 한다. 바로 초기 값을 정하는 것을 말한다. 레벨은 1과 같고, 점수는 0, 버그는 5, 여분의 릴레이는 4, 플레이어 이름을 "John"이라 하자.

LEVEL = 1
SCORE = 0
BUGS = 5
RELAYS = 4
PLAYERNAME = John

대화형 게임을 만들려면 단순히 위에서 아래로 실행하는 것 이상의 프로그램의 흐름을 제어할 수 있어야 한다. 이를 위해 제어 흐름 문(Control Flow Statements)을 사용한다. 몇 가지 유형이 있지만 if 문(if statements)이 가장 일반적이다. "X가 사실이라면, 다음 Y를 하십시오"라고 생각할 수 있다. if 문은 도로의 갈래와 같다. 어떤 경로를 택할지는 조건(Conditional)이 참인지 거짓인지에 달려있다. 이러한 표현을 조건문(Conditional statements)이라고 한다. 대부분의 프로그래밍 언어에서 if 문은 다음과 같이 보인다.

IF expression THEN
    code here
    ...
    ...
END IF

예를 들어, 레벨이 1이면 점수를 0으로 설정하시오. 왜냐하면 플레이어가 방금 시작했기 때문이다. 그리고 벌레 수를 지금은 약간 쉽게 1로 정한다고 한다면 다음과 같다.

IF LEVEL is 1 THEN
    SCORE = 0
    BUGS = 1
END IF

물론, 조건식을 우리가 테스트하고 싶은 대로 변경할 수 있다. "점수가 10보다 작으면"이나 "벌레 수가 1보다 적으면"같이 말이다. 또한 if 문은 else문과 결합할 수 있다. else문은 표현식이 거짓이면 다른 작업을 하도록 한다. 

IF LEVEL is 1 THEN
    SCORE = 0
    BUGS = 1
ELSE
    BUGS = LEVEL * 3
END IF

위의 프로그램은 레벨이 1이 아니면, 대신 else 블록이 실행되고 플레이어가 잡아야 하는 벌레 수를 레벨의 3배로 설정하도록 한다. 레벨 2에서는 6마리의 벌레가 있을 것이고, 레벨 3에는 9마리가 있을 것이다. 점수는 else 블록에서 조정되지 않으므로 플레이어는 얻은 점수를 유지하게 된다.

다른 프로그래밍 언어의 if-then-else 문의 예

프로그래밍 언어마다 구문이 조금씩 다르긴 하지만 근본적인 구조는 거의 같다. if 문이 한 번 실행되면, 조건부 경로가 선택되고 프로그램이 계속 진행된다.

 

여러 번 문장을 반복하기 위해서는 조건부 루프를 만들어야 한다. 한 가지 방법은 while (반복) 문이라고도 하며 while 루프라고 부르기도 한다.

WHILE expression
    code to be
    looped here
    ...
LOOP

이 루프는 조건이 참일 동안만 while 코드를 반복한다. 프로그래밍 언어에 관계없이 위와 같이 보인다.

 

다시 게임 안의 어떤 시점에서, 동료가 플레이어에게 릴레이를 보낸다고 가정해보자. 재고를 최대 4개까지 보충해주는 동작을 만들려면, while 루프를 사용할 수 있다. 먼저 플레이어에게 동료가 들어올 때 1개의 릴레이가 남아 있다고 가정하자.

WHILE RELAYS < 4
    RELAYS = RELAYS + 1
LOOP

while 루프를 시작할 때, 제일 먼저 컴퓨터는 조건문을 테스트한다. 릴레이는 4보다 작습니까? 릴레이는 현재 1이기 때문에 참이다. 이제 루프를 시작한다. 다음 코드 줄을 보면 "RELAYS = RELAYS +1"라고 되어 있다. 할당 문에서 변수가 자기 자신을 사용하고 있어서 혼란스러울 수 있다. 우리는 항상 등호의 오른쪽부터 있는 것을 알아내는 것으로 시작하면 된다. 그러면 RELAYS + 1을 먼저 하면 릴레이의 현재 값이 1이니까 1+1은 2와 같다. 그런 다음 이 결과가 변수 RELAYS로 저장되어 이전 값을 덮어쓴다. 이제 릴레이는 2를 저장한다.

 

while 루프가 끝났으므로 프로그램을 다시 시작한다. 이전과 마찬가지로 조건부를 테스트하여 루프를 시작할 수 있는지 확인한다. 릴레이는 4보다 작습니까? 릴레이는 2이므로 참이다. 다시 루프를 시작한다. 2 + 1은 3이므로 릴레이에 3이 저장된다. 다시 반복한다. 3이 4보다 작습니까? 참이다. 다시 루프를 시작한다. 3+1은 4이므로 릴레이에 4를 저장한다. 다시 반복한다. 4는 4보다 작습니까? 거짓이다. 그래서 조건은 이제 거짓이며, 따라서 루프를 빠져나와 남아 있는 코드로 이동한다. 이게 while 루프가 작동하는 방식이다.

 

다른 반복문으로 for 루프도 있다. 조건이 거짓이 될 때까지 영원히 반복되는 조건 제어 루프 대신 for 루프는 횟수를 조정할 수 있어 특정 횟수만큼 반복한다.

FOR variable = start_value TO end_value
    code to be looped here
    ...
NEXT

실제로 값을 입력해보자

FOR i = 1 TO 10
    code to be looped here
    ...
NEXT

이 예제는 변수 'i'가 값 1에서 시작하여 10까지 가도록 명시되어 있기 때문에 루프가 10번 반복된다. for 루프의 독특한 점은 NEXT를 실행할 때마다 'i'에 1을 더한다. 'i'가 10일 때, 컴퓨터는 그것이 10번 반복되었다는 것을 알고 루프를 종료시킨다.

 

이제 플레이어에게 남은 릴레이 수에 대해 각 레벨의 끝에 보너스를 주고 싶다고 하자. 게임이 어려워질수록 사용하지 않은 릴레이를 얻는 데에 더 많은 기술이 필요하다. 그래서 우리는 레벨에 따라 기하급수적으로 올라가는 보너스를 원하게 된다. 우리는 지수를 계산하는 코드를 작성해야 한다. 그것은 특정한 횟수만큼 자기 자신을 곱하는 코드이다. 루프가 이것에 딱이다.

BONUS = 1
FOR i=1 TO LEVEL
    BONUS = BONUS * RELAYS
NEXT

먼저 BONUS라는 새로운 변수를 초기화하고 1로 설정한다. 그런 다음 1에서 시작하는 FOR루프를 만들고, 레벨 숫자까지 반복한다. 그 루프 안에서 보너스에 릴레이의 숫자를 곱하고 새로운 값을 다시 보너스로 저장한다. 예를 들어, 릴레이가 2개이고, 레벨이 3이라고 한다면 FOR루프는 세 번 반복되고 보너스는 보너스 * 릴레이 수 * 릴레이 수 * 릴레이 수가 된다. 만약 릴레이가 2개라면 보너스는 2*2*2=8이 된다. 이것은 2의 3승이다.

 

만약 이 지수 코드가 유용해서 코드의 다른 부분에서 사용하길 원할지도 모른다. 이걸 모든 곳에 복사해서 붙여 넣는 것은 귀찮고, 변수 이름을 매번 업데이트해야 한다. 또 만약 벌레를 발견하면 사냥을 하고 우리가 사용했던 모든 장소들을 업데이트해야 한다. 이건 코드를 이해하기 힘들게 만든다. 적은 게 더 좋은 것이다.

따라서 우리가 원하는 것은 지수 코드를 패키지화해서 그걸 사용하여 결과를 얻는 방법이다. 모든 내부 복잡성을 볼 필요가 없이 말이다.

복잡성을 분류하고 숨기려면 프로그래밍 언어는 코드 조각을 명명된 함수(functions)로 패키지화할 수 있다. 함수는 다른 프로그래밍 언어로 메서드(methods) 또는 서브 루틴(subroutines)이라고도 불린다.

 

이러한 함수는 해당 프로그램의 다른 부분에서 이름을 호출하면 사용될 수 있다. 지수 코드를 함수로 변환해 보자.

FUNCTION exponent (base, exp)
    result = 1
    FOR i=1 TO exp
        result = result * base;
    NEXT
RETURN result

먼저 HappyUnicorn과 같이 우리가 원하는 어떤 것이든 이름 붙일 수 있다. 하지만 우리 코드는 지수를 계산하기 때문에 EXPONENT라고 짓겠다. 또한 RELAYS나 LEVEL 같은 특정 변수 이름을 사용하는 대신 Base 및 Exp와 같은 포괄적인 변수 이름을 지정한다. 이 변수의 초기값은 프로그램의 다른 부분에서 함수로 전달된다. 나머지 코드는 이전과 동일하며, 이제는 함수와 새로운 변수 이름을 사용한다. 마지막으로 지수 코드의 결과를 요청한 프로그램의 부분으로 되돌려야 한다. 이를 위해 RETURN 문(return statement)을 사용하고 'result'의 값이 반환하도록 지정한다. 이제 우리는 이 함수를 프로그램 어디서나 사용할 수 있다. 단순히 함수를 호출하고 두 개의 숫자를 전달해서 말이다.

예를 들어, 2의 44승을 계산하려고 한다면 exponent(2,44)와 같이 단지 exponent에 2와 44를 부르기만 하면 된다. 그럼 뒤에서 2와 44가 함수 안의 base와 exp로 저장된다. 필요한 만큼 모든 루프를 수행한 다음 함수는 결과를 반환한다.

 

새로 작성한 함수를 사용해서 점수 보너스를 계산해보자.

BONUS = 0
IF RELAYS > 0
    BONUS = exponent(RELAYS, LEVEL)
END IF

먼저 BONUS를 0으로 초기화하고 플레이어의 RELAYS가 남아있는지 if문으로 확인한다. 만약 참이면 지수 함수를 호출해서 릴레이의 숫자와 레벨의 수를 전달한다. 릴레이 수를 레벨의 수만큼 곱하고 결과로 반환하여 보너스로 저장한다. 이 보너스 계산 코드는 나중에 유용할 수 있으므로 다시 함수로 정리하도록 하자. 함수를 호출하는 함수를 만드는 것이다.

FUNCTION calcBonus(RELAYS, LEVEL)
    BONUS = 0
    IF RELAYS > 0
        BONUS = exponent(RELAYS, LEVEL)
    END IF
RETURN BONUS

나중에 우리는 이 함수를 더 복잡한 함수 안에서 사용할 수도 있다.

 

플레이어가 레벨을 완료할 때마다 호출하는 것을 작성해보자. 그것을 LEVELFINISHED라고 부를 것이다. 남은 릴레이 수와 레벨, 현재 점수를 알아야 한다. 이 값들은 함수에 전달되어야 한다. 함수 안에서는 calcBonus 함수를 사용하여 보너스를 계산해 그것을 현재 점수에 더한다. 또한 만약 현재 점수가 게임의 높은 점수보다 더 높다면, 새로운 점수와 플레이어 이름을 저장한다. 마지막으로 현재 점수를 반환한다.

FUNCTION levelFinished(RELAYS, LEVEL, SCORE)
    SCORE = SCORE + calcBonus(RELAYS, LEVEL)
    IF SCORE > HIGHSCORE
        HIGHSCORE = SCORE
        HIGHPLAYER = PLAYERNAME
    END IF
RETURN SCORE

이것을 다음과 같은 한 줄의 코드로 호출하면, 복잡성은 숨겨진다.

TOTALSCORE = levelFinished(2,5,21)

모든 내부 루프와 변수를 보지 않으며, 반환된 결과만을 보게 된다. 마치 마법처럼 보이지만 이것은 마법이 아니고, 추상화의 힘이다. 이것이 함수의 힘이자, 현대 프로그래밍의 본질이다.

글로 쓰는 것은 불가능하다. 예를 들어 웹 브라우저를 거대하고 긴 문장의 목록으로 쓸 순 없다. 그것은 수백만 줄이면서 이해하기 불가능할 것이다. 대신 소프트웨어는 수천 개의, 서로 다른 기능을 맡는 작은 함수들로 구성되어 있다. 

현대 프로그래밍에서는 100줄보다 더 긴 함수를 찾아보긴 어렵다. 그때쯤이면 아마 끌어내서 자체 기능으로 만들어야 하는 무언가 있을 것이 때문이다.

 

함수로 프로그램을 모듈화 하는 것은 한 명의 프로그래머가 전체 앱(애플리케이션)을 쓰는 걸 가능하게 할 뿐 아니라, 팀 구성원들이 매우 큰 프로그램 안에서도 효율적으로 작업할 수 있도록 한다.

다른 프로그래머가 다른 함수 작업을 할 수 있다. 만약 모든 사람이 그들의 코드가 정확하게 작동한다고 확신하면 모든 것이 입력되었을 때, 전체 프로그램은 작동해야 한다. 그러면 현실 세계에서 프로그래머들은 지수를 쓰는 것과 같은 시간 낭비를 하지 않는다.

 

현대 프로그래밍 언어는 미리 작성된 함수들, 라이브러리(Libraries)라고 불리는 엄청난 묶음으로 이루어져 있다. 이들은 전문 코더에 의해 쓰이고 효율적이게 만들며 엄격하게 테스트 한 다음, 모든 사람에게 제공된다. 네트워킹, 그래픽 및 사운드를 포함하는 거의 모든 것을 위한 라이브러리가 있다.

728x90
반응형

'컴퓨터공학 > 기초' 카테고리의 다른 글

자료구조  (0) 2022.08.09
알고리즘 소개  (0) 2022.08.08
최초의 프로그래밍 언어  (0) 2022.08.03
초기의 프로그래밍  (0) 2022.08.03
고급 CPU 설계  (0) 2022.08.02

댓글