전체 보기
🍀

15, 16일차_코드 리뷰를 통해 성장하기(1)_다른 사람의 코드 읽으면 배운 것 정리

작성일자
2023/11/02
태그
DIARY_DEVELOP
프로젝트
PreCourse
책 종류
1 more property

1. 다른 사람의 코드를 읽으며 배운 것

다른 사람들의 코드를 최대한 열심히 읽어보았다. 그 과정에서 코드 리뷰를 통해 논의한 것들에 대해 정리한다.
리뷰 주고 받은 분들 PR

일급 컬렉션은 본인이 감싸고 있는 콜렉션을 반환해도 될까?

결론부터 말하자면 된다. 일급 컬렉션의 정의가 중요한데 일급컬렉션이란 Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 상태를 말한다. 그렇다면 여기서 Collection은 어떤 것들이 있을까? 우리가 흔히 쓰던 List, Set, Map, Queue를 이야기할 수 있다.
그럼 일급컬렉션에서 본인이 Wrapping하고 있는 Collection을 반환해도 될까? 되긴 된다. 하지만 주의할 점이 있다. Collection의 특징을 생각해보면 좋은데, 예를 들어 List의 경우 add로 충분히 다른 곳에서 조작가능하다. 따라서 일급 컬렉션에서 Collection을 반환하되 Collections.unmodifiableList(list)와 같이 감싸 반환해야 한다. 이렇게 보내면 다른 곳에서 add를 막아 Collection을 일급 컬렉션 외에 다른 곳에서 조작하는 걸 막아줄 수 있다.
마무리하며 일급 컬렉션에서 컬렉션 객체를 다룰 때 특징을 정리하자면 아래와 같다.
컬렉션 객체를 변수나 매개변수에 할당할 수 있고, 반환값으로도 사용할 수 있음
컬렉션 객체는 필요한 경우 메서드에서 생성할 수 있음
컬렉션 객체는 다른 객체와 동등한 지위 지님

뷰에서 처리할 예외와 모델에서 처리할 예외를 어떻게 구분할까?

mvc 패턴이라 전제하고 예외 처리(검증 로직)를 어디서 해야 할지 논의했다. 물론 사람마다 다르겠지만 내가 내린 결론은 아래와 같다. 내가 정답은 아니기에 이건 참고 정도만 해도 좋을 거 같다.
뷰와 모델이 가질 검증 책임을 각각 어디까지로 둘지 생각해보았다. 그리고 아래와 같이 나눠줬다.
뷰 : 타입 변환 중 생기는 예외 처리 (String ->intString -> List<String> 등)
모델 : 비즈니스 로직과 관련한 예외 처리 (횟수가 1회 이상인지이름이 5글자 이하인지중복된 이름인지 등)
사실 비즈니스 로직과 관련한 예외처리를 어디까지로 볼지 애매한데 나는 api 개발할 때를 떠올렸다. 보통 requestBody의 각 필드에 대해 아무리 유효성 검증을 안한다해도 타입은 지정해두고 받아온다.
그래서 뷰의 역할도 마찬가지로 단순히 문자열을 받는 거에서 더 나아가 딱 타입 변환까지만 확장해줬다.

Java17부턴 긴 문자열을 가독성 좋게 쓸 수 있다 (특히 테스트코드에서)

원래라면 개행 문자(\n)를 넣었을 텐데 아래처럼 사용해줄 수도 있다.
String expectedOutput = """ car1 :\s car2 : - car3 : -- """; String actualOutput = outputStream.toString(); assertThat(actualOutput).isEqualTo(expectedOutput);
Java
복사

테스트가 어렵다면 코드를 바꾸는 게 좋을까?

