Object Oriented Programmer's Productivity 를 읽고 씁니다. 글에서 주장하는 바를 간략히 요약하자면 이렇습니다:

  1. OOP 하면 동적 디스패치(dynamic dispatch, 대충 쉽게 말해서 다형성)가 가장 먼저 떠오르는데,
  2. 동적 디스패치는 코드를 읽기 어렵게 만들기 때문에 위험하고, OOP는 이를 장려하기 때문에 결국 문제가 된다.
  3. OOP의 장점은 다형성에 있다기 보다는 캡슐화에 있는데, 그 이유는 구조적 프로그래밍에 비해 전역 변수를 덜 쓰도록 장려하기 때문이다.

1)번은 동의하는 바이고(OOP란 조건문을 줄이는 것 참고), 2,3번에는 동의하지 않습니다. 하나씩 따져보면


1. "동적 디스패치는 코드를 읽기 어렵게 만든다"는 주장에 대해

이 문제는 정적인 프로그램(즉 소스 코드)과 동적인 프로세스(dynamic process) 사이의 간극이 넓어지면 프로그램을 분석하기가 어려워진다는 문제의 한 가지 사례인데요(혹시 부연 설명이 필요하시면 Goto 문과 AOP, 그리고 Subtext 참고), 간단히 말해서 코드를 읽다 말고 실제로 무엇이 실행되는지 파볼(drill-down) 필요가 생기지만 않는다면 문제될 것이 없습니다. 그러니깐, 설계를 잘 하면 된다는 말입니다.

이 맥락에서 올바른 설계란... 대충 중요한 것을 꼽자면 첫째, 일반화(generalization)가 올바르게 되어 있고(즉, LSP 혹은 contract - design by contract에서 말하는 - 를 잘 지키고 있고), 둘째, 클래스 및 인스턴스의 이름이 적절히 지어져 있으며(intention revealing), 3) 해당 코드의 주변부와 추상화의 수준(level of abstraction)에 일관성이 있는 것을 말합니다.

이런 상황이라면 특정 오퍼레이션(operation)에 대한 구현 코드(method)의 내용이 무엇인지 궁금해할 일이 없습니다.

사실 동적 디스패치가 코드 읽기를 어렵게 만든다는 식의 문제 제기라면 글쓴이가 좋아하는 Haskell(함수형 언어의 일종입니다)도 문제가 되는데요, 왜냐하면 higher-order programming이라는 것 자체가 함수를 인자로 넘기거나 함수를 반환값으로 받아서 쓰는 것이고 이렇게 되면 결국 늘상 일어나는 일이 동적 디스패치거든요.


2. "OOP의 장점은 다형성에 있다기 보다는 캡슐화에 있는데, 그 이유는 구조적 프로그래밍에 비해 전역 변수를 덜 쓰도록 장려하기 때문이다"는 주장에 대해

다형성은 문제이고 캡슐화가 진정한 장점이라는 얘긴데 그럴거면 OOP라는 것이 있을 이유가 없고 그냥 모듈화 프로그래밍(modular programming)이라는 말이면 충분하겠죠.

캡슐화라는 것은 구조적 프로그래밍이나 객체지향 프로그래밍과 직교적(orthogonal)인 개념으로 보는 것이 자연스럽습니다. 대부분의 구조적 언어는 모듈화 프로그래밍을 지원하고 있고, 객체지향 언어 또한 마찬가지라서 프로그래머가 어떻게 잘 쓰느냐에 따라 캡슐화가 잘 될 수도 있고 아닐 수도 있습니다.

이를테면 코드와 데이터를 묶으려면 구조체에 함수 포인터 넣어두고, 각 함수는 첫번째 인자로 자신이 속한 구조체를 받으면(python의 self와 유사) 되는 것이죠. 구조체가 없거나 함수 포인터가 없는 언어(구조적 언어의 요건은 모두 갖추었으나 함수 포인터가 없는 언어로는... 이를테면 QBasic이나 QuickBasic이 그렇습니다)라면 prefix나 postfix로 그룹핑을 하는 관습을 만들면 그만입니다(실제로 널리 쓰이던 관습입니다. namespace 개념이 없거나 약했거든요).

뭐 구현 가능성에 대해서는 그렇다치고, 구조적 프로그래밍에 비해 객체지향 프로그래밍이 캡슐화를 좀 더 강조하고 있다는 주장도 있는데 이 또한 별 설득력이 없습니다(시스템을 모듈로 나누는 기준에 대하여 참고. 추측컨데 저자가 구조적 프로그래밍을 충분히 경험해보지 못한 것이 아닌가 싶습니다).

저자는 캡슐화의 단적인 예로 전역 변수(global variable) 문제를 들고 있는데 이에 대해서는 의존의 자기유사성을 참고하시면 좋겠습니다. 전역 변수 문제라는 것은 사실 스케일의 차이만 있을 뿐 일반적인 의존성 문제의 하나이고, 객체지향 프로그래밍을 하건 구조적 프로그래밍을 하건, 전역 변수가 있건 없건 항상 존재하는 문제이고 신경 써야 하는 문제입니다.

