CH04 : 불변 활용하기

CHAPTER 4 : 불변 활용하기

범위 : 42p ~ 57p
단계 : 실전
제목 : 불변 활용하기, 안정적으로 동작하게 만들기
내용 : 예측할 수 있는 코드를 작성하는데 필요한 불변성을 다룹니다.

INTRO

이 장에서는 3장에서 다루었던 가변과 불변에 대해서 자세히 설명합니다.

변수의 값을 변경하는 등 상태를 변경할 수 있는 것을 가변(mutable)이라고 합니다. 반면 상태를 변경할 수 없는 것을 불변(immutable) 이라고 합니다.

가변과 불변을 적절하게 설계하지 못하면 동작들이 예측하기 어렵고 혼란스러워 집니다.

  • 예를 들어서 ‘이 값은 이렇게 변경될 것이다’라고 생각하고 구현했는데, 의도하지 않은 다른 값으로 변경되는 상황이 생길 수 있습니다.

가능한 한 상태가 변경되지 않도록 설계해야합니다. 이때 불변이라는 개념이 활용됩니다. 불변은 최근 프로그래밍 스타일의 표준 트렌드라고 할 수 있습니다.

4.1 재할당

변수에 값을 다시 할당하는 것을 재할당 또는 파괴적 할당 이라고 합니다.

  • 재할당은 변수의 의미를 바꿔 추측하기 어렵게 만듭니다. 또한 언제 어떻게 변경되었는지 추적하기 힘들게 합니다.

아래는 데미지를 계산하는 코드입니다.

int damage(){
	int tmp = member.power() + member.weaponAttack();
	tmp = (int)(tmp * (1f + member.speed() / 100f ));
	tmp = tmp - (int)(enemy.defence / 2);
	tmp = Math.max(0, tmp);
	return tmp
}

다양한 값을 가져와 최종 대미지를 구하는 과정에서 변수 tmp를 계속 재사용하고 있습니다. 변수 tmp는 기본 공격력, 보정, 값, 대미지 등이 계속 재할당되면서 값의 의미가 바뀝니다.

중간에 의미가 바뀌면, 읽는 사람은 헷갈릴 수 밖에 없습니다. 헷갈리면 버그를 만들어 낼 가능성이 높아집니다.

따라서 재할당은 피하는 것이 좋습니다.

  • 변수 하나를 재활용하지 않고, 계속해서 새로운 변수를 만들어 사용하면 재할당을 피할 수 있습니다.

4.1.1 불변 변수로 만들어서 재할당 막기

재할당을 기계적으로 막을 수 있는 방법은 변수에 final 수식자를 붙이면 된다.final을 붙인 변수는 변경할 수 없다. 따라서 다음과 같이 작성하면 컴파인 단계에서 오류가 발생한다.

void doSomething() {
	final int value = 100;
	value = 200; // compile error
}

4.1.2 매개변수도 불변으로 만들기

매개변수도 마찬가지이다. 매개변수를 변경하면 값의 의미가 바뀔 수 있다. 이렇게 의미가 바뀌면 코드를 읽는 사람이 헷갈리므로 버그의 원인이 될 수 있다.

// 코드 4.4 : 매개변수 productPrice에 재할당하는 코드

void addPrice(int productPrice) {
	productPrice = totalPrice  + productPrice;
	if(MAX_TOTAL_PRICE < productPrice) {
		throw new IllegalArgumentException("구매 상한 금액을 넘었습니다.")
	}

}

4.2 가변으로 인해 발생하는 의도하지 않은 영향

인스턴스가 가변이면 다른 부분에 의도하지 않은 영향을 주기 쉽다.

  • 코드를 변경했을 때, 생각하지도 못헀던 위치에서 상태가 변화하여 예측하지 못한 동작을 하는 경우도 있다.

의도하지 않게 영향을 끼치는 경우 두 가지를 소개하고 이어서 문제를 개선해주는 설계 방법을 설명한다.

4.2.1 사례1 : 가변 인스턴스 재사용하기

인스턴스를 재사용하면, 한 쪽의 변경이 다른 한 쪽에 영향을 준다. 이러한 상황을 예방하려면, 인스턴스를 재사용하지 못하게 만드면 된다.

인스턴스를 개별적으로 생성하고, 재사용하지 않는 로직으로 변경하자.

4.2.2 사례2 : 함수로 가변 인스턴스 조작하기

예상하지 못한 동작은 함수 떄문에 발생하기도 한다.

4.2.3 부수 효과의 단점

함수의 부수 효과는 ‘함수가 매개변수를 전달받고, 값을 리턴하는 것’ 이외에 외부 상태(인스턴스 변수 등)를 변경하는 것을 가리킨다.

함수에는 주요 작용부수 효과가 있다.

  • 주요 작용 : 함수(메서드)가 매개변수를 전달받고, 값을 리턴하는 것
  • 부수 효과 : 주요 작용 이외의 상태 변경을 일으키는 것

여기에서 상태 변경이란 함수 밖에 있는 상태를 변경하는 것을 의미한다. 예를 들어 다음과 같은 것이다.

  • 인스턴스 변수 변경
  • 전역변수 변경
  • 매개변수 변경
  • 파일 읽고 쓰기 같은 I/O 조작

