2주간의 프리코스 🔥

1주차 미션이 끝나고 사람들에게 코드리뷰를 받는 과정에서 내 코드에 대해 명쾌하게 설명할 수 없는 내 모습을 통해 아직 메타인지가 부족함을 깨달을 수 있었다.

나름 에 대해 고민하고 개발한다고 생각해왔지만 Deep한 고민은 아직 부족했던 모양이다.

때문에 이번 미션에선 아주 진득하게 고민하고 개발해보고자 다짐하였다.

그렇게 다짐해서인지 코드 한 줄을 적을 때도 이런 저런 생각이 많이 들었고, 아직 내가 지식이 부족함을 다시 한 번 깨달을 수 있었다. 우테코는 프리코스만 참여해도 많이 배워간다고들 하는데, 나 또한 몰입하는 과정에서 정말 많이 배우고 성장해나가고 있는 중인 것 같다.

나는 이번 미션을 풀어나가면서 단지 미션을 수행하는 것에 목적을 두는 것이 아닌, 미션에서 마주하는 문제들을 해결하고 개선해나가는 것에 더욱 초점을 맞추었다.

“코드만 작성하는 코더가 되지 말고 문제를 해결하는 개발자가 되어라”
라는 말을 최근에 본 적이 있는데, 이 말을 점점 실천해나가고 있는 것 같아 정말 뿌듯했고 나에게 수고했다고 말해주고 싶었다.



[좋았던 점 & 배운 점 📈]

⚙️ String.matcher()와 Pattern 객체 사용 간의 성능 비교 후 최적화

정규표현식을 사용할 때 String.matcher()를 사용하는 방식과 Pattern 객체를 생성해두고 이를 이용하는 방식을 고민하면서,

String.matcher()를 사용하면 메서드 호출 시마다 Pattern 객체를 생성하기 때문에 메모리 낭비가 발생한다는 것을 학습할 수 있었고

Pattern객체를 미리 만들어두어 사용하는 방식으로 이를 해결하여 필요없는 자원 낭비를 막는 경험을 할 수 있었다.

배운 내용을 기록한 포스팅



⚙️ 반복 작업 시, For-Loop와 Stream 사용의 성능 비교 후 최적화

List<String> 객체를 입력받아 반복문을 통해 Car 객체 생성 작업을 반복하여 수행할 때, For-Loop 사용과 Stream 사용 중 어느 것이 성능면에서 우위에 있을 지를 직접 코드를 짜보며 비교하고 학습하였다.

그 과정에서 Stream 사용의 경우 최적화가 많이 진행되지 않아 최적화가 잘 되어있는 For-Loop 문을 사용하는 경우보다 동작 속도가 느리다.

하지만 Wrapped Type의 경우 Stack에 값이 바로 할당되어 빠르게 참조가 가능한 Primitive Type과 다르게, Stack에 저장된 Heap 메모리의 주소를 통해 객체에 접근하기 때문에 성능이 저하되는 이슈가 있는데,

그러한 성능 저하로 인해 For-Loop 문을 사용해도 Stream과의 성능 격차가 완화되거나 Stream 방식이 오히려 더 좋은 성능을 보이는 등, 비슷한 성능을 발휘하게 되기 때문에 가독성이 더 좋은 방법을 선택할 수 있다는 점을 직접 테스트하며 학습하였다.

이러한 검증 과정을 통해 코드가 짧고 간결한 Stream 방식을 채택하여 가독성있는 코드를 작성할 수 있었다.

배운 내용을 기록한 포스팅



⚙️ 테스트 코드 작성을 통해 객체에서 상태와 행위를 관리하는 것의 장점 학습

어느정도의 책임부터 역할이라고 생각하고 객체로 생성할 지 정말 많이 고민했는데, 이때 테스트 코드 작성이 굉장히 많은 도움이 되었다.

테스트 로직은 실행 시에 자동으로 촤라락 실행되어야 하기 때문에 Console.readLine()을 사용해서 입력을 받아 진행하는 테스트는 의도를 벗어나는 코드라고 생각이 들었다.

하지만 레이싱카 미션을 수행하며 이동 시도 횟수InputView에서 입력받아 NumberFormatException에 대한 검증을 처리하고, int값으로 파싱하여 사용하도록 코드를 작성하니
Console.readLine()으로 입력받지 않는 이상 NumberFormatException 발생에 대한 검증 테스트를 수행하는 것이 불가능했다.

입력값을 객체를 통해 관리하지 않고 입력값을 그대로 사용하니, Console.readLine()을 하지 않는 이상 값에 대한 테스트는 불가능했던 것이다.