게다가 자바처럼 전역 변수라는 개념이 아예 없는 언어에서도 전역 변수 문제는 여전히 존재할 수 있고(전역 변수가 없지만 전역 변수 문제는 존재한다는 표현이 좀 이상하지만 뭐 맞는 말입니다) 실제로 많은 개발자들이 이 문제로 허덕이고 있습니다. 이에 대해서는 모드 없는 소스코드 중 "모드와 숨은 변수들" 섹션 참고하시기 바랍니다.

OOP 덕에 전역 변수가 많이 줄었다는 것은 아쉽게도 착각입니다. 일례로 Singleton Pattern을 보세요. 이건 사용이 장려되고 있는 (디자인 패턴이라는 탈을 쓰고 나타난) 전역 변수 아닌가요?


3. 결론 및 부연

첫째, 동적 디스패치가 일어나는 부분이 읽기 어렵다면 그건 설계/코딩을 잘못했기 때문입니다. 아무리 좋은 도구라도 쓰는 사람이 잘 쓰지 못하면 문제가 생기는거죠.

둘째, 캡슐화라는 것은 그걸 ADT(Abstract Data Type)라고 표현했건, 모듈이라고 표현했건 간에 객체지향프로그래밍과 무관하게 예전부터 있어 왔던 개념과 별 다른 점이 없습니다. 애초에 모든 (쓸모있는) 프로그래밍 언어는 조합(combination)과 추상화(abstraction) 요소를 갖추고 있는데(Structure and Interpretation of Computer Programs), 객체지향언어도 예외는 아닌 것이죠.

저작자 표시
신고

// AOP 개념정리 시리즈 요약

1. GOTO

4년~5년 쯤 전에 다익스트라의 "Goto Statement Considered Harmful"이라는 유명한 논문을 읽은 적이 있습니다. 선정적인 제목과 달리 제가 이해한 논문의 핵심은 "GOTO 문장이 나쁘니까 쓰지 말아야 한다"가 아니라 다음 문장이었습니다:
We should do (as wise programmers aware of our limitations) our utmost to shorten the conceptual gap beween the static program and the dynamic process, to make the correspondence between the program(spread out in text space) and the process(spread out in time) as trivial as possible.

우리는(자신의 한계를 잘 인식하고 있는 현명한 프로그래머로서) 정적인 프로그램과 동적인 프로세스 사이의 개념적 격차를 줄이기 위해 최선을 다해야 한다. 이를 통해 텍스트 공간 상에 펼쳐져 있는 프로그램과 시간 상에 펼쳐져 있는 프로세스 사이의 대응을 가능한 한 쉽게 만들 수 있다.
프로그래밍을 한다는 것은
  • 소스 코드를 읽으면서 이 코드의 실행 결과, 즉 프로세스를 그려내거나
  • 원하는 프로세스로부터 소스 코드를 만들어내는
것인데, 소스 코드라는 것은 2차원 평면(종이 혹은 편집기 화면)이라는 공간 상에 펼쳐진 문자열인 반면, 프로세스는 시간 상에 펼쳐진 개념입니다. 따라서 이 둘 사이의 차이가 적을 수록 프로그래밍이 (읽고/쓰기에) 쉬워진다는 것입니다.

예를 들어 제어문(조건문/반복문 등)이 전혀 없는 코드는 위에서 아래로 순서대로 실행되는데, 이러한 코드는 시간과 공간 사이의 대응이 아주 쉽습니다. 반면 GOTO가 들어가면 시간과 공간 사이의 대응이 어려워지기 때문에 GOTO가 나쁘다고 말하는 것입니다. (물론 while, for, function 등도 GOTO와 비슷하지만 이들은 나쁘다고 말하지 않습니다. GOTO와 이들 - 즉, Structured Programming을 위한 개념들 - 사이의 차이를 설명하기 위해 다익스트라는 Programmer Independent Coordinate System이라는 개념을 도입하는데, 이에 대한 설명은 생략하겠습니다)

2. Aspect Oriented Programming

예전에 하이텔 자바동(8년 쯤 전?)에서 AOP에 대한 글을 접한 후로 관심을 가지고 조금씩 살펴보고 있다가, 3년 쯤 전에 사내 세미나 주제로 AOP가 선정되는 바람에 Aspect Oriented Software Development 라는 책을 읽을 기회가 생겼습니다. 이 덕에 그마나 대략적인 개념탑재가 되었습니다.

이 책에서는 프로그래밍 언어의 발전 방향에 대해 다음과 같이 설명하고 있습니다:
The earliest computer machine-language programs had a strict correspondence between the program text and the execution pattern. Generally, each programming language statement was both unitary and local - unitary in that it ended up having effect in precisely one place in the elaborated program, and local in that it was almost always proximate to the statements executing around it.

