본문 바로가기

부트캠프/우아한테크코스

우아한테크코스 회고 - 사다리 타기

반응형

페어 미션 (사다리 타기)

 

미션 키포인트 - TDD

 

  • 프로덕션 코드(Production Code) 
    프로그램 구현을 담당하는 부분으로 사용자가 실제로 사용하는 소스 코드
  • 테스트 코드(test code)
    프로덕션 코드가 정상적으로 동작하는지를 확인하는 코드

 

TDD란?

  • Test Driven Development(테스트 주도 개발, TDD)
    TDD = TFD(Test First Development) + 리팩토링
TDD란 프로그래밍 의사결정과 피드백 사이의 간극을 의식하고 이를 제어하는 기술이다.
- 켄트벡, Test Driven Development by Example 중
TDD의 아이러니 중 하나는 테스트 기술이 아니라는 점이다. TDD는 분석 기술이며, 설계 기술이기도 하다.
- 켄트벡, Test Driven Development by Example 중

 

 


 

배운 점

 

페어와 많은 이야기를 하면서 다양한 지식을 얻었지만, 여전히 궁금했던 부분들을 리뷰어에게 질문했었다. 

다음은 내가 의문이 들었던 내용과 리뷰어에게 받은 답변을 정리해 봤다.

 

Q. 중복 검사할 때 Set과 Stream.distinct() 중 무엇을 사용하는게 좋을까요?

저는 평소에 중복된 값이 있는지 확인할 때, stream으로 변환해서 distinct() 메소드를 사용한 후 count를 비교했습니다.

if (names.stream().distinct().count() != names.size()) {
    throw new IllegalArgumentException(EXCEPTION_MESSAGE_DUPLICATION_NAME);
}

 

근데 페어는 Set으로 변환하고 size를 비교하는 방법을 새롭게 알게되었습니다.

if (new HashSet<>(names).size() != names.size()) {
    throw new IllegalArgumentException(EXCEPTION_MESSAGE_DUPLICATION_NAME);
}

 

 

두 방법 중 Set을 쓰는게 리스트를 많이 변환하지 않아서 더 좋은 코드라고 생각해서 결국에는 Set을 사용했습니다.

Stream을 쓸 경우 : List → Stream → distinct로 다시 Stream
Set을 쓸 경우 : List → Set

 

해당 부분에 대해서 저희가 set이 더 나을 것 같다고 판단한 이유가 적절한지, 그리고 과연 set이 더 적절한 것이 맞는지 궁금하다.

 

A. 중복 제거 목적이라면 어느 쪽이든 큰 차이는 없을 것 같습니다.

사실 성능적으로는 Set이 유리하긴 하겠지만, 데이터 사이즈가 그리 크지 않기 때문에 성능을 고려할 필요는 없을거에요.

그러면 어떤 쪽이 더 명시적인지, 읽기 편한지 등을 고려해볼 수 있을텐데요.

stream을 쓰면 지금보다 코드 길이가 길어지면서 한 눈에 안 들어올 수도 있을 것 같다는 생각이 드네요.

중복 제거 목적이라면 어느 쪽이든 큰 차이는 없을 것 같습니다.

 


 

가장 많이 사용하는 표준 예외에는 IllegalArgument/IllegalState 예외가 있습니다. 이 둘의 차이점은?

 

RuntimeException :시스템 환경적으로나 인력 값이 잘못된 경우, 혹은 의도적으로 프로그래머가 잡아내기 위한 조건 등에 부합할 때 발생한다.

 

IllegalArgumentException extends RuntimeException

  • 사용자가 값을 잘못 입력한 경우에 발생한다. (사용자의 잘못으로 발생한 에러)
  • 부정한 인수, 또는 부적절한 인수를 메서드에 건네준 것을 나타내기 위해서 발생한다.

 

IllegalStateException extends RuntimeException

  • 사용자가 값을 제대로 입력했지만, 코드가 잘못 처리한 경우에 발생한다. (코드 문제로 발생한 에러)
  • 부정 또는 부적절한 때에 메서드가 불려 간 것을 나타냅니다.
    • 즉, Java 환경 또는 Java 어플리케이션은 요구된 오퍼레이션에 적절한 상태가 아닙니다.

 


 

Q. Stream#reduce도 활용해볼 수 있습니다.

어떤 방식의 구현이 좋을지는 고민해보시면 좋을 것 같네요.

 

A. 사용해봤지만 for문이 더 가독성 좋다고 생각합니다.

범블비의 의견에 따라 reduce를 사용해봤습니다.

처음에는 다음 형태를 사용하여 코드를 작성하였습니다.

reduce(T identity, BinaryOperator<T> accumulator)

 

작성한 코드

        return lines.stream()
                .reduce(position,
                        (currentPosition, line) -> line.findNextPosition(currentPosition));

 

이렇게 하니 currentPosition의 객체 타입이 Line으로 여겨져서 문제가 발생했습니다.

"그럼 어떻게 해결 할 수 있을까?" 찾아본 결과 다음 형태의 reduce를 사용하기로 하였습니다.

reduce(U identity,
	BiFunction<U, ? super T, U> accumulator,
	BinaryOperator<U> combiner);

 

이유는 반환 타입을 line이 아닌 integer로 변환하기 위해서 입니다 !

그래서 의미없는 코드인 (result1, result2) -> result2)를 마지막 인자로 추가해서 다음과 같이 구현하면 Stream#reduce를 사용하여 해당 코드를 수정할 수 있었습니다.

return lines.stream()
                .reduce(position,
                        (currentPosition, line) -> line.findNextPosition(currentPosition),
                        (result1, result2) -> result2);

 

하지만 과연 이게 더 좋은 구현일까 고민해본 결과 사용하지 않는 함수가 생성되어 가독성에 문제가 생기기 때문에 원래 전에 코드를 사용하는 걸로 하였습니다 !


소감

 

이번 페어 프로그래밍 하면서 TDD를 최대한 사용하려고 하였다.

TDD를 처음 들은 나로써는 'TDD는 무엇인가'부터 새로운 정보가 많았다. 근데 그렇다고 엄청 복잡한 개념은 아니었다. 쉽게 Test 먼저, Test가 통과되도록 프로덕션 코드 변경.  

 

그래서 '그래 말 그대로 테스트 먼저 해보자' 식으로 코드를 작성하기 시작했다. 정말 배우는 입장이 되어서 쓸모없는 생성부터 테스트 코드를 구현하고 프로덕션 코드를 구현하였다.

 

그러다 보니 내가 생각하는 좋은 점은 명확했다.

 

첫째, 추후 코드 변경이 생겼을 시, 해당 코드 변경으로 인해 영향을 받아 문제가 되는 로직을 찾기 쉽다는 것이었다. 

원래 같으면 프로덕션 코드 구현 후 테스트를 구현하다보니 귀찮아서 안할 때도 많고 무엇을 테스트해야할지 복잡할 때가 있었다. 그래서 테스트 코드의 이점을 명확하게 느끼지 못했던 것 같다. 하지만 이번에 TDD를 하면서 Test 코드의 이점이 명확하게 알게된 것 같다.

 

둘째, 요구사항에 필요한 로직만 작성하게 된다. 

요구사항을 기반으로 테스트를 먼저 작성하고 프로덕션 코드를 구현하게 되니 요구사항에 필요한 로직만 구현되었다. 쓸모없는 코드가 생성되지 않는다는 것, 너무 좋은 효과라고 생각한다. 

 

뭐 이렇다면 안쓸 이유가 없다고 생각한다 ! 앞으로 종종 TDD 방식을 사용해버려고 한다.

반응형