테스트 하기 좋은 코드여야 설계가 잘 되어 있단 뜻이다. 라는 말이 있다. 우리가 구현을 마치고 테스트 코드를 짜려는데 기존 코드를 바꾸지 않으면 테스트를 짤 수 없는 상황을 마주할 때가 있다. 이땐 어떻게 해야 합리적인 대처법이라 할 수 있을까? 이에 대해 나는 아래와 같이 정의하고 싶다.
1.
테스트만을 위한 코드를 프로덕션 코드에 추가하는 건 막자
2.
확장성을 고려 못한 코드라고 판단되면 프로덕션 코드를 수정할 수 있다
→ 이 경우 테스트를 위해 코드 바꾸는 순간 앞뒤가 바뀌는 거 아니에요? 라고 물을 수도 있다. 하지만 내가 말하는 코드 수정은 프로덕션 코드를 확장성 있게 리팩토링하는 것까지만 한정한다고 답하고 싶다.
이번 2주차 과제에서 아마 많은 사람들이 동시에 고민했을 부분이 랜덤 관련한 부분일 거 같다. 자동차가 이동할지 말지를 결정할 때 프로덕션 코드에선 랜덤 숫자를 이용하는 반면, 테스트 코드에선 랜덤 숫자를 이용하지 않기에 나 역시 그 차이를 메꾸는 데에서 고민했다.
처음엔 숫자를 생성하는 인터페이스를 만들고, 랜덤 숫자를 생성하는 클래스와 테스트용 숫자를 생성하는 클래스 두 개가 이를 구현하게 만들어줬다. 이 코드의 문제점이 뭘까? 테스트 용이성만을 위해 코드(클래스)가 추가됐단 점이다.
이를 해결하기 위한 간단한 방법으론 Stub을 사용해 적어도 테스트용 숫자를 생성하는 클래스를 프로덕션 코드에서라도 분리해주는 것이다. 그렇게 되면 1번 규칙을 지킬 수 있다. 이에 대해선 바로 다음 문항에 적어두었다.
또 다른 방법으론 2번 규칙을 적용한 것으로 전략 패턴을 사용하는 것이 있다. 이때, 전략 패턴을 프로덕션 코드엔 적용하지 않고 테스트코드에만 적용하는 것이다. 그렇게 되면 프로덕션 코드에선 랜덤 숫자를 생성하는 게 고정되는 태초의 설계대로 구현될 것이고, 테스트코드에선 테스트용 숫자를 생성하거나 다른 방식으로도 숫자를 생성할 수 있게 유동적인 전략패턴을 적용한 설계가 적용될 것이다.
public class Car { private final String name; private final MoveStrategy moveStrategy; private int position = 0; public Car(String name) { validateName(name); this.name = name; this.moveStrategy = new RandomMoveStrategy(); // 프로덕션에서 사용 } public Car(String name, MoveStrategy moveStrategy) { validateName(name); this.name = name; this.moveStrategy = moveStrategy; // 테스트 시 사용 }
Java
복사
//test static class FixedMoveStrategy implements MoveStrategy { private List<Boolean> movables; private int index = 0; public FixedMoveStrategy(List<Boolean> movables) { this.movables = movables; } @Override public boolean determineMovable() { if (index >= movables.size()) { return false; } return movables.get(index++); } } ... Car car = new Car("test", new FixedMoveStrategy(List.of(true, false, true)));
Java
복사

Stub을 이용해 테스트코드를 위한 코드 만든다

Stub이란 원래의 구현을 단순한 것으로 대체하는 것을 말한다. 즉, 테스트에 맞게 단순히 원하는 동작을 수행하게 해주는 것이다. 원래 프로덕션 코드에선 랜덤 숫자를 만들어주던 반면, 테스트 코드에선 단순히 iteration을 돌려 숫자를 만들게 하던가, 아님 생성자에서 들어온 숫자 그대로를 뱉게 해주던가 하는 식으로 사용할 수 있다. 주의할 점은 아래 stub들은 프로덕션 패키지가 아닌 테스트 패키지에 넣어주자.
또 , 더 나아가서 stub에서 하는 일이 단순히 들어온 숫자를 그대로 뱉는 일이라면 함수형 인터페이스를 사용하는 것도 추천한다. 따로 구현체 만들지 않고도 람다로 () -> List.of(1, 2, 3, 4, 5, 6)와 같이 사용할 수 있기 때문이다.
public class Car { private final String name; private final IntGenerator intGenerator; private int position = 0; public Car(String name, IntGenerator intGenerator) { validateName(name); this.name = name; this.intGenerator = intGenerator; }
Java
복사
iteration 돌려 반환하는 코드
//test public class SequentialIntGeneratorStub implements IntGenerator { private final List<Integer> numbers; private int index = 0; public SequentialIntGeneratorStub(List<Integer> numbers) { this.numbers = numbers; } @Override public int pickNumber() { int number = numbers.get(index); index++; if(index >= numbers.size()) { index = 0; } return number; } } ... cars = Cars.createCars(carNames, new SequentialIntGeneratorStub(List.of(4,4,3)));
Java
복사
단순 숫자 반환하는 코드
//test public class CustomIntGeneratorStub implements IntGenerator { private final int number; public CustomIntGeneratorStub(int number) { this.number = number; } @Override public int pickNumber() { return number; } } ... cars = Cars.createCars(carNames, new CustomIntGeneratorStub(3));
Java
복사

커스텀 sort를 할 수 있다

C++에서만 사용해봤었는데, java에서도 sort를 커스텀할 수 있다.

2. 이번주 코수타/공통 피드백에서 인상 깊었던 것

인상 깊었던 부분만 정리하고 간단히 내 생각()을 덧붙이겠다.
README.md 상세히 작성
 : 이번주에 정말 엄청난 리드미를 작성한 분을 봤다. 그 중 인상깊었던 부분은 고민한 부분과 지난주코드리뷰에서 나왔던 부분을 적용한 부분을 적어둔 건데 나도 차용하면 좋을 거 같다
기능 목록 재검토
 : 1주차에 기능목록을 함수, 변수까지 설계한 채로 작성했었다. 그렇게 했더니 설계에 너무 많은 시간이 들었고 생각보다 변경할 부분도 많았어서 이번 2주차에는 딱 클래스단위로 필요한 기능들과 예외처리만 써두는 식으로 구성했다. 클래스도 사실상 역할을 구분하는 정도로만 설계했다. 그렇게 했더니 feature, test 단위 나누는 부분에도 도움을 받을 수 있었고 설계에 딱 적당한 시간만 들었다.
기능 목록 업데이트
 : 살아있는 문서라는 말이 인상깊었다. 이번 2주차에서 기능 목록을 일단 작성해두고 구현을 시작하니 위치나 상세 내용이 달라진 기능들이 몇 개 있었는데 이가 나쁜 일이 아니란 걸 깨달았다. 3주차에도 완벽한 기능 목록을 추구하기보단 부담가지 않는 선에서 적당히 기능 목록을 작성하고 구현을 하며 조금씩 변경해 살아 있는 문서를 가지려 노력해야 겠다
하드코딩된 값 쓰지 않기
 : 하드코딩된 값을 매직 넘버라고도 부르는데, 이들은 상수로 처리해주는 게 좋다. 이때 문자열까지 상수 처리를 해줘야 하는지 살짝 고민됐는데 해주기로 했다
변수 이름에 자료형 쓰지 않기
 : 부끄럽게도 1주차 내 코드에 자료형을 이름에 쓴 코드가 있다…….. 이런 표기법을 헝가리식 표기법이라고도 하는데, 예전에 ide가 발전하지 않았을 때 변수의 자료형을 쉽게 확인하고 싶어 만들어진 표기법이다. 현재는 ide가 발전해 마우스를 대기만 해도 자료형을 알려주니 쓸 필요가 더는 없다. 헝가리식 표기법은 쓰지 말자
한 함수가 한 가지 기능만 담당하게 하기
 : 한 가지 일이라는 게 애매하게 들릴 수 있다. 안내 문구 출력, 사용자 입력, 유효값 검증 등을 여러 가지 일이라고 정의해줘서 도움이 됐다. 안내 문구 출력과 사용자 입력 받는 걸 지금껏 한 함수에 넣어줬었는데 앞으론 분리해줘도 좋을 거 같다.
테스트를 작성하는 이유에 대해 본인의 경험 토대로 정리해보기
 : 이에 대해 지난 2주차 소감에 담았었는데 요약하자면, 각각의 테스트가 독립적이라 의존적인 코드들을 각각 분리해 동작 여부를 보장할 수 있다라는 점이었다. 피드백에선 ‘단지 기능 점검을 위해 테스트를 작성하는 게 아닌 피드백, 학습도구로도 활용할 수 있다’고 나와있었는데 이 역시 동의한다. 테스트코드는 내 코드에 대한 주석 역할도 일정부분 해주기 때문이다. 또 테스트코드를 같이 작성하니 확장에 열린 코드를 짜는데에 도움이 되었다.
처음부터 큰 단위의 테스트 만들지 않기
 : 내 코드에 대한 빠른 피드백을 위해 테스트를 작게 만들라는 말이 인상깊었다. 이 작게 만든다는 것의 단위가 기능인지는 잘 모르겠다.
테스트 활용 팁
배열 반환 시 → assertj의 contains()와 containsExactly() 활용해 반환값 확인하기
테스트 의도 → DisplayName으로 드러내기
exception → assertThatIllegalArgumentException() 제공되니 사용해보기
중복 코드 제거
ParameterizedTest 이용
입력값에 따라 값이 다른 경우 CsvSource 이용

Today in 프리코스

TIL 작성하기
준비
코수타 보고 정리하기
공통 피드백 내용 정리하기
동반성장
코드 리뷰 하기
내 코드 리뷰 답변 달기
이번주 계획
목,금
지난 과제 리뷰
이전 과제 발표 스터디 참여 + 코수타 듣기
토,일
기능 명세 작성 + 기능 구현
커뮤니티 함께-자라기 참여
기능 구현 + 테스트 코드 작성
리팩토링 + 테스트 결과 확인
회고 작성 + 검토 + 제출
201 Created 자바 스터디 글 작성
Search
1일차_둘러보기, 환경설정하기
2023/10/19
DIARY_DEVELOP
1일차_둘러보기, 환경설정하기
2023/10/19
DIARY_DEVELOP