프로그래밍에는 세가지 패러다임이 존재한다. 순차/분기/반복을 사용하는 구조적 프로그래밍. OO의 원칙을 이용하여 더 큰 단위의 소프트웨어를 설계할수 있는 객체지향프로그래밍. 불변성과 람다 계산법에 기초한 함수형 프로그래밍. 로버트 마틴에 따르면 이들 패러다임은 1958년부터 1968년에 걸쳐 10년동안 모두 만들어졌고 지금까지 수십년이 지났지만 새로운 프로그래밍 패러다임이 등장하지 않았으므로 프로그래밍 패러다임은 앞으로도 이 세가지만 존재할거라고 한다.
나는 과거에(어쩌면 최근까지) 이 프로그래밍의 패러다임에 대해서 오해를 하고 있었다. 하나의 언어는 하나의 패러다임만을 위한 언어라고 생각했던 것이다. 그러나 프로그램을 작성하는데 있어서 이 세가지 패러다임은 상호배타적인 개념이 아니다. 즉 하나를 선택하면 다른것은 사용할수 없다는게 아니라는 것이다. 모든 프로그램은 위에서 부터 아래로 순차실행되고, if/else같은 분기가 필요하며, while과 for같은 반복문을 사용한다. 적어도 폰노이만 기반 컴퓨터 아키텍처에서 이렇게 순차/분기/반복을 사용하지 않는 프로그래밍언어는 존재하지 않고 존재할수가 없다. 또한 프로그램의 규모가 커질수록 기능과 비기능 요구사항 변경에 대응하기 쉬운 객체지향 패러다임을 적용한 프로그램을 작성 하게 되고 그렇게 해야한다. 또한 세분화된 모듈 즉 함수를 보다 예측가능하고 부수효과(side-effect) 없이 작성하기 위해서는 함수형 프로그래밍 패러다임 개념을 적용해볼 수 있다. 이 모든 것들은 프로그램을 작성하는데 있어서 서로 상호 보완적인 개념들이지 상호 배타적인 개념이 아니다.
모든 언어는 이 세가지 프로그래밍 패러다임을 조금씩은 지원한다고 볼수 있다 . 그래서 특정 언어를 설명할때 "이 언어는 어떠한 패러다임 언어입니다. 라고 설명하는것은 약간 부정확한것 같다. 그것보다는 "이 언어는 어떤 패러다임을 더 잘 지원하는 언어 입니다" 라고 설명해야 더 정확하다고 생각한다. 다시말해 특정 패러다임 오리엔트 언어라고 설명하는게 더 명확할 것이다.
어떤 언어에 특정 패러다임을 지원하는 문법적 요소가 없더라도 그 개념자체를 구현할 수 는 있다. 객체지향 프로그래밍의 원칙이 상속(Inheritance), 추상화(Abstraction), 캡슐화(Encapsulation), 다형성(Polymolphysm)이라고 할때, 놀랍게도 구조적 패러다임의 끝판왕인 C 언어로 이 모든것들을 구현할 수 있다. 다만 C언어에 해당 컨셉을 정확히 지원하는 문법요소가 없기 때문에 그 구현방식이 원시적이고 다소 복잡할 뿐이다. 그래서 이것을 더 쉽고 간결하게 작성할수있도록 C++같은 OO언어가 탄생한 것이다.
C는 애초에 구조적 프로그래밍 오리엔트 언어이지만 구조체와 함수포인터를 이용하면 클래스 처럼 만들수 있어 OO의 캡슐화(Encapsulation)를 구현할 수 있다. C는 단지 OO패러다임을 언어적 차원(언어가 제공하는 문법과 기능)에서 약하게 지원할 뿐이다. OO의 핵심은 각 엘리먼트의 의존관계를 통제 하는데 용이한 패러다임 이라는것인데, 이를 달성하기 위한 문법요소중 하나로 Interface가 있다. C언어에서도 원시적인 방식으로 OO의 Interface를 구현할수있다. 예를 들어 리눅스 커널에는 인터페이스 역할을 하는 file_operations 구조체가 존재한다. 클라이언트 레이어에서는 이 file_operations에 등록된 메서드만 의존하고, 개별 디바이스드라이버 구현체에서 이 인터페이스를 Implement해야한다. 이것이 리눅스 커널이 가이드하는 디바이스 드라이버 작성 방법아다. 이것을 통해 객체지향의 SOLID원칙의 DIP를 준수할 수 있다.
현대의 프로그래밍 언어들은 새로운 버전을 릴리즈 함에 따라 점차 모든 프로그래밍 패러다임의 요소들을 필요에 맞게 혼합하여 지원하고있다. 왜냐하면 그것이 프로그래밍 언어 사용자들의 요구사항이기 때문이다.
Python은 현대에 가장많이 사용되는 프로그래밍 언어일것이다. 그런데 Python은 1991년에 탄생한 생각보다 오래된 언어이다(Java,1995년 보다도 더 오래되었다!) 따라서 지금까지 많은 버전 릴리즈를 통해 진화하면서 다양한 프로그래밍 패러다임을 지원하게 되었다. 다음은 Python이 객체지향 패러다임을 지원한 버전과 년도이다.
또한 Python은 함수형 패러다임의 문법요소는 언제부터 지원하게 되었을까? 무려 1994년 1.0버전 부터다. 아래에 있는 Python의 문법요소는 함수형 패러다임을 지원하기 위한 문법의 일부다.
비교적 최근에 만들어진 언어이자 시스템 프로그래밍의 대안으로 관심받고 있는 Rust는 언어 차원에서 지원하는 세가지 패러다임 비율이 대략 1:1:1이다. 구조적 프로그래밍 언어가 제공하는 포인터도 존재하고, 객체지향의 polymolphysm은 Trait Object라는 문법 요소를 이용해 구현할 수 있다. 구조체에 Iterator Trait를 구현하면 OO의 반복자 디자인패턴을 적용할수 있다. 그리고 Rust의 변수는 다른 함수형 프로그래밍 처럼 기본적으로 immutable이다. 또한 map, flatmap, filter, fold 같은 고차함수를 제공하고, Closure라는 익명함수를 제공한다.
그렇다고 현대의 프로그래밍 언어들이 세가지 패러다임에 존재하는 모든 문법 요소를 지원하는것은 아니다. 각자 언어가 가지는 철학에 맞게 필요한 문법적 요소를 채용해서 지원하고 있다. 가령 Rust가 많은 객체지향 문법을 제공하긴 하지만 객체지향 개념을 모두 지원하는것은 또 아니다. Rust에는 Class도 없고 상속도 없다. 인터페이스만 존재한다. 이것은 Rust의 철학에 기반한 의도된 설계인데, 상속은 모듈간의 결합도(coupling)를 높히기 때문이다. 따라서 상속 대신 인터페이스에 의존하여 더 나은 OO 설계를 언어차원에서 강제하는 것이다. Rust에서 인터페이스 개념은 Trait 라는 문법 요소가 담당한다. 각각의 구조체는 행위를 명세한 Trait들를 Implement할수 있다. 다른 엘리먼트는 구체적인 구조체를 의존하는것이 아니라 Trait즉 인터페이스에 의존함으로써 SOLID원칙의 DIP를 만족시킬수 있다.
프로그래밍 언어는 패러다임을 상호배타적으로 차용하지 않는다. 하나의 언어가 하나의 패러다임을 지원하기 위해 존재한다는것은 잘못된 생각이다. 프로그래밍 패러다임들은 각자가 가진 장단점이 존재하고 각 프로그래밍 언어는 그들이 가진 철학에 따라 패러다임에 존재하는 문법요소를 적용하고 조합하여 설계한것이다.
인간의 언어 처럼 프로그래밍 언어도 진화한다. 현대의 소프트웨어와 그 개발 방법론이 변화하고 발전하면서 그 요구하는 사항을 잘 만족하는 프로그래밍 언어는 계속 생존해왔고, 그렇지 못한 언어는 점점 쓰임이 줄어들고 결국 사라졌다. 한가지 확실한것은 현대의 소프트웨어 개발은 세가지 패러다임을 모두 지원하는 멀티 패러다임을 언어를 요구한다는 것이다. 모든 패러다임의 개념을 적절히 사용하면 더욱 좋은 프로그램을 작성할수 있기 때문이다. 앞으로 새로 등장할 새로운 프로그래밍 언어는 이 패러다임의 경계가 더욱 희미해질 것이다. 우리는 앞으로 그 언어가 어떤 패러다임 언어다 라고 딱 짚어 분류할수 없게 될것이다. 그리고 그것은 프로그래밍 언어가 진화해야할 올바른 방향이다.
참고