본문 바로가기

책을 읽어보자/이펙티브 코틀린

1장 - 아이템 1 : 가변성을 제한하라

1장 안정성

우선 장의 시작이니 1장의 큰 범주를 이야기해보자 

1장에서는 안정성에 대하여 다룬다고 한다 안정성이란 무엇인가?

-> 크래시가 잘안나는 즉 오류가 잘안나는 코드이다.

근데 코틀린은 언어적 차원에서 안정성이 아주 높다고 한다. 그렇다고 그냥 코틀린을 쓴다고 안정성이 높은것이 아니라 

언어적으로 지원을 하니 개발자가 그것을 사용할줄 알아야 한다.

그래서 코틀린이 안정성을 위해 제공하는 기능들을 알아보고 사용법을 알아본다  -> 오류가 덜 발생하는 코드를 만들자

 

예전에 네이버 웹툰 면접을 볼때 주니어와 시니어의 개발의 차이가 그냥 무조건 try catch 로 오류처리하는 것이 아닌 try catch 를 줄이며 오류처리를 하는것에서 확연히 다르다고 했는데 이런 부분으로 오류가 애초에 안일어나게 막는것 아닌가 싶다.

 

아이템 1. 가변성을 제한하라

-용어가 이해가 안됐던것 설명

type alias : https://hanyeop.tistory.com/241

top-level-property :https://june0122.tistory.com/9 -> 정적:static -> companion object같은거 그냥 class 에서 분리되었다고 하기는 뭐하고 어쩃든 인스턴스 안만들고 쓸수있는거  java에서 final 은 변수에 붙었을때 상수를 의미 -> 못바꿈 값

근데 이거 관련 내용은 아니였음  ㅠㅠ

 

만약 다른분들이 이글을 보신다면 그냥 끝에 요약부분만 참고하시면 될것 같습니다!!

 

-지금부터 내용정리 시작

이해한대로 1줄 정리: 바뀔수있는거 나쁘다 -> 최대한 바뀌는 지점을 없애고 바뀐다해도 캡슐화하고 개발자가 컨트롤할수 있는 선에서 변경점을 만들어라

 

Mutable 의 단점

변경될수있는것 var 혹은 mutable 객체는 상태를 가질수있다.

상태는 양날의, 검이다 -> 관리하기 굉장히 어렵기 떄문  

관리하기 어려운 이유

1. 프로그램을 이해하고 디버그 하기 어렵다

-> 상태 변경이 많아지면 추적하는게 힘들어지고 이런것들을 많이 가진 클래스는 이해하기 어렵고 이로 인해 오류를 수정하기 어려워짐

2. 코드의 실행을 추론하기 어려워짐 

-> 시점에 따라서 값이 변하므로 예측 당시의 값을 알아야 코드가 어떻게 돌아가는지 예측할수있음 그리고 당장 확인한다고 상태가 변할지 안변할지 예상할수없음 (뭔 양자역학임? 얼탱이없네 이래서 어렵나보다)

3.멀티스레드 프로그램일때 동기화가 필요함

-> 변경일 일어나는 모든 부분에서 충돌이 일어날수 있다 -> 동시에 같은 자원에 접근하면 충돌나는거

4.테스트하기 어렵다

모든 상태를 테스트 해야하므로  상태들의 조합의 경우의수가 엄청 늘어나서 테스트를 엄청 많이 만들어야할것임

5. 상태 변경할 떄 다른부분에 알려야 하는 경우가 있다

ex) 정렬 리스트에 가변요소를 추가하면 요소 변경이 있을때마다 리스트를 다시 정렬해야하는것

 

Immutable 의 장점

1.한번 정의된 상태가 유지되므로 코드를 이해하기 쉽다

2.immutable 객체는 공유했을때도 충돌이 따로 나지 않아 병렬처리 하기쉽다.

3.immutable 객체에 대한 참조는 변경되지 않으므로 쉽게 캐시할수있다.

4. 방어적 복사본을 만들 필요가없다.

 

이러한 느낌으로 다양한 문제점이 있다고 한다. 물론 이거 엄청나게 대규모 프로젝트에서 나는 문제같아서 현실로 와닿지는 않는다 때되면 오류들이 알아서 날와서 두들겨패주면 그때 이해하며 아 그래서 그렇게 하지말라고 했구나 라고 생각하면될거같다. 물론 이책을 읽었으니 약간 예방접종 느낌으로 하지말라는거 안할꺼다

 

이제 이런것에 대해 궁금하면 책을 다시봐보자 멀티쓰레드로 프로퍼티에 접근하여 충돌에 의해 연산이 생략되는것을 예시로 작성해놓았다

-> 코루틴을 사용하면 스레드가 적게 관여해서 연산이 씹히는걸 적게 만들지만 어쩃든 근본적인 해결은 안된다.

 

해결책 동기화를 해줘야하는데 동기화를 잘구현하는것은 어려운 일이다 -> 변화지점(var,mutable) 이 더 많아지면 훨씬 어려워진다.

예시에서 synchronized로 동기화 해놓았는데 간략히 설명하자면 자바에서 동기화를 위해 스레드 사용시 다른스레드를 막아버리게 하는것이다. -> 코틀린에서는 함수에 @synchronized 어노테이션 붙이는거 찾아보면된다.

 

순수 함수형 언어는 아예 가변성을 막아 놓았다고 한다. 

 

코틀린에서 가변성 제한하기

코틀린 가변성 제한하기 쉽게 만들어짐

-읽기 전용 프로퍼티 val

