0. 들어가기 전
현재 우테코 생활은 벌써 3번째 미션을 향해 달려가고 있다.
3번째 미션을 향해 달려가고 있지만,
첫 번째 미션인 자동차 경주 게임 피드백을 블로그에 따로 정리해놓지는 않은 것 같아서 기록하려고 한다!
피드백 받았던 당시에 기록했으면 더 좋았을 수도 있었겠지만
피드백을 다시 상기시키는 느낌에서 피드백을 받은지 1주 지난 지금,
다시 기록하면서 보는 것도 나쁘지 않을 것 같다!
1. 공통 피드백 중 내가 지키지 못했던 것들
1-1. 🎯 객체 입장에서 생각해보기
랜덤 값으로 자동차 전진 여부를 결정할 때,
자동차 객체는 자동차 전진 기능에서 들어오는 파라미터가 '랜덤 값'인지 알 필요가 없다.
자동차 입장에서 전진 기능의 판별은 엑셀의 깊이? 느낌으로 power 같은 네이밍이 적절할 것이다.
public class Car {
public void move(int randomValue) { ... } (x)
public void move(int power) { ... } (o)
}
1-2. 🎯 구현 순서도 코딩 컨벤션이다
정해진 구현 순서 : 상수 - 클래스 변수 - 인스턴스 변수 - 생성자 - 메소드
* 클래스 변수 : 'static'이 붙은 정적 변수로, 클래스가 메모리에 올라갈 때 생성
* 인스턴스 변수 : 클래스 생성 시가 아니라, 인스턴스가 생성될 때 생성
원래 상수가 맨 앞에 구현되어야 하는 것을 알고 있었지만,
생각이 없었던 탓인지, 다른 구현에 집중을 한 탓인지 상수를 중간에 선언해버렸었다.
구현 순서도 고려하면서 코드를 작성하자!
1-3. 🎯 IDE의 코드 자동 정렬 기능 활용
더 깔끔한 코드를 위해서 커밋 전에 코드 자동 정렬 기능을 실행하는 것을 습관화하자!
사용하지 않는 import문 제거도 습관화하자!
(사실 2번째 미션에서도 습관화 하지 못함... 사실 까먹었다가 포스팅하는 지금 다시 보고 떠오름 😎)
단축키는 다음과 같다.
1. 코드 자동 정렬 기능 : Command + Option + L
2. 사용하지 않는 import문 제거 : Control + Option + O
1-4. 🎯 변수 이름에 자료형 사용하지 않기
맵 자료 구조를 사용할 때 변수 이름에 Map을 붙여서 사용했었다.
Map 자료 구조 네이밍은 2번째 미션 피드백에서 받았는데,
ChatGPT에게 네이밍 추천을 받아 캡쳐를 해주셨다...
리뷰어님은 'By'를 붙이는 것을 선호하지 않는다고 하셨는데, 개인 취향이라고 하셨다.
나는 '-s'를 붙이면 처음에 변수명을 봤을 때 List로 헷갈릴 것 같아서 'By'를 붙여 네이밍을 할 것 같다.
1-5. 🎯 final 키워드를 사용해서 값의 변경 막기
재할당을 막기 위해 final을 붙이는 연습을 하자!
이 부분은 지금도 습관화가 안 되어 있다 ㅠㅠ 😭
생성자 파라미터로 final을 붙이는 것, 재할당이 되면 안되는 객체 필드에 final 붙이기 등
자꾸 까먹고 있는데 다음 미션때 의식적으로 쓰도록 노력해야겠다.
2. 코치님의 구현 중 배울 부분
이 부분은 코치님의 실시간 코드 작성 강의를 보면서 배워야 할 점들을 기록해놨었다.
따로 설명은 하지 않고 기록해놓고 자주 봐야겠다.
- 요구사항을 나열해놓고 하나씩 정리하기
1. 요구사항 정리할 때 객체의 변수도 생각나면 정리하기
ex : '자동차는 이름이 있다.'
2. 추후 고려사항도 readme에 작성한다.
3. 객체 및 객체의 변수 조건도 정리한다.
ex) 자동차 이름
- 5자 이하만 가능하다.
- 공백이 불가능
- 콤마로 구분
4. 핵심 기능, 자동차 먼저 구현(자동차)
5. 기능 구현 시 기능 목록을 가져와서 하나씩 구현때마다 지운다.
6. 기능 구현, 테스트, 리팩토링은 왔다갔다하면 헷갈리니
리팩토링할 것은 TODO로 남겨두자.
7. 리팩토링 시에 기존 것은 남겨두고(헷갈릴 수 있으니)
리팩토링할 것을 추가로 만들자.
8. 객체 코드를 짤 때는 항상 도메인이 되어(도메인 중심) 생각하자.
9. 테스트 클래스 위에도 @DisplayName을 붙여서 공통되는 요소를 잡을 수 있다.
ex)
@DisplayName("자동차는")
class carTest { ... }
10. Assertions.assertDoesNotThrow(), Assertions.assertThatThrownBy 사용
예외가 발생하지 않고 성공 테스트 시 Assertions.assertDoesNotThrow() 사용
11. 기능을 처음 알았으면 학습 테스트를 작성하자.
ex) isInstanceOf()를 알았으므로 isInstanceOf 테스트를 하자.
12. 리스트 생성자 받을 시 복사본을 저장하자
public RacingGame(final List<Car> cars) {
this.cars = new ArrayList<>(cars);
}
* this.cars = cars로 받게 되면,
외부 컨트롤러에서 cars를 생성해서 파라미터로 넘겨줬다고 할 때,
RacingGame 내부 로직에서 cars의 값을 변경한다면
외부 컨트롤러에서 생성한 cars의 값까지 변경된다.
만약 외부 컨트롤러에서 cars를 생성하고, RacingGame을 생성한 후
더 이상 cars를 사용하지 않으면 상관없지만,
만약 cars를 재사용하게 된다면 의도치 않은 버그가 발생할 수 있다.
13. 객체를 설계할 때 애매한 부분이 있으면 객체 중심으로 말로 해보자.
14. final을 붙이는 것을 습관화하자.
생성자 파라미터나, 변수 등등 불변하는 요소에는 final을 붙여서 표시하자.
3. 자동차 경주 게임 리뷰어님 답변 & 피드백
3-1. 🎯 static 사용 관련
1단계에서 RandomGenerator 객체에서 생각없이 static을 사용해서
인스턴스를 생성하지 않고 바로 RandomGenerator의 기능을 사용했었다.
static에 대해서 찾아본 결과로 다음과 같은 장점이 있었다.
따로 인스턴스를 만들지 않고도 메모리 영역에서 꺼내서 사용할 수 있다는 장점이 있다.
리뷰어님은 해당 장점이 장점이 될 수도 있지만, 단점이 될 수도 있다고 하셨다.
나는 static을 생각할 때, 단순히 '성능' 관점에서만 바라봤었다.
static으로 선언하면 메모리 영역에 올라가므로, 따로 인스턴스를 만들지 않고
여러 곳에서 사용할 수 있으니 여러 곳에서 사용하는 객체면 static이 유용하겠다!
이렇게 생각만 했었는데, 리뷰어님이 객체의 '캡슐화'에 대해서 생각해보라고 방향을 알려주셨다.
그래서 생각해보니, static으로 객체를 선언하게 되면
'객체를 사용한다'라는 의미인 인스턴스화를 하지 않고도 객체에 접근할 수 있다는 문제가 있었다.
확실히 객체지향을 지향하는 자바에서 성능보다는 '객체지향적' 관점으로 바라봐서,
근거없는 static 사용은 지양해야겠다! 라고 생각했다.
3-2. 🎯 접근제어자 지정하기
OutputView outputView = OutputView.getInstance();
InputView inputView = InputView.getInstance();
RacingCars racingCars;
이렇게 Controller에서 접근제어자를 지정하지 않고 변수를 선언하게 됐다.
원인은, 리턴타입을 맞춰서 변수로 뽑아주는 단축키 Command + Option + V를 사용하다보니
접근 제어자를 까먹을 때가 많다.
(요즘에도 가끔씩 까먹는 것 같지만, 그래도 피드백을 받았던 기억이 나서 곧바로 고치고 있다! 😃)
접근 제어자를 생략했을 때는 'default' 접근 제어자로 지정이 된다.
default 접근 제어는 같은 패키지 안에 다른 객체들이 접근이 가능하므로,
객체의 캡슐화가 깨질 수 있어 private으로 객체를 보호해주는 것이 좋다!
3-3. 🎯 랜덤 값 테스트 - 전략 패턴
자동차 전진 기능에서 랜덤 값으로 4 이상이면 전진하도록 기능이 작동하고 있어서,
랜덤 요소 때문에 해당 기능을 테스트하기가 어려웠다.
리뷰어님께서 '전략 패턴'에 대한 참고 링크를 주셔서 전략 패턴을 사용하여
인터페이스의 구현체를 주입시키는 방식으로 구현하게 됐다.
랜덤 요소를 받는 클래스에 인터페이스를 받도록 구현하고,
실제 프로덕션 코드에서는 랜덤 값을 생성하는 구현체를 넣고
테스트 코드에서는 고정된 값을 생성하는 구현체를 넣어 테스트를 쉽게 진행할 수 있었다.
3-4. 🎯 NullPointerException의 발생 가능성 고려
if (sameMaxPositionCar != null) {
winners.add(sameMaxPositionCar);
}
같은 위치인 차를 찾는데, 없다면 null을 반환하도록 생각없이 구현했었다.
리뷰어님이 자바에서 null을 기반으로 판단이 이루어지는건 NPE의 위험성을 갖고있다고 말씀해주셔서
null이 아닌 boolean을 반환해서 판단하도록 리팩토링했다.
코드에 null을 기반으로 판단이 이루어지는 것을 지양하자!
3-5. 🎯 있을 법한 기능은 찾아볼 생각을 가지자!
private boolean isBlankName(String carName) {
return carName.equals("");
}
자동차 이름을 검증하는 부분에서 자동차 이름이 공백인지를 판단하기 위해
carName.equals("")을 사용했었다.
해당 사용에는 2가지 문제가 있었다.
1. NullPointerException 발생 가능성 존재
2. '이미 존재하는 기능'이라는 생각을 하지 못함
해당 기능은 String의 isBlnak() 메소드가 해주고 있었다.
이러한 기능이 존재할 것이라고 생각조차 하지 않았다는 것에 문제가 있었다.
'이미 존재하는 기능'이란, 수많은 개발자들이 문제를 겪고, 버그를 해결하여 나온 기능이다.
따라서 기능을 테스트할 시간과 노력을 덜어주고, 신뢰를 가지고 사용할 수 있다.
존재하는 기능을 사용하지 않고 내 머릿속에서 구현했더니 어떤 일이 발생하였는가?
NullPointerException이라는 거대한 오류를 일으킬 수 있는 코드를 작성하게 된 것이다.
존재하는 기능을 사용하면, 위와 같은 버그가 이미 해결된 상태일 것이다.
따라서, '있을 법한 기능'이라고 생각이 들면 꼭 찾아보는 습관을 들이자!
자동차 경주 게임 구현한지 거의 2주가 지난 지금 다시 피드백을 살펴보니 새로웠다.
피드백 받았는지 조차 까먹은 부분이 거의 많았다,,
나 자신을 반성하면서 역시 기록을 남겨야 한다는 사실을 또 다짐하면서 마무리한다! 🥳
'우아한테크코스 5기 > 미션 피드백 정리' 카테고리의 다른 글
[웹 자동차 경주] 배운 것 정리 (0) | 2023.04.25 |
---|---|
우아한테크코스 LV 1 - 사다리 타기 피드백 정리 (2) | 2023.02.28 |