Tiny Star
본문 바로가기
Dev Log/Kotlin

[Kotlin] 견고한 코드 만들기: require와 check를 활용한 방어적 프로그래밍

by 별터 2026. 1. 4.

애플리케이션을 개발할 때 가장 중요한 것 중 하나는 잘못된 데이터가 시스템 내부로 들어오지 못하게 막는 것입니다. 이를 '방어적 프로그래밍'이라고 합니다.

 

코틀린은 이를 위해 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()
    }
}

여기서 requireLottoStore 객체의 생성이 아니라, sellLotto라는 행위의 정당성을 판단합니다.


4. 번외: require vs check

 

코틀린은 require 외에도 check라는 함수를 제공합니다. 둘의 차이는 예외 종류와 의미에 있습니다.

  • require (요구사항):
    • 던지는 예외: IllegalArgumentException
    • 의미: "너(호출자)가 잘못된 값을 줬어." (매개변수 검증)
  • check (상태 확인):
    • 던지는 예외: IllegalStateException
    • 의미: "나(객체)의 현재 상태가 이 작업을 하기에 적절하지 않아." (객체 내부 상태 검증)
class VendingMachine {
    var isOpen = false

    fun getItem() {
        // 문이 열려있지 않으면 꺼낼 수 없다. (내 상태 문제)
        check(isOpen) { "자판기 문이 닫혀있습니다." } 
    }
}

5. 정리

  • init 블록 안의 require: 객체의 생명과 관련됨. 올바르지 않으면 태어나지도 마라. (생성자 검증)
  • 메서드 안의 require: 객체의 행동과 관련됨. 올바른 재료를 주지 않으면 일하지 않겠다. (인자 검증)

이 두 가지를 구분해서 사용하면, 에러가 발생했을 때 이것이 '객체 생성의 문제'인지 '메서드 호출의 문제'인지 훨씬 빠르게 파악할 수 있습니다.