해당 포스팅은 OOP 이해를 돕기위해 작성되었습니다.
1. 절차적 프로그래밍
절차적 프로그래밍의 정의를 보면, "루틴", "서브루틴", "메소드", "함수" 등 "프로시저"를 이용한 프로그래밍 패러다임을 뜻한다고 되어있습니다.
일단 절차적 프로그래밍을 쉽게 이야기하면, 특정 작업을 수행하기 위한 프로그램을 작성할 때, "루틴", "프로시저"으로 구성되게끔 프로그래밍
하는 프로그래밍 패러다임이라는 이야기입니다.
그럼 여기서 루틴이 무엇인가가 중요해집니다. 루틴은 무엇인가?
루틴(Routine)의 단어의 정의는 "틀에 박힌 일"입니다. 말 그래도 우리가 할 일에 대한 순서가 정해져있고, 그것이 변하지 않는다는 거죠.
프로시저(Procedure)의 단어 정의는 "순서"를 의미합니다.
이러한 루틴과 프로시저란 단어의 뜻을 숙지하고 다음 C언어 예제 코드를 봅시다. C언어를 모르셔도 상관없습니다.
1 2 3 4 5 | int main(void){ return 0; } | cs |
위의 코드는 C언어를 하시는 분은 항상 보시는 것일 거고,
다른 언어를 쓰시는 분들도 main이라는 써져있는 어떤 것 혹은 다르게 정의되어있는 프로그램 진입점에서 프로그램을 주로 작성하실 것입니다.
해당 코드를 컴파일하면, 실행이 가능한 파일이 생성될 것이고, 해당 프로그래밍을 실행시켜본다면 실행이 되자마자 프로그램이 종료되는 것을
확인할 수 있습니다.
그 이유는 해당 코드의 논리 흐름이 프로그래밍을 실행시켰을 때, 그 프로그램 덩어리는 main이라는 하나의 코드 블럭(block)과 같기 때문입니다.
코드 내부 블럭 "{ }" 안을 보면, return 0;이라는 것이 보입니다. 이 return 0;이라는 구문은 프로그램이 정상적으로 종료되었음을 알리는
0이라는 숫자를 반환하기 때문입니다.
코드 실행 순서를 보면,
- int main(void)의 진입점으로 진입한다.
- 0 이라는 숫자를 반환한다.
즉, 시작하자마자 종료하겠다는 프로그램을 짠 것이라고 할 수 있지요.
이렇게 위와 같은 예제에서 main과 같이 프로그래머가 정의한 "틀에 박힌 일 역활"을 위에서 아래로 순차적으로 실행하는 것을
"루틴", "프로시저"라고 합니다.
다시 돌아와서, 절차적 프로그래밍이란
특정 작업을 수행하기 위한 프로그램을 작성할 때, "프로시저", "루틴"으로 구성이 되게 프로그래밍하는 것을 절차적 프로그래밍이라고 합니다.
이제 조금 이해가 되시나요?
절차적 프로그래밍에는 2가지 종류의 패러다임이 있습니다.
* 제가 학습하면서 느껴지기에 이런형태의 분류가 맞는거 같아서 분류는 하지만 아직 해당부분은 개념이 정확하지 않으니 참고수준에서만
읽어주시고 틀렸다고 생각되시면 언제든지 알려주시면 감사하겠습니다.
- 구조적 프로그래밍
- 비구조적 프로그래밍
1). 비구조적 프로그래밍
장점으로는 속도적인 측면에서 약간 유리하며 단점으로는 디버그가 굉장히 어려워집니다. 장점은 조금 있다가 루틴, 서브 루틴, 함수를
설명할 때, 자세하게 설명하도록 하고, 단점 부분에서 왜 디버그가 어려워지냐 하면,
그 이유는 흐름 제어시 GOTO문에 의존하게 되는데, 이 GOTO문을 을 남발하게 되면, 코드의 로직 순서가 GOTO로 인해 엉망이 되어
스파게티코드가 됩니다. 이런 스파게티 코드는 로직의 흐름이 엉망이 되기 때문에 디버그시 로직의 흐름과 값의 변화를 비교해가면서 점검할 때,
매우 어려워지는 문제때문이지 않나 생각하고있습니다.
비구적인 프로그래밍의 예를 보겠습니다.
10 i = 0 20 i = i + 1 30 PRINT i; " squared = "; i * i 40 IF i >= 10 THEN GOTO 60 50 GOTO 20 60 PRINT "Program Completed." 70 END
위 코드의 실행 순서는 다음과 같습니다.
- 10번: 변수 i를 0으로 초기화합니다.
- 20번: 변수 i를 기존 변수 i의 값과 1을 더한 값으로 갱신합니다.
- 30번: 변수 i의 값과 i의 제곱된 값을 출력합니다.
- 40번: 만약 변수 i의 값이 10보다 크거나 같다면 60번으로 이동합니다. 변수 i의 값이 10보다 작다면 그냥 지나칩니다.
- 50번: 20번으로 이동합니다.
- 60번: "Program Completed"를 출력합니다.
- 프로그램이 종료됩니다.
2). 구조적 프로그래밍
이런 비구조적 프로그래밍의 단점을 보완하고자 구조적 프로그래밍 패러다임이 등장하게 되었습니다.
구조적 프로그래밍 이론은 순차, 분기, 반복만으로 계산 가능한 함수를 표현할 수 있다는 구조적 프로그래밍 정리가 토대입니다.
구조적 프로그래밍은 구조화 기법이랑 방법론에 따라서 세가지 종류로 나뉩니다.
- 잭슨의 구조적 프로그래밍
- 데이크스트라의 구조적 프로그래밍
- 데이크스트라의 관점에서 파생된 관점
일반적으로 사람들이 구조적 프로그래밍이라고 이야기하면 "잭슨의 구조적 프로그래밍"과 "데이크스트라의 구조적 프로그래밍"을 의미합니다.
여기서는 일반적인 구조적 프로그래밍에 대해서 언급하겠습니다.
구조적 프로그래밍은 "저수준 관점"과 "고수준 관점"으로 나뉩니다.
2)-1 저수준 관점
저수준 관점에서 구조적 프로그래밍은 간단하고, 계층적인 프로그램 제어 구조로 구성됩니다.
이러한 제어 구조는 다음과 같은 종류가있습니다.
- 순차(concatenation)
: 구문 순서에 따라서 순서대로 실행하는 것을 의미합니다
- 선택(selection)
: if, else, if else, switch, case와 같은 조건문을 의미합니다.
- 반복(repetition)
: while, for, do while 등과 같은 반복문을 의미합니다.
2)-2. 고수준 관점
코드 작성자는 큰 조각의 코드를 이해하기 쉬운 작은 하부 프로그램 (함수, 메소드, 블록) 등으로 나누어야 합니다.
일반적으로 전역 변수는 전역 변수를 거의 사용하지 않아야 하며, 하부 프로그램들은 지역 변수나, 값이나, 참조에 의한 인자를 받아야 합니다.
이러한 기법은 전체 프로그램을 이해하지 않고, 분리된 작은 코드 조각을 쉽게 이해하는데 도움이 됩니다.
이를 개별적으로 구현, 테스트 후, 합치는 방식을 택합니다.
이러한 구조적 프로그래밍은 다음과 같은 장점을 갖습니다.
- 함수를 이용한 코드의 재사용성 증가
- 메인 프로시저 뿐만 아니라 함수 내의 호출을 이용한 여러 구현부 생략으로 인한 프로그램 흐름 간략화, 가독성 증가
- 모듈화, 구조화가 더 용이해져 대규모 프로그래밍에서 이점을 갖음
그리고 다음과 같은 단점이 있습니다.
- 프로시저 호출 시, 직접 메인 프로시저에서 코드를 쓰는 것(인라인)보다 느리다.
3). 서브루틴, 함수
자 이제 맨 위에서 언급했던, 서브루틴이 무엇인지, 그리고 함수가 무엇인지를 정리할 시간이 되었습니다.
서두에 이야기했던 다음과 같은 예제에서 main은 메인 루틴이라고 했습니다.
1 2 3 4 | int main(void){ return 0; } | cs |
메인 루틴(Main Routine)과 서브 루틴(Sub Routine). 뭔가 비슷하지 않나요?
루틴이라는 용어를 같이 쓰고, 메인과 서브만 차이 있을 뿐입니다.
다음 예제를 봅시다.
1 2 3 4 5 6 7 8 | int function (int x, int y){ return x + y; } int main(void){ return 0; } | cs |
main이라는 것과 function이라는 것이 보일 겁니다. 여기서 main은 메인 루틴이, function은 서브 루틴이 됩니다.
서브 루틴은 메인 루틴과 마찬가지로 진입점에서 return 까지 혹은 블록 끝까지 순차적으로 실행되는 것에 있어서 같은 성격을 갖습니다.
서브 루틴은 구조적 프로그래밍 고차원 관점에서 큰 프로그램(하나의 프로시저)에서 작은 프로그램(여러개의 프로시저)로 나누는데
사용됩니다. 다음 예제를 봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int function (int x, int y){ return x + y; } int main(void){ int tmp = 0; tmp = function(3, 5); print("value : %d\n", tmp); return 0; } | cs |
프로그램이 시작되면 main이라는 메인 루틴 진입점에서 시작되서 위에서 아래로 코드가 순차적으로 실행됩니다.
예제의 실행 순서는 다음과 같습니다.
- 메인 루틴 진입점 진입
- int형 변수 tmp를 생성하고 tmp의 값으로 0을 대입함
- 인자 3과 5을 전달하면서 서브 루틴 function 진입점으로 진입
- 인자 3과 5를 더하고 그 결과를 반환함
- 다시 메인 루틴으로 돌아와서 서브 루틴으로부터 반환된 값을 tmp에 대입함
- tmp의 값을 출력함
- 성공적으로 프로그램이 실행되고 종료되었다는 의미로 0을 반환하고 프로그램(메인 루틴)을 종료함
1 2 3 4 5 6 7 8 9 10 11 | int main(void){ int tmp = 0; tmp = 3+5; print("value : %d\n", tmp); return 0; } | cs |
- 메인 루틴 진입점을 생성합니다.
- 코드를 읽어들여서 명령어 대기열에 명령어를 6가지를 넣습니다.
- int형 변수 tmp를 생성하고 tmp의 값으로 0을 대입함
- 인자 3과 5를 더하고 그 결과를 tmp에 대입함
- tmp의 값을 출력함
- 성공적으로 프로그램이 실행되고 종료되었다는 의미로 0을 반환하고 프로그램(메인 루틴)을 종료함
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int function (int x, int y){ return x + y; } int main(void){ int tmp = 0; tmp = function(3, 5); print("value : %d\n", tmp); return 0; } | cs |
- 메인 루틴 진입점을 생성
- 서브 루틴 진입점을 생성
- 코드를 읽어들여, 명령어 대기열에 8가지를 넣습니다.
- int형 변수 tmp를 생성하고 tmp의 값으로 0을 대입함
- 인자 3과 5을 전달하면서 서브 루틴 function 진입점으로 진입
- 인자 3과 5를 더하고 그 결과를 반환함
- 다시 메인 루틴으로 돌아와서 서브 루틴으로부터 반환된 값을 tmp에 대입함
- tmp의 값을 출력함
- 성공적으로 프로그램이 실행되고 종료되었다는 의미로 0을 반환하고 프로그램(메인 루틴)을 종료함