본문 바로가기

회고

[TDD, 클린 코드 with Java] 1.자동차 경주

728x90
반응형

*개요

문자열 계산기와 자동차 경주 과제 구현 및 피드백을 회고한다.

 

2단계 - 문자열 덧셈 계산기

선언형 스타일로 정의하라

 

엘레강트 오브젝트 책에서 선언형 스타일로 코딩 할 것을 추천해서 사용해 봤습니다. boolean형을 Boolean으로 인터페이스 선언하고 LessThan이라는 같거나 작은 경우를 satisfied() 메서드를 통해 실행합니다. 피드백 과제전형의 좋은 점은 다양한 스타일을 시도하고 공유할 수 있다는 점입니다.

 

 

Pattern에 static을 추가하라

 

 static은 어디서나 접근이 가능한 점도 있겠으나, 미리 컴파일 한다는 의미가 더 큽니다. 그런 점에서 반복 사용되는 상수 개념의 경우 미리 static으로 만든다면 이후 호출에서 불필요한 메모리 사용을 예방할 수 있습니다. CUSTOM_MATCHER 를 static 하는 것에서 한단계 더 나아가 Pattern은 그 자체로 고유하게 반복사용되므로 Pattern에 static을 부여하는 것이 좀 더 효율적으로 보입니다.

 

매직넘버를 피하라

 

 소스코드에 숫자를 그대로 쓰는 것을 매직넘버라고 부르며 이는 소스코드를 이해하기 어렵게 만듭니다. 따라서 정확히 어떤 뜻인지 의미가 담긴 상수 문자열로 표현하는 것이 좋습니다.

 

코딩 컨벤션을 지켜라

 

무의식적으로 컨벤션을 어기고 있는 부분이 있다면,  if, for 등을 사용할 때 괄호를 사용하지 않고 붙여서 사용하는 것 입니다. 이왕 컨벤션을 따라 코딩한다고 한다면 이런 사소한 부분들도 지키면 좋습니다.

 

 

자동차 경주(3단계 - 자동차경주)

커밋을 작은 단위로 유지하라

 

 실질적으로 객체지향 과제를 시작하면서 무작정 구현을 시작하다보니 커밋 단위를 잘 나누지 못했습니다. 갈피를 잡지 못해 여러 번 수정하고 고민하면서 큰 설계도를 그리지 못한 점이 큽니다. 미리 기능 목록 리스트를 만들고 구현을 한다면 훨씬 적절한 커밋 메세지 작성을 할 수 있습니다.

 

메서드명을 명확하게 드러내라

 

 추후 해당 부분에 인터페이스를 사용하도록 가이드를 해주었는데 자체적으로 인터페이스로 미리 구현했던 부분이어서 기뻤습니다. 단지, 조건에 따라 이동이 가능한 메서드이기에 try를 붙였는데 오히려 너무 포괄적인 개념으로 비춰질 수도 있다고 하여 try를 제거하도록 했습니다. 객관적으로 다시 돌아보는게 필요합니다. 다른 사람이 봤을 때도 명확히 이해를 할 수 있는지 돌아보게 됩니다.

 

 

검사를 메서드로 분리하라

 

 조건식을 가지는 유효성 검사의 경우, 별도의 메서드로 분리하는 것이 훨씬 가독성도 좋고 테스트에 용이합니다. 1~2개의 검사라면 상관없지만 만약 여러 개가 나와야 한다면 분리하는게 훨씬 좋습니다.

 

 

의미 있는 변수명을 사용하라

 

 Boolean의 b의 이름이 모호하다고 느낄 수 있습니다. 선언형은 변수명 자체가 가지는 의미보다 선언형으로 선언된 과정이 중요하다고 생각해서 최대한 간결하게 표현하고자 했습니다. 그럼에도 조금 더 의도를 드러내면 사용자 입장에서 편합니다.

 

 

 

사용하지 않는 로컬변수는 제거하라

 

 사소한 부분 일 수 있지만, 별도의 로컬변수를 사용하지 않고 코드 양이 적다면 한번에 정의하는 것이 좋습니다.

 

 

반복 사용되는 코드는 재사용하라

 

 for 문 안에서 계속 새로운 객체를 생성하고 있는데 단순 반복적으로 사용되는 코드라면 재사용이 가능합니다. 0~9까지 랜덤이라는 의도를 드러내기 위해서 작성했지만 따라서 for문 위에서 객체를 생성하고 반복사용하는 것이 낫습니다.

 

 