때문에 시도횟수를 Turn이라는 객체로 생성하여 몇 번의 이동 시도를 할 지 int타입의 count라는 필드에 담아 관리하고,
Console.readLine()의 입력 값인 String을, Turn 객체에 생성자 주입하여 NumberFormatException을 검증 후 Integer.parseInt를 통해 얻은 시도 횟수를 사용하여 count 필드를 초기화하도록 구현하니

new Turn(”1”);assertThatThrownBy로 예외가 발생하는 지 검사해주면 되어
입력값에 대해 굉장히 간편하게 테스트 코드를 작성할 수 있게 되었다.

상태와 행위를 객체를 만들어 관리했을 때 테스트 작성까지 간편해지는 것을 배웠고 더욱 깊이있게 이해하게 되었다.

상태와 행위를 가질 수 있다면 역할로 생각하고 객체로 만들어 Testable 하도록 개발하는 것도 꽤나 괜찮은 방법이라는 것을 배울 수 있었으며,

생성자 주입 방식을 사용하여 객체 생성 시, 객체 초기화 전 단계에 필수적으로 검증 로직 호출이 가능함과 동시에 Testable 해지는 코드를 보며 생성자 주입 방식을 지향하는 이유에 대해 몸소 느낄 수 있었다.

물론 생성자 주입을 사용하는 이유는 더욱 많지만 말이다.



⚙️ toList() 활용

지난 1주차에서 Java17에 대해 학습하며
Stream에서 collect(Collectors.toList()); 가 아닌 toList() 메서드를 사용했을 때, unmodifiableList를 return하기 때문에 사이드 이펙트를 방지하고 List 내부의 값들을 안전하게 보관할 수 있어 이를 활용해보고자 다짐하였는데

이번 미션에서 기본적으로 변경이 일어나선 안되는 List에는 toList()를 사용하였고, 순위 측정을 위해 정렬이 필요한 경우에는 collect(Collectors.toList()); 을 사용하는 등 상황에 따라 적절히 활용하며 사이드 이펙트를 최대한 방지하도록 유도하며 코드를 작성하였다.



⚙️ Text Blocks 활용

지난 1주차에서 뉴라인이 필요한 문자열을 관리할 때, \n기호를 사용하여 관리하였는데 문자열 중간중간에 \n기호가 추가되어 있어 가독성이 떨어지는 문제점이 있었다.

때문에 이번 미션에선 테스트 코드에서 실행 결과 출력 양식에 대해 테스트할 때 Text Blocks(“””) 를 활용하여 가독성있는 테스트 코드 작성을 할 수 있었다.

하지만 아쉬운 점은 \n기호 활용 시, 1 line만을 활용하여 변수를 선언할 수 있었는데 Text Blocks를 활용하면 라인이 굉장히 늘어날 우려가 있다.

때문에 우선은 정말 명확하게 텍스트를 명시해야하는 테스트 환경에서만 활용해보고자 한다.



[고민했던 점 🤔]

💭 Application 객체의 main 메서드는 어느정도의 책임을 가질 수 있을까에 대해 많이 고민했다.

처음엔 애플리케이션이 어떻게 동작하는지 명시해야하는 곳이 바로 Applicationmain메서드이기 때문에
이곳에 의존성 주입에 필요한 객체 초기화, 의존성 주입, 메인 기능 start 정도의 책임을 부여해주었는데 이렇게 구현하는게 맞는지 틀린지 모르겠어서 고민이 정말 많았다.

하지만 결국 CarRacingManager 라는 객체를 생성하여 이곳에 의존성 주입에 필요한 객체 초기화, 의존성 주입, 메인 기능 start 라는 역할을 부여하였고, main메서드에서는 CarRacingManager객체를 생성하고 메인 기능 start의 역할만 수행하도록 변경하였다.

당장은 main에 작성하는 것이 가독성이 괜찮았지만, 만약 이 프로그램의 기능과 동작 방식이 확장된다면 분명 더 많은 객체들이 필요하게 될 것이고 그것들을 모두 Application 객체에 구현하다보면 가독성이 낮고 지저분한 코드로 전락하게 될 것을 생각하였기 때문이다.

때문에 main메서드는 애플리케이션의 실행만 담당하도록 하였고, 애플리케이션의 실행 로직과, 의존성을 관리하는 CarRacingManager 객체를 생성하여 IoC 컨테이너와 같이 애플리케이션의 전체적인 흐름 컨트롤이라는 역할을 부여했다.



💭 UI로직과 도메인 로직 분리 중 겪은 고민

1번 로직

carNameposition을 받아 출력을 수행하는 위 로직은 특정 객체에 의존하지 않는다.

public void printCarPosition(String carName, int position) {
		StringBuilder positionText = new StringBuilder();
		for (int i = 0; i < position; i++) {
				positionText.append(CAR_POSITION_MARKER);
    }
    printMessage(String.format(CAR_POSITION_OUTPUT_MESSAGE, carName, positionText));
}

