[우아한테크코스] 3주차 회고
3주차에는 로또 미션을 마무리하는 시기를 가졌습니다.
이번 미션에서 중점적으로 배웠던 내용은 다음과 같습니다.
가변인자
인자의 갯수가 정해져 있지 않은 가변인자
fun sum(vararg numbers: Int): Int {
var total = 0
for (number in numbers) {
total += number
}
return total
}
fun main() {
val result1 = sum(1, 2, 3, 4, 5)
val result2 = sum(10, 20, 30)
println("첫 번째 결과: $result1")
println("두 번째 결과: $result2")
}
고차함수 인자로 넘기는 ::
numbers.map { LottoNumber(this) } 대신에
numbers.map(::LottoNumber) 가능하다.
타입이 맞는 경우 이렇게 간단한 방법으로 생성자 또는 함수를 호출할 수 있다.
Delegate 패턴
deletgate 패턴 (일급컬렉션)
이를 대신하여 확장함수를 활용하면 더 유연하게 사용할 수 있다 (오버 엔지니어링, 지정된 것만 생성하는 등)
객체 설계
내가 만약 특정 객체의 프로퍼티를 꺼내서 쓴다면, 그 함수는 해당 클래스에 들어가야 할 수도 있다. 현실과 객체지향을 무조건 매칭시킬 필요는 없다.
오류를 던진다는 것
오류를 던진다는 것은 분명히 필요할지 모르지만, 오류를 던졌을 때 그 오류를 미처 잡아내지 못했다면 앱은 크래시됩니다. 따라서 상황에 따라서 예외를 던지는 대신 Kotlin의 nullable을 활용하거나 nullable을 통해서는 오류 메세지를 알 수 없으므로, sealed class를 활용해 볼 수 있습니다.
리스트를 간단하게 선언하는 것
private fun generateRandomLotteries(): List<Lottery> {
val lotteries = mutableListOf<Lottery>()
repeat(lotteryPurchasePattern.autoLottoCount) {
lotteries.add(Lottery(RandomLotteryGenerator.generateNumbers()))
}
return lotteries
}
이 방법 대신 아래 방법을 생각해낼 수 있습니다.
private fun generateRandomLotteries() = List(lotteryPurchasePattern.autoLottoCount) {
Lottery(RandomLotteryGenerator.generateNumbers())
}
데이터 클래스와 클래스
데이터 클래스는 언제 사용하는게 의미론적으로 와닿을까? 개인적으로 저는 '이 클래스가 단순 데이터만을 보관하고 전달하는데에만 사용되는지' 를 여부로 데이터 클래스를 정의합니다. 반대하실 분들도 계시겠지만 이에 더해 DTO를 활용할때 주로 사용합니다.
원래는 아래 클래스를 class로 정의했습니다. 이유는 이 클래스에서는 유효성 검증까지 진행하고 있다고 생각했기 때문입니다. 하지만 이는 클래스의 객체가 생성되기 전의 로직이지, 생성된 뒤에는 어떠한 유의미한 로직도 갖고 있지 않기 때문에 data class로 선언하는 관점에 대해서 배우게 되었습니다.
data class LotteryPurchasePattern(val manualLottoCount: Int, val autoLottoCount: Int) {
companion object {
private const val LOTTERY_TICKET_PRICE = 1000
const val EXCEPTION_IS_NOT_NUMBER = "숫자만 입력하셔야 합니다"
const val EXCEPTION_INSUFFICIENT_FUNDS = "금액보다 구매하려는 갯수가 더 많습니다"
const val EXCEPTION_IS_NOT_POSITIVE = "0 이상의 수를 입력하셔야 합니다"
fun ofManual(
amount: Amount,
manualInput: String,
): LotteryPurchasePattern {
val availableTicketCount = amount.money / LOTTERY_TICKET_PRICE
return manualInput.toInt().validatePositive().validateFund(amount.money).run {
LotteryPurchasePattern(this, availableTicketCount - this)
}
}
private fun String.toInt(): Int {
return this.toIntOrNull() ?: throw IllegalArgumentException(EXCEPTION_IS_NOT_NUMBER)
}
private fun Int.validatePositive(): Int {
require(this >= 0) { EXCEPTION_IS_NOT_POSITIVE }
return this
}
private fun Int.validateFund(money: Int): Int {
require(money / LOTTERY_TICKET_PRICE >= this) { EXCEPTION_INSUFFICIENT_FUNDS }
return this
}
}
}