[2주차] 로또 과제 강의를 듣고나서

1. TDD 까지의 단계

  1. 학습 테스트

    1. 새로운 라이브러리나 개념을 익힐 때, 꾸준히 사용한다.

    2. 테스트 코드를 통해 라이브러리나 개념의 사용법을 이해한다.

  2. 단위 테스트

    1. 내가 구현한 프로덕션 코드에 대한 단위 테스트를 작성한다.

  3. TDD

    1. 프로덕션 코드보다 테스트코드를 먼저 작성한다.

    2. 프로덕션 코드 없이 테스트부터 작성해야하기 때문에 가장 어려운 단계이다.

2. TDD 진행하는 방법

  1. 기능 목록 작성하기

  2. 기능 목록을 커밋 단위로 삼고 TDD 진행하기

  3. 만약에 코드를 작성하는 중간에 새로운 기능목록이나 개선사항이 떠오르면, 바로 코드를 구현하지 않고, 기능목록에 추가한다.

3. TDD 로 구현할 기능 찾기

  • 구현 중간 부분을 자르는 연습을 해야한다.

  • 즉, 프로그램이 실행되는 특정 시점의 상태값으로 시작한다는 것을 의미한다.

  • 예를들면, 1주차의 자동차 경주 과제를 보면,

    • 자동차 경주 게임을 완료한 상태값을 테스트 코드에서 지정할 수 있어야 한다.

    • 자동차 경주를 완료한 상태를 move() 매소드를 호출하여 맞추는 것이 아니라, 한번에 테스트 데이터로 세팅하여 결과값을 만들어낼 수 있어야 한다는 것!

4. 로또에서 TDD 로 구현할 기능을 찾기

  1. 로또 구매 금액을 전달하면, 구매할 수 있는 로또의 장수를 반환한다.

  2. 구매할 로또의 장 수만큼 자동 구매할 경우, 자동 로또 생성해 반환한다.

  3. 구매한 한 장의 로또 번호와 당첨번호를 넣으면 당첨 결과를 반환한다.

  4. 구매한 전체 로또의 당첨 결과를 입력하면, 당첨금 총액을 반환한다.

  5. 당첨 금액과 구매 금액을 넣으면 수익률을 반환한다.

5. 로또 강의를 들으면서 다시 한 번 기억할 것들

  • 일급 컬렉션을 쓰고, 래퍼 클래스로 원시값을 자꾸 감싸다보면, 클래스 vs. 클래스 내부의 값들을 비교해야할 상황이 닥쳤을 때, getter 를 통해서 원시값을 비교하고자 하는 욕심이 생기게 된다. 이때, 원시값을 getter 를 통해서 비교하려고 하지 말고, 객체에게 값을 전달하고 메시지를 보내, 해당 객체 내부에서 비교하고 로직을 짤 수 있도록 한다.

    • lottery 값을 당첨번호 winning lottery 와 비교할 때, lottery numbers 를 순회하면서 winning lotto 가 해당 값을 가지고 있는지 비교하는 방식으로 코드를 짤 수 있다.

  • 하드 코딩을 방지하기 위해서 상수값이 너무 많이 쓰인다면, 관련된 값들을 묶어서 enum class 로 만들 생각을 해봐야 한다.

6. 로또 강의를 통해 새롭게 깨닫게 된 것들

6-1. 자바의 컬렉션 적재적소에 잘 이용한다

  • 자바의 컬렉션을 적재적소에 잘 이용해보자. 중복값이 허용되지 않는다면 리스트 형태가 아니라 set 타입으로 변수를 가지고 있을 수 있다.

    • getter 를 통해 정렬된 상태로 반환해야한다면, 그때 변환하는 방법도 있다.

6-2. private method 에 대한 테스트가 고민되면, 별도의 클래스로 쪼개본다

  • 리펙토링 과정에서 메소드를 자꾸 쪼개다보면, private method 가 생기게 된다. 이때, 해당 메소드를 테스트 코드로 짜야할지 고민이 된다면, 해당 메소드가 그 클래스에 위치하는 것이 적절한지를 고민해본다.

  • 클래스를 분리하면 public 으로 객체가 오픈되고, 자연스럽게 테스트할 수 있는 코드가 된다.

  • 덩달아서 해당 메소드와 연관된 유효성 검사 및 테스트도 해당 클래스 내부로 이동하게 되어 하나의 객체에 대해 좀 더 꼼꼼한 테스트가 가능해진다.

