오봉이와 함께하는 개발 블로그
성숙한 클래스 - 내 코드가 그렇게 이상한가요? 본문
성숙한 클래스
내 코드가 그렇게 이상한가요? 라는 책을 보며 알지 안전한 클래스를 생성하는 방식에 대해 정리한다.
사실 이론적인 내용으로 알지 못 하지만, 습관적으로 다들 안전한 코드를 작성하고 있다 생각하는데 간혹 무심결에 작성한 코드가 안전하지 않은 코드를 작성할 수 있고, 혹은 정말 모르고 작성한 코드가 안전하지 않은 코드가 될 수 있기 때문에 누군가 보고 도움이 되길 바라는 마음 + 공부의 목적으로 글을 작성한다.
생성자 유효성 검사
데이터 클래스를 생성할 때 무심결에 다음과 같이 사용할 수 있다.
class Money {
int acmount;
}
Money money = new Money();
money.setAmount(100);
이렇게 사용 하면 따로 값을 할당하지 않을 경우 문제가 발생할 수 있기 때문에 Args가 없는 생성자를 사용하지 말고 객체를 사용하기 위해 Args가 존재하는생성자를 사용할 수 있다.
class Money {
int acmount;
Money(int amount) {
this.amount = amount;
}
}
Money money = new Money(100);
위와 같이 사용하면 인스턴스 변수가 무조건 초기화 되지만, 이것 만으로 충분하지 않을 수 있다.
값을 생성할 때 매개변수로 잘못된 값을 전달할 수 있기 때문이다.
class Money {
int acmount;
Money(int amount) {
this.amount = amount;
}
}
Money money = new Money(-1000);
여기서 몰랐던 부분을 책에서 배우게 되는데 다음과 같이 생성자 내부에 유효성 검사 로직을 추가하여 인스턴스 생성을 안전하게 진행할 수 있다.
class Money {
int acmount;
Money(int amount) {
if (amount < 0) {
throw new IllegalArgumentException("Amount must over 0")
}
this.amount = amount;
}
}
Money money = new Money(-1000);
위와 같은 코드를 통해 인스턴스를 생성하면 올바른 값만 인스턴스 변수에 저장할 수 있게 된다.
처리 범위를 벗어나는 조건을 메서드 가장 앞 부분에서 확인하는 코드를 Guard 라고 한다.
Guard를 적용하면 항상 안전한 인스턴스만 존재할 수 있게 된다.
계산 로직 구현 (응집도가 높은 코드를 작성하라)
코드 작성 지향점 중 하나는 결합도는 낮게, 응집도는 높게 작성한다는 말 다들 한 번쯤은 들어봤을 거라 생각한다.
결합도가 낮고 응집도가 높게 작성하는 것이 개념적으로 어떤 것인지 내가 이해한 바로는 아래와 같다.
클래스와 클래스 사이에 의존성을 낮추고, 해당 클래스에서 사용하는 변수는 해당 클래스에서 모든 것을 관리한다.
쉽게 말하면 A 클래스에서 특정 동작을 수행하기 위해 B 클래스가 필요하지 않게, A 클래스에서 사용하는 변수는 A 클래스에서 변경할 수 있도록 하게 하는 것이다.
특정 클래스에서 사용하는 변수를 조작하는 로직이 분리되어 있다면 응집도가 낮은 코드가 될 것이다.
class Money {
void add (int other) {
this.amount += other
}
}
위와 같이 Money에서 사용하는 amount라는 데이터를 Money 클래스 내부에서 처리할 수 있다면 응집도가 높은 코드가 된다.
사이드 이펙트 방지
인스턴스 변수를 자꾸 변경하는 코드가 있다면 내부에서 어떻게 해당 변수가 바뀌게 되는지 추적하기 어렵다.
그렇기 때문에 final 키워드를 붙여 불변(immutable) 변수로 만들면 값을 한 번 할당 후 다시 바꿀 수 없기 때문에 재할당이 불가능 하여 변수 통제에 용이함을 얻을 수 있다.
하지만, final 키워드를 추가하면 변수 자체를 바꿀 수 없기 때문에 문제가 발생하지 않나? 라고 생각하는 것은 매우 합리적인 생각이다.
그래서 아래와 같이 새로운 인스턴스를 만들어서 사용하면 final을 사용하면서 안전하게 값을 변경할 수 있게 된다.
Class Money {
final amount;
Money add (int other) {
int added = this.amount + other;
return new Money(added);
}
}
위와 같은 코드를 통해 final로 하여 값을 변경할 수 있으며 추적도 용이한 코드를 작성할 수 있다.
지역변수, 매개변수에 적용하기
메서드의 매개변수와 지역변수는 내부적으로 변경이 가능하다.
값이 중간에 변경되면 추적이 어려운 코드가 되기 때문에 마찬가지로 final을 통해 변경이 불가능 하도록 작성할 수 있다.
void something(final int value) {
value = 100; // 컴파일 오류
}
지역변수도 마찬가지로 final을 사용할 수 있다.
class Money {
Money add (final int other) {
final int added = amount + other;
return new Money(added);
}
}
일급 콜렉션 사용
일급 콜렉션이란 단어는 소트웍스 앤솔로지에서 객체지향 생활체조에서 언급된 단어로 알고 있다.
규칙 8: 일급 콜렉션 사용
이 규칙의 적용은 간단하다.
콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다.
각 콜렉션은 그 자체로 포장돼 있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된셈이다.
필터가 이 새 클래스의 일부가 됨을 알 수 있다.
필터는 또한 스스로 함수 객체가 될 수 있다.
또한 새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다.
이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다.
콜렉션은 실로 매우 유용한 원시 타입이다.
많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에 의미적 의도나 단초는 거의 없다.
-소트웍스 앤솔로지 객체지향 생활체조편-
가령 아래와 같은 코드가 있다고 치자
final int ticketCount = 3;
money.add(ticketCount);
money.add 라는 코드는 가격을 더하는 코드라고 했을 때 가격을 더한 것이 아니라 엉뚱한 티켓의 숫자를 더했다.
이런 실수는 빈번하게 할 수 있는 가벼운 실수이면서 무거운 실수가 될 수 있다.
엉뚱한 값을 전달하지 않도록 일급 컬렉션을 매개변수로 받게 하면 위와 같이 자료형만 맞는 코드를 방지할 수 있다.
Class Money {
Money add (final Money other) {
final int added = amount + other.getAmount();
return new Money(added);
}
}
프로그래밍 언어에서 기본적으로 제공하는 primitive type을 사용하지 말고 일급 콜렉션을 사용해서 코드의 의도, 동작을 확실하게 하면 누군가가 봤을 때 더 알기 쉽고 버그도 방지할 수 있는 좋은 코드가 될 것이다.
'자바' 카테고리의 다른 글
강한 결합 깨기 (상속 part) - 내 코드가 그렇게 이상한가요? (0) | 2023.11.16 |
---|---|
결합도와 책무 - 내 코드가 그렇게 이상한가요? (1) | 2023.11.14 |
Java - Reflection API (0) | 2022.11.26 |
Java - Stream의 기본 개념 (0) | 2022.11.26 |
객체 생성 - 정적 팩토리 메소드 (0) | 2022.09.25 |