setter 가 없는 상태로 만들어지는 것이다. -> 일반적인 방법으로 값이 변하지 않음

완전한 불변은 아니다 -> 불변을 사용하고싶다면 final 키워드를 사용하라

mutable 객체를 담고있다면 내부는 언제든 바뀔수 있다.

val의 값은 변경될수 있지만 프로퍼티 레퍼런스 자체는(getter에 넣어놓은 내용같은것) 변경할수 없다 -> 동기화 문제를 줄일수 있다.

getter만 가지고 있는건데  getter 를 어떤 함수로 변경한다면 getter 함수를 실행하고 return 값을 가져온다.

val은 초기화할때 바로 옆에 상태가 바로 적히므로 코드의 예측이 쉽다.-> 스마트 캐스트도 해준다.

 

기존에 잘 안써서 이질적이였던 사용법들

var name: String = "Marcin"
var surname: String = "Mkskala"
val fullName 
    get() = "$name $surname"
//요렇게 쓸수있는데
//name 변경하면 full Name 도 바뀐다
fun calculate(): Int {
    print("계산중...")
    return 42
}

val fizz = calculate() // 이렇게 선언할떄 계산중... 출력됨
val buzz 
    get() = calculate()
    
print(fizz) //42 출력
print(buzz) //계산중 ... 42 출력

이것들은 별내용은 아니고 내가 이렇게 잘사용 안해서 정리했다 나중에 써야지 ㅎㅎ

 

 

-mutable 과 immutable 컬렉션들

iterable 자체가 그냥 immutable 한거고 getter만 있는 인터페이스 들이다 

setter 있는 mutableiterable 인터페이스들 구현하면 이제 setter들 생기는 것이다.

근데 결국 둘다 반환하는것들은 ArrayList 값이 변경이 가능한 것을들 리턴하는데 애초에 읽기전용 인터페이스가 이를 지원하는 함수같은것들이 없기 때문에 접근해서 변경할수가 없는것이다.

 

결국 실제로는 imutable 하지않고 코틀린이 immutable 하게 보이게 작업해놓은것이다

근데 개발자가 다운캐스팅(자식클래스의 참조변수가 부모클래스 객체 참조하는것 -> 애초에 원래 오류남)이런 행위를 하면 코드가 안정하지 않고 이상한 일이 벌어짐 

예시  

val list = listOf(1, 2, 3)

//이러면 오류남
if (list is MutableList) {
	list.add(4)
}

이제 이따위 행동을 하면 오류난다 이것에 대해서 책에서 설명하는데 Arrays.ArrayList와 Array.ArrayList 가 뭐가 다른건지 구글링해도 잘안나와서 모르겠지만 어쩃든 저렇게 Listof 디컴파일 해보면 [1,2,3] 이렇게 아예 배열로 만들어진다 저 list 고로 add같은게 안될것이다 그니까 그냥 의도에 맞게 쓰지 이상한 다운캐스팅 같은 뻘짓하지말자.

 

그래서  결론적으로  읽기전용에서 mutable fh qusrudgodigksekaus copy 를 통해서 새로운 인스턴스만들어서 사용하면 list.toMutableList 를 사용해야한다.

val list = listOf(1,2,3)

val mutableList = list.toMutableList()
mutableList.add(4)
//이렇게 써야한다

이렇게하면 기존객체는 immutable 하므로 안전하다.

-내가 만든 immutable 객체를 위한 copy 함수(data class 꺼)

여기서 보면 내가 만든 클래스 자체를 val 로 불변하는 프로퍼티로 만들어주고 내부 변경울위한 setter를 만들어줘야하는 것을 data class 를 활용 copy를 이용하여 쉽게 변경하게 만들라는것이다.

 

+ 여기서도 적용되지만 mutable 프로퍼티를 사용하고 내부객체는 불변으로 생성하라는 원칙이 적용된다.

 

 

-다른 종류의 변경 가능지점

mutable 프로퍼티에 immutable 객체를(List 같은것들) 담는것이 멀티스레드에서 안정성이 더 좋고 사용자 정의 세터를 이용하여 변경하므로 변경을 추적하기 쉽다 그리고 객체(컬렉션)를 변경하는 여러 메서드 대신 세터 사용하면되고 private로 만들수있다.

 

어쩃든 최대한 피해야하는것은 var 와 mutable 을 동시에 사용하는 미친짓이다.

 

 

-변경 가능 지점 노출하지말기 

책을 읽어보면 나오지만 객체 자체를 외부로 노출시키지말고 immutable 타입으로 업캐스트하여 반환해서 가변성을 제한하던 copy메서드를 통해 방어적 복제를 하여 반환하던 해서 바로 객체가 외부에서 변경가능하게 만들면 안된다.

 

 

 

 

결론:

  1. var 보다 val 사용하는것이 좋다.
  2. mutable 프로퍼티 보다는 immutable프로퍼티를 사용하는것이 좋다
  3. mutable 객체와 클래스 보다는 immutabler객체와 클래스를 사용하는것이좋다.
  4. 변경이 필요한 대상을 만들어야 한다면, immutable 데이터 클래스로 만들어 copy를 활용하는것이 좋다.
  5. 컬렉션에 상태를 저장해야한다면 mutable 컬렉션 보다면 읽기 전용 컬렉션을 사용하는것이 좋다.
  6. 변이 지점을 적절하게 설계하고 불필요한 변이지점을 만들지 마라
  7. mutable 객체를 외부로 노출하는것은 좋지않다.