💋 TDD에 대한 생각
TDD를 처음 적용해서 설계를 할 때는 굉장히 어려웠다.
TDD의 아이러니 중 하나는 테스트 기술이 아니라는 점이다. TDD는 분석 기술이며, 설계 기술이기도 하다.
- 켄트벡, Test Driven Development by Example 중
특히 처음에는 이 말이 너무너무 어려웠다. 나에게 TDD는 테스트 코드를 먼저 짜는 것 뿐이었다.
TDD가 설계 기술이라는 것을 인지하지 못한 채, 테스트 코드부터 짜기 위해서는 내가 테스트하고 싶은 메서드의 시그니처(파라미터, 반환값의 타입)는 반드시 알아야 했다. 굉장히 작게 어떤 메서드를 테스트한다는 생각만 가지고 있어서 오히려 더 설계를 방해했던 것 같기도 하다.
하지만 지금 느끼는 TDD는 다르다. 우선, 설계부터가 중요하다.
한 객체가 다른 객체에게 어떤 요청을 보내서 어떤 응답을 받고 서로 어떻게 협력하는 지에 대해 정리한다. 폭포가 떨어지듯이 상위 객체에서 하위 객체로 흘러내려가는 요청, 거슬러 올라가는 응답을 생각해본다. 그리고 가장 하위에서 상위 객체가 시키는 일에 대해 하청만 하는 것 같은 객체부터 어떤 요청을 받아 어떤 응답을 보내야 하는지에 대해 생각한다.
가장 아래 레벨의 객체부터 진행하는 것은, 그 객체는 스스로 변경되어 테스트 코드가 깨질 일은 있어도 더 하위의 객체가 없(다고 현재로는 생각하)기 때문에 다른 객체에 의존하지 않기 때문이다. 그리고 가장 간단하기도 하다 ><
내가 TDD, 리팩토링을 하는 이유는 무엇인가?
기존에 구현하는 방식과 TDD로 코드를 구현할 때 어떠한 차이를 느꼈는가?
프리코스 때와 달리 본코스에서 점점 프로그램이 복잡해지고 있다. 구현에 대한 상상의 범위가 굉장히 좁아질 때가 많고, 도메인에 대한 이해가 잘 되어 상상의 범위가 넓더라도 한 번에 구현하려고 하다보면 머리에 과부하가 걸리기 굉장히 쉽다. TDD를 하게 되면 도메인 전체에 대한 파악은 하지 않더라도, 먼저 구현하고자 하는 내용에 대해 객체 간 협력 구조에 대해서 생각해보고 들어가야 한다. 따라서 객체 간 메세지 전달에 좀 더 초점을 맞춰서 설계하는 연습을 할 수 있다.
테스트 코드 작성, 테스트 코드 성공하게 만들기, 세부내용 리팩터링 하기 중 한 번에 한 가지만 하게 되기 때문에 이것저것 생각하면서 구현할 때보다 오히려 더 빠르게 구현할 수 있게 되는 것 같기도 하다!
미션을 진행하면서 리팩토링을 경험하며 어떠한 어려움을 겪었는가?
열심히 작성한 테스트 코드가 깨지는 경험이 많았다. 사실 테스트 코드가 깨진다면 당신이 방금 한 것은 리팩터링이 아니다... 리팩터링은 결과의 변경 없이 코드의 구조를 재조정하는 일이기 때문이다...! 잘 짜여진 코드 구조 내에서 세부 구현을 리팩터링하는 일은 크게 어렵지 않았지만, 리팩터링인 줄 아는데 리팩터링이 아니었던, 실제로는 객체 내 역할과 협력 구조를 변경하던 많은 일들이 힘들었다. 대답해보니 질문과 맞지 않는 것 같다 머쓱
리팩토링 과정에서 어려움을 줄이기 위해 어떠한 시도를 해볼 수 있는가?
어떤 객체가 어떤 역할을 맡을 것인지에 대해서 조금 더 명확하게 생각하고 들어간다면 결과의 변경이 줄어들 것이니, 조금 더 편해질 것 같다! TDD를 잘하려면 결국에는 객체 간 메세지를 정확하게 이해하고 있어야 할 것 같다는 결론이다.
따라서 위에서 저 캔트벡 아저씨는 TDD가 테스트 기술이 아닌 분석과 설계 기술이라고 말한 것 같다.
💋 자바에 대한 생각
본인이 생각하는 자바를 잘한다는 기준은 무엇인가?
내가 하고싶은 행동을 자바 코드로 표현할 수 있으며, 작성하는 코드에서 발생할 수 있는 일에 대한 예측력과 상상력이 뛰어나다면 자바를 잘한다고 볼 수 있을 것 같다.
자바를 잘하는 사람이 되기 위해 어떠한 시도를 할 것인가?
먼저 자바에 대해 알아야 한다. 자바는 내가 태어나기도 5년 전에 태어났기 때문에 아직 내가 모르는 것이 참 많다.
나보다 늙은 개발자들이 만들었던 시행착오들과, 이전에 안좋은 것들을 보완하기 위해 태어난 기술 등등 자바가 걸어온 역사와 기술의 등장 원리에 대해 공부해야 할 것이다. 그 후에는 직접 내가 원하는 프로그램을 만들어 보면서 내가 시행착오를 겪어 자바 코드의 동작에 대해 예측력, 상상력을 기를 것이다.
미션을 진행하며 위 시도를 하였을 때 본인이 성장하고 있는지 어떻게 측정할 수 있는가?
디버깅 속도가 빨라진다거나, 테스트 코드가 실패했을 때, 아! equals 재정의를 안해서 그렇구나, return을 너무 빨리 해버렸구나 등등 디버깅 속도가 빨라진다면 코드 동작에 대한 예측력이 조금 더 향상되었다고 볼 수 있다.
또 도메인을 이해한 뒤에 설계를 시작했을 때, 어떤 객체가 어떤 책임을 갖는 지에 대해서 머리 속에서 잘 나누어진다거나, 두 가지 방식으로 설계를 했을 때 어떤 장단점이 있을 지에 대해서 빠르게 상상할 수 있다면 내가 성장했다고 생각할 수 있다.
💋 예외처리에 대한 생각
Checked Exception을 사용하는 경우와 Unchecked Exception을 사용하는 자신만의 원칙은 무엇인가?
런타임 예외(Unchecked Exception)을 사용하려고 한다. 되도록이면 컴파일 예외(Checked Exception)은 사용하지 않고 있다. 컴파일 예외는 호출하는 쪽에서 반드시 예외에 대한 처리를 해야 하는데, 내가 만드는 예외가 그렇게까지 강제적일 필요가 없다고 생각하기 때문이다. (사실 아직 잘 모르겠다.)
아래에 어떤 상황에 어떤 일을 하는 것이 좋을까에 대해서는 아직 잘 모르겠다.
그치만 어떤 것이 안좋은지는 이제 겨우 분간할 수 있게 되었다. 엉뚱한 소리만 할지도 모르겠지만, 일단 지금의 내가 생각하는 대로 적어보겠다!
어떠한 상황에 예외를 복구하는 것이 좋다고 생각하는가? 왜 그렇게 생각하는가?
좋다고는 보지 않지만, 이제까지는 사용자가 잘못된 입력을 했을 때 IllegalArgumentException을 던지고, 예외를 잡은 후에 다시 입력을 받기 위해서 복구를 사용해 왔다.
private Names receiveNames() {
try {
return new Names(inputView.readNames());
} catch (IllegalArgumentException exception) {
outputView.printExceptionMessage(exception);
return receiveNames();
}
}
이 경우 내가 생각하기에 안좋은 점이 두 가지가 있다.
1. 재귀가 깊어지면 stackoverflow가 발생할 수 있다. (근데 소문으로만 들리고 실제로 당한 적은 없긴 하다.)
재귀를 대신할 방법으로는 반복이 있다. 반복과 비교해 보았을 때, 재귀는 함수를 호출하고 종료할 때마다 스택 프레임을 구성하고 해제하는 과정에서 성능적으로 떨어진다.
2. 반환값을 Optional 등으로 사용할 수 있는데, 예외로 흐름을 제어하는 느낌이 든다.
예외는 내가 정의하기 나름이지만, 극단적으로 이런 것도 그러면 예외를 통해서 복구하는 것이라고 볼 수 있을까?
private static int readHeight() {
try {
return Integer.parseInt(InputView.readHeight());
} catch (NumberFormatException exception) {
return readHeight();
}
}
이 괴이한 코드를 예외를 처음 만든 누군가는 예상할 수 있었을까?
이전에 우테코 최종 코테에서 나는 예외를 흐름 제어용으로 사용했었다.
public class MainController {
private final Map<ApplicationStatus, Controllable> controllers;
public MainController() {
this.controllers = new EnumMap<>(ApplicationStatus.class);
initializeControllers();
}
private void initializeControllers() {
controllers.put(ApplicationStatus.INITIALIZE_MENUS, new InitializingController());
controllers.put(ApplicationStatus.RECEIVE_COACH_DATA, new CoachDataController());
controllers.put(ApplicationStatus.GIVE_RECOMMENDATION, new MenuRecommendationController());
}
public void play() {
ApplicationStatus applicationStatus = process(ApplicationStatus.INITIALIZE_MENUS);
while (applicationStatus.playable()) {
applicationStatus = process(applicationStatus);
}
}
public ApplicationStatus process(ApplicationStatus applicationStatus) {
try {
return controllers.get(applicationStatus).process();
} catch (NullPointerException exception) {
return ApplicationStatus.APPLICATION_EXIT;
}
}
}
마지막 process() 메서드를 보면 Status에 대해서 END에 대해 정의해놓지 않은 후에, controllers Map에 들어있지 않은 key가 들어오는 경우에 그냥 EXIT 시키는 식으로 만들었다... ㅎㅎㅎ 이런 경우에는 END인 경우에 Controllable을 정의하고 NullPointerException으로 흐름을 제어하지 않도록 수정하는 것이 더 좋을 것 같다.
어떠한 상황에 예외를 회피하는 것이 좋다고 생각하는가? 왜 그렇게 생각하는가?
상위에서 예외를 일괄적으로 처리할 것이라 생각할 때 회피하는 것이 좋을 것 같다.
컨트롤러에서 예외의 종류에 따라서 다른 로직이 진행된다고 하면 상위로 넘겨줘야 할 것이다.
근데 언제까지 예외 폭탄돌리기 할 수 있을까..?
어떠한 상황에 예외를 무시하는 것이 좋다고 생각하는가? 왜 그렇게 생각하는가?
예외를 만난 내가 모두 예상한 상황일 때 무시한다고 생각한다.
근데 예외를 무시하고 있다면, 정말로 이번엔 정말로 예외로 내가 흐름을 제어하는 것이 아닌지 다시 한 번 생각해 보자.
예외를 무시하게 되면 IntelliJ에서 자동으로 예외의 파라미터 이름을 ignored로 바꿀 것이냐고 제안하는데, 혼선을 막기 위해서는 필수다!
어떠한 상황에 예외를 전환하는 것이 좋다고 생각하는가? 왜 그렇게 생각하는가?
예를 들어, SQLException이 터졌다고 하자. 잡으면 무엇을 할 거에요?
이렇게 시스템에서 예상치 못한 예외의 경우, 일반적으로 예외 로그를 찍고 SQLException이 아닌 커스텀 예외 혹은 다른 표준 예외를 던지게 된다. SQLException을 담은 RuntimeException을 계속해서 상위로 상위로 전파하게 되다가 사용자에게까지 전달된다고 생각해 보자. 원치 않는 메세지가 사용자에게 전달될 수 있고 그 내부에는 외부에 보여지면 안되는 보안과 관련된 정보가 포함될 수도 있다.
private void saveChessGame(final ChessGame chessGame) {
final var query = "INSERT INTO chess_game (position, piece_type, piece_color) VALUES (?, ?, ?);";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
...
} catch (final SQLException e) {
throw new IllegalStateException("체스 게임 결과를 저장하는데 실패했습니다."); // 커스텀 예외를 사용할 수도 있다!
}
}
이렇게 예외를 전환하고, 새로운 메세지를 포함해서 던질 수 있다.
'우아한테크코스5기' 카테고리의 다른 글
[우테코] 체스 미션 3, 4단계 회고: 예외 처리는 조심스럽게, 테스트 코드의 맹점, 리팩터링할 때는 기존 코드를 그대로 두면서... (0) | 2023.04.11 |
---|---|
[우테코] 레벨로그: 레벨1 인터뷰를 준비하며 (0) | 2023.03.29 |
[우테코] 레벨1을 끝내고 답해보자(1): 단위 테스트, 코드 품질 (0) | 2023.03.27 |
[우테코] 블랙잭 미션 회고: 상태 패턴 사용, abstract/final 제어자, 중복되는 내용을 필드로 놓는 것에 대해.. 등등 (2) | 2023.03.24 |
[우테코] 체스 미션 1, 2단계 리팩터링 회고 (feat. 네오의 피드백 강의) (2) | 2023.03.21 |