작업 실행 순서에 의존하게 되는 코드는 결과를 예측하기 힘들며 유지보수하기 힘들다(49p)

4.2.4 함수의 영향 범위 한정하기

예상치 못한 동작을 막으려면, 함수가 영향을 주거나 받을 수 있는 범위를 한정하는 것이 좋다. 따라서 함수는 다음항목을 만족하도록 설계하는 것이 좋다.

  • 데이터(상태)는 매개변수로 받는다.
  • 상태를 변경하지 않는다.
  • 값은 함수의 리턴 값으로 돌려준다.

따라서 매개변수로 상태를 받고, 상태를 변경하지 않고, 값을 리턴하기만 하는 함수가 이상적이다.

메서드에서 인스턴스 변수를 사용하는 것도 좋지 않을까?

  • 인스턴스 변수는 불변으로 만들어 영향이 전달되지 않게 할 수 있으므로, 예상치 못한 동작 문제를 회피할 수 이다.
  • 추가로 객체 지향 프로그래밍 언어는 함수의 부수 효과로 인한 범위를 클래스 내부까지 허용하는 것이 일반적이다.

4.2.5 불변으로 만들어서 예기치 못한 동작 막기

불변을 기반으로 코드를 다시 설계하기


class AttackerPower {
	static final int MIN = 0;
	final int value;

	AttackerPoer(final int value) {
		if(value < MIN){
			throw new IllegalArgumentException(;)
		}		
		this.value = value;
	}

	AttackerPower reinforce(final AttackerPower increment){
		return new AttackerPower(this.value + increment.value);
	}

	AttackerPower disable(){
		return new AtackkerPower(MIN);
	}

}

4.3 불변과 가변은 어떻게 다루어야 할까

실제로 개발할 때는 불변과 가변을 어떻게 다루어야 할까?

4.3.1 기본적으로 불변으로

변수를 불변(imutable)으로 만들면 다음과 같은 장점이 존재한다.

  • 변수의 의미가 변하지 않으므로, 혼란을 줄임
  • 동작이 안정적이게 되므로, 결과를 예측하기 쉬움
  • 코드의 영향 범위가 한정적이므로, 유지보수가 편리함

따라서 기본적으로는 불변으로 설계하는 것이 더 좋다. 이 책에서도 불변을 표준 스타일로 사용함

자바의 경우 변수를 불변으로 만들려면 변수 선언 시 final 수식자를 붙여야 하므로, 코드가 쓸데없이 길어짐, 하지만 장점이 더 많음

최근 등장하는 프로그래밍 언어는 불변이 디폴트가 되도록 만들어지고 있으며, 그만큼 불변이라는 성질을 중요하게 여기는 것

4.3.2 가변으로 설계해야하는 경우

가변이 필요한 경우도 존재한다. 성능(perfomance)이 중요한 경우이다.

  • 예를 들어 대량의 데이터를 빠르게 정리해야하는 경우, 이미지를 처리하는 경우, 리소스에 제약이 큰 임베디드 소프트웨어를 다루는 경우 가변을 사용하는 것이 좋을 수 있다.

불변이라면 값을 변경할 때 인스턴스를 새로 생성해야 한다.

  • 만약 크기가 큰 인스턴스를 새로 생성하면서 시간이 오래 걸려 성능에 문제가 생긴다면, 불변보다는 가변을 사용하는 것 좋다.
  • 또한, 스코프 국소적인 경우에는 가변을 사용해도 좋다. (Ex, 반복문 카운터, 반복 처리 스코프)

4.3.3 상태를 변경하는 메서드 설계하기

인스턴스 변수를 가변으로 만들었다면, 메서드를 만들 때 주의해야 할 점이 있다.

상태를 변화시키는 메서드를 ‘뮤테이터(mutater)’라고 한다. 조건에 맞는 올바른 상태로 변경하는 뮤테이터로 바꿔야한다.

4.3.4 코드 외부와 데이터 교환은 국소화하기

불변을 활용해서 아무리 신중하게 설계해도, 코드 외부와의 데이터 교환은 주의해야한다.

  • 코드를 아무리 주의깊게 작성해도, 파일이나 데이터베이스는 코드 외부에 있는 상태이다.

최근에는 이러한 영향을 그나마 줄일 수 있게, 코드 외부와 데이터 교환을 국소화하는 테크닉을 많이 사용한다. 국소화 하는 방법으로는 리포지터리 패턴이 있다.

리포지터리 패턴은 데이터베이스의 영속화를 캡슐화하는 디자인 패턴이다.

  • 리포지터리 패턴은 특정 클래스 내부에 데이터베이스 관련 로직을 격리하므로, 애플리케이션 로직이 데이터베이스 관련 로직과 섞이지 않는다. 리포지터리 패턴은 집합체라는 단위로 데이터를 읽고 쓰게 설계하는 것이 링반적이다.
  • 데이터베이스 영속화는 데이터베이스에 데이터를 저장하는 것을 의미한다.

Categories:

Updated:

Leave a comment