1.5 PL 기초 개념 (3) - 프로그래밍 방법론

10973 단어 PLPL

현대 PL에 영향을 준 요소1 - 컴퓨터 아키텍쳐

  지난 포스팅에서, 대부분의 프로그래밍 언어들, 그중에서도 Imperative 언어들이 변수, 메모리 할당, 사칙연산 등의 동작 수행 방식이 우연히 그렇게 만들어진 것이 아니라, Von-Neumann 컴퓨팅 모델을 바탕으로 설계되었기에 필연적으로 그런 방식으로 설계된 것이라 설명한 바 있다.

~> 현대 프로그래밍 언어들은 대부분 "폰-노이만 모델의 컴퓨터 HW를 어떻게 동작하게 만들어 기능을 수행하게 할까?"라는 생각을 바탕으로 설계된 것이다.

현대 프로그래밍 언어들이 현재의 모습(메인 함수와 서브루틴이 있고, 메모리 Cell에 변수명을 두는 등의)을 갖추게 된 이유1 : 컴퓨터 아키텍쳐(HW 동작 방식)를 고려하였기 때문이다.


  • 모든 Imperative Programming Language는 Von-Neumann Computing Model을 바탕으로 설계되었다.
    • HW 구조 및 동작에 대한 '고급 추상화(High-Level Abstaction)'의 결과가 PL이다.
      ex) 변수(메모리 Cell)
      ex) 메모리 할당(Fetching과 Storing)
      ex) Expression(Arithmetic)
      ex) Loop(Conditional Jump)

※ Fetch-Execute Cycle of CPU
0번. 멈추지 않고 Program Counter 값(PC)을 초기화
1번. PC가 가리키는 명령을 메모리에서 Fetch
2번. PC값을 Increment!
3번. Fetch한 명령을 Decode
4번. 명령을 수행(Execution) ~> 다시 1번으로 반복

=> Imperative 프로그래밍 언어들은 모두 이러한 과정을 고려하여 설계되었다.


현대 PL에 영향을 준 요소2 - 프로그래밍 방법론

현대 프로그래밍 언어들이 현재의 모습을 갖추게 된 이유2 :
프로그래밍 방법론을 고려하였기 때문이다.

  SW의 Cost가 높아짐에 따라 1970년대부터 다양한 프로그래밍 방법론들이 대두되었다. 현대 프로그래밍 언어들은 모두 이러한 방법론을 토대로 각 방법론에 맞추어 발전하는 모습을 보여왔다.

1. Structured Programming (구조적/절차적 프로그래밍)

  • Top-Down Design (Gotoless한 절차적 구조)
  • Stepwise Refinement (스텝 단위의 사고)
  • Procedure-Oriented Programming (절차 지향 프로그래밍)
  • Task-Oriented Programming, Modular Programming
  • 특징
    • 모듈의 도입(모듈화) : 구조적/절차적/Top-Down 방식의 프로그래밍을 구현하기 위해 각종 문법 구문들과 '모듈화(Subprogram)' 기능이 도입되었다.

      • Goto 없이도 절차적으로 프로그램이 흐를 수 있도록 Language Level에서 Control Statements를 Support한 것이다.

      ~> 학부 어셈블리언어 수업에서 배웠다시피, 어셈블리어는 다양한 분기를 위해선 불가피하게 Goto와 Jump를 난잡하게 사용해야한다. 절차적 프로그래밍 방법론에서는, 언어 수준에서 Modular Programming과 각종 문법을 지원하여 이를 최대한 막게 한 것이다.

  • 예시 언어 : (초기)C, Pascal, Algol(1970년대 초)

※ 모듈(프로시저)을 도입한 이유 : 프로그램을 일 단위로 분류해 Task를 좀 더 효율적으로 다루기 위해 이러한 기능이 도입되었다. (Structured Programming이 곧 Task-Oriented Programming인 이유)

  • 절차적 프로그래밍의 단점 : 모듈 단위로 프로그래밍을 할 경우, 모듈이 Global Variable을 취급하는 경우, Global 변수의 변경은 곧 '모든 모듈의 변경 및 수정'이 된다. 즉, 매우 번거로워진다.
    ~> 그렇다고 해서, 마냥 Local로 다룰수도 없는 것이, Local을 많이 두고, 파라미터로 필요한 변수를 계속 넘기는 것은 Overhead(데이터 카피, 주소 넘기기 등)를 급격히 늘리는 일이라 효율 측면에서 좋지 않다.

=> 즉, Structured Programming은 모듈화로 인한 Dillema가 발생한 것! 그래서 나온 것이 아래의...


2. Data-Oriented Programming (데이터 지향 프로그래밍)

  상기한 절차적 프로그래밍의 단점을 보완하기 위해, 다음과 같은 생각을 하게 되었다.

모듈을 Task가 아닌, Data 단위로 나누어보자!

~> 즉, 모듈이 참조하는 각 Global 변수들을 기준으로 모듈을 나누자는 것이다. Task단위로 모듈화를 하는 것이 아니라, Data단위로 모듈화를 하자는 것이다.

  • Data Abstraction(데이터 추상화) : 밖에서는 안의 내용을 몰라도 쓸 수 있게 하였다. 추상화된 데이터에 대해서, 허용된 함수들로만 접근할 수 있게 하고, 그 외의 접근을 불허(컴파일 에러)하는 것이다. (Language Level에서!)

    • 어셈블리어나 (초기의)C언어로 이러한 Data Abstraction이 가능한가? : 물론 가능하다.
      • C를 예로 들어, test1.c, test2.c, test3.c와 같이 파일 단위로 필요한 기능과 데이터들을 나누어 놓고, 이들을 참조하는 식으로 하면 Data-Oriented Programming을 구현할 수 있다. 하지만, 이렇게 하여도, (구식) C를 기준으로는, 예를 들어, test1.c에서 test2.c의 함수나 변수를 access하는 것이 컴파일 단계에서 제한되지 않는다. 즉, Language Level의 Support가 부재하는 것이다. 이것이 바로 Data-Oriented와 Structured의 차이이다.
    • 이러한, Data-Oriented한 Module을 DOP 언어에서 Package 또는 Class라고 부른다.
  • 예시 언어 : (현재는 사장된) Simula67, CLU, Ada, Modula-2, (초기)C++ (1970년대 말)


