일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 안드로이드 레트로핏 crud
- 스택 큐 차이
- 클래스
- rxjava hot observable
- 객체
- 2022 플러터 안드로이드 스튜디오
- 서비스 vs 쓰레드
- 안드로이드 라이선스 종류
- android retrofit login
- jvm 작동 원리
- 서비스 쓰레드 차이
- 안드로이드 os 구조
- ar vr 차이
- rxjava cold observable
- 2022 플러터 설치
- Rxjava Observable
- 안드로이드 레트로핏 사용법
- 안드로이드 유닛 테스트 예시
- jvm이란
- android ar 개발
- 플러터 설치 2022
- 큐 자바 코드
- ANR이란
- 안드로이드 유닛테스트란
- 멤버변수
- 자바 다형성
- 안드로이드 라이선스
- rxjava disposable
- 스택 자바 코드
- 안드로이드 유닛 테스트
- Today
- Total
나만을 위한 블로그
[이펙티브 코틀린] 아이템 47. 인라인 클래스 사용을 고려하라 본문
하나의 값을 갖는 객체도 inline으로 만들 수 있다. 코틀린 1.3부터 도입된 기능으로, 기본 생성자 프로퍼티가 하나인 클래스 앞에 inline을 붙이면 해당 객체를 사용하는 위치가 모두 해당 프로퍼티로 교체된다.
inline class Name(private val value: String) {
// ...
}
이런 인라인 클래스는 타입만 맞다면 값을 곧바로 집어넣는 것도 허용된다.
fun main() {
val name = Name("김철수")
// 위 코드는 컴파일 시 아래와 같이 바뀐다
val name: String = "김철수"
}
인라인 클래스의 메서드는 모두 정적 메서드로 만들어진다.
fun main() {
val name = Name("김철수")
name.greet() // 안녕, 내 이름은 김철수야
}
inline class Name(private val value: String) {
fun greet() = print("안녕, 내 이름은 ${value}야")
}
인라인 클래스는 다른 자료형을 래핑해서 새 자료형을 만들 때 많이 사용된다. 이 때 어떤 오버헤드도 발생하지 않는다. 인라인 클래스는 다음 상황에서 많이 쓰인다.
- 측정 단위를 표현할 때
- 타입 오용으로 발생하는 문제를 막을 때
측정 단위를 표현할 때
타이머 클래스를 만드는 경우를 가정한다. 이 클래스는 특정 시간 후에 파라미터로 받은 함수를 호출한다.
interface Timer {
fun callAfter(time: Int, callback: () -> Unit)
}
여기서 time은 정확히 무슨 단위인가? 밀리초, 초, 분 중에서 어떤 단위인지 명확하지 않다. 따라서 심각한 실수로 여러 문제가 발생할 수 있는 지점이다. 이런 문제를 해결할 수 있는 가장 쉬운 방법은 파라미터 이름에 측정 단위를 붙이는 것이다.
하지만 함수를 사용할 때 프로퍼티 이름이 표시되지 않을 수 있으므로 여전히 실수할 수 있다. 아래 코드의 decideAboutTime은 시간을 어떤 단위로 리턴하는지 알려주지 않는다.
interface User {
fun decideAboutTime(): Int
fun wakeUp()
}
interface Timer {
fun callAfter(timeMillis: Int, callback: () -> Unit)
}
fun setUpUserWakeUpUser(user: User, timer: Timer) {
val time: Int = user.decideAboutTime()
timer.callAfter(time) {
user.wakeUp()
}
}
더 좋은 해결법은 타입에 제한을 거는 것이다. 제한을 걸면 제네릭 유형을 잘못 사용하는 문제를 줄일 수 있다.
inline class Minutes(private val minutes: Int) {
fun toMillis(): Millis = Millis(minutes * 60 * 1000)
}
inline class Millis(val milliseconds: Int) {
// ...
}
interface User {
fun decideAboutTime(): Minutes
fun wakeUp()
}
interface Timer {
fun callAfter(timeMillis: Int, callback: () -> Unit)
}
fun setUpUserWakeUpUser(user: User, timer: Timer) {
val time: Minutes = user.decideAboutTime()
timer.callAfter(time) { // Type mismatch 컴파일 에러
user.wakeUp()
}
}
이렇게 하면 올바른 타입 사용이 강제된다. 프론트엔드 개발에선 px, mm, dp 등의 여러 단위를 사용하는데 이런 단위를 제한할 때 활용하면 좋다.
타입 오용으로 발생하는 문제를 막을 때
SQL DB는 일반적으로 ID를 써서 요소를 식별한다. ID는 일반적으로 단순한 숫자다. 학생 성적 관리 시스템이 있다고 할 때 학생, 교사, 학교 등의 데이터들이 모두 ID를 갖고 있을 것이다.
@Entity(tableName = "grades")
class Grades(
@ColumnInfo(name = "studentId")
val studentId: Int,
@ColumnInfo(name = "teacherId")
val teacherId: Int,
@ColumnInfo(name = "schoolId")
val schoolId: Int
)
이런 코드는 모든 ID가 Int 타입이라서 잘못된 값을 넣을 수 있다. 이런 문제가 발생해도 어떤 오류도 발생하지 않아서 문제 찾기가 힘들어진다. 이런 문제를 막으려면 Int 타입의 값을 인라인 클래스를 활용해 래핑한다.
@Entity(tableName = "grades")
class Grades(
@ColumnInfo(name = "studentId")
val studentId: StudentId,
@ColumnInfo(name = "teacherId")
val teacherId: TeacherId,
@ColumnInfo(name = "schoolId")
val schoolId: SchoolId
)
inline class StudentId(val studentId: Int)
inline class TeacherId(val teacherId: Int)
inline class SchoolId(val schoolId: Int)
이렇게 하면 ID 사용이 안전해지며 컴파일 시 타입이 Int로 대체되므로 코드를 바꿔도 별도의 문제가 발생하지 않는다. 이렇게 인라인 클래스를 쓰면 안전을 위해 새 타입을 도입해도 추가적인 오버헤드가 발생하지 않는다.
'책 > Effective Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 아이템 49. 하나 이상의 처리 단계를 가진 경우 시퀀스를 써라 (0) | 2023.05.15 |
---|---|
[이펙티브 코틀린] 아이템 48. 더 이상 쓰지 않는 객체의 레퍼런스를 제거하라 (0) | 2023.05.14 |
[이펙티브 코틀린] 아이템 46. 함수 타입 파라미터를 갖는 함수에 inline 한정자를 붙여라 (0) | 2023.05.05 |
[이펙티브 코틀린] 아이템 45. 불필요한 객체 생성을 피하라 (0) | 2023.04.29 |
[이펙티브 코틀린] 아이템 44. 멤버 확장 함수의 사용을 피하라 (0) | 2023.04.21 |