[우테코] 블랙잭 미션 피드

Yerin·2023년 5월 1일

우아한테크코스

목록 보기
9/10
post-thumbnail

진짜.. 블로그 글 왤케 밀린걸까..
바쁘다고는 생각하지만 그정도까지는 아닌거같은데 ...ㅎ
(열심히 삽시다 제발 ~)

블랙잭 미션은 앞의 미션과 비슷하게, 객체지향의 원칙을 적용하도록 요구했다.

블랙잭 미션 추가 요구사항

1단계

  • 일급 컬렉션 사용
  • 3개 이상의 인스턴스를 가진 클래스 금지
  • 모든 원시값과 문자열 포장

2단계

  • 1단계와 동일함

동반 객체와 mutable 객체

companion object {
        private val ALL_CARDS: MutableSet<Card> = CardNumber.values()
        	.flatMap { cardNumber ->
        		listOf(
            		Card(cardNumber, Shape.CLOVER),
            		Card(cardNumber, Shape.HEART),
            		Card(cardNumber, Shape.DIAMOND),
        	    	Card(cardNumber, Shape.SPADE)
        		)
    		}.toMutableSet()

    fun draw(): Card {
        val card = ALL_CARDS.random()
        ALL_CARDS.remove(card)

        return card
    }
}

변경 가능한 컬렉션을 static하게 선언해두는 것은 좋지 않습니다.
ALL_CARDS가 모두 remove되는 경우에 이 프로그램은 더 이상 정상적으로 동작하지 않을거에요.

모든 카드를 불변으로 캐시해두는 것과 그 카드들을 하나씩 draw 하는 기능을 별도로 분리해보세요.

진짜.. 왜 저렇게 했는지 기억이 안나지만.. companion object 내부에 변경 가능한 변수를 선언해두었다 ;; companion object

ALL_CARDS를 Set 컬렉션을 바꿔주고, 직접 remove() 하지 않도록 코드를 변경하였다. 추가적으로, CardPack 클래스를 생성하여, 그 내부에서 draw()를 통해 카드를 뽑도록 코드를 수정하였다.

private val ALL_CARDS: Set<Card> = CardNumber.values()
	.flatMap { cardNumber ->
        Shape.values().map {shape ->
            Card(cardNumber, shape)
        }
    }.toSet()

enum class와 ui 로직

enum class Shape(val description: String) {
    HEART("하트"),
    CLOVER("클로버"),
    SPADE("스페이드"),
    DIAMOND("다이아몬드")
}

UI 로직입니다. UI layer에서 데이터를 받아서 그리도록 만들어보세요.

이 미션에서 ui에 사용하는 문자열들을 죄다 enum 클래스에 적어두었었다... 그래서 같은 리뷰를 3개나 받음 ㅎㅎ..

enum class Shape {
    HEART, CLOVER, SPADE, DIAMOND
}

// ui
private fun makeShapeDescription(shape: Shape): String {
	return when (shape) {
        Shape.HEART -> HEART_DESCRIPTION
        Shape.DIAMOND -> DIAMOND_DESCRIPTION
        Shape.CLOVER -> CLOVER_DESCRIPTION
        Shape.SPADE -> SPADE_DESCRIPTION
    }
}

그래서 enum class를 다음과 같이 재정의하고, ui에서 string으로 바꿔주는 로직을 추가해주었다.


읽기 좋은 테스트

테스트 본문에는 테스트하고자 하는 것을 이해할 수 있는 정보를 모두 가지고 있어야 하고, 반대로 불필요한 내용을 전부 감추어야 합니다.

읽기 좋은 테스트의 조건에는 clarity(명확성), resilience(탄력성), completeness and conciseness(완전성과 간결성)이 있다.
리뷰어님은 완전성과 간결성에 대해 말씀하셨다.

❕ 테스트는 이해하는데 필요한 모든 정보가 포함됨과 동시에, 불필요한 정보들을 포함하지 않고 있을 때 완전하면서 간결해진다. 생성자에 포함된 많은 산만한 정보들은 메서드로 따로 분리하여 세부 정보를 숨겨야 완전하고 간결한 테스트를 작성할 수 있다.

자세한 예시는 너무 길어서 내가 작성한 프롤로그를 첨부한다 !
읽기 좋은 테스트


LinkedList

fun draw(): Card {
    val card = cards.shuffled()[0]
    cards.removeAt(0)

    if (cards.size == 0) {
        cards.addAll(Card.ALL_CARDS.toList())
    }

    return card
}

직접 0번째 카드를 가져오는 대신 LinkedList와 같이 이미 잘 만들어진 자료구조를 활용해보는건 어떨까요?

리뷰어님이 내가 뻘짓 하는 모습을 못참으셨는지 LinkedList를 사용하라고 직접 언급해주셨다..ㅎㅎ
왜 pop 같은 기능이 없을까 ~ 라고 생각했는데 이미 있었다는거 ㅎㅎ^^:;

fun draw(): Card {
    val card = cards.removeFirst()

    if (cards.size == 0) {
        cards.addAll(Card.ALL_CARDS)
        cards.shuffle()
    }
    
    return card
}

Linked List에 대해서는 컴공이라면, 학교에서 기본적으로 배웠을거라 생각하고 짧게 정리해본다.

메모리 공간에 값이 한 번에 나열되어 있는 Array와 달리,
Linked List는 각 요소 내에 다음 요소를 가리키는 Pointer가 존재하여 연결되는 구조이다. 즉, 메모리 공간에 모여있지 않고 산재되어질 수 있다(non-continuous). Kotlin에서는 Reference를 통해 서로를 연결한다.

연결리스트에서는 추가 및 삭제가 자유롭지만 대신 탐색은 느리다.
반면 배열에서는 탐색은 빠르지만, 추가 및 삭제는 메모리 이동이 필요하므로 느리다. 이를 이용해서 적절히 사용할 때를 잘 구분하자 ~.~



시간이 너무 지나서 작성하다보니 당연한게 된 리뷰들이 많아서.. 조금 많이 건너 뛰었다.. 리뷰 피드백은 바로바로 작성하도록 하자 🥲

우기랑.. 페어한것도 기억이 안난다.. ㅋㅋㅋㅋㅋㅋ 나름.. 내가 치근덕거리며 잘 지냈다고 생각했는데 ㅎ.. 정신 차리고 살자 ~

profile
𝙸 𝚐𝚘𝚝𝚝𝚊 𝚕𝚒𝚟𝚎 𝚖𝚢 𝚕𝚒𝚏𝚎 𝙽𝙾𝚆, 𝙽𝙾𝚃 𝚕𝚊𝚝𝚎𝚛 !

0개의 댓글