The history (of this part) of programming languages has been about moving away from purely local and unitary languages and toward mechanisms that let the programmer separate concepts into pragmatic assemblages or modules, instead of being tied to saying things just where they happen. The first exceptions to locality were subprograms (i.e., procedures, subroutines, functions). Subprograms were a great invention, enabling abstracting out some behavior to someplace else. (...omitted...)

Inheritance (and related mechanisms like Delegation) in Object Oriented Programming was another important introduction of non-locality. Executing inherited behavior is non-local.

초창기의 기계어 프로그램에는 프로그램 텍스트와 실행 패턴 사이에 정확한 대응이 존재했었다. 일반적으로 각각의 문장은 일원적이면서 동시에 지역적이었다. 일원적이라는 것은 문장이 정확히 한 지점에만 영향을 미친다는 의미이고, 지역적이라는 것은 그 영향이 거의 항상 해당 문장의 주변 문장들로만 한정된다는 의미이다.

(이러한 관점에서) 프로그래밍 언어의 역사란 순수하게 지역적이고 일원적인 언어에서 벗어나, 실행이 되는 해당 지점에 모든 사항을 써내려가는 것이 아닌 실용적 묶음 혹은 모듈 단위로 개념을 분리할 수 있도록 변해가는 과정이었다. 지역성에 대한 첫 번째 예외는 서브 프로그램(프로시저, 서브루틴, 함수)이었다. 서브 프로그램은 특정 행위에 대한 상세한 기술을 다른 위치에 적을 수 있게 하여 추상화를 가능케 해준 위대한 발명이었다. (...중략...)

객체지향 프로그래밍의 상속(및 델리게이션 등의 유사한 메커니즘)은 또다른 주요한 비-지역성의 하나이다. 상속된 행위를 실행하는 것은 비-지역적이기 때문이다.
통찰력 있는 글입니다.

하지만 이 글을 읽자마자, 전에 읽었던 다익스트라의 글이 떠오르면서 "뭔가 충돌이 있다"는 생각이 들었습니다. 다익스트라는 정적 프로그램과 동적 프로세스 사이의 차이를 최소화 하는 것에 대해 말하고 있고, AOP는 프로그램 텍스트와 실행 패턴 사이의 결속으로부터 벗어나는 것에 대해 말하고 있기 때문이죠.

2005년에 쓰인 Carl Zetie의 AOP Considered Harmful? 이라는 글에서는 AOP가 다익스트라가 지적한 문제점을 가지고 있기 때문에 해롭다고 말하고 있습니다(비싸서 요약만 읽었습니다만 ㅎㅎ)만, 별로 동의할 수는 없었습니다. 둘 사이에 충돌이 있는 것은 맞지만 그렇다고 해서 둘 중 하나가 잘못된 것이라고 생각해버릴 수는 없었던거죠.

3. Subtext

별 다른 결론을 얻지 못하고 몇 년이 지났는데, 어제 38분짜리 Subtext2의 동영상을 보고서야 답을 찾았다는 느낌이 들었습니다. (아래 글은 위 동영상을 보신 후에 읽으면 좋습니다)

다익스트라가 강조한 것은 소스 코드와 프로세스 사이의 간극을 최소화해야 한다는 것이고, AOSD에서 강조한 것은 모듈화와 추상화입니다. 문제는 모듈화/추상화의 결과로 인하여 소스 코드와 프로세스 사이의 간극이 벌어질 수 밖에 없다는 점입니다. 결국 둘 중 하나를 포기하거나 적당히 타협을 해야하는데, 이 부분이 찝찝한 것이죠.

하지만, Subtext는 다른 방식으로 이 충돌을 해결합니다. 소스 코드가 2차원 평면 상에 펼쳐진 문자열이어야 할 필요가 없다는 것이죠. Subtext에서는 프로그램과 프로세스가 "동일"합니다. 소스 코드는 코드인 동시에 실행 결과이기도 합니다. 코드를 수정하면 결과도 수정됩니다. 코드 내에 다섯 가지 분기가 존재하면 화면 상에 다섯 가지 흐름이 나타납니다.

그 결과 모듈화/추상화를 하면서도 여전히 지역성을 유지할 수 있게 됩니다. 뿐만 아니라 OOP의 핵심적인 개념에 대해서도 매우 직관적으로 익힐 수 있는데, 이를테면 기존 언어의 조건문(if/else 혹은 switch)과 OOP의 다형성(polymorphism)이 결국은 같은 것이라는 사실이 Subtext에서는 시각적으로 명확하게 표현됩니다.

몇 년 동안 찜찜하게 남아있던 고민 하나를 해결하고나서 괜히 혼자 들 뜬 마음에 주절거려봤습니다.

PS) 요즘 관심 있게 보고 있는 것 중 하나가 Xanadu Project 인데(완전 뒷북이죠 ㅎㅎ), 이게 또 Subtext와 재미있는 유사성을 가지고 있는 것 같습니다. 인간은 언제 쯤 "네모난/평평한 종이"에 대한 강박에서 완전히 해방될 수 있을까요?
신고
< Newer     Older >

티스토리 툴바