우아한테크코스 6기

우아한테크코스 6기(모바일 앱) 프리코스 2주차 공부 기록

Alsong 2023. 11. 6. 19:22

1주차가 끝나고 2주차가 시작되었습니다! 뭔가 1주일이 느리게 흘러간 것 같은 느낌이었어요. 1주일 동안 정말 빽빽히 공부를 했기 때문인 건가? ㅎㅎ

 

운영국에서 10월 26일 목요일 3시에 2주차 미션을 메일로 보내주셨습니다. 공통 피드백이 들어있어서 잽싸게 읽어보았습니다. 그중 중요하다고 생각되는 것들만 정리해보았어요! (다른 내용들은 이미 실천 중이거나 무슨 말인지 이해가 안 되거나..)

 

1. 커밋 메시지를 의미 있게 작성한다

커밋 메시지에 해당 커밋에서 작업한 내용에 대한 이해가 가능하도록 작성한다.

 

2. 이름을 통해 의도를 드러낸다

나 자신, 다른 개발자와의 소통을 위해 가장 중요한 활동 중의 하나가 좋은 이름 짓기이다. 변수 이름, 함수(메서드) 이름, 클래스 이름을 짓는데 시간을 투자하라. 이름을 통해 변수의 역할, 함수의 역할, 클래스의 역할에 대한 의도를 드러내기 위해 노력하라. 연속된 숫자를 덧붙이거나(a1, a2, ..., aN), 불용어(Info, Data, a, an, the)를 추가하는 방식은 적절하지 못하다.

 

3. 축약하지 않는다

의도를 드러낼 수 있다면 이름이 길어져도 괜찮다.

 

4. 의미 없는 주석을 달지 않는다

변수 이름, 함수(메서드) 이름을 통해 어떤 의도인지가 드러난다면 굳이 주석을 달지 않는다. 모든 변수와 함수에 주석을 달기보다 가능하면 이름을 통해 의도를 드러내고, 의도를 드러내기 힘든 경우 주석을 다는 연습을 한다.

 

역시 지금까지 개판으로 코딩했던 습관을 고치기가 매우 어려운 것 같습니다.. 3번의 경우에는 변수명이 너무 길면 타이핑을 치기가 불편해서 짧은 변수명을 쓰곤 했는데(특히 알고리즘 문제 풀 때) 사실 요즘 IDE들은 자동완성 기능이 다 잘 되어 있어서 크게 문제 될 것은 없는 것 같기도 하네요. 아무튼 클린 코딩을 위해서라면 지금부터라도 버릇을 뜯어고쳐야겠죠..!

 

그리고 2주차가 끝났으니 저번과 같이 공부한 점과 아쉬웠던 점을 포스팅으로 남기려 합니다!

 

 

1. 추가된 요구사항

