Effective Kotlin_아이템1. 가변성을 제한하라
1장. 안정성 - 아이템1. 가변성을 제한하라
TIL (Today I Learned)
코틀린은 모듈로 프로그램을 설계하는데 모듈은 클래스, 객체, 함수, 타입 별칭, 톱레벨 프로퍼티 등 다양한 요소로 구성된다.
이런 요소 중 일부는 상태를 가질 수 있다.
상태는 var
을 사용하거나 mutable
을 사용하는 경우이다.
이번 장에서는 이런 상태를 적절히 관리하는 방법에 대해서 공부했다.
오늘 읽은 범위
- 1부. 좋은 코드
- 1장. 안정성
- 아이템1. 가변성을 제한하라
- 1장. 안정성
책에서 기억하고 싶은 내용
가변성을 제한하는 3가지 방법
코틀린에서 가변성을 제한하는 방법은 크게 다음 3가지가 있다.
- 읽기 전용 프로퍼티 (
val
) - 가변 컬렉션과 읽기 전용 컬렉션 구분하기
- 데이터 클래스의
copy
val
은 읽기 전용 프로퍼티이지 변경이 불가능하다는 뜻은 아니다. mutable
객체를 담고 있다면 내부적으로 변할 수 있다.
완전히 변경할 필요가 없다면 final
프로퍼티를 사용하는 것이 좋다.
mutable
이 붙은 인터페이스는 읽기 전용 인터페이스를 상속 받아서 변경을 위한 메서드를 추가한 것이다.
만약 읽기 전용에서 mutable
로 변경해야 한다면 copy
를 통해서 새로운 mutable
컬렉션을 만드는 list.toMutableList
등을 활용해야 한다.
코틀린에서 가변성을 제한하기 위해서 immutable
객체를 많이 사용하는데 그 장점은 다음과 같다.
- 한 번 정의된 상태가 유지되므로 코드 이해가 쉽다
- 객체를 공유했을 때 충돌이 발생하지 않으므로 병렬 처리에 안전하다
- 객체에 대한 참조가 변경되지 않으므로 쉽게 캐시할 수 있다
- 방어적 복사본(defensive copy)을 만들 필요가 없고, 객체를 복사할 때 깊은 복사를 따로 하지 않아도 된다
- 다른 객체를 만들 때 활용하기 좋다
set
또는map
의 키로 사용할 수 있다
단점은 객체를 변경할 수 없다는 점이다. immutable
객체를 변경하기 위해서는 자신의 일부를 수정한 새로운 객체를 만들어 내는 메서드를 가져야 한다.
예를 들어 Int의 plus
나 minus
, Iterable
의 map
, filter
등의 메서드가 있다.
새로운 객체를 만들어 내는 메서드를 하나하나 만드는 것은 비효율적이기에 data
한정자를 사용한다.
data
한정자는 copy
메서드를 가진다. copy
메서드를 사용하면 모든 기본 생성자 프로퍼티가 같은 새로운 객체를 만들어 낼 수 있다.
변경 가능 지점
변경할 수 있는 리스트를 만드는 방법은 다음 2가지가 있다.
mutable
컬렉션을 만드는 것var
로 읽고 쓸 수 있는 프로퍼티를 만드는 것
두 방법은 변경 가능 지점(mutating point)의 위치가 다르다.
첫 번째 방법은 구체적인 리스트 구현 내부에 변경 가능 지점이 있다. 멀티스레드 처리가 이루어질 경우, 내부적으로 적절한 동기화가 되어 있는지 확실히 알 수 없어 위험하다. 두 번째 방법은 프로퍼티 자체가 변경 가능 지점이다. 멀시스레드 처리의 안정성이 더 좋다고 할 수 있다. (하지만 잘못 만들면 일부 요소가 손실될 수 있다)
:x: 프로퍼티와 컬렉션 모두 변경 가능한 지점으로 만들면 안된다
var list = mutableListOf<Int>()
mutable 객체 사용의 주의점
mutable
객체를 사용할 때 객체를 외부에 노출하는 것은 굉장히 위험하다.
어떤 클래스에서 private으로 선언된 mutableList
를 그대로 반환한다면 private으로 선언되었음에도 불구하고 외부에서 변경될 수 있다.
이를 방지하기 위해 다음 2 가지 방법이 사용된다.
- 리턴되는
mutable
객체를 복제한다. -> 방어적 복제(defensive copying)라고 한다.- 이때 data 한정자로 만들어지는 copy 메서드를 활용하면 좋다.
class UserHolder { private val user: MutableUser() fun get(): MutableUser { return user.copy() } // ... }
- 이때 data 한정자로 만들어지는 copy 메서드를 활용하면 좋다.
- 가변성을 제한한다. 컬렉션은 읽기 전용 슈퍼타입으로 업캐스트하여 가변성을 제한한다.
data class User(val name: String) class UserRepository { private val storedUser: MutableMap<Int, String> = mutableMapOf() fun loadAll(): Map<Int, String> { return storedUsers } // ... }
정리
가변 지점을 제한하며 코드를 작성하자. 이때 활용할 수 있는 규칙들은 다음과 같다.
- var보다는 val 사용
mutable
프로퍼티보다는immutable
프로퍼티 사용mutable
객체와 클래스보다는immutable
객체과 클래스 사용- 변경이 필요한 대상을 만들 경우
immutable
데이터 클래스로 만들고 copy 활용 - 컬렉션에 상태를 저장할 때
mutable
컬렉션 보다는 읽기 전용 컬렉션 사용 - 변이 지점을 적절하게 설계, 불필요한 변이 지점은 만들지 않는다
mutable
객체를 외부에 노출하지 않는다
단, 가끔 효율성 때문에 immutable
객체보다 mutable
객체를 사용하는 것이 더 좋을 때가 있다
-
immutable
객체를 사용할 때 멀티스레드를 사용할 때 많은 주의를 기울여야 한다. -
immutable
객체와mutable
객체를 구분하는 기준은 가변성이다.
오늘 읽은 소감은? 떠오르는 생각
기존에 val
, var
, mutable
, immutable
을 잘 사용하고 있었지만 이 파트를 읽고 나서 더욱 적절한 때에 적절한 방법을 사용해야겠다는 생각이 들었다. kotlin이 java의 단점을 보안하기 위해 탄생한 언어라는 것을 잘 보여주는 파트였다. java 대신 kotlin을 사용하는 가장 큰 이유는 안정성 때문이라고 생각한다. 또한 코틀린이 안정성을 신경써서 설계되었지만 개발자가 잘 알지 못하고 무분별하게 사용한다면 불안정한 설계를 할 수 있다는 사실도 알게 되었다.
궁금한 내용, 잘 이해되지 않는 내용
방어적 복사
위 내용에서 등장한 방어적 복사란 용어가 생소했다.
아쉽게도 Kotlin 공식 문서에서 Defensive copying에 대한 정의를 내리고 있지 않았다. 일부 블로그를 찾아보니 방어적 복사를 다음과 같이 정의 했다.
MutableCollection을 인자로 받는 함수에 전달할 때에는 원본의 변경을 막기 위해 컬렉션을 복사한다. 이를 방어적 복사(defensive copy) 패턴이라고 한다.
얕은 복사 vs 깊은 복사
immutable
객체를 사용하는 장점 중 깊은 복사를 일부러 하지 않아도 된다는 부분이 있었다.
깊은 복사
- 데이터 자체를 통째로 복사
- 복사된 두 객체는 완전히 독립적인 메모리를 가진다
- value type 객체들은 깊은 복사를 한다
- data class에서
copy
를 사용하면 깊은 복사가 된다
얕은 복사
- 인스턴스가 메모리에 새로 생성되지 않는다
- 주소값을 복사하는 것이다
- reference type 객체를 복사하면 주소값이 복사되는 얕은 복사를 한다
멀티스레딩 안정성
책 16페이지에 다음과 같이 설명되어 있다.
mutable
객체는 멀티스레드 처리가 이루어질 경우 내부적으로 적절한 동기화가 되어 있는지 확실하게 알 수 없으므로 위험하다.
하지만 책 20페이지에서는 다음과 같이 설명되어 있다.
immutable
객체를 사용할 때는 언제나 멀티스레드 때에 더 많은 주의를 기울여야 한다는 것을 기억하라.
mutable
이 위험하다는 것인가 immutable
이 위험하다는 것인가?
두 내용의 모호성 때문에 많은 고민을 했다.
mutable이 위험한 경우
책에 다음과 같은 내용이 있다.
immutable
객체는 공유했을 때 충돌이 발생하지 않으므로 병렬 처리를 안전하게 할 수 있다
위 내용을 바탕으로 생각했을 때 mutable 객체는 내부에서만 변화가 있기 때문에 thread에서 같은 부분에 접근하여 충돌이 발생할 수도 있기 때문에 동기화가 되었는지 확실히 알기 어렵다고 표현했다고 생각했다.
immutable이 위험한 경우
구글링 하던 중 다음과 같은 내용을 발견했다.1
읽기 전용 컬렉션은 항상 Thread-Safe 하지 않다는 점을 명심해야 한다. 읽기 전용 컬렉션 타입으로 받았지만 구현체는 읽기 전용이 아닐 수 있다. 해당 구현체를 멀티스레드 환경에서 내용을 변경하는 일이 생긴다면
ConcurrentModificationException
이 발생할 수 있다.
위 내용으로 보아서 읽기 전용 컬렉션이 Thread-Safe 하지 않고, 멀티스레드 중 예외가 발생할 수 있다는 것으로 보아 immutable이 위험하다고 할 수 있을 것 같다.
이처럼 둘 다 멀티스레드 때에 문제가 발생할 수 있기 때문에 무엇이 더 안정적이다 라는 결론을 내리지 못했다.
-
https://ncucu.me/182 ↩