본문 바로가기
Blog/TIL

2025-01-20 (월)

by 코젼 2025. 1. 20.
728x90
반응형

제네릭

  • Generic 은 코드의 타입을 미리 지정하지 않고, 사용하는 시점에 타입을 결정하는 기능이다.
  • 타입 안정성 유지 및 유연한 재사용이 가능한 코드 작성 가능.
  • 제네릭을 사용할 경우
    • O 가 정확한 객체 타입을 가지도록 강제할 수 있음.
    • T 가 필드의 타입과 일치해야만 하도록 강제할 수 있음.
    • 컴파일 타입에서 타입 검증이 가능해 런타임 오류를 최소화함.

      예제

  • obj 가 어떤 타입의 객체인지 알 수 없고, value 가 필드 타입과 일치하는지 보장할 수 없음.
  • 잘못된 타입이 들어와도 컴파일러가 체크하지 못하고, 런타임에서만 오류가 발생.
  • 제네릭을 사용하면, obj 와 value 가 올바른 타입인지 컴파일 시점에 확인할 수 있음.
    fun setPrivateField(obj: Any, fieldName: String, value: Any) { ... }
  • 둘다 T 로 하면 안됨. obj 와 value 사이의 관계가 불분명해짐.
    fun <T> setPrivateField(obj: T, fieldName: String, value: T) // 문제 발생 가능
  • 제네릭 타입을 분리해서 사용하면, 올바른 타입 검증이 가능해짐.
  • O: Any -> 객체의 타입(Object) / T -> 필드 값의 타입
  • O 는 User, Product 같은 클래스 타입을 나타냄.
  • T 는 클래스의 특정 필드 타입(String, Int, Boolean) 등을 의미함.
    fun <O: Any, T> setPrivateField(obj: O, fieldName: String, value: T)

name 필드는 String이므로, value에도 String만 허용됨.
age 필드는 Int이므로, value에도 Int만 허용됨.
잘못된 타입(IntString 필드에 넣으려는 경우) 컴파일 오류 발생 🚀

class User(private var name: String, private var age: Int)

fun main() {
    val user = User("Alice", 25)

    // ✅ 올바른 타입 사용 → 정상 동작
    ReflectionUtil.setPrivateField(user, "name", "Bob")
    ReflectionUtil.setPrivateField(user, "age", 30)

    // ❌ 잘못된 타입 사용 → 컴파일 오류 발생
    // ReflectionUtil.setPrivateField(user, "name", 30) // ❌ String 필드에 Int 할당 → 컴파일 오류!
}

제네릭 타입 명명 규칙과 작명 가이드

제네릭 타입 네이밍 관례

일반적으로 제네릭 타입 파라미터는 한 글자로 표현하는 것이 일반적이며, 의미에 따라 특정 문자나 단어를 사용합니다.

제네릭 명 의미 예시
T Type (임의의 타입) fun <T> doSomething(value: T)
E Element (컬렉션 요소) fun <E> List<E>.findElement()
K, V Key, Value (Map에서 사용) fun <K, V> Map<K, V>.getEntry()
N Number (숫자 타입) fun <N : Number> sum(numbers: List<N>)
R Return Type (결과 타입) fun <R> transform(input: String): R
O Object (객체의 타입) fun <O : Any> setObject(obj: O)

Object vs inline fun

특징 object inline fun
상태 유지 ✅ 가능 (싱글톤이므로 상태 유지 가능) ❌ 불가능 (호출될 때마다 실행됨)
메모리 사용 한 번만 로드 (싱글톤) 불필요한 객체 생성 방지
성능 최적화 ✅ 내부적으로 캐싱 가능 ✅ 실행 시점에서 인라이닝 (성능 최적화)
정적 메서드 대체 ✅ 가능 (object.method()) ✅ 가능 (inline fun method())
제네릭 타입 검사 ✅ 가능 reified 키워드 사용 시 가능
클래스 확장 가능 여부 ✅ 가능 (클래스 멤버로 사용 가능) ❌ 확장이 어려움
항목 object ReflectionUtil inline fun
불필요한 객체 생성 ❌ (싱글턴 객체 생성) ✅ (인스턴스 생성 없음)
코드 최적화 ✅ (object는 한 번만 생성됨) ✅ (호출마다 인라이닝됨)
성능 일반적인 경우 적절 자주 호출되면 성능 향상 가능
확장성 ✅ 여러 유틸 함수 포함 가능 ❌ 단일 함수로만 사용
코드 가독성 ReflectionUtil.setPrivateField() setPrivateField() 직접 호출 가능
reified 사용 가능 여부 ❌ 사용 불가능 ✅ 사용 가능
  • relified: 제네릭 타입을 런타임에서 확인할 수 있도록 하는 기능.
    • 제네릭의 타입 소거(Type Erasure) 문제 해결 가능.
fun <T> printType() {
    println(T::class) // ❌ 오류 발생! 제네릭 타입을 런타임에서 알 수 없음
}

inline fun <reified T> printType() {
    println(T::class) // ✅ 정상 작동!
}

fun main() {
    printType<String>() // 출력: class kotlin.String
    printType<Int>()    // 출력: class kotlin.Int
}