코딩 컨벤션을 지켜라

 

코딩 컨벤션에 어긋나는 부분들은 사소할 수 있지만 주의해서 작성합니다.

 

 

유틸성 클래스에 대한 고민

 

 엘레강트 오브젝트에서는 객체지향은 당연히 하나의 클래스가 생성자를 가져 객체로 사용될 때 의미가 있다고 봅니다. 따라서 별도의 유틸 클래스라는 명칭을 싫어하고 심지어 유틸 성격의 클래스를 만든다면 객체를 활용하기를 권장 할 것입니다. 하지만 이번 과제에서는 과하지 않은 선에서 다양한 시도를 해봤습니다.

 

커밋 단위 고민

 

 

 커밋 단위에 대한 고민은, 미리 기능 목록을 작성하고 하나씩 구현하는 방식으로 해결하면 좋습니다. 컴파일 에러가 없이 실행이 가능 한 상태로 커밋을 하면 좋은데 테스트까지 다 고치고 커밋을 해야 하는가? 는 꼭 그럴 필요가 없다고 생각합니다. 양이 너무 많거나 급한 경우 일단 소스코드를 수정하고 테스트도 수정이 필요하다는 커밋 메세지를 남기고 다음 작업에 테스트를 수정하는 정도는 괜찮다고 생각합니다. 

 

 

추상화 대상을 구체적 의존에서 확장하라

 

 기존에는 단순히 랜덤 숫자만 추상화하였으나 실제 구현되는 로직에서는 너무 구체적이어서 코드 양이 많았습니다. 따라서, 좀 더 큰 범위의 대상으로 의존성을 확대하면 훨씬 가독성이 올라갑니다. 추상화 범위의 적절성에 대한 고민이 필요합니다.

 

 

테스트 범위가 많으면 경계값을 테스트하라

 

 0~99까지 모두 테스트가 필요하다고 한다면, 경계값을 테스트하는게 효율적입니다.

 

 

자동차 경주(4단계 - 자동차 경주(우승자))

유효성 검증 방식 고민

 

유효성 검증 시, if문 안에 있는 검증은 !를 사용하지 않는 편이 훨씬 가독성이 좋습니다. 즉, 긍정을 사용합니다.

 

테스트를 위해 적절한 클래스 구조를 만들어라

 

 우승자를 구하는 로직을 테스트하기 위해서 우승자를 구하는 클래스를 따로 만들면 좋습니다. 우승자만을 관리 할 수도 있으며 테스트를 위해 데이터를 주입하고 검증하는 것이 편리합니다. 어떤 것들을 객체로 생각하고 클래스를 만들 것인지 고민이 필요합니다.

 

 

일부만 사용하는 경우 상수가 아닌 테스트 내부 로컬 변수로 선언하라

 

 자동차 이름 길이 검증은 다수 혹은 모든 테스트에서 사용 하는 것이 아니므로 테스트 내부로 옮기는 편이 낫습니다.

 

 

메서드 작성 시, lowerCamelCase를 준수하라

 

 언더바(_) 이후에 나오는 문자는 소문자를 쓰는 것이 코딩 컨벤션이므로 수정해야 합니다.

 

테스트에서 @NullAndEmptySource를 활용하라

 

 빈 값을 테스트 할 경우, @NullAndEmptySource를 사용 할 수 있습니다. 적절하게 활용하면 편하게 테스트를 할 수 있습니다.

 

 

의미가 있는 로직은 메서드로 분리하라

 

 우승자를 구하는 기준은 하나의 의미를 가지는 비지니스 로직입니다. 추후 우승자를 구하는 기준이 변경 될 수도 있습니다. 따라서 해당 부분을 별도의 메서드로 분리합니다.

 

최대 거리와 우승자 구하기가 분리 된 코드

 

 

 

일급 컬렉션의 생성자 유효성 검사를 추가하라

 

 일급 컬렉션에서 우승자 객체는 최소한 1대 이상의 자동차를 가지는지 검사합니다. 아무런 우승자가 없어도 객체가 생성될 수 있다면 최종적으로 우승자를 구하는 로직에서 예상치 못한 오류가 날 수도 있습니다.

 

 

다양한 선언형 코딩을 활용하라

 

 기존에 비교에서 다양한 선언형 방식을 사용하였기 때문에 선연형 방식 사용을 제안해 주셨습니다. 해당 부분도 충분히 사용할 수 있는 곳이지만, 이전에도 이미 여러 번 적용했던 부분이라 굳이 추가하지 않았던 부분입니다.

 

 

