개발독서/코드품질

[개발독서] 내 코드가 그렇게 이상한가요 (3~4장 클래스 설계 & 불변 활용하기)

보리시스템 2024. 3. 24.

 

<더 생각해보기>
성능이 중요한 경우 불변변수가 아닌 가변변수를 사용하라고 하는데, 불변변수가 가변변수보다 성능에 더 좋은 경우는 무엇이 있을지?

 

메모리 관리 불변변수는 한 번 생성되면 변경할 수 없으므로 메모리 관리가 간단합니다. 하지만 가변변수는 값이 변경될 때마다 메모리를 할당하고 해제하는 오버헤드가 발생할 수 있습니다.
캐시 일관성 불변변수는 값이 변하지 않기 때문에 여러 스레드 간에 안전하게 공유될 수 있습니다. 이는 캐시 일관성을 유지하는 데 도움이 됩니다. 반면 가변변수는 값이 변경될 수 있기 때문에 캐시 일관성을 유지하는 데 추가적인 오버헤드가 발생할 수 있습니다.
병렬화 불변변수는 여러 스레드 간에 안전하게 공유될 수 있으므로 병렬화를 쉽게 할 수 있습니다. 가변변수의 경우 값이 변경될 때 동기화 메커니즘이 필요할 수 있으며 이는 병렬화의 이점을 제한할 수 있습니다.
예측 가능한 동작 불변변수는 값이 변하지 않기 때문에 예측 가능한 동작을 제공합니다. 이는 코드를 이해하고 디버깅하는 데 도움이 될 수 있습니다.
코드 최적화 일부 컴파일러나 런타임은 불변성을 감지하여 코드를 최적화할  있습니다. 예를 들어, 불변변수는 불필요한 복사를 피하고  효율적인 메모리 관리를   있습니다.

 


 

3장 클래스 설계: 모든 것과 연결되는 설계 기반
3.1 클래스 단위로 잘 동작하도록 설계하기
3.2 성숙한 클래스로 성장시키는 설계 기법
3.3 악마 퇴치 효과 검토하기
3.4 프로그램 구조의 문제 해결에 도움을 주는 디자인 패턴

4장 불변 활용하기: 안정적으로 동작하게 만들기
4.1 재할당
4.2 가변으로 인해 발생하는 의도하지 않은 영향
4.3 불변과 가변은 어떻게 다루어야 할까

 


 

3장 클래스 설계: 모든 것과 연결되는 설계 기반

3.1  클래스 단위로 잘 동작하도록 설계하기

1. 클래스의 구성 요소
2. 모든 클래스가 갖추어야 하는 자기 방어 임무

 

1. 클래스의 구성 요소

  • 클래스?

 

클래스 기반 '데이터'와 '그 데이터를 조작하는 논리'를 클래스라는 기본 단위로 묶어서 정의해 가며, 프로그램을 작성하는 방법
클래스 기반 객체 지향 언어 자바, C# 등
클래스 구성요소 인스턴스 변수, 메서드

 

  • 좋은 클래스?

 

잘 만들어진 클래스 1. 인스턴스 변수
2. 인스턴스 변수에 잘못된 값이 할당되지 않게 막고, 정상적으로 조작하는 메서드
좋은 클래스 구성 반드시 메서드와 인스턴스 변수를 함께 사용해야 함
좋지 않은 클래스 구성 메서드, 인스턴스 변수 중 하나라도 빠지면 안됨. 다만 목적에 따라 예외적으로 이러한 구성이 좋을 수도 있음
이유 1. 데이터 클래스는 일반적으로 인스턴스 변수를 조작하는 로직이 다른 클래스에 구현되어 있음
1) 연관성을 알아채기 어려워 코드 중복될 수 있음
2) 수정하다 중복 코드 중 일부를 그대로 두는 일이 발생할 수 있음
3) 가독성을 낮춤

2. 인스턴스 생성하더라도 인스턴스 변수들은 아직 유효하지 않은 상태이므로 초기화를 별도로 해줘야 함
1) 초기화하지 않고 사용하면 버그 발생할 수 있음
2) 데이터 클래스의 초기화 작업 코드조차 다른 클래스에 구현되어 있음

3. 인스턴스 변수에 어떠한 값이든 넣을 수 있어 잘못된 값이 쉽게 들어감
1) 데이터 클래스가 자신을 보호할 수 있는 로직을 가지고 있지 않음(잘못된 값이 들어가지 않도록 하는 유효성 검사도 다른 클래스에 구현되어 있음)

 

