이 글은 내 코드가 그렇게 이상한가요? 책을 읽고 정리한 내용을 바탕으로 작성하였습니다.
재할당
-
재할당
은 변수에 값을 다시 할당하는 것을 말하며,파괴적 할당
이라고도 말한다.재할당
은 변수의 의미를 바꿔 추측하기 어렵게 만들고,언제 어떻게 변경되는지 추척하기 힘들게 한다.
-
따라서
재할당
을 막기 위해서는변수
에 final 수식자를 붙여준다.마찬가지로,
매개변수
에도 final 수식자를 붙인다.
가변으로 인해 발생하는 의도하지 않는 영향
- 인스턴스가 가변이면 다른 부분에 의도하지 않은 영향을 주기 쉽다.
부수 효과의 단점
-
함수의
부수 효과
는 함수가 매개변수를 전달받고, 값을 리턴하는 것 이외에 외부 상태(인스턴스 변수)를 변경하는 것을 가리킨다. -
조금 더 구체적으로 설명하면, 함수(메서드)에는 주요 작용과 부수 효과가 있다.
-
주요 작용: 함수(메서드)가 매개변수를 전달받고, 값을 리턴하는 것
-
부수 효과: 주요 작용 이외의 상태 변경을 일으키는 것
-
-
여기서
상태 변경
이란 함수 밖에 있는 상태를 변경하는 것을 의미한다. 예를 들어 다음과 같은 것이다.-
인스턴스 변수 변경
-
전역 변수 변경
-
매개변수 변경
-
파일 읽고 쓰기 같은 I/O 조작
-
-
작업 실행 순서에 의존하는 코드는 결과를 예측하기 힘들며, 유지 보수하기 힘들다.
함수의 영향 범위 한정하기
-
부수 효과가 있는 함수는 영향 범위를 예측하기 힘들다.
-
따라서 예상치 못한 동작을 막으려면, 함수가 영향을 주거나 받을 수 있는 범위를 한정하는 것이다.
-
함수는 다음 동작을 만족하도록 설계하는 것이 좋다.
-
데이터(상태)는 매개변수로 받는다.
-
상태를 변경하지 않는다.
-
값은 함수의 리턴 값으로 돌려준다.
-
-
따라서 매개변수로 상태를 받고, 상태를 변경하지 않고, 값을 리턴하기만
함수
가 이상적이다.
불변으로 만들어서 예기치 못한 동작 막기
-
지금까지 설명한 방식에 따라, 예상하지 못한 동작을 막기 위해 불변을 기반으로 코드를 설계한다.
-
부수 효과의 자체를 없애는 방법은 간단하다.
인스턴스 변수
에 final 수식자를 붙여서 불변으로 만들면 된다.
원시(기본) 타입(primitive type): byte, char, short, int, long, float, double, boolean
참조 타입(reference type): 배열 타입, 열거 타입, 클래스, 인터페이스
참고로, int형과 같은
원시 타입
(primitive)은 참조값이 존재하지 않기 때문에 외부에서도 그대로불변
으로 존재하게 된다.하지만,
참조 타입
인 객체나 Array, List와 같은 컬렉션일 경우에는불변
을 보장하려면 setter를 포함하지 않아야 하며, getter 사용시 방어적 복사를 통해 값을 전달해야 한다. 또한, 참조 변수 객체 내부 또한 불변이어야 불변이 성립한다.
-
불변 변수로 만들면 변경할 수 없기 때문에, 변경된 값을 사용하고 싶다면 새로운 값을 가진 새로운 인스턴스 변수를 만들어서 사용해야 한다.
-
아래 코드의 reinforce 메서드와 disable 메서드처럼
AttackPower
인스턴스를 새로 생성하고 리턴하는 구조로 변경한다.
class AttackPower() {
static final int MIN = 0;
final int value; // final로 불변으로 만들기
AttackPower(final int value) {
if(value < MIN) {
throw new IllegalArgumentException();
}
this.value = value;
}
/**
* 공격력 강화하기
* @param increment 공격력 증가량
* @return 증가된 공격력
*/
AttackPower reinforce(final AttackPower increment) {
return new AttackPower(this.value + increment.value); // 인스턴스를 새로 생성하고 리턴하는 구조로 변경
}
/**
* 무력화하기
* @return 무력화한 공격력
*/
AttackPower disable() {
return new AttackPower(MIN); // 인스턴스를 새로 생성하고 리턴하는 구조로 변경
}
}
불변과 가변은 어떻게 다루어야 할까?
-
지금까지 설명한 것처럼 변수를 불변으로 만들면 다음과 같은 장점이 있다.
-
변수의 의미가 변하지 않으므로, 혼란을 줄일 수가 있음.
-
동작을 안정적이게 되므로, 결과를 예측하지 쉬움
-
코드의 영향 범위가 한정적이므로, 유지 보수가 편리해짐
-
-
따라서 기본적으로는
불변
으로 설계하는 것이 좋다. (이 책에서도 불변을 표준 스타일로 사용한다)
가변으로 설계해야 하는 경우
-
기본적으로 불변으로 설계하는 것이 좋지만, 가변이 필요한 경우도 있다.
-
바로 성능(performance)이 중요한 경우이다.
-
대량의 데이터를 처리해야 하는 경우,
-
이미지를 처리하는 경우
-
리소스에 제약이 큰 임베디드 소프트웨어를 다루는 경우
-
-
위와 같은 예시에서는 가변을 사용하는 것이 좋을 수 있다.
-
불변이라면 값을 변경할 때 인스턴스를 새로 생성해야 한다.
-
만약 크기가 큰 인스턴스를 새로 생성하면서 시간이 오래 걸려 성능에 문제가 생긴다면 불변 보다는 가변을 사용하는 것이 좋다.
상태를 변경하는 메서드 설계하기
-
어떤 게임에서 히트포인터에 대한 기본적인 조건은 다음과 같다.
-
히트포인트는 0이상
-
히트포인트가 0이 되면, 사망 상태로 변경
-
-
히트포인트가 0이 될때 뮤테이터(mutater)를 통해 사망 상태로 변경하자.
뮤테이터(mutater): 상태를 변화시키는 메서드
class Hitpoint {
...
/**
* 대미지 받는 처리
* @param damageAmount 대미지 크기
*/
void damage(final int damageAmount) {
hitPoint.damage(damageAmount) {
if(hitPoint.isZero) {
states.add(StateType.dead); // 사망 상태로 변경
}
}
}
}