경계값 테스트를 추가하라

 

 메서드명이 isNameLengthInvalid인 것도 다소 맘에 들지 않습니다. Valid를 써도 좋았을 것입니다. 조건을 반대로 적용하여 이름이 5자를 초과하지 않아도 예외를 던졌습니다. 메서드명에서 최대한 부정형을 쓰지 않고 Valid 같은 형식을 사용하고 실제 조건문에서도 긍정형을 사용하며 경계 값 테스트를 통해 확실하게 검증하는 방식으로 꼼꼼하게 작성 할 필요성을 느꼈습니다.

 

 

객체지향 패러다임을 고려해 객체에 메세지를 보내라

 

 객체의 값을 getter로 획득하는 것보다, 메세지를 보내서 해결하는 방식이 객체지향에서 주요한 개념입니다. 메세지를 보내는 것보다 때로 getter 사용이 편리하여 간과하는 경우들이 있는데 객체지향 패러다임을 좀 더 고민하여 주의해야 합니다.

 

 

자동차 경주(5단계 - 자동차 경주(리팩토링))

테스트 전용 메서드는 제거하라

 

 테스트 전용 메서드는 좋지 않습니다. 어떻게 테스트를 위해 데이터를 주입할 수 있을지 다른 방식으로 고민해봐야 합니다. 생성자의 경우에는 부분적 허용이 가능해도 메서드는 혼란만 증가시킵니다.

 

 

 마찬가지로 우승자 비교도 테스트 전용으로 메서드를 만드는 것보다 차라리 EqualsAndHashcode를 구현하여 객체끼리 비교하게 하는 것이 훨씬 코드의 양을 줄일 수 있습니다.

 

print 관련 기능은 도메인이 아닌 View에 추가하라

 

 도메인에 print 관련 로직이 있다면, 출력의 조건이 바뀌면 ResultView 뿐만 아니라 도메인도 변경되야 합니다. 이는 MVC 패턴의 원칙을 지키지 못해 장점을 잃어버립니다. 따라서 직접적인 문자열 작성은 도메인에서 하지 않습니다.

 

 

비슷한 테스트는 하나로 통일하라

 

 사실상 동일한 테스트는 한번만 테스트하던지, 테스트가 여러 번 수행되어야 하는 기준이나 이유를 명확하게 명시해야 합니다. race() 메서드 2개가 있어서 1번만 수행할 수도 있고, 여러 번 수행할 수 있어서 각 상황에 따라 테스트를 추가하려고 했는데 읽는 사람 입장에서 충분히 이해하지 못했습니다.

 

 

하위에서 테스트 했다면 상위에서 테스트를 할지 고민하라 

 

 RacingGame 은 직접적인 우승자를 구하는 로직이 없고 Cars의 역할을 한번 감싸는 역할을 합니다. 따라서 중복되는 테스트가 됩니다. 별도의 추가 로직이 있는 것이 아니라면 작은 단위인 Cars에서 테스트 하는 것으로 대체가 가능합니다.

 

 

객체의 책임과 역할 부여를 고민하라

 

 MaxMoving은 이동거리를 구하는 하나의 방법일 뿐입니다. 만약에 MaxMoving의 방식이 바뀐다면, Cars 내부의 로직도 변경되어야 합니다. 따라서 MaxMoving과 Cars 로직을 모두 수정하지 않도록 관련된 로직은 모두 MaxMoving에서 구현해보도록 합니다. 정책 변경에 따라 변할 수 있는 부분인지, 절대적으로 상관없는 부분인지 고민하여 어떤 객체에 역할과 책임을 부여할지 고민합니다. 

 

변경 된 로직

 

원시 값을 포장하라

 

 객체지향 생활체조 원칙에 따라서 이동거리를 포장할 수 있습니다. 이동거리는 Moving 혹은 Position으로 포장하여 유효성 검증을 추가하여 객체가 서로 협력하는 관계로 만들 수 있습니다.

728x90
반응형

'회고' 카테고리의 다른 글

[TDD, 클린 코드 with Java] 3.사다리타기 - FP, OOP  (0) 2024.01.02
[TDD, 클린 코드 with Java] 2.로또 - TDD  (0) 2023.12.30
TIL_20221027  (0) 2022.10.28
TIL_20221025  (0) 2022.10.26
TIL_2022.10.21  (0) 2022.10.22