2. 모든 클래스가 갖추어야 하는 자기 방어 임무

  • 데이터 클래스에 자기 방어 임무를 부여해 다른 클래스에 맡기던 일을 스스로 할 수 있게 설계하기

 


 

3.2 성숙한 클래스로 성장시키는 설계 기법

1. 생성자로 확실하게 정상적인 값 설정하기
2. 계산 로직도 데이터를 가진 쪽에 구현하기
3. 불변 변수로 만들어서 예상하지 못한 동작 막기
4. 불변 변수를 변경하고 싶다면 새로운 인스턴스 만들기
5. 메서드 매개변수와 지역 변수도 불변으로 만들기
6. 엉뚱한 값을 전달하지 않도록 하기
7. 의미 없는 메서드 추가하지 않기

 

1. 생성자로 확실하게 정상적인 값 설정하기

  • 데이터 클래스는 디폴트 생성자(매개변수 없는 생성자)를 사용해 인스턴트 생성 뒤, 인스턴스 변수에 따로 값을 항당해 초기화함
    • 로우 데이터 객체로서 초기화되지 않은 상태를 유발하는 클래스 구조이므로 적절한 초기화 로직을 생성자에 구현하여 로우 데이터 객체를 방지
    • '유효성 검사'를 생성자 내부에 정의하여 예외를 발생시켜 잘못된 값의 유입을 방지
      • 가드(guard)를 활용해 불필요한 요소를 메서드 앞부분에서 제외해 로직 간단하게 만들기

 

2. 계산 로직도 데이터를 가진 쪽에 구현하기

  • 응집도가 낮은 구조(데이터와 데이터 조작 로직이 분리되어 있는 구조 등)에서 발생하는 문제를 방지하기 위해 클래스 성숙하게 만들기
    • 계산 로직도 해당 클래스 내부에 구현하기

 

3. 불변 변수로 만들어서 예상하지 못한 동작 막기

  • 인스턴스 변수를 불변(immutable)으로 만들기
    • 분변 변수: 값을 한 번 할당하면 다시는 바꿀 수 없는 변수로 final 수식자를 사용

 

4. 불변 변수를 변경하고 싶다면 새로운 인스턴스 만들기

  • 인스턴스 변수의 내용을 변경하는 것이 아닌 변경된 값을 가진 새로운 인스턴스를 만들어 사용

 

5. 메서드 매개변수와 지역 변수도 불변으로 만들기

  • 값이 중간에 바뀌면 값의 변화를 추적하기 힘들기 때문에 버그를 발생시킬 수 있으므로 매개변수와 지역변수도 불변으로 만들어 사용

 

6. 엉뚱한 값을 전달하지 않도록 하기

  • 매개변수의 자료형을 int에서 Money로 변경하는 등의 방식으로 Money 이외의 자료형 전달을 방지
    • int, string 등 기본 자료형(primitive type, 프로그래밍 언어가 표준적으로 제공하는 자료형) 위주로 사용할 경우 의미가 다른 값을 전달하는 오류가 발생할 수 있음

 

7. 의미 없는 메서드 추가하지 않기

  • 시스템 사양에 필요하지 않은 메서드를 의미없이 추가하면 이후에 다른 개발자가 이를 무심코 사용해 버그가 될 수 있음

 


 

3. 3 악마 퇴치 효과 검토하기

클래스 설계란 인스턴스 변수가 잘못된 상태에 빠지지 않게 하기 위한 구조를 만드는 것

 


 

3.4 프로그램 구조의 문제 해결에 도움을 주는 디자인 패턴

디자인 패턴(설계 패턴, design patter)?
응집도가 높은 구조로 만들거나, 잘못된 상태로부터 프로그램을 방어하는 등 프로그램의 구조를 개선하는 설계 방법 

 

디자인패턴 효과
완전생성자 잘못된 상태로부터 보호
값 객체 특정한 값과 관련된 로직의 응집도를 높임
전략(strategy) 조건 분기를 줄이고 로직을 단순화함
정책(policy) 조건 분기를 단순화하고, 더 자유롭게 만듦
일급 컬렉션(First Class Collection) 값 객체의 일종으로 컬렉션과 관련된 로직의 응집도를 높임
스프라우트 클래스(Sprout Class) 기존 로직을 변경하지 않고, 안전하게 새로운 기능을 추가함

 


 

