대응과 객체지향 프로그래밍:
‘논리적’ vs ‘물리적’
대응에 대해
'대응(correspondence)'은 인간이 직관적으로 하는 인식 능력이다. 쉬운 예를 들면 사과 한 개가 있을 때 그것을 숫자 1에 대응시키는 것이 있다. 이렇게 물리적 실체(사과)와 논리적 개념(숫자 1)을 대응시키는 것은 누구나, 별다른 노력 없이도 할 수 있고 일상적으로 하고 있다.
이 세상 모든 것이 인간의 의식이 일으키는 착각일 수도 있지만, 그러한 관점을 수용하는 사람은 얼마 없을 것이다.(유명한 사고실험 '통속의 뇌') 현재 받아들여지는 세계에 대한 관점은, 물리적 실체가 존재하고 그 실체의 일부인 인간이나 여타 동물들에게는 의식이 발현된다고 본다. 컴퓨터 관련 이론에서, 실체에 해당하는 것에 대해 말할 때는 '물리적'이라는 표현을 쓰고, 인간의 의식이 생각하는 방식에 적합한 것에는 '논리적'이라는 표현을 쓴다.
'대응'이란 말을 쓰긴 했지만, 정확히 대응이 무엇이냐 물으면 명확하게 답하기가 힘들다. 사과 한 개와 숫자 1을 대응시키는 가장 간단한 대응에 대해서 생각해보면, 보통은 시각적인 영역으로 구분해서 개체의 개수를 센다고 볼 수 있다. 사과와 그 주변 배경은 시각적으로 구분되기 때문에 사과의 테두리 안쪽의 영역 전체를 '사과'라는 것으로 인식한다. 그 옆에 모양이 조금 다른 사과가 하나 더 있어도 시각적 유사성을 통해 그것을 사과로 인식하고 숫자 2에 대응시킨다.
사과의 개수를 셀 때 사과 한 개 보면 숫자 1이 떠오르는 그 자체가 대응이라고 볼 수 있다. 숫자 1이 연상되는 그 과정 자체는 인간의 뇌 안에서 일어나는 것인데, 현재까지는 구체적인 메커니즘은 알 수 없다. 그저 인간 정도의 지능을 가진 생물에게서 저절로 일어나는 연상이다. 지능을 갖게 하는 것은 인간의 뇌를 구성하는 일군의 물리적 구조이다. 여러 종류의 여러 원자들이 특정한 구조로 구성되어 있는 계(system, 여기서는 뇌)가 의식을 만들어내는 것은 미스테리 중 하나이다.
생각의 편의를 위해서 '대응'이라는 것을, 두 대상 간의 연상이 자연스럽게 이루어지는 것이라고 해두자. 사과 한 개가 숫자 1에 대응되는 것이 '부자연스럽다, 이상하다'라고 느끼는 사람이 있다면 다른 사람들과의 대화는 힘들 것이다.
그런데 사과 한 개와 숫자 1을 대응시키는 것이 자연스럽다는 것은, 덧셈, 뺄셈, 곱셈, 나눗셈 등의 연산을 해주어도 그러한 대응이 잘 유지가 된다는 것을 학습을 통해 알고 있기 때문이다. 사과 한 개 옆에다가 다른 사과를 가져다 놓는 '행위'를 덧셈에 대응시킬 수 있다. 물리적 실체의 변화(사과를 가져다 놓는 것)에 '+'라는 기호를 대응시키는 것이다. '사과 하나 옆에 사과 하나를 더 놓는 것'을 사람은 '1+1=2'라는 기호로 바꾸어서 생각한다. '1', '2'는 수학적 대상(object)이고, '+', '='는 그 대상들에 대한 규칙을 나타낸 기호이다. 사과 뿐만 아니라 귤, 자동차, 사람 등에 대해서도 같은 수학 기호로 표현할 수 있다. 이렇게 여러 사물에 대해서도 곧바로 개수를 셀 수 있는 것은 사물의 형태를 시각적으로 구분해서, '덩어리 진 것은 1에 해당한다'라는 생각을 부지불식간에 적용하기 때문이다. 덩어리지지 않은 것에 대해서는 개수를 얘기할 때 즉각적으로 주저하게 된다. '저기 엎질러진 물이 몇 개냐?'라고 묻는다면 곧바로 이상하다고 생각한다. 물은 어느 한쪽만 들어올린다고 해서 전체가 연결되어 움직이지 않기 때문에 덩어리진 것이라고 인식하지 않기 때문이다.
대응에 있어서 핵심은, 대상들을 이용한 연산을 했을 때 그 연산 결과 또한 대응이 잘 되는가 하는 점이다. 사과의 개수에 대한 대응은 이러한 조건이 잘 충족된다. 사과 하나는 1에 대응되고, 옆에 사과 하나를 더 가져다 놓는 것은 +1에 대응되며, 그 결과인 2는 사과 두 개와 대응된다. 그렇기 때문에 사과를 박스 단위로 엄청 많은 양을 가져다 놓더라도 그것을 일일이 세지 않고 사람의 의식이 갖추어 놓은 숫자에 대한 덧셈과 곱셈 등으로 사과의 개수를 알 수 있다. '인간의 의식에 만들어 놓은 체계 안에서 생각하는 것만으로 물리적 실체의 변화의 결과를 알 수 있다'는 것이 가장 중요하다.
컴퓨터에 있어서는 이러한 대응 개념이 많이 중요해진다. 왜냐하면 대응의 불일치가 다른 분야에 비해서 중요한 비중을 차지하기 때문인데, 사람이 생각하는 방식을 100% 똑같이 컴퓨터의 물리적 장치들로 구현할 수 없기 때문이다. 컴퓨터는 전자의 유무(0, 1)로 대상을 나타내고, 그것들간의 연산으로 변화를 일으킨다. 그에 반해 인간의 의식이 일을 처리하는 방식은 논리적, 개념적이기 때문에 둘 사이의 불일치가 나타난다. 가장 단적인 예로 double float number에 대한 대응의 불일치가 있다.
class DoubleFloatCompare {
public static void main(String[] args) {
twoDoubleFloatCompare(1., 0.3*3+0.1);
}
static void twoDoubleFloatCompare(double a, double b){
System.out.printf("a = %10.20f\nb = %10.20f\n", a, b);
if(a!=b){
System.out.print("a != b");
} else {
System.out.println("a == b");
}
}
}
a = 1.00000000000000000000
b = 0.99999999999999990000
a != b
사람의 의식 안에 있는 수학적 구조에서는 $1 = 0.3\times3 + 0.1$이다. 하지만 컴퓨터에서 float 타입의 수로 이 둘을 비교해보면 값이 다르다고 나온다. 이런 불일치가 발생하는 이유는, 컴퓨터의 저장 장치에서는 $0.3$, $0.1$을 정확히 표현하지 못해서 그와 가장 가까운 값으로 근사시키기 때문이다.($1$은 정확한 값으로 나타낸다.) IEEE-754-2008 표준에 따라 $0.1$은 float 타입의 아래 수로 변환된다.($a$라고 하자) \[ \small a = \frac{1}{2^4}\left[\left(1+\frac{1}{2}\right)+\left(\frac{1}{2^4}+\frac{1}{2^5}\right)+\cdots+\left(\frac{1}{2^{48}}+\frac{1}{2^{49}}\right)+\frac{1}{2^{51}}\right] \] \[\small \simeq 0.1000000000000000055511151231257827021181583404541015625 \] $0.10000000000000000555$ 또한 $a$로 변환된다. 즉, $a$라는 컴퓨터 저장장치가 표현하는 숫자 하나에, 여러 개의 논리적 숫자가 대응되는 것이다. 여기에 더해서 연산 또한 논리적 연산과는 다르게 된다. 표현할 수 있는 유효자리 수의 한계 때문이다.
이처럼 프로그래밍 언어를 배울 때 가장 처음에 접하는 곳에서부터 논리적 개념과 물리적 실체의 차이가 존재한다. 하지만 대충 숫자 표현의 한계가 있어서 계산 값에 약간씩 차이가 있다는 정도만 언급하고 넘어간다. 그렇지만 이러한 차이는 객체지향 프로그래밍을 접하면서부터 중요한 문제가 된다.
객체지향 프로그래밍
객체지향 프로그래밍이란, 프로그램을 만들 때 웬만하면 속성과 함수로 객체를 나타내는 것부터 시작하는 방식이다. 객체라는 것은 논리적 대상을 의미한다. 앞서 말한 바와 같이 '논리적'이라는 말은 사람의 의식, 개념에 관련되어 있다는 뜻이다. 사람이 어떤 것을 하나의 대상으로 간주한다면 그것을 프로그래밍 언어에서 객체화 하는 것이다. 우리는 '사람'을 객체로 만들 수 있다. 객체로 만들 때 주의해야 할 것은, 우리가 필요한 만큼만 속성과 함수를 만들어야 한다는 것이다. 사람의 평균 키를 구하는 프로그램을 만들 때 사람 객체의 속성에 몸무게를 넣을 필요는 없다.
그런데 보통은 객체로 만들고자 하는 대상이 어떤 보통명사인 경우가 많다. 위에서처럼 '사람'이라는 명사는 하나의 개체로 특정되지 않는다. 수많은 사람들을 하나로 묶어서 통칭하는 것이다. 딱 하나만 존재하는 고유명사의 경우라도, 그것이 속한 집합의 한 개체로 표현된다. 예를 들어 '중국'을 표현한다면 '국가'라는 더 상위의 개념에 속한 하나의 요소로 나타내는 것이다. 그 요소('중국')의 속성으로는 인구수, 국토면적, GDP 등이 가능할 것이다.
그래서 보통명사 혹은 집합적 개념을 '클래스'로 나타낸다. 프로그래밍 언어에서 표현하고 싶은 대상(객체)은 위에서 말한 바와 같이 하나의 상위 개념에 포함된 하위 요소인 경우가 많기 때문에 상위 개념에 해당하는 클래스와 그에 속하는 요소인 객체 두 가지로 구분된다. 수학에서 배우는 집합을 예로 들면, 집합 이름이 클래스에 해당하고 집합의 원소가 객체에 해당한다.
어떤 새로운 개념을 배울 때 어렵다고 느낀다면 그 개념이 이미 알고 있던 개념이나 존재하는 물리적 대상과 '대응'이 되지 않기 때문인 경우가 많다. 대부분의 사람들이 수학을 배우면서 그런 경험을 할 때가, 허수 $i=\sqrt{-1}$를 접할 때다. 이 수를 어떤 물리적 대상에 대응시켜야 할지 도무지 알 수가 없다. 사람들은 자신이 이미 알고 있는 것으로 연결시켜야만 이해를 했다고 느낀다. 그런데 허수는 그럴 수가 없다. 허수라는 이름도 오해를 불러일으킨다. '허(虛)'는 비어있다, 헛되다 라는 뜻인데 이것으로부터 의미를 알기는 어렵다. 영어 표현인 imaginary number가 이해하기엔 낫다. 상상으로 만들어낸 수라는 뜻이다. 어떤 개념이 반드시 물리적 대상에 대응되어야 하는가?에 대해 생각해봐야 한다. 반드시 그래야 하는 이유는 없다. 어떤 개념이 추상적이고, 개념적이기만 해서 물리적 대상 혹은 익숙한 개념과 곧바로 대응되지 않더라도 유용할 수는 있는 것이다. $i$가 그 자체로는 아무 의미 없어 보이더라도 연산을 통해 나오는 결과가 물리적 실체를 설명하는 데 도움이 된다면 유용한 것이다. 실제로도 그러하다.(허수가 어떻게 쓰이는지는 복소변수함수론이나 미분방정식을 공부해보면 알 수 있다.)
객체지향 프로그래밍에서도 마찬가지이다. '클래스'나 '객체'에 직접적으로 대응되는 물리적 대상이나 익숙한 개념은 존재하지 않는다. 많은 책들에서 클래스나 객체에 관련된 비유를 많이 사용하지만 그것만으로는 완전하지 않다. 대략적인 유사성을 중심으로 새로운 개념과 기존에 알고 있던 개념이나 사물에 대응을 시키거나 비유를 하되, 불일치가 있는 부분에 주의하며 그 개념들간의 관계성을 통해서 이해해야 한다. 객체는 개별적 사물에 비교적 잘 대응이 된다. 그리고 클래스는 그 객체가 속한 집합의 속성과 함수를 정의하는 문서와 같은 것이라 보면 된다. 혹은 집합의 이름을 클래스, 집합에 속하는 원소를 객체라고 보아도 된다.
스프링 프레임워크에서 DispatcherServlet 클래스는 대응시킬만한 물리적 실체가 없다. 앞서 객체는 개별적 사물에 비교적 잘 대응이 된다고 했으나, 이는 사물을 프로그래밍 언어에서 표현할 때만 그렇다. 우리가 프로그래밍 언어에서 접하는 클래스나 객체들은 DispatcherServlet처럼 대응시킬 물리적 실체가 없는 경우가 대부분이다. 순전히 우리가 '이러한 기능을 하는 클래스를 만들자'라고 기능적으로만 구상한 클래스들을 수도 없이 만나게 된다. 이럴때는 그러한 기능을 하는 상상의 장치를 상정해야 한다.
댓글남기기