불변성

undefcat·2023년 8월 14일
1

생각

목록 보기
4/4
post-thumbnail

엘레강트 오브젝트 책을 읽던 도중, 불변과 상수에 대해 생각할만한 주제를 보게 됐습니다.

책 127쪽 3.4 충성스러우면서 불변이거나, 아니면 상수이거나 챕터에 나온 내용인데요. 우선, 불변상수 각각을 저는 다음과 같이 생각했었습니다.

  • 불변성: 상태가 변하지 않는다.
  • 상수: 데이터가 변하지 않는다.

책에서도 저자는 다음과 같이 얘기합니다.

불변성에 반대하는 사람들은 세상은 본질적으로 가변적이기 때문에 불변 객체만으로 세상을 표현하기란 불가능하다고 주장합니다. 실제로도 파일, 스트림, 웹 페이지, 버퍼 등의 모든 입출력 엔티티는 본질적으로 상태 변경에 기반하고 있으며, 이들의 구현 코드 역시 가변적입니다.

이런 관점이 많은 사람들의 상식에는 부합할지 몰라도 저는 동의할 수 없습니다. 물론 세상은 가변적이지만, 그렇다고 해서 불변 객체로 세상을 모델링할 수 없는 것은 아닙니다. 이 점에 대해 많은 사람들이 혼란스러워 하는 이유는 서로 다른 개념인 상태(state)데이터(data)에 관해 오해하고 있기 때문입니다.

저자도 상태데이터를 언급하는데요. 이 대목에서 살짝 움찔했습니다. "나도 혹시 오해하고 있는건가?" 싶어서요.

불변성

우선 책의 내용을 따라가봅시다. 다음과 같은 자바 코드 예제를 보겠습니다.

class WebPage {
	private final URI uri;
    
    WebPage(URI path) {
    	this.uri = path;
    }
    
    public String content() {
    	// HTTP GET 요청을 전송해서
        // 웹 페이지의 컨텐츠를 읽은 후,
        // 읽혀진 컨텐츠를 UTF-8 문자열로 변환한다.
    }
}

WebPage는 과연 불변일까요, 가변일까요?

저는 불변이라고 생각했습니다. 왜냐하면

  • 불변성이란 상태와 관련있다.
  • WebPage의 상태는 uri 필드이다.
  • 이 필드는 final 로 선언됐다. 즉, 재할당 될 수 없다.
  • 따라서 상태가 변할 수 없으므로, 불변이다.

다행(?)스럽게도 저자 역시 이 객체는 불변이라고 얘기합니다.

애초에 이 예제를 보고 불변이냐 가변이냐? 라는 질문을 한 이유는 바로 content() 메서드 때문입니다. 아무래도 content()를 호출할 때마다 해당 결괏값이 언제나 동일한 결과를 반환할 확률은 낮기 때문입니다.

그러니까, 객체의 메서드를 호출했는데 값이 항상 같다는 보장이 없으니 이게 과연 불변이냐? 가변이냐? 하는 질문을 할 수 있었던 것이죠. 사실 여기까지는 저도 저자와 같은 생각이었기 때문에 무난하게 넘어갔습니다. WebPage의 상태는 변하지 않으니 불변이라고 말이죠.

그럼 다음 예제를 보겠습니다.

class ImmutableList<T> {
	private final List<T> items = LinkedList<T>();
    
    void add(T number) {
    	this.items.add(number);
    }
    
    Iterable<T> iterate() {
    	return Collections.unmodifiableList(this.items);
    }
}

ImmutableList는 과연 불변일까요, 가변일까요?

저는 이 질문을 보고 감탄을 금치 못했습니다. 그리고 불변성이라는 개념이 생각보다 심오한 주제라는 생각을 갖게 됐습니다.

제가 생각하는 불변성에 따르면, 이는 당연히 불변입니다. 왜냐하면 ImmutableListitems 필드를 갖고 있는데, 이 필드는 final입니다. 즉, ImmutableList의 상태는 변하지 않습니다. 저자 역시 불변이라고 얘기합니다. 상태가 바뀌지 않기 때문이죠.

그럼에도 저는 WebPage와는 다르게 이 객체가 불변이라고 바로 생각하기가 쉽지 않았습니다. 왜냐하면 add() 메서드는 items의 상태를 변경하기 때문입니다. 문제는 그건 items의 상태일 뿐, ImmutableList의 상태는 아니라는 점입니다. 따라서 ImmutableList는 불변입니다. 그런데도 왜 저는 불변이라는 생각에 거부감이 들었을까요?