2번 로직

Car 객체를 직접 받는 출력 방식은 더 구체적인 입력 형태이며, 특정 클래스(Car)에 의존한다.
이 메서드는 Car 객체에 의존하므로, Car 클래스의 내부 구현이 변경되면 이 메서드의 동작도 영향을 받을 수 있다.

public void printCarPosition(Car car) {
		StringBuilder positionText = new StringBuilder();
    for (int i = 0; i < car.getPosition(); i++) {
				positionText.append(CAR_POSITION_MARKER);
		}
		printMessage(String.format(CAR_POSITION_OUTPUT_MESSAGE, car.getName(), positionText));
}

이 중에 뭐가 더 좋은 설계일까?

  • 2번은 출력 로직에서 특정 객체에 의존성을 갖기 때문에, 객체의 설계가 변경되는 경우 영향을 함께 받는다.
  • 1번은 특정 객체에 의존성을 갖고 있지 않기 때문에, 객체의 변경에 영향을 받지 않는다.

본인은 carNameposition을 활용하여 메시지를 만들어 출력한다는 것이 정해져있는 점을 주시하여,
객체가 변경됨에 따라 정해져있는 출력 로직도 변경되면 안된다는 생각에 UI 로직도메인 로직에 의존하지 않도록 설계해보았다.

출력 방식이 변경되면 UI 로직만 손보면 되고, 객체의 설계가 변경되면 도메인 로직만 손보면 되도록 의존성을 제거했었다.

하지만 두 로직 모두 UI로직도메인 로직에 의존성을 갖는 것은 매한가지라고 생각이 들었다.


때문에 MessageConverter 객체를 만들어 요구 사항에 맞는 형태의 메시지 String을 생성하도록 하고,
OuputView에는 단순히 출력 이라는 책임만을 쥐어주어 도메인 로직에 대한 UI로직의 의존성을 완전히 제거해보고자 한다.

이렇게 관리하면 OutputView 객체는 최소한의 출력 로직만을 가짐에도 불구하고 원하는 모든 메시지를 출력할 수 있는 유연성확장성을 가지게 되며, 도메인 로직에 더는 의존하지 않는다.

뿐만 아니라 만약 출력 형태가 변경되면 MessageConverter 객체만 수정하면 되어 확실한 역할, 로직의 분리가 이루어지게 된다고 생각한다.

이번주 미션은 이미 종료되었으니, 다음 주 미션에서 이를 적용해보고자 한다!



[아쉬웠던 점 😢]

테스트 코드를 먼저 작성함으로써 내가 작성할 코드에 대해 깊이 있게 생각해보고, 그 과정에서 예외 케이스를 발견하는 등의 이점을 얻기 위해 TDD 적용해보고자 하였지만, 아직 TDD에 대한 지식이 깊지 않아 제대로 진행하고 있는 것이 맞는 지 판단이 서질 않았다.

물론 이번 미션에서는 TDD를 통해 얻고자 하는 이점을 모두 얻었다고 느꼈지만, 개인적으로 더 잘해낼 수 있을 거라고 생각이 들었고 더 깊이있게 학습하여 프리코스에서 최대한 많이 배워갈 수 있도록 노력해야겠다.

또한 테스트 코드 작성 자체가 익숙하지 않다보니, 어린아이의 낙서같은 테스트 코드를 작성한 것 같다.
반복되는 코드가 많았고 이로 인해 테스트 코드의 효율이 떨어진다고 생각됐다.
정말 꾸준히 학습하여 다음 미션에서는 더욱 좋은 코드를 작성해보고자 한다.



[다음 미션 목표 🏁]

  • 저번 미션보다 깊이있게 고민하고 생각하며 한 주 동안 정말 많이 성장했다고 느꼈다.
    때문에 다음 미션에서도 근거있는 코드 작성을 하기 위해 정말 많은 시간을 투자해보고자 한다.
  • Junit5를 적극 활용하여 반복되는 코드를 제거하고 가독성 좋은 클린한 테스트 코드 작성하고자 한다.
  • MessageConverter 객체를 두어 도메인 로직에 대한 UI로직의존성을 제거하고, 유연하고 확장성 있는 UI로직을 개발해보고자 한다.
profile
하나씩 천천히 깊이있게 쌓아가는 백엔드 개발자 최승준입니다.

2개의 댓글

comment-user-thumbnail
2023년 11월 5일

정말 많이 보고 배우고 있어요 !!!!!!!

답글 달기
comment-user-thumbnail
2023년 12월 11일

I have learned many new and interesting things from reading your post slope and hope you will post more interesting information in the near future.

답글 달기