Object

  • 싱글톤(Singleton) 패턴을 간단하게 구현하는 방법.
  • 프로그램 전체에서 단 하나의 인스턴스만 생성되도록 보장하는 객체.
  • 클래스 선언 없이 즉시 객체를 생성할 수 있음.
  • 반복해서 객체를 생성할 필요가 없음.
  • 정적 메서드 및 변수가 필요할 때 사용.
  • 클래스 상속 가능.
📌 싱글톤(Singleton):
- 하나의 클래스에 오직 하나의 인스턴스만 존재하도록 하는 디자인 패턴.
- 여러 곳에서 동일한 인스턴스를 공유할 때 사용.
- `object`를 사용하면 직접 싱글턴 패턴을 구현할 필요 없이 자동으로 싱글턴이 생성됨.
object Singleton {
    val name = "Singleton Instance"

    fun printMessage() {
        println("This is a Singleton object.")
    }
}

fun main() {
    println(Singleton.name)  // "Singleton Instance"
    Singleton.printMessage() // "This is a Singleton object."
}

inline fun

  • 객체를 생성하지 않고도 고차 함수나 유틸 함수를 최적화 할 수 있음.

    ✅ 장점

  1. 불필요한 객체 생성을 방지
    • object를 사용하면 JVM에서 싱글턴 인스턴스를 만들지만, inline fun을 사용하면 이런 인스턴스조차 필요 없음.
    • setPrivateField가 자주 호출되는 경우, 불필요한 메모리 사용을 줄일 수 있음.
  2. 성능 최적화 (inline)
    • 컴파일 시점에 코드가 인라이닝되어 함수 호출의 오버헤드가 줄어듦.
    • 특히 고차 함수에서 성능 향상 효과가 큼.
  3. 가독성 향상
    • ReflectionUtil.setPrivateField(...) 대신 setPrivateField(...) 형태로 직접 호출 가능하여 가독성이 좋아짐.
  4. reified 키워드로 O의 런타임 타입 검사 가능
    • inline fun이므로 reified를 사용할 수 있어 O::class를 바로 참조 가능
    • (일반적인 object에서는 O::class 사용 불가능)

❌ 단점

  1. 코드 중복 증가
    • inline으로 인해 호출하는 곳마다 코드가 복사되므로, 코드 크기가 증가할 수 있음.
    • 특히 setPrivateField가 자주 사용된다면 object 기반이 더 나을 수도 있음.
  2. 함수 확장이 어렵다
    • object는 여러 메서드를 포함할 수 있지만, inline fun은 단일 함수로만 사용 가능.
    • 만약 getPrivateField() 같은 함수도 추가하려면, object가 더 적합할 수 있음.
  3. inline은 무조건 성능을 개선하는 것이 아님
    • 함수 크기가 크다면 inline을 사용하면 오히려 성능이 저하될 수도 있음.
    • setPrivateField는 비교적 짧은 함수이므로 성능 저하는 크지 않겠지만, 무조건 inline이 좋은 것은 아님.

MockMvc

  • Spring 의 웹 계층(Web Layer) 를 테스트하는 도구
  • 단위 테스트, 통합 테스트 둘 다 사용 가능.
유형 설명 MockMvc 사용 방식
단위 테스트 (Unit Test) 웹 계층(Controller)만 테스트 @WebMvcTest 사용
통합 테스트 (Integration Test) 실제 애플리케이션을 포함하여 테스트 @SpringBootTest + @AutoConfigureMockMvc 사용

@Autowired constructor

  • MockMvc 객체를 자동으로 주입(inject) 하도록 하는 Kotlin 의 생성자 주입 방식
  • Spring 이 자동으로 MockMvc 빈(Bean) 을 찾아 자동으로 객체를 주입함
  • Java 에서는 보통 필드 주입을 많이 쓰지만, Kotlin 에서는 생성자 주입이 더 권장되는 스타일.
  • @Autowired constructor -> lateinit var 로 변경하여 Kotest 에서 인스턴스 생성 가능하도록 수정할 수 있음.
    @WebMvcTest(BusinessUserController::class)
    class AuthAnnotationTest @Autowired constructor(
      private val mockMvc: MockMvc
    ) : DescribeSpec({

🚀 @WebMvcTest의 특징 (단위 테스트)

항목 @WebMvcTest (단위 테스트) @SpringBootTest (통합 테스트)
로딩 범위 Controller 계층만 애플리케이션 전체
Service 계층 로드 ❌ 안 됨 (Mock 필요) ✅ 실제 빈(Bean) 로드
Repository 계층 로드 ❌ 안 됨 ✅ 실제 DB 연동 가능
Spring Context 크기 ⚡ 매우 가벼움 🐢 무거움
테스트 속도 🚀 빠름 🐌 느림

📌 단위 테스트를 원한다면?

  • @WebMvcTest를 유지하면서 서비스(Service)를 @MockBean으로 주입해서 사용해야 합니다.
728x90
반응형

'Blog > TIL' 카테고리의 다른 글

2025-01-22 (수)  (0) 2025.01.22
2025-01-21 (화)  (1) 2025.01.21
2025-01-16 (목)  (0) 2025.01.16
2025-01-15 (수)  (0) 2025.01.15
2025-01-14 (화)  (0) 2025.01.14

댓글