int main() {
	stack1 a;		// stack1이라는 class
    a.push();
    a.pop();
}

class stack1 {
private:
	int x[100];
public:
	void push(int x);
	int pop();
};

class stack2 {
private:
	int x[100];
public:
	void push(int x);
	int pop();
	void push2(int x);
	int pop2();
};

=> Data-Oriented Programming Language에서는, 위의 예시처럼, stack1이라는 class를 사용하다가, 더 기능이 추가된 stack 클래스가 stack1과는 별도로 추가로 필요하게 되면, 위처럼 stack2라는 class를 별도로 선언하여 함께 운용하여야했다.

굳이, 이렇게 만들지 말고, 새로운 class에 기존의 stack1 class를 그대로 끌어와 연결시켜주어 중복되는 것은 냅두고, push2와 pop2라는 새로운 기능만 포함시킬 순 없을까?

  그렇다. Data-Oriented Programming은 이러한 단점에 직면하게 된 것이다. 이를 해결하면서 나온 방법론이 우리가 모두 알고 있는 OOP인 것이다.


3. Object-Oriented Programming (객체 지향 프로그래밍)

  상기한 데이터 지향 프로그래밍의 단점을 보완하기 위해, 다음과 같은 생각을 하게 되었다.

클래스의 상속(Inheritance) 개념을 추가해 SW 재사용성(Reusability)과 응용성을 높이자!

~> DOP : 데이터 단위로 모듈을 나누고, 추상화시켜놓은 것까지 지원
~> OOP : DOP에다가 SW 재사용성(클래스 상속 기능)을 포함시키고, 클래스끼리의 Message Sending 개념을 포함시켜 완성되었다.

Object-Oriented Programming Methodology : DOP + Class Inheritance + Message Sending

  • OOP = DOP + Inheritance + Message Sending
  • Data Abstraction + Encapsulation + Dynamic Type Binding
    • 참고로, C++도 Dynamic Type Binding을 할 수 있다. 메인 방식은 아니지만.
  • SW Reusability : 기존에 존재하는 SW를 재사용 by class inheritance
  • 예시 언어 : (현재는 사장된) Smalltalk, (현대)C++, CLOS, Java, Python (1980년대)

※ 절차적 언어이든, DOP이든, OOP이든, 사실 이들은 모두 어셈블리 단위에서는 Goto로 이루어진 Naive한 코드로 변환된다는 사실을 잊지말자. 현대 프로그래밍 언어들이 '특정 방법론'을 지원하기 위해 그러한 방향으로 개발된 것일 뿐, 이들의 근본이 다른 것이 아니다.


4. Process-Oriented Programming (프로세스 지향 프로그래밍)

  • 'Concurrent Program Unit(동시 프로그램)'을 생성하고 제어하는 기능에 집중한 방법론이다.
  • Parallel Programming에 적합한 방법론
  • 예시 언어 : Cocurrent Pascal, (현대)Ada
  • 현업에서 쓸일이 많지 않은 언어들이 많다. 그럼에도, 이를 아느냐 모르느냐의, 차이는 분명 있으니 이러한 방법론도 있다는 것을 기억하도록 하자.

※ Parallel Programming

parallel {
	sub1()
    sub2()
}

라는 프로그램이 있다고 해보면, sub1과 sub2라는 서브프로그램들이 동시에 수행되는 프로그램을 병렬 프로그램이라고 한다. 물론, 사실은 CPU가 Time Sharing으로 조금씩 조금씩 상호 배타적으로 '수행-휴식'을 반복하고 있는 것이지만 말이다.

ex) 우리가 인터넷에서 무언가 다운로드받을 때, Concurrent Programming(Parallel Programming)이 지원되지 않으면, 다운로드되는 동안 아무 일도 할 수 없게 된다.


프로그래밍 언어 설계와 관련한 Trade-Off들

  • Reliability vs Cost of Execution : 신뢰성과 수행속도는 서로 Tradeoff 관계이다. 예를 들어, Division by Zero Error Checking과 같은 Exeception Handling 기능이 있는 언어는 나누기를 할 때마다 이를 체크한다. Pascal의 Array Index Range Checking도 마찬가지로, 매번 배열 파라미터 패싱마다 이를 체크한다.
    ~> 수행속도 효율을 낮추는 대신 신뢰성은 높인다.

  • Flexibility vs Safety : Static Type Binding과 Dynamic Type Binding의 비교

  • Flexibility vs Efficiency : Static Type Binding과 Dynamic Type Binding의 비교

  • 조밀하고(Compact) 엄밀한(Concise) 수식 표현을 지원하는 언어는 수학적으로는 이뻐보일지 몰라도, 이해하기는 더 어려워진다.
    ~> 예를 들어, 재귀 호출 기능이 있어, fact(n) = fact(n-1)*n과 같은 표현이 가능한 언어라 해보자. 수학적으로는 보기 좋지만, 수행속도 측면에서는 함수의 잦은 호출 때문에 좋지 않고, 그 호출 흐름 관계를 이해하기도 쉽지 않다.

=> 아무래도, 이 내용은 1.4 포스팅에서 더 자세히 알 수 있다.


※ C++의 포지션

좋은 웹페이지 즐겨찾기