우리가 ImmutableList를 사용할 때, 이 객체의 메서드를 보면 add()라는 무언가 상태를 변경하는 행동이 있습니다. 그리고 이 add()를 호출하고 iterate()로 값을 확인하면, add()의 호출 여부에 따라 그 결괏값이 항상 다를 것입니다. 그러니 자연스럽게 가변이라는 생각을 갖게 됩니다.

만약 우리가 아래와 같은 인터페이스를 본다고 생각해봅시다.

interface ImmutableList<T> {
	void add(T item);
    
    Iterable<T> iterate();
}

어떻게 생각하시나요? 이걸보고 우리는 과연 불변이라고 생각할까요? 인터페이스의 이름이 ImmutableList라는 이유로요? "누가 이름을 이렇게 지어놨어?" 라고 생각하지 않을까요?

그런데 WebPage도 별반 다르지 않습니다. content() 메서드의 행동은 호출할 때마다 항상 같을것이라는 보장은 없습니다.

좀 더 생각해볼까요? 아래의 WebPage를 한 번 봅시다.

class WebPage {
	private final URI uri;
    
    WebPage(URI path) {
    	this.uri = path;
    }
    
    public void modify(String content) {
    	// HTTP PUT 요청을 보내서
        // 웹 페이지 내용을 수정한다.
    }
}

WebPage는 불변인가요, 가변인가요?

상태의 관점에서 봤을때, WebPage는 여전히 불변입니다. modify()WebPage의 상태를 변경하지 않습니다. WebPage가 바라보는 타겟의 상태 변경을 요청할 뿐입니다. 즉, 자신의 상태를 변경하지 않는다는 관점에서 ImmutableList와 다를바가 없습니다.

저자는 WebPage 예제를 보여주면서 이렇게 얘기했습니다.

content() 메서드가 호출할 때마다 서로 다른 값이 반환되더라도 이 객체는 불변입니다. 여기에서 객체의 행동이나 메서드의 반환값은 중요하지 않습니다. 핵심은 객체가 살아있는 동안 상태가 변하지 않는다는 사실입니다. 그리고 이 사실로 인해 대부분의 사람들이 불변 객체의 개념을 혼동합니다.

즉, 행동은 중요하지 않다고 얘기합니다. 제가 ImmutableList을 보고 멈칫한 바로 그 이유말입니다. 그럼에도 저는 왜 여전히 뭔가 찝찝한 걸까요? 상태의 관점에서 불변성을 따지자면 분명히 맞는데도요.

저자는 불변성뿐 아니라 상수에 대해서도 언급했습니다.

상수

아래와 같은 자바 코드를 보겠습니다.

class ConstantList<T> {
	privaet final T[] array;
    
    ConstantList() {
    	this(new T[0]);
    }
    
    private ConstantList(T[] numbers) {
    	this.array = numbers;
    }
    
    ConstantList with(T number) {
    	T[] nums = new T[this.array.length + 1];
        System.arraycopy(this.array, 0, nums, 0, this.array.length);
        nums[this.array.length] = number;
        return new ConstantList(numbs);
    }
    
    Iterable<T> iterate() {
    	return Arrays.asList(this.array);
    }
}

ConstantList는 아래와 같이 사용할 수 있습니다.

ConstantList list = new ConstantList()
	.with(1) // [1]
   	.with(15) // [1, 15]
    .with(5); // [1, 15, 5]

위에서 중요한 점은, with()를 호출해서 새로운 아이템을 추가할 때마다 항상 새로운 객체가 생성된다는 점입니다.

저자가 말하길, 많은 사람들은 상수불변이라고 생각한다고 합니다. 저 역시 이쪽이 좀 더 불변에 가깝다고 느꼈습니다. 저자는 상수를 다음과 같이 말합니다.

직관적으로 사람들은 불변 객체의 메서드를 호출할 때마다 상수처럼 매번 동일한 데이터가 반환되리라 기대합니다.

불변 객체의 정의에 따르면 적절해 보일 수도 있겠지만 결론적으로 이런 사고 방식은 틀렸습니다. 완전히 틀렸다고 하기는 어렵지만 불완전한 그림입니다. 상수처럼 동작하는 객체는 단지 불변성의 특별한 경우일 뿐이기 때문입니다.

객체가 대표하는 실제 엔터티와 객체의 상태가 동일한 경우에는 불변대신 상수라는 용어를 사용하길 권장합니다.

불변의 특수한 경우중 하나라고 합니다. 즉, 불변이 좀 더 포괄적인 개념인 것이죠.

불변성의 정의(Definition)를 찾아서

위키피디아(Immutable object)는 불변객체를 어떻게 정의하는지 봅시다.

In object-oriented and functional programming, an immutable object (unchangeable object) is an object whose state cannot be modified after it is created.

생성된 뒤에 상태가 바뀔 수 없는 객체를 불변객체라고 위키피디아에서는 말합니다. 더 읽어보겠습니다.

