일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 | 31 |
- ozd
- Galaxy Watch
- git-push
- Kotlin
- socket-server
- NoSuchMethodError
- firebase-database
- firebase-storage
- Dva
- mosquitto
- JNI
- TIZEN
- hung-up
- BottomSheetDialog
- mqtt
- Flavors
- cloud-firestore
- git
- socket.io
- AWS
- socket-client
- Java8
- 오즈뷰어
- Android
- ActivityResult-API
- gradle
- OZViewer
- google-login
- Firebase
- 워치
- Today
- Total
Hyeyeon blog
이펙티브 코틀린 - 1장 안정성 본문
이펙티브 코를린을 읽고 내용을 정리해봅니다.
나도모르게 사용하고있는 안티패턴을 경계하려는 마음을 한가득 담아... 🥹
1장 안정성
- 가변성 제어하기
- 변수의 스코프 최소화
- 플랫폼 타입 사용 지양하기
- inferred 타입으로 리턴하지 않기
- 예외를 활용하여 코드에 제한걸기
- 사용자 정의 오류보다 표준 오류 사용하기
- 결과 부족이 발생한 경우, null과 Failure 사용하기
- 적절하게 null 처리하기
- use를 사용하여 리소스 닫기
1. 가변성 제한하기 (가변지점 제한):
val, immutable 클래스/프로퍼티 사용
변경이 필요한 대상은 immutable data class로 만들고 copy() 활용
컬렉션의 상태 저장할 시에는 읽기전용 컬렉션 사용
mutable 객체는 private으로 사용 (외부노출X)
mutable 리스트 대신 mutable 프로퍼티를 사용: 프로퍼티 자체가 변경 가능한 지점인 경우, 멀티스레드 안정성 더 좋음
val list1: MutableList<Int> = mutableListOf()
var list2: List<Int> = listOf() <-- 추천 (Int: immutable 객체)
var list3 = mutableListOf() <-- 최악
2. 변수의 스코프 최소화 => 프로그램 추적 및 관리가 쉬움
프로퍼티보다 지역 변수 사용 (최대한 작은 스코프로 변수 사용)
변수 선언 시 if, when, elvis(?:) 등을 사용하여 초기화하기
여러 프로퍼티 설정 시, 구조분해 선언 활용
// 나쁜 예
val desc: String
val color: Int
if (degrees < 5){
desc = "cold"
color = Color.BLUE
} else {
desc = "hot"
color = Color.RED
}
// 나은 예
val (desc, color) = when {
degrees < 5 -> "cold" to Color.BLUE
else -> "hot" to Color.RED
}
3. 플랫폼 타입 사용 지양하기
플랫폼타입: 다른 언어에서 코틀린 코드로 넘어와서 nullable 여부를 알수 없는 타입
자바와 코틀린을 같이 사용한다면, 자바 코드에 @Nullbable, @NotNull 어노테이션 붙여서 사용
statedType 사용 지향 => 오류 발생 위치가 명확하여 파악이 쉬움
// 지향
fun statedType() {
val value: String = JavaClass().value // NPE
...
println(value.length)
}
// 지양 (플랫폼타입)
fun platformType() {
val value = JavaClass().value
...
println(value.length) // NPE
}
4. inferred 타입으로 리턴하지 않기
타입이 확실하면 명시적으로 지정하기
open class Animal
class Zebra: Animal()
fun main() {
var animal: Animal = Zebra()
// var animal = Zebra() 일 경우, 아래 라인에서 Type mismatch 발생
animal = Animal()
}
5. 예외를 활용하여 코드에 제한걸기
1) Argument 제한
require()로 제한 조건을 확인하고, 조건에 미충족 시 IllegalArgumentException을 throw함.
함수 실행 전에 사전 검사용으로 사용.
fun sendEmail(..){
requireNotNull(user.email)
require(isValidEmail(user, email))
}
fun factorial(n:Int) {
require(n>=0)
..
}
2) 상태 제한
특정 조건에 만족해야만 함수를 실행할 경우, check() 활용.
지정한 예측이 틀렸을 경우, IllegalArgumentException 발생함.
require 블록 뒤에 배치하여 사용.
함수 실행 중에 중간 검사용으로 사용.
fun next(): T {
check(isOpen)
checkNotNull(token)
}
3) Assert 계열 함수 사용
단위 테스트 시 함수가 잘 구현됬는지 확인하는 방법 (assertEqual 등)
6. 사용자 정의 오류보다는 표준 오류를 사용
다른 사람들이 코드를 더 쉽게 파악하기위해 표준 오류 사용을 지향
7. 결과 부족이 발생한 경우, null과 Failure를 사용
try-catch 블록 내부에 코드를 배치하면 컴파일러가 할 수 있는 최적화가 제한됨.
충분히 예측 가능한 범위의 오류는 null, Failure를 사용하고, 예측이 어려운 오류는 예외를 throw하여 처리.
Result 같은 공용체(union type)을 리턴하면 when으로 처리할 수 있음. => try-catch보다 효율적임.
sealed result 클래스는 추가 정보를 전달할 때 사용하고, null은 그렇지 않을 경우에 사용.
nullable을 리턴하면 안됨. getOrNull, Elvis 연산자 등으로 무엇이 리턴되는지 예측할 수 있도록 해야함.
import kotlin.Result
fun main() {
val result1 = divide(10, 2)
val result2 = divide(10, 0)
result1.fold(
onSuccess = { value -> println("Result of division: $value") },
onFailure = { exception -> println("Exception occurred: ${exception.message}") }
)
result2.fold(
onSuccess = { value -> println("Result of division: $value") },
onFailure = { exception -> println("Exception occurred: ${exception.message}") }
)
}
fun divide(a: Int, b: Int): Result<Int> {
return if (b == 0) {
Result.failure(ArithmeticException("Division by zero"))
} else {
Result.success(a / b)
}
}
8. 적절하게 null을 처리하기
1) null을 안전하게 처리: Ellvis 연산자와 스마트 캐스팅
printer?.print() // 안전호출
if (printer != null) printer.print() // 스마트 캐스팅
2) 오류 throw 하기
위 코드에서 printer가 null일 경우, print()를 호출하지 않기 때문에 개발자가 오류를 찾기 어려움.
throw, !, requireNotNull, checkNotNull 등을 활용하여 개발자에게 오류를 강제로 발생시켜주는 것이 좋음.
3) not-null assertion(!!) 관련 문제
!! 연산자는 NPE를 발생시킬 수 있기 때문에, lateinit 혹은 Delegates.notNull을 사용.
!! 연산자가 의미있는 경우는 드물며, nullability가 제대로 표현되지 않는 라이브러리 사용할 때 정도에만 사용해야함.
ㄴ 코틀린을 대상으로 설계된 API를 사용하는데 !! 연산자를 사용하면 이상하게 생각해야함.
일반적으로 !! 연산자 사용을 피해야함.
4) 의미없는 nullability 피하기
null은 중요한 메시지를 전달하는데 사용될 수 있기에, 의미가 없어 보일 때는 null 사용을 지양해야함.
무의미한 null 사용 시, !! 연산자 사용 혹은 많은 예외처리로 코드가 더러워짐.
* nullability 피하는 방법
- 클래스에서 nullability에 따라 여러 함수를 제공하기도 함. (ex: List의 get, getOrNull 함수)
- 클래스 생성 이후에 확실하게 설정된다는 보장이 있는 경우엔 lateinit 혹은 Delegates.notNull 사용
- 빈 컬렉션 대신 null 리턴 금지. 요소가 없는 경우엔 빈 컬렉션을 사용함.
- nullable enum은 별도로 처리해야하는 경우이며, None enum은 정의에 없으므로 필요한 경우에 사용하는 쪽에서 추가해서 활용하라는 의미임.
// nullable enum 예시
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
fun printDirection(direction: Direction?) {
if (direction != null) {
println("Direction: $direction")
} else {
println("Direction is null")
}
}
// None enum 예시
// None enum은 "No option selected"과 같은 상황에서 사용됩니다.
enum class Option {
NONE, OPTION1, OPTION2
}
fun processOption(option: Option) {
when(option) {
Option.NONE -> println("No option selected")
Option.OPTION1 -> println("Option 1 selected")
Option.OPTION2 -> println("Option 2 selected")
}
}
5) lateinit 프로퍼티와 notNull 델리게이트
lateinit은 선언 후 반드시 초기화될 것을 예상하는 상황에서 활용함.
lateinit을 사용할 수 없는 경우(Int, Long 등 기본 타입과 연결된 타입)은 Delegates.notNull 사용.
private var dId: Int by Delegates.notNull()
private var fromNoti: Boolan by Delegates.notNull()
override fun onCreate(..) {
dId = intent.extras.getInt(D_ID_ARG)
fromNoti = intent.extras.getBoolean(FROM_NOTI_ARG)
}
// 프로퍼티 위임으로 아래와 같이 사용 가능 (2부 3장에 나옴)
private var dId: Int by arg(D_ID_ARG)
9. use를 사용하여 리소스 닫기
리소스에 대한 레퍼런스가 없어질 때 가비지 컬렉터가 처리하나, 처리가 느리며 그동안 리소스 유지비용이 많이 들어감.
따라서 명시적으로 close 메서드를 호출해주는 것이 좋음. (ex: InputStream, java.sql.Connection, java.io.Reader 등)
try-finally로 처리할 경우, 두 블록에서 오류 발생 시 둘 중 하나에만 전파됨.
Closable 객체에 use 함수를 사용하면 해당 블록이 수행된 후 자동으로 객체를 닫아서 안전하게 처리할 수 있음.
// 기존
reader = File(filename).bufferedReader()
try {
return reader.lineSequence().sumBy { it.length }
} finally {
reader.close()
}
// 변경
File(filename).bufferedReader().use { reader ->
val content = reader.readText()
println("File content: $content")
}
'개발 > Android' 카테고리의 다른 글
이펙티브 코틀린 - 3장 재사용성, 4장 추상화 설계 (0) | 2024.04.08 |
---|---|
이펙티브 코틀린 - 2장 가독성 (0) | 2024.03.10 |
[Android] JNI 오류 - error=2, No such file or directory (0) | 2023.02.25 |
[Android] Github Actions으로 Android CI 구현하기 (0) | 2023.01.04 |
[Android] DataBinding으로 RecyclerView 만들기 (리스트 높이 제한 방법) (0) | 2022.12.31 |