6-3. 점진적인 리펙토링을 한다. 과도기가 필요하다.

  • 안전한 리펙토링을 위한 과도기가 필요하다

  • 안전하게 리펙토링하려면 임시적으로 중복된 메소드를 하나 더 추가하고, 기존의 메소드가 쓰이지 않도록 만들면서 점차 없애나간다.

  • 매소드 변경시

    • 변경하려는 메소드를 완전히 복사해 새로운 매소드를 만든다. match() 메소드라면 match2() 를 만든다.

    • match() 코드를 사용하는 모든 곳에서 match2() 를 사용하도록 변경한다.

    • match() 가 더이상 쓰이지 않는다면, 삭제한다.

    • match2를 match() 로 바꾼다.

  • 인스턴스 변수의 타입 변경시 (= 데이터 베이스의 데이터를 변경할 때에도 똑같이 적용된다)

    • String 타입의 변수를 Integer 타입으로 변경하려고 한다.

    • 총 2번의 배포가 필요하다.

    • 첫번째 배포시에는 String type, Integer type 두개의 데이터를 동시에 쌓는다.

    • 그리고 두번째 배포시에는 String type 을 제거한 Integer type 변수만 남긴채 배포한다.

    • 점차 string type 이 쓰이지 않게 되면, 삭제한다.

  • 단계적으로 접근한다.

    • 1단계 : 매소드를 다른 클래스로 옮기는 경우

    • 2단계 : 매소드의 인자 타입이나, 수, 반환깂이 변경되는 경우

    • 3단계 : 인스턴스 변수(필드)의 타입이 변경되는 경우

  • 점진적으로 바꾸면서 테스트 코드를 중간 중간에 계속 실행한다. 변경에 대한 피드백을 받는 주기를 최대한 빠르게 하는 것이다.

    • rank() -> Rank

    • match() -> match(), match2() -> match() 삭제 -> match2() match로 rename

6-4. 매소드(함수)에 전달될 인자(매개변수)의 개수는 최대 2개이다.

  • 클린코드 책에 따르면, 매소드(함수)에 전달될 인자의 개수 기준은 다음과 같다.

    • 0개는 최상, 1-2개는 보통이다.

    • 3개는 가능하면 피하면 좋다.

    • 4개 이상은 어떠한 경우에서라도 피해야한다.

  • 그렇다면 어떻게 인자의 개수를 줄일 수 있을까?

    • 전달되는 인자를 하나의 클래스로 묶어서 전달하면 3개의 인자도 1개처럼 될 수 있다. 매개변수로 같이 전달되는 데이터들은 라이프사이클을 함께할 가능성이 높다.

6-5. 일급 컬렉션을 만들 때는 상속보다는 조합의 방법으로 접근한다.

  • 일급 컬렉션을 만들 때, is-a 관계인 상속의 방법, has-a 관계인 조합의 방법, 2가지로 구현이 가능하다.

  • 상속방법을 이용하면, 재사용성이 높고, 조합 방법을 사용하면, 유연성이 높아진다.

  • TDD에서는 코드의 변경에 영향을 최소화하고 변경에 빠르게 대응하는 것이 훨씬 중요하므로 대부분의 경우에 유연성을 높이는 조합의 방법으로 구현을 먼저 시도해보자.

6-6. primitive type 을 클래스로 감쌀 때, 생성자를 통해 원시값을 받아 클래스 내부에서 변환해주는 방법을 고민해본다.

  • 내부 데이터들이 primitive type 에서 클래스 타입으로 점차 변경될 때, 덩달아서 테스트 코드를 짜기가 어려운 경우가 생긴다. 이때는 생성자에서는 원시값을 전달받아 내부에서 래퍼 클래스 타입으로 변경시키는 방법을 고려해보면 좋다.

  • 테스트를 하기가 어렵다면 이게 정말 맞는 방법인지 고민해본다.

  • 생성자를 하나 더 추가해보는 것도 좋은 방법이다.

6-7. 어느정도 범위가 정해진 원시 포장값은 미리 만들어둔다.

  • 로또의 경우, LottoNumber 클래스가 갖게 되는 수의 범위는 결국 1- 45 사이의 45개일 것이다.

  • 그런데, 계속 primitive type 사용을 막기 위해서 새로운 인스턴스를 생성하는 것은 값이 중복된 객체를 과도하게 많이 생성하여 성능상 이슈를 만들 수 있다.

  • 이때, 미리 static method 로 1-45 사이의 45개 객체를 만들어두고, 정적 펙토리 매소드를 통해서 생성된 객체를 가져오는 방식으로 인스턴스 캐싱을 적용할 수 있다.

6-8. 처음부터 너무 성능만 고려하지 않는다.

  • 지금 시대는 성능상 이슈보다, 변경에 의한 코드의 유지보수에 대한 비용이 더 큰 시대이다.

  • 너무 처음부터 성능을 고려하느라 유지보수 하기 어려운 설계를 하고 코드를 작성하지 말고, 클린 코드를 짜는 것을 우선시하고, 성능에 이슈가 생겼을 때, 이를 구조를 개선하고, 방법을 찾아보는 것도 나쁘지 않다.

Last updated