In some cases, an object is considered immutable even if some internally used attributes change, but the object's state appears unchanging from an external point of view. For example, an object that uses memoization to cache the results of expensive computations could still be considered an immutable object.

위키피디아에서 말하는 불변은 저자가 말하는 불변보다는 좀 더 유연함을 알 수 있습니다. Memoization을 예로 들면서, 내부적으로는 값의 상태를 객체가 변경시키더라도 외부에서 봤을 때에는 같은 경우에도 경우에 따라 이를 불변이라고 볼 수 있다고 말합니다.

아마 저자는 Memoization 객체는 불변객체라고 말하지 않을 것 같습니다. 외부에서는 항상 같은 값을 리턴받을지라도, 실제로 객체 내부에서는 상태가 변할 것이기 때문이죠.

불변성: 상태로 식별되는 객체

아무래도 저자가 생각할 때, 불변 객체란 DDD에서 얘기하는 Value Object의 개념에 가까운 것 같습니다.

불변 객체와 가변 객체의 중요한 차이는 불변 객체에는 식별자가 존재하지 않으며, 절대로 상태를 변경할 수 없다는 점입니다.

위에서 봤던 WebPage를 다시 보겠습니다. 만약 동일한 URI를 가진 서로 다른 WebPage 객체가 있다면, 이 둘은 같을까요 다를까요? 아마도 같을 것입니다(물론 동일성동등성은 그 자체로도 또 하나의 주제이지만 문맥상 여기에서는 동등성의 개념으로 봐야 할 것 같습니다).

불변성이 중요한 이유

왜 불변성이 중요할까요? 불변성의 장점을 찾아보면 아마 다음과 같이 꼽을 수 있을 것입니다.

예측가능한 프로그래밍으로 버그를 줄일 수 있다.

불변 객체는 생성된 이후 내부 상태를 변경할 수 없기 때문에 예측가능한 프로그래밍이 가능합니다. 중간에 값이 변경되지 않음을 보장받기 때문이죠. 이로 인해 버그를 상당히 줄일 수 있습니다.

thread-safety 하다.

동시성 프로그래밍에서 발생하는 대부분의 문제는 바로 데이터로 인한 문제일 것입니다. 상태가 변하지 않는다면 이는 곧 thread-safe 하게 사용할 수 있다는 뜻입니다.

불변성이 중요한 이유들을 생각해보면, 저자의 물리적 상태에 기반한 불변성 정의가 좀 더 잘 와닿는 것 같습니다. 외부에서 보여지는 행동이 아니라, 객체의 상태 그 자체가 변하지 않는 경우에만 불변성이라고 하는게 맞는 것 같아 보입니다.

객체가 나타내는 사상이 중요하다

WebPage, ImmutableList를 살펴보면서 불변성에 대해 많은 생각을 해봤습니다. 그러다가 불변성의 개념이 잘 안받아들여졌던 이유를 생각해보니, 아무래도 객체의 본질을 잊고 있어서 그렇지 않았나 하는 생각을 하게 됐습니다.

결국 OOP에서의 객체는 현실세계의 사상을 프로그래밍 세계에서 표현하기 위함입니다. 객체란 어떤 사상을 대표하는 요소입니다.

WebPage의 경우 URI로 지정된 각 웹페이지를 대표할 것입니다.

ImmutableList의 경우, 이 객체 내부에 저장된 데이터가 어떤 의미로 인코딩된 데이터들이냐에 따라 나타내는 바가 다를 것입니다. 물론 ImmutableList의 경우 어떤 사상을 나타낸다기보다는 사상을 나타내는 어떤 객체가 갖고 있는 자료구조겠지만요.

세상에는 같은 이름을 가진 사람들이 많이 있습니다. 하지만 그들은 모두 다 다른 사람입니다. 이름이 같다고 해서 이들이 모두 같은 사람이 되는 것은 아닙니다. 실제로 객체지향세계에서 Person이라는 객체들이 존재할 것이지만, 아마도 같은 Person객체는 존재하지 않을 것입니다. 심지어 일란성쌍둥이는 물리적인 DNA가 같다 할지라도, 세상은 둘을 같은 사람이라고 보지 않습니다. 프로그래밍 세계에서도 당연히 그래야겠지요. 오히려 현실세계에서보다 OOP 세계에서의 일란성쌍둥이는 그 둘이 일란성쌍둥이인지조차도 모를 정도로 표현될 것입니다.

결국 불변성이야말로 현실세계를 제대로 반영하기 위한 개념이 아닌가 합니다.

profile
undefined cat

1개의 댓글

comment-user-thumbnail
2023년 8월 14일

글 잘 봤습니다.

답글 달기