목록Kotlin (69)
Hanbit the Developer
배경 개인적으로 Retrofit2의 구현을 분석한 적이 있는데 이때 suspend fun이 디컴파일될 때 마지막 인자로 Continuation이 붙으면서 처리된다는 것을 알게 되었고, 원리를 직접 알아보고 싶었습니다. 예시 1: 간단한 코루틴 다음과 같은 간단한 예시를 작성하였습니다. 기존 코드 class SampleViewModel : ViewModel() { fun logout() { viewModelScope.launch { val userId = getUserId() logoutUser(userId) } } private suspend fun getUserId(): Int = 0 private suspend fun logoutUser(userId: Int) { Log.d("SampleViewMod..
배경 구현을 하다보면 Room이나 DataStore에서의 Flow를 stateIn 함수를 통해 StateFlow로 변환하여 사용하는 경우가 잦다. public fun Flow.stateIn( scope: CoroutineScope, started: SharingStarted, initialValue: T ) scope, initialValue는 어떤 것인지 명확하게 알 수 있으나 started는 아니다. started 인자의 타입인 SharingStarted에 대해서 알아보고자 한다. Eagerly SharingStarted에는 3가지 companion object가 있다.(Eagerly, Lazily, WhileSubscribed) 첫번째는 Eagerly이다. Sharing is started imme..
배경 Flow는 구독(collect 등) 시작 시점에 동작을 시작하는 cold stream이고, StateFlow는 구독자의 구독 시점과 상관없이 동작을 시작할 수 있는 hot stream이다. 이 주제에 대해 말로만 인지하고 있었어서 코드로 확인해보고 싶었다. 동작 확인 Flow class FlowViewModel : ViewModel() { val tmp: Flow = flow { val nums = listOf(1,2,3,4,5,6,7,8,9,10) nums.forEach { delay(1000) emit(it) } } fun onClick() { viewModelScope.launch { tmp.collect { Log.e("tmp", it.toString()) } } } } 위 코드를 실행하고 ..
아이템 49: 하나 이상의 처리 단계를 가진 경우에는 시퀸스를 사용하라 *Sequences | Kotlin Documentation를 읽어보시는 것을 적극 권장합니다. Sequences는 Iterable과 매우 흡사하지만, 연산을 lazy하게 해서 더 효율적으로 처리하도록 설계되어 있으며, 다음과 같은 장점이 있다: 자연스러운 순서를 유지한다. 최소한만 연산한다. 무한 시퀸스 형태로 사용할 수 있다. 각각의 단계에서 컬렉션을 만들어 내지 않는다. 자연스러운 순서를 유지한다. .filter { print("F$it, "); it % 2 == 1 } .map { print("M$it, "); it * 2 } .forEach { print("E$it, ") } 위 코드를 listOf(1, 2, 3)와 sequ..
아이템 45: 불필요한 객체 생성을 피하라 객체 생성 비용 실제로 필요한 데이터보다 더 많은 용량을 차지한다. 추가적인 함수 호출이 필요하다. 생성되는 과정에 비용이 있다. 객체가 생성되고 메모리 영역에 할당되고 래퍼런스를 만드는 등의 작업이 필요하다. 객체 생성 줄이기 싱글톤 활용 예시: LinkedList 클래스에서 Empty가 자주 사용되기 때문에 object를 붙여 싱글톤으로 활용한다. 캐시 활용(Memoization): 메모리를 활용하여 시간을 줄인다. 메모리 관리를 위해 SoftReference 사용하면 좋다. *WeakReference vs SoftReference: WeakReference는 래퍼런스가 끊기고 GC 타이밍이 되면 GC를 곧바로 하지만, SoftReference는 다 같으나 ..
아이템 36: 상속보다는 컴포지션을 사용하라 상속의 장점 다형성을 활용할 수 있다. 상속의 단점 복잡한 계층구조가 만들어지기 쉽다. 인터페이스 분리 원칙 위배: 불필요한 것까지 가져온다. 코드를 읽기 어렵다. 컴포지션 장점 다른 클래스의 public한 것들에만 의존하므로 안전하다. 유연하다. 컴포지션 단점 상속 사용 시 보다 코드를 수정해야 하는 경우가 더 많다. → 일반적으로 컴포지션을 사용하는 것이 좋다. → 상속을 사용하는 경우: 명확한 ‘is-a’ 관계 아이템 37: 데이터 집합 표현에 data 한정자를 사용하라 data 한정자를 붙이면 다음과 같은 함수가 자동으로 생성된다: equals() hashCode() toString() copy() componentN() 장점: 가독성: Pair vs ..
아이템 33: 생성자 대신 팩토리 함수를 사용하라 장점은 다음과 같다: 함수에 네이밍 가능 → 가독성 확보(ex. ArrayList(3): 3이 무슨 의미일까? 의문 → ArrayList.withSize(3)) 다른 타입 리턴 가능(ex. listOf(): 각자의 플랫폼에서 다른 구현체를 반환할 수 있다.) 싱글톤 사용 가능 아직 존재하지 않는 객체 리턴 가능 → 빌드 이전에 코드에서 해당 타입을 사용할 수 있다는 뜻 가시성 제어 inline, reified 사용 가능 복잡한 객체 생성 생성자와 달리 슈퍼클래스나 기본생성자를 호출하지 않아도 됨 팩토리 함수 유형 companion 팩토리 함수 확장 팩토리 함수(fun Tool.Companion.createBigTool(): BigTool): compani..
아이템 26: 함수 내부의 추상화 레벨을 통일하라 올바른 예시: fun makeCoffee() { boilWater() brewCoffee() pourCoffee() } 잘못된 예시: fun makeCoffee() { // boil water val water = findWater() val kettle = findKettle() kettle.boil(water) brewCoffee() pourCoffee() } 아이템 27: 변화로부터 코드를 보호하려면 추상화를 사용하라 추상화 할 수 있는 방법은 다음과 같다: 상수로 추출한다. 동작을 함수로 래핑한다. 함수를 클래스로 래핑한다. 인터페이스 뒤에 클래스를 숨긴다. 보편적인 객체를 특수한 객체로 래핑한다. 다만 추상화가 과도하면 코드가 복잡해지기 때문에 ..
아이템 19: knowledge를 반복하여 사용하지 말라 프로그래밍의 가장 큰 규칙은 다음과 같다: 프로젝트에서 이미 있던 코드를 복사해서 붙여넣고 있다면, 무언가가 잘못된 것이다. knowledge 의도적인 정보를 뜻하며 코드나 데이터로 표현할 수 있다. 상속 시 오버라이드가 안 되게 강제한다는 것은 ‘해당 메서드가 슈퍼클래스와 동일하게 동작하기 원한다’는 knowledge를 함유한다. 알고리즘 작동 방식, UI 형태, 원하는 결과 등이 모두 의도적인 정보이다. 가장 중요한 knowledge 두 가지는 다음과 같다: 로직: 프로그램이 어떤 식으로 동작하는지, 프로그램이 어떻게 보이는지 공통 알고리즘: 원하는 동작을 하기 위한 알고리즘 둘의 차이점은, 전자의 경우 계속해서 변하지만 후자는 그렇지 않다는 ..
💡 컴퓨터가 인식할 수 있는 코드는 바보라도 작성할 수 있지만, 인간이 이해할 수 있는 코드는 실력 있는 프로그래머만 작성할 수 있다. - Martin Flowler, 아이템 11: 가독성을 목표로 설계하라 인식 부하 감소 일반적으로 자주 사용하는 패턴을 활용할 것 극단적이게 되지 않기 경험이 많은 코틀린 개발자만 쉽게 이해할 수 있는 코드가 있으면, 제너럴하게는 이해하기 어려운 코드가 된다. 그럼에도 불구하고 이러한 ‘비용’을 지불할 만한 이유가 있다면 트레이드 오프를 진행하는 옵션을 고려할 수 있다. 컨벤션 아이템 12: 연산자 오버로드를 할 때는 의미에 맞게 사용하라 factorial() 함수를 쉽게 표현하기 위해 operator fun Int.not() = factorial()을 작성하여 사용하면..