이번 프로그래밍 요구 사항에서는 1주차에는 없었던 3개의 요구 사항이 추가가 되었습니다.

 

  • indent(인덴트, 들여 쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
    • 테스트 도구 사용법이 익숙하지 않다면 'test/kotlin/study'를 참고하여 학습한 후 테스트를 구현한다.

 

첫 번째와 두 번째 요구 사항은 물론 코딩 컨벤션에 나와 있던 내용입니다. 하지만 1주차에서는 크게 터치하지 않았다가, 이번 주차 부터는 규칙을 명시해 주셨네요! 저도 1주차에서는 인덴트 규칙을 지켰지만 이번 주차에서는 2번 규칙도 지킬 수 있도록 해야겠어요. 그런데 3번 테스트 코드는 처음엔 이게 뭔지 몰랐는데, 아래에서 자세하게 적도록 하겠습니다.

 

2. 테스트 코드

사실 저는 테스트 코드가 무엇인지 전혀 몰랐습니다. 테스트 코드라는 단어를 저는 처음에 이렇게 생각했어요. "테스트 코드? 테스트 하는 코드인가? 'println()' 함수 써서 제대로 출력되는지 테스트하는 그런 거 말하는 건가?" ㅋㅋㅋ..

 

커뮤니티에서도 자주 테스트 코드라는 단어가 눈에 보였는데, 이미 지금까지 테스트코드가 무엇인지 알고 작성을 하고 계시다는 것에 놀랍고 대단하시다고 느꼈네요..ㅠㅠ

 

테스트 코드가 중요하다는 글을 많이 보았기 때문에 저도 테스트 코드를 작성해야겠다는 마음에, 일단 테스트 코드가 무엇인지에 대해서부터 알아보았습니다. 테스트 코드란? 내가 작성한 메서드가 실제로 작동하는지를 테스트하는 코드입니다. 이것은 단순히 프로젝트를 build해서 테스트하는 것이 아니라 모듈 단위로 단위 테스트를 하는 것을 의미합니다. 물론 테스트 코드에는 단위 테스트 이외에도 통합 테스트, UI 테스트 등이 존재하는 듯합니다만, 일반적으로 테스트 코드를 작성하는 이유는 단위 테스트를 하기 위함인 듯하더군요. 테스트 코드가 없다면 메서드가 제대로 동작하는지 보기 위해서 전체 프로젝트를 build해야 하는데, 이렇게 한다면 작은 함수 하나 테스트 하는 데에도 상당히 많은 시간이 소요되겠죠!!

 

이외에도 테스트 코드가 필요한 이유로 제가 느낀 것인데, 특정 랜덤 값에 대해 메서드가 제대로 작동하는지를 확인할 수 있다는 점입니다. 예를 들어 어떤 변수에 매우 낮은 확률로 0이라는 값이 들어가도록 프로그램했는데, 0이 들어갔을 때 제대로 작동하는지를 테스트하려고 프로젝트를 일일이 build해서 0이 나오기를 기다린다면 너무 힘들겠죠? 이럴 때 랜덤변수에 직접 0을 집어넣어 테스트할 수 있다는 것입니다.

 

요구사항에 적혀있던 JUnit5와 AssertJ는 또 무엇일까요? 한 번 알아보았습니다.

JUnit5란? 자바 개발자가 많이 사용하는 테스팅 프레임워크를 말합니다.  JUnit Platform과 JUnit  Jupiter, Junit Vintage가 결합되어 있습니다.

JUnit Platform: 테스트를 실행해 주는 런처와 TestEngine API를 제공함.
Jupiter: TestEngine API 구현체로 JUnit5에서 제공함.
Vintage: TestEngine API 구현체로 JUnit3, 4에서 제공함.

솔직히 잘 모르겠기 때문에 자바에서 사용하는 테스트 프레임워크다. 이 정도만 알고 넘어가야겠습니다. 허허..

 

AssertJ란? 많은 assertion을 제공하는 자바 라이브러리이다.

이것 또한 잘 모르겠기는 마찬가지군요 흑.. 어써션(Assertion)에 대한 개념도 아직 제대로 잡히지가 않습니다.

 

그런데 제가 뭐 처음부터 확실히 이해하고 넘어가는 개념이 몇이나 있었습니까.. 이것 또한 얕게라도 이해하고 넘어가고, 나중에 확실히 하도록 하겠습니다.

 

아무튼 이런 도구들을 사용하면 단위 테스트가 가능하다!!라는 것 같습니다. 그런데 "테스트 코드 그래서 그거 어떻게 하는 건데?"라는 물음은 생길 수밖에 없었죠.. 우테코 측에서도 저 같은 사람이 많을 거라고 생각하셨는지, 학습용 테스트 코드를 친절하게도 제공해 주셨더군요!

// StringTest.kt

package study

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class StringTest {
    @Test
    fun `split 메서드로 주어진 값을 구분`() {
        val input = "1,2"
        val result = input.split(",")
        assertThat(result).contains("2", "1")
        assertThat(result).containsExactly("1", "2")
    }

    @Test
    fun `split 메서드 사용시 구분자가 포함되지 않은 경우 값을 그대로 반환`() {
        val input = "1"
        val result = input.split(",")
        assertThat(result).contains("1")
    }
    
    // 중간 생략...
    
}

테스트 코드를 작성하려면 아까 JUnit과 AssertJ라는 도구가 필요했으니, 이를 import 해 준 것이겠군요! 그리고 @Test라는 이상한 녀석의 바로 밑에 테스트하는 함수를 정의하면 저 함수들이 돌아가면서 테스트를 수행하게 되는 것 같습니다. 솔직히 아직은 테스트 코드 작성에 대해 익숙하지 않은 것이 사실이니, 우테코에서 제공한 테스트 코드를 바탕으로 테스트 케이스를 몇 개 추가하는 정도로만 테스트 코드를 작성해 보았습니다. 언젠가는 저도 테스트 코드를 자유자재로 활용할 수 있으면 좋겠습니다!

// MyTest.kt

package racingcar

import camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeTest
import camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest
import camp.nextstep.edu.missionutils.test.NsTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class MyTest : NsTest() {
    @Test
    fun `전진 정지`() {
        assertRandomNumberInRangeTest(
            {
                run("Al,Song", "3")
                assertThat(output()).contains("Al : ---", "Song : -", "최종 우승자 : Al")
            },
            MOVING_FORWARD, STOP, MOVING_FORWARD, STOP, MOVING_FORWARD, MOVING_FORWARD
        )
    }

    @Test
    fun `이름에 대한 예외 처리`() {
        assertSimpleTest {
            assertThrows<IllegalArgumentException> { runException("asas   ,AllSSSong", "1") }
        }
    }

    public override fun runMain() {
        main()
    }

    companion object {
        private const val MOVING_FORWARD = 4
        private const val STOP = 3
    }
}

 

 

3. 맵

코틀린에는 Map이라는 기능이 있었습니다. 이는 파이썬의 Dictionary와 유사한 자료구조였습니다. 맵이란 keyvalue를 쌍으로 저장하는 자료구조입니다.

# Python Dictionary

testDictionary = {'A' : 1, 'B' : 3}
# {'A' : 1, 'B' : 3}
// Kotlin Map

val testMap: MutableMap<String, Int> = mutableMapOf("A" to 1, "B" to 3)
// {A=1, B=3}

파이썬에서는 쌍을 의미하는 문자가 : 였는데, 코틀린에서는 맵을 정의할 때에는 to를, 출력할 때에는 =를 쓰는군요. 차이점을 꼭 숙지해야겠습니다.

 

이번 미션에서 저는 입력받은 자동차들이 나아간 거리와 진척도를 나타내는 도표를 맵을 이용 해 구현하면 좋겠다고 생각했습니다. {"al"=3, "song"=5}와 같은 식으로 말이죠. 그래서 이번 기회에 맵을 공부 해 두기로 했습니다.

 

  • 파이썬과 마찬가지로 testMap["Al"] = 5와 같은 식으로 키와 밸류를 추가할 수 있다.
val testMap: MutableMap<String, Int> = mutableMapOf("A" to 1, "B" to 3)
testMap["C"] = 5
// {A=1, B=3, C=5}
  • 리스트와 마찬가지로 UnMutable로 정의하면 수정할 수 없다.

 

 

4. 커밋메시지

이번 주차에서 가장 아쉬웠던 것입니다.. 바로 제가 커밋메시지를 너무 중구남방으로 쓴다는 점이었습니다..

다시봐도 지저분한 나의 커밋 메시지..

저는 지금까지 Git을 자주 쓰지 않았던지라 이게 문제인지조차 인지도 못하고 있었습니다. 이번 주차가 끝나고 다른 분들의 코드를 참고할 겸 들어갔다가 다들 커밋 메시지가 너무 깔끔하고 정돈되어 있어서 굉장히 놀랐습니다..! 특히 대부분의 분들의 커밋 메시지가 다음과 같은 것을 확인했는데요,

이런 식으로 기능 구현이라면 feat, 수정이라면 fix 등등.. 앞에 어떤 유형의 커밋인지를 꼭 명시를 해준다는 것이었습니다. 너무 보기 좋고 Git을 협업에 이용할 때에도 알아보기 편한 방법이라고 생각해 다음 미션부터는 꼭 저도 이렇게 커밋 메시지를 작성해야겠습니다!

 

 

5. 기능구현 목록 미리 작성하기

이 또한 아쉬운 부분이었네요.. 1주차 때 마음이 급해 기능구현 목록 README를 미리 작성하지 않고 기능구현부터 하고 "아 맞다!! README 먼저 쓰라했지.." 했었는데 이번에도 또 까먹고 기능구현부터 해버렸습니다.. 정말 바보군요

 

이는 지금까지 미리 계획을 세우지 않고 일단 짜고 보는 저의 습관이 또 말썽을 일으킨 것이었습니다. 정말 이번 우테코 프리코스를 하면서 잘못들인 습관을 정말 많이 고치게 되네요..

 

 

6. 예기치 못한 오류

IDEA에서 정상 동작하고 테스트케이스도 다 통과했는데, 제출하니까 "예기치 못한 오류로 인하여 실행에 실패하였습니다."라는 메시지가 나왔습니다. 당황스러워서 커뮤니티를 확인했는데 다른 분들도 같은 문제를 겪고 계신 분들이 많더라고요..? 그래서 처음엔 그냥 우테코 서버 문제겠지~ 하고 넘겼고 실제로 코드 수정 안 했는데 문제가 해결되었다고 하신 분도 계셔서, 자고 일어나면 알아서 해결되어 있을 줄 알고 그냥 넘겼습니다.

 

그런데 자고 일어나서 다음날 제출날이 되었는데도 아직 같은 오류가 뜨는 게 아닙니까?? 기한이 하루밖에 안 남아서 당황하기도 했지만 다른 분들도 해결 안 되신 분들이 많아서 뭔가 연대감(?)도 느껴지고.. ㅋㅋㅋ '나만 잘못한 게 아니다!!'라고 생각이 들면서 오히려 마음이 편해지고 차분하게 문제를 해결할 수 있었네욬.. ㅋㅋㅋ

 

아무튼 문제를 해결하기 위해 코드를 다 초기화해버린 후 제가 짠 클래스와 함수를 하나씩 추가하고 주석처리 하면서 어느 부분에서 오류가 발생하는 건지 수사망을 좁혀나갔습니다. (하나 수정하고 커밋 푸시하고 테스트 돌리고.. 또 하나 수정하고 커밋 푸시하고 반복.. 생노가다) 

 

그래서 결국 원인을 찾았는데, 요 녀석이 문제였습니다.

for (dummy in 0..< numberOfTryStr.toInt()) {
	...
}

for문 안에서 range를 사용하기 위해 0..< numberOfTryStr.toInt()를 썼는데(numberOfTryStr은 제가 만든 변수), 아마 range를 사용할 때에는 부등호를 사용하면 안 되는 것 같습니다! 도대체 왜지..? IDE상에서는 전혀 문제가 없고 문법 문제도 아닌 듯한데, 아마 우테코 서버 상에서는 이를 오류로 받아들이는 것 같습니다(정확하진 않아요 뇌피셜임). 

 

아무튼 그래서 다음과 같이 바꿔주었습니다.

for (dummy in 0..numberOfTryStr.toInt()-1) {
    ...
}

심지어 이렇게 코딩했을 때 IDEA에서 자체적으로 회색 밑줄 띄우면서 오류 나는 코드로 리팩토링 해주던데.. 과연 오류가 맞나 하는 생각이 듭니다.

 

그 외에 알아낸 점이, 커밋&푸시 이후에 지원 탭에서 바로 테스트를 실행하면 안 되고 조금 시간차를 뒀다가 실행해야 하는 것 같아요. 바로 실행하면 우테코 서버에 반영이 조금 늦게 되는 건지 이전 버전이 실행되는 것 같습니다.

 

 

7. 느낀 점

유용한 기능을 많이 학습할 수 있어서 역시 보람찬 미션이었다고 생각합니다. 특히 가장 크게 얻어갔던 것은 테스트 코드와 커밋메시지였던 것 같네요. 또 마구잡이로 코딩하던 기존의 습관을 조금씩 뜯어고치고 있는 것 같아서 나름 기분이 좋습니다. 4주차까지 꼭 미션을 완수할 수 있도록 노력해야겠어요!!

https://github.com/woowacourse-precourse/kotlin-racingcar-6/pull/39

 

[자동차 경주] 송제욱 미션 제출합니다. by songpink · Pull Request #39 · woowacourse-precourse/kotlin-racingcar-6

구현한 기능 레이싱카 게임을 생성하는 클래스 RacingCarGame을 정의한다. (추가) 자동차의 이름들과 시도횟수를 입력받는 클래스 ReadCar를 정의해 이 클래스를 통해 입력받는다. 입력받은 자동차 이

github.com

괜찮다면 코드 리뷰도..! 해주시면 정말 감사하겠습니다..! (제가 먼저 하고 싶기는 한데 제 실력이 워낙 형편없다 보니 제가 먼저 했다가 오히려 악영향을 끼치게 될까 봐 두려워요.. 그래도 먼저 해주시면 최대한 노력해서 반사 가보도록 하겠습니다..!)