우아한테크코스 로또(자동) 미션에서는 TDD를 도입하여 페어프로그래밍을 하였습니다.
기존과 다르게 TDD 사이클을 기반으로, 3단계(실패하는 테스트 코드 작성 -> 가능한 빠르게 성공하는 테스트 코드 작성 -> 리팩터링)를 거치다 보니 시간이 촉박하였습니다.
하지만 1주차때와 달리, 테스트 코드를 우선적으로 작성하다 보니 가능한 많은 경우에 대해 생각해 볼 수 있었고, 도메인 모델에 기능을 추가할 때 더 높은 신뢰를 가질 수 있었습니다.
기능이 수정되어도 기존에 작성해놓은 테스트 코드를 통해 쉽게 검증할 수 있었기에, 테스트 코드를 믿고 과감하게 리팩터링 할 수 있었습니다.
또한, 코치님의 말씀대로 TDD를 적용하니, Green에서 Refactor 단계로 넘어가는 과정에서 어떠한 로직이 추가되어야 할 근거를 생각해 볼 수 있었습니다.
또한, 지난 미션과는 다르게 원시값을 래퍼클래스로 만들고 일급컬렉션을 적용하라는 요구사항이 추가되었습니다.
프리코스를 진행하면서 이 둘을 적용해보기는 했으나, 리뷰어님들의 피드백과 수업을 통해 이들의 필요성에 대해서 조금 더 깊게 공부하는 시간을 가질 수 있었습니다.
로또 미션 피드백
객체를 현실 세계의 사물처럼 표현하는 이유
객체지향 프로그래밍은 프로그램을 서로 상호작용하는 객체들의 집합이라고 바라보는 프로그래밍 패러다임입니다.
객체지향에서 왜 객체를 현실 세계의 사물처럼 표현할까요?
만약 요구사항이 변경되어, 로또 번호의 범위를 변경해야 한다면 여러분들은 어떤 클래스를 떠올릴 수 있을까요?
대부분의 사람들은 자연스럽게 Lotto 또는 LottoNumber 클래스를 떠올릴 것입니다.
이것이 객체지향에서 객체를 사물로 표현해야 하는 이유입니다.
프로그래밍은 사람이 하는 것이기 때문에 현실 세계와 대입하여 클래스를 설계하면 훨씬 이해하기 쉽다는 장점이 있습니다.
Happy case vs Exception case
테스트 코드를 작성할 때에는 정상적인 상황보다 예외 상황에 대해 더 고민해볼 필요가 있습니다.
일반적으로 프로그래밍을 할 때, 정상적인 상황을 기대하기 때문에 예외 상황을 놓치기 쉽기 때문입니다.
그렇기 때문에 TDD의 중요성을 깊이 생각해볼 수 있었습니다.
테스트 코드를 우선적으로 작성하다보니, 여러 상황에 대해 고민해 볼 수 있기 때문입니다.
초보자라면 객체지향 생활체조를 참고하자!
리팩터링을 할 때에는 객체지향 생활체조 원칙을 참고하는 것이 많은 도움이 됩니다.
객체지향 생활체조가 완전한 정답이라고 할 수는 없지만, 초보자인 우리가 봤을 때 객체지향을 이해하는 데에 많은 도움이 되기 때문입니다.
누군가 좋은 객체 지향 설계가 무엇이냐고 묻는다면, 다수의 코치들조차 추상적으로 말할 수밖에 없을 것입니다. (코치님의 말씀 인용)
따라서 객체지향 생활체조를 따라서 학습해보는 것이 OOP를 이해하는 데에 전반적으로 큰 도움이 될 것입니다.
위에서 언급했듯이, 모든 것에는 정답이 없기 때문에 잘 모른다면 일단 따라 해보고 자신만의 기준을 세우는 것이 중요합니다.
동등성 vs 동일성
동등성은 두 객체가 동일한 정보를 가지고 있음을 의미합니다.
동일성은 두 객체가 온전히 동일함을 의미합니다.
다시 말해, 객체가 가지고 있는 값이 같다면, 두 객체는 동등하다고 표현할 수 있습니다.
반면, 두 객체가 같은 메모리 주소를 가지고 있다면, 두 객체는 동일하다고 할 수 있습니다.
class Person(
private val name: String,
private val age: Int
)
// 동등성
val man1 = Person("man", 25)
val man2 = Person("man", 25)
// 동일성
val man1 = Person("man", 25)
val man2 = man1
조금 더 쉽게 이해하기 위해 하나의 예시를 들자면,
현실 세계에서도, 쌍둥이의 나이와 키가 같다면 너희 둘은 동등해! 라고 표현합니다. 하지만, 이 둘이 동일하지는 않습니다.
Kotlin(또는 Java)에서 동일성의 예시를 가장 잘 표현할 수 있는 것은 VO(Value Object)입니다.
VO는 레이어간 데이터를 전달하기 위한 DTO와 달리, 프로퍼티가 불변이라는 특징을 가지고 있습니다.
또한, 이름의 의미 그대로 값에 대한 객체이기 때문에, 인스턴스명이 달라도 내부 프로퍼티의 값이 같다면 모두 동일한 객체라고 할 수 있습니다.
따라서, VO 클래스는 equals()와 hashcode()를 Override 하여 == 연산자 호출 시, 프로퍼티가 같다면 True를 반환하도록 해주어야 합니다.
Kotlin에서는 data class 또는 value class 키워드를 이를 쉽게 구현할 수 있습니다.
더 이상 클래스를 붕어빵 틀로 생각하지 마라!
Java를 처음 접할 때 클래스(Class)는 객체를 만들어내는 틀이고, 객체(Object)는 Class라는 틀을 통해 만들어낸 하나의 데이터라고 배웠을 것입니다.
하지만 더 이상 클래스를 단순히 객체를 생성하는 틀로 보지 말아야 합니다.
class LottoNumber(private val value: Int)
클래스는 객체의 팩토리(factory)이며, 필요할 때 객체를 만들고, 추적하며, 적절한 시점에 파괴합니다.
클래스는 객체를 생성하며, 일반적으로 클래스가 객체를 인스턴스화(instantiate)한다고 표현합니다.
종종 클래스를 객체의 템플릿이라고 보지만 '객체의 능동적인 관리자'로 생각해야 합니다.
클래스는 객체를 보관하고 필요할 때 꺼낼 수 있어야 하며 더 이상 필요하지 않을 때에는 객체를 반환할 수 있는 저장소(storage unit) 또는 웨어하우스(warehouse)로 바라봐야 합니다.
극단적으로 생각해 보았을 때, 객체가 동등하지만, 10,000개를 인스턴스화한다고 가정해 보겠습니다.
물론, 필요 없는 객체는 JVM의 GC(가비지컬렉터)가 알아서 정리해 주겠지만, 10,000개의 인스턴스를 모두 사용하고 있어 정리하지 못한다면 어떻게 될까요?
우리의 메모리 공간은 한정적이기 때문에 성능이 좋지 않은 컴퓨터에서는 OOM(Out of Memory)가 발생할 가능성이 높습니다.
또한, 값이 모두 같은데 굳이 10,000개를 생성해 줄 필요가 있을까요?
class LottoNumber private constructor(private val value: Int) {
companion object {
private const val MINIMUM_NUMBER = 1
private const val MAXIMUM_NUMBER = 45
private val NUMBERS: Map<Int, LottoNumber> = (MINIMUM_NUMBER..MAXIMUM_NUMBER).associateWith(::LottoNumber)
fun from(value: Int): LottoNumber {
return NUMBERS[value] ?: throw IllegalArgumentException()
}
}
}
위 코드는 LottoNumber 클래스가 NUMBERS라는 Map으로 객체를 미리 캐싱하고 있습니다.
그리고 from() 메서드를 통해, 필요한 시점에 객체를 가져올 수 있도록 구현되어 있습니다.
같은 숫자에 대해서 하나의 LottoNumber 객체를 공유하고 있기 때문에 더 이상 메모리 공간을 낭비하지 않아도 됩니다.
또한 같은 숫자에 대해 동일한 메모리의 객체를 공유하기 때문에, 동일성 비교 시 True를 반환할 것입니다.
즉, LottoNumber 클래스가 단순히 객체를 생성하는 것을 넘어서서 객체의 관리와 필요한 시점에 반환하는 역할을 하고 있습니다.
하지만 이 방식이 매번 좋은 것은 아닙니다.
LottoNumber처럼 생성해야 하는 객체의 수가 45개로 고정되어 있고 값이 불변인 경우에는 위와 같은 방식(캐싱)으로 구현하면 메모리 절약이라는 긍정적인 효과를 볼 수 있지만, 일반적으로 같은 주소값의 객체를 공유하는 것은 매우 위험할 수 있습니다.
의도치 않게 객체의 값이 변할 수 있기 때문입니다.
(하지만, 위 LottoNumber와 같이 프로퍼티가 불변을 띄고 있을 경우에는 문제가 없을 가능성이 크다.)
'대외활동 > 우아한테크코스' 카테고리의 다른 글
[우아한테크코스] 원시값 포장과 일급컬렉션은 무엇이고, 어디까지 감싸야 할까? (1) | 2023.02.27 |
---|---|
[우아한테크코스] 2주차 - 점진적 리팩터링(Incremental Refactoring) (5) | 2023.02.24 |
[우아한테크코스] 2주차 - TDD(Test Driven Development) (1) | 2023.02.15 |
[우아한테크코스 5기] 1주차 회고 - (양념 반 후라이드 반) 설렘 반 걱정 반 (6) | 2023.02.13 |
[우아한테크코스 5기] 프리코스 4주차 회고 - Kotlin 안드로이드 (2) | 2022.11.23 |