4장 불변 활용하기: 안정적으로 동작하게 만들기

4.1  재할당

재할당(파괴적 할당)? 변수에 값을 다시 할당하는 것
=> 변수의 의미를 바꿔 추측하기 어렵게 만듦
=> 언제 어떻게 변경되었는지 추적하기 힘들게 함
1. 불변 변수로 만들어서 재할당 막기
2. 매개 변수도 불변으로 만들기

 

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

  • 변수에 final 수식자 붙이기

 

2. 매개 변수도 불변으로 만들기

  • 매개 변수에 final 수식자 붙이기
  • 매개 변수에 어떤 연산을 적용하고 싶다면, 불변 지역 변수를 만들어 활용

 


 

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

사례 1. 가변 인스턴스 재사용하기
사례 2. 함수로 가변 인스턴스 조작하기

 

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

  • 가변 인스턴스 변수는 예상하지 못한 동작을 일으킴
    • 인스턴스를 재사용하지 못하게 만들어 이런 경우를 방지해야 함

 

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

  • 함수의 부수효과라는 구조적 문제를 가짐으로써 함수(메서드) 또한 예상하지 못한 동작을 일으킴
    • 작업 실행 순서에 의존(동일한 결과를 내기 위해서는 동일한 순서로 실행해야 함)하는 등 결과를 예측하기도, 유지보수하기도 힘듦
    • 함수의 부수효과? '함수가 매개변수를 전달받고, 값을 리턴하는 것'(함수의 주요 작용) 이외에 외부상태(인스턴스 변수 등)를 변경하는 것
      • 상태 변경? 함수 밖에 있는 상태를 변경하는 것(인스턴스 변수 변경, 전역변수 변경, 매개변수 변경, 파일 읽고 쓰기 같은 I/O 조작 등)
      • 함수 내부에 선언한 지역 변수의 변경은 부수효과? 함수 외부에 영향을 주지 않기 때문에 부수효과라고 할 수 없음
더보기
  • 함수의 영향 범위 한정하기!
    • 부수 효과가 있는 함수는 영향 범위를 예측하기 힘들기 때문에 함수가 영향을 주거나 받을 수 있는 범위를 한정해 예상치 못한 동작 방지하기
      • 함수 설계 시
        • 데이터(상태)는 매개변수로 받기
        • 상태 변경하지 않기
        • 값은 함수의 리턴값으로 돌려주기 

 

  • 불변으로 만들어서 예기치 못한 동작 막기!
    • 부수효과의 여지를 없앨 수 있도록 불변 변수로 만들기
    • 변경된 값을 사용하고자 한다면 새로운 값을 가진 새로운 인스턴스 변수를 만들어 사용

 


 

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

1. 기본적으로 불변으로
2. 가변으로 설계해야 하는 경우
3. 상태를 변경하는 메서드 설계하기
4. 코드 외부와 데이터 교환은 국소화하기

 

1. 기본적으로 불변으로

자바 - final 수식어
코틀린, 스칼라 - val 키워드(불변)
- var 키워드(가변)
자바스크립트 - const 키워드(상수)
러스트(Rust) - 불변이 디폴트
- mut 키워드
  • 변수를 불변으로 만들 때의 장점
    • 변수의 의미가 변하지 않으므로, 혼란을 줄일 수 있음
    • 동작이 안정적이게 되므로, 결과를 예측하기 쉬움
    • 코드의 영향 범위가 한정적이므로, 유지보수가 편리해짐

 


 

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

  • 성능(performance)이 중요한 경우
    • 대량의 데이터를 빠르게 처리해야 하는 경우
    • 이미지를 처리하는 경우
    • 리소스에 제약이 큰 임베디드 소프트웨어를 다루는 경우 등

 

  • 스코프가 국소적인 경우
    • 반복문 카운터 등 반복 처리 스코프에서만 사용되는 지역 변수의 경우 등

 


 

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

  • 인스턴스 변수를 가변으로 만들었다면, 메서드를 만들 때 조건에 맞는 올바른 상태로 변경하는 뮤테이터로 바꾸기
    • 뮤테이터(mutater)? 상태를 변화시키는 메서드

 


 

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

  • 불변을 활용해 설계했더라도, 코드 외부와의 데이터 교환은 주의해야 함
    • 리포지터리 패턴(데이터베이스에 데이터를 저장하는 것을 캡슐화하는 디자인 패턴) 등을 활용해 코드 외부와 데이터 교환을 국소화해야 함