애플리케이션을 개발할 때 가장 중요한 것 중 하나는 잘못된 데이터가 시스템 내부로 들어오지 못하게 막는 것입니다. 이를 '방어적 프로그래밍'이라고 합니다.
코틀린은 이를 위해 require, check, assert와 같은 내장 함수를 제공합니다. 오늘은 그 중에서도 가장 많이 쓰이는 require를 어디에 써야 하는지, 그리고 init 블록 안과 밖의 차이는 무엇인지 알아보겠습니다.
1. require란 무엇인가?
require는 주로 매개변수(Argument)의 유효성을 검사할 때 사용합니다. 조건이 false이면 IllegalArgumentException을 발생시킵니다.
fun purchase(money: Int) {
require(money >= 1000) { "금액은 1000원 이상이어야 합니다." }
// ... 로직 수행
}
2. init 블록 안의 require: "태어날 때부터 완벽하게"
클래스의 init 블록 안에 require를 넣는 것은 '이 객체는 생성되는 순간부터 무결해야 한다' 는 것을 의미합니다. 객체 지향 생활 체조 원칙 중 '일급 컬렉션'이나 VO(Value Object)를 만들 때 필수적입니다.
사용 시점:
- 객체 생성 시 필수 조건 검증
- 생성자 파라미터 유효성 검사
class Lotto(private val numbers: List<Int>) {
init {
// 객체 생성 시점에 즉시 검사
require(numbers.size == 6) { "[ERROR] 로또 번호는 6개여야 합니다." }
require(numbers.distinct().size == 6) { "[ERROR] 로또 번호는 중복될 수 없습니다." }
}
}
이렇게 하면, 잘못된 상태를 가진 Lotto 객체는 아예 생성조차 되지 않습니다.
프로그램 내에 돌아다니는 모든 Lotto 객체는 '6개의 숫자'를 가지고 있음을 100% 보장받게 됩니다.
3. init 밖(메서드)의 require: "행동할 때 올바르게"
init 블록 밖, 즉 일반 메서드 내부에서 사용하는 require는 해당 메서드를 실행하기 위한 조건을 검사할 때 사용합니다. 객체는 이미 생성되었지만, 특정 행동을 하기 위해 추가적인 값이 올바른지 확인할 때 씁니다.
사용 시점:
- 메서드 인자값 검증
- 비즈니스 로직 수행 전 입력값 체크
class LottoStore {
// 로또를 구매하는 행위(메서드)
fun sellLotto(payment: Int): Lotto {
// 이 행동을 하기 위한 조건 검사
require(payment % 1000 == 0) { "[ERROR] 구입 금액은 1,000원 단위여야 합니다." }
return createLotto()
}
}
여기서 require는 LottoStore 객체의 생성이 아니라, sellLotto라는 행위의 정당성을 판단합니다.
4. 번외: require vs check
코틀린은 require 외에도 check라는 함수를 제공합니다. 둘의 차이는 예외 종류와 의미에 있습니다.
require(요구사항):- 던지는 예외:
IllegalArgumentException - 의미: "너(호출자)가 잘못된 값을 줬어." (매개변수 검증)
- 던지는 예외:
check(상태 확인):- 던지는 예외:
IllegalStateException - 의미: "나(객체)의 현재 상태가 이 작업을 하기에 적절하지 않아." (객체 내부 상태 검증)
- 던지는 예외:
class VendingMachine {
var isOpen = false
fun getItem() {
// 문이 열려있지 않으면 꺼낼 수 없다. (내 상태 문제)
check(isOpen) { "자판기 문이 닫혀있습니다." }
}
}
5. 정리
init블록 안의require: 객체의 생명과 관련됨. 올바르지 않으면 태어나지도 마라. (생성자 검증)- 메서드 안의
require: 객체의 행동과 관련됨. 올바른 재료를 주지 않으면 일하지 않겠다. (인자 검증)
이 두 가지를 구분해서 사용하면, 에러가 발생했을 때 이것이 '객체 생성의 문제'인지 '메서드 호출의 문제'인지 훨씬 빠르게 파악할 수 있습니다.
'Dev Log > Kotlin' 카테고리의 다른 글
| [Kotlin] 클래스의 품격: data class와 enum class 내부 동작 원리부터 실무 활용까지 (0) | 2026.01.04 |
|---|---|
| [Kotlin] 함수를 변수처럼? 고차함수와 더블 콜론(::) 완벽 이해하기 (1) | 2026.01.04 |
| [Kotlin] 클래스 초기화의 비밀: init 블록과 실행 순서 완벽 가이드 (0) | 2026.01.04 |
| [Kotlin] 반복문 리팩토링: for 루프 대신 'repeat'를 써야 하는 이유 (0) | 2026.01.03 |
| [Kotlin] 코틀린 컬렉션 함수 정복하기: map, filter, forEach의 모든 것 (0) | 2026.01.03 |