[우아한테크코스] 2주차 프리코스 회고
https://github.com/woowacourse-precourse/javascript-racingcar-7/pull/202
[자동차 경주] 강은지 미션 제출합니다. by eunjikangi · Pull Request #202 · woowacourse-precourse/javascript-raci
javascript-racingcar-precourse 목차 1. 기능 및 입출력 요구사항 2. 요구사항 추가정의 3. 예외 처리 4. 기능 요구 사항 4.1. 동작 순서 4.2. 객체 1. 기능 및 입출력 요구사항 초간단 자동차 경
github.com
2주차 목표 설정
1주차 과제에서 부족했던 부분들을 보완하기 위해서 2주차 시작 전, 아래와 같은 목표를 설정해두고 지키기 위해 노력했다.
하지만 2주차에도 부족함이 많았다. 그래도 해야할 것들을 미리 정해놓고 시작하니까 개발 단계 마다 방향성도 잘 잡히고 1주차에 비해 많이 나아진 모습을 확인할 수 있었다.
[설계]
- 예외 처리 고려
- 상수 처리
- TDD - Jest TestCase 작성
[개발]
- JS API 활용
- 기능 명세 단위 Commit
- Vanila JS에서 함수 type 명시
[PR 및 코드리뷰]
- 다이어그램 첨부
- 1주차때 기록한 BP 사례 참고하여 PR 작성
[공통]
- 내가 잘 알아볼 수 있게 배운 내용 잘 정리하기
설계
예외 처리 세분화
1주차 때는 기능 구현에 정신이 팔려 예외처리 세분화 작업을 후순위로 미워두다가 결국 만족할 만큼의 예외 처리를 하지 못했다.
하여 이번 과제에서는 설계 단계에서 에러 상황과 메세지, 그리고 검사 시점을 미리 결정해놓고 개발을 시작하기 위해 많이 노력했다.
User Input 종류에 대해서 들어올 수 있는 에러를 세분화 해 놓으니 중복으로 검사 될 항목과 Exception 개수가 한눈에 보여서 개발할 때도 편했고, TC를 만들 때도 편했다.
다음엔 에러 메세지 변수명을 표에 추가하고, 예외처리 번호 매핑을 한다면 문서와 코드가 잘 연결된 프로그램을 만들 수 있을 것 같다.
Need Work
그럼에도 불구하고 여전히 빼먹은 예외가 있었다. (자동차 이름이 A,,B 로 입력되는 경우!)
👾 user input은 매번 비슷한 유효성 검사를 할 것 같은데, 어떤 항목들을 주로 체크하는지 학습을 해봐야겠다.
유효성 검사 시점
이 부분에 대한 고민이 많았다. 나만 고민이 많은 줄 알았는데, 웹 개발에서는 꽤 뜨거운 주제였다.
초반에 User Input을 받자마자 유효성 검사를 전부 진행하면 코드는 깔끔해진다.
하지만 User Input으로 받은 string 데이터가 어디까지 가공되어야 할지 확실하지도 않고, User Input하는 곳에서 다 가공하자니 책임이 분리가 안되는 이슈가 있다. (가공 : Delimeter로 Split을 진행하는 것을 표현 함)
하여 유효성 검사 책임에 대한 여러 글을 읽어보고, 아래 주장에 대해 깊이 공감을 했다.
그렇기 때문에 각 레이어마다 Validation을 해줘야 하는 범위가 있어야 한다고 생각합니다.
각 레이어의 책임과 목적에 맞게 Validation이 되어야 하고 Clean Architecture에서도 언급하듯이 각 레이어의 책임이 분산되어야 제대로 유효성 검사를 할 수 있습니다.
그래서 클래스별로 본인들이 필요한 데이터에 대해서, 딱 지금 필요한 만큼, 할 수 있는 만큼만 유효성 검사를 하도록 설계를 진행했다.
👾 내가 지금 생각할 수 있는 방법 중에는 제일 괜찮은 방법 같았는데, 다른분들 리뷰를 보며 더 나은 방법이 있는지 확인 해봐야겠다.
Ref
[Spring] Validation은 어디서 해야할까?
하나의 작은 프로젝트(?)를 만들다 보면 슬슬 고민이 되는 부분 바로 유효성검사이다! 크게 Controller, Service, Repository, Entity(model)로 나눠서 작업하는 경우가 많은데 그렇다면 요청에서 넘어온 값들
velog.io
https://systemdata.tistory.com/82
Validation 책임과 범위는 어떻게 가져가야할까?
목차개요Client vs ServerLayer별 책임3-1. Controller Layer3-2. Service Layer3-3. Repository Layer결론1. 개요개인 프로젝트를 진행하다 보니 작은 고민이 생겼다.과연 어떤 Layer에서 Validation을 체크하는게 맞을까?
systemdata.tistory.com
https://blog.frankdejonge.nl/where-does-validation-live/
Where does my validation live?
This is a question I've received over and over again, a question that does not have a single answer. Sometimes when I explain how I approach this I get surprised reactions, so I figure I might as well share it in a blog post and see what other people think
blog.frankdejonge.nl
[개발인턴] Validation의 적절성
안녕하세요. R&D 개발 그룹 인턴 강소라입니다. 지금까지 Validation을 생각하며 코드를 작성한 적이 있었던가? Validation 자체에 대해 고민해 본 적이 있는가? 돌아보면, input값이 들어왔는지 확인하
blog.barogo.io
기능 목록 작성
1주차에는 기능을 와다다다 구현해서 한번에 커밋해놓고 코드를 Temp에 옮긴 뒤 기능별로 쪼개어 커밋을 하는 바보같은 짓을 했었다.
이번엔 그러지 않기 위해 기능 요구 사항을 (하나의 기능 == 함수 == 커밋 단위) 라고 생각하고 꼼꼼하게 작성하기 위해 노력을 해봤다.
기능 목록 순서대로 체크 박스를 하나씩 추가하면서 구현을 하니까, 개발 속도가 향상되고 책임이 분리되어 리팩토링 하기도 편했다.
코드리뷰를 확인해보니 다른 분들은 이미 (기능 커밋 + Readme 기능 목록 체크) 를 한 세트로 진행하고 계셨다. 나만 몰랐었구나 !!!
Need Work
1) '유효성을 검사한다' 라고 뭉뚱그려 적기 보단, 어떤 검사가 진행되는지 디테일하게 적었으면 더 좋았을 것 같다.
2) '자동차 정보 관리' 가 아닌, '경주 시작, 우승자 출력' 같이 Activity 중심으로 적혔다면 더 좋았을 것 같다. 지금은 너무 객체 중심으로 쓰였다. 개발 목록이 아닌 기능목록 임을 유의하자.
상수 처리
에러 메세지와 정규표현식을 util에 상수화 해놓고, 필요한 부분에서 가져다 쓰도록 했다.
코드가 한결 깔끔해졌다.
Need Work
👾 하지만 상수 Scope에 따른 선언 위치 그리고 함수를 상수화 하는 부분에 대한 학습이 필요하다고 생각했다. 이건 다른 포스팅으로 따로 적어봐야겠다.
TDD
예외처리에 대한 TDD는 잘 진행했으나, 기능 단위 그리고 Class 단위에 대한 TDD는 거의 진행하지 못했다.
기능 구현도 물론 중요하지만 좋은 개발 습관을 배우기 위해 프리코스에 참여한 것임을 다시 명심하자. 하던것만 하고 가면 프리코스 하는 의미가 없다.
개발
커밋 메세지
좋은 git 커밋 메시지를 작성하기 위한 7가지 약속 : NHN Cloud Meetup
git커밋
meetup.nhncloud.com
'본문은 어떻게보다 무엇을, 왜에 맞춰 작성하기'
커밋 단위는 개선이 되었으나, 본문에 의미 있는 커밋메세지를 작성하진 못했던 것 같다.
역시나 조급함이 원인이었던 것 같다.
코드 개발 전에 본문과 제목을 미리 작성해놓는 방법도 좋을 것 같다. 그러면 내가 현재 개발하고자 하는 기능에 대해 더 자세히 생각해볼 수 있게 될 것 같다.
JavaScript API 활용
1주차엔 자바 스크립트 API 활용을 잘 하지 못했다. 어떤게 있는 지도 잘 몰랐다.
하지만 2주차때는 조금 익숙해져서, mdn 문서를 열심히 돌아다니고 구글링을 해가며 적절한 API를 사용하기 위해 노력했다.
getWinner() {
const maxDistance = Math.max(
...this.carList.map((car) => car.getDistance()),
);
const winners = this.carList
.filter((car) => car.getDistance() === maxDistance)
.map((car) => car.getName());
return winners;
}
- Math.max() 를 활용한 max 값 추출
- filter() 를 활용하여 max값에 해당하는 car 요소 추출
- map() 을 활용하여 filtering된 car의 name만으로 array 재생성
if (new Set(carNameList).size !== carNameList.length) {
throw Error(ERROR_MSG.DUPLICATE_CAR_NAME);
}
- Set() 을 사용하여 중복된 요소를 제거한 뒤, 원래 Array의 length와 비교하여 중복된 요소가 있는지 검사 함
renderCarInfo(car, distance) {
this.print(`${car} : ${'-'.repeat(distance)}`);
}
- distance를 출력할 때 string.repeat() 을 활용하여 '-' 가 distance만큼 반복해서 출력되게 함
Vanila JS에서 함수 type 명시
javascript에서 'declare'를 활용해보려고 했으나, 나는 아직 TypeScript가 익숙치가 않아서 요걸 사용해도 될 지 잘 모르겠다.
언제 사용되는 건지 좀 더 학습한 뒤에 제대로 알고 쓰고 싶어서 남겨두는 중이다.
배운점
Jest
- test.each() 를 학습하고 써먹어보았다.
- mock함수도 학습했는데 써먹진 못했다. 3주차에 써먹자.
- SpyOn도 이해는 했는데(과연?) 써먹진 못했다. 3주차에 써먹자.
비동기처리
비동기 처리에 대한 이해가 부족하여 나름대로 테스트를 진행해보며 학습했다.
혼자 볼 목적으로 정리해둔거라 좀 부끄럽긴 하지만 일단 기록 목적으로 회고에 첨부해본다.
비동기 코드
FastCampus 강의를 듣고 비동기 코드의 종류에 대해 배웠다.
setTimeout, XML어쩌구는 그 자체로 비동기 함수이구나!
비동기 기능을 통해서 기다리던 기능이 완료된 이후, 특정 기능을 수행하고 싶을 때는 Callback함수를 전달해주면 된다.
setTimeout이라는 비동기 함수가 수행되면 내부에서 Callback함수를 호출하여 특정 기능을 수행한다.
function fetchData(callback) {
console.log("1. fetchData:");
setTimeout(() => {
const data = "비동기 데이터";
console.log("3. setTimeout:", data);
callback(data);
}, 0);
}
fetchData((data) => {
console.log("4. Callback :", "당신은", data, "입니다");
});
console.log("2. 콘솔 로그 실행");
Promise
Test1 : Promise 후속처리 따로 따로 해보기 → 실패
- 다들 then()다음에 chain으로 catch 를 사용하길래 나는 따로 떼어내서 써봤다. 그러니까 실패함.
- 한번 처리하고나면 소멸되나봄. 그래서 멱살 잡고 끌고가야 되나보다.
- then()이나 catch() 같은걸 호출하고 나면 Promise (자기자신)를 다시 return하는데, 그걸 받아주지 못하면 소멸되나보다. 그래서 그놈을 다시 잡아다가 호출해줘야하나보다.
const promise1 = new Promise((resolvefunc, rejectfunc) => {
// 비동기 작업
rejectfunc();
});
promise1.then(() => {
console.log("성공");
});
promise1.catch(() => {
console.log("실패");
});
promise1.finally();
Test2 : Return된 Promise를 변수에 가둬서 재사용하기
- 그래서 return 값을 변수로 잡아둬봤다. 그러니까 잘 된다 히히 기분좋ㅇ앙 ><
const promise1 = new Promise((resolvefunc, rejectfunc) => {
// 비동기 작업
rejectfunc();
});
const promise2 = promise1.then(() => {
console.log("성공");
});
promise2.catch(() => {
console.log("실패");
});
Test3 : executor 함수 내에서 exception이 발생했을 경우, rejectfunc를 호출했을때랑 동일하게 동작한다.
/*
1. resolve 는 executor 내에서 호출할 수 있는 또 다른 함수입니다.
resolve 를 호출하게 된다면 "이 비동기 작업이 성공했어!" 라는 뜻입니다.
2. reject 또한 executor 내에서 호출할 수 있는 또 다른 함수입니다.
reject 를 호출하게 된다면 "이 비동기 작업이 실패했어…" 라는 뜻입니다.
*/
// Promise 생성자의 param에 들어가는 화살표함수는 executor라고 칭한다.
const promise1 = new Promise((resolvefunc, rejectfunc) => {
// 비동기 작업
throw Error("error"); // 여기서 바로 return
resolvefunc("data"); // 수행 X
});
const promise2 = promise1.then((data) => {
console.log("성공", data);
});
const promise3 = promise2.catch((data) => {
console.log("실패" + data);
});
promise3.finally(() => {
console.log("마무으리");
});
Callback 함수를 Promise 를 써서 처리해봄
function setTimeoutPromise(delay) {
console.log("Promise 생성", delay);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(delay);
}, delay);
});
}
console.log("시작");
setTimeoutPromise(1).then((data) => {
console.log("", data);
});
setTimeoutPromise(3).then((data) => {
console.log("", data);
});
setTimeoutPromise(2).then((data) => {
console.log("", data);
});
console.log("끝");
// 결괴
시작
Promise 생성 1
Promise 생성 3
Promise 생성 2
끝
1
2
3
Async, Await
https://springfall.cc/article/2022-11/easy-promise-async-await
async function asyncFunc(input) {
if (input) return "Hello";
throw Error("Bye");
}
// 호출부
asyncFunc(true).then((data) => {
console.log("성공", data);
});
asyncFunc(false)
.then((data) => {
console.log("성공", data);
})
.catch((data) => {
console.log("실패", data);
});
- async 함수의 리턴 값은 무조건 Promise 입니다 !
- resolve 는 return으로 대신하고
- reject 는 throw로 대신함 !!
await
- await 는 왠지 wait 이 있으니까 기다리라는 뜻 같습니다. 맞습니다. await 는 Promise 가 fulfilled 가 되든지 rejected 가 되든지 아무튼 간에 끝날 때까지 기다리는 함수입니다.
- 문법적으로 await [[Promise 객체]] 이렇게 사용합니다.
async function asyncFunc(input) {
if (input) return "Hello";
throw Error("Bye");
}
async function awaitTest(param) {
const promise = asyncFunc(param);
try {
const value = await promise;
console.log(value);
} catch (e) {
console.log(e);
}
}
// 호출부
awaitTest(true);
awaitTest(false);
우와 이렇게하니까 호출부가 훨씬 깔끔해진다!!! 위에 함수랑 결과는 동일함.
👾 2주차에 느낀 것들
리뷰로 더 얻어가자
같은 기능을 구현한 다른 사람들의 코드를 확인하는 기회는 흔치 않다.
더군다나 고수들이 참 많다.
배워갈점들이 도처에 널려있는데 나는 기능구현하기 급급해서 리뷰를 잘 하지 못했던 것 같다.
앞으로 남은 시간들이 있으니 리뷰를 통해 더 성장할 수 있도록 해야겠다.
나는 과제를 하러 온 것이 아니다. 고민하고 배우러 온 것이다.
모르는 것은 대충 넘기며 돌아가는 코드를 구현하는 데 급급한 내 모습을 발견했다.
클린코드, 설계에 대한 깊은 고민이 필요한 시점에 생각하는 것을 미뤄두는 내 모습을 발견했다.
모르는 것들을 정면으로 마주하자 !
코드리뷰를 통해 많이 배우고, 많이 나누자 !!!!!!!!!!!!
회고의 중요성
아직 부족한 점이 많지만 1주차에 비해 조금은 나아진 것 같다.
경험을 돌아보고 개선할 점을 찾고, 그 부분을 채우려고 의식적으로 노력했기에 가능했던 것 같다.
회고가 이래서 중요한가보다.
이번에도 회고를 작성하면서 생각이 정리되고 Action Item들이 정리가 되었다.
까먹지 말자 회고의 중요성!
🫸🏻🫷🏼앞으로 힘 써볼 것들 정리
- 디버거 활용
- 매일 학습내용 깔끔하게 정리 → 하루에 조금씩 잘 정리해서 마지막날에 폭탄 맞지 않도록 해라!
- 리뷰 하루에 2개씩 하기 → 리뷰를 통해 배우고 소통하자. 커뮤니케이션 스킬을 늘리자, 나의 무지를 깨닫자!!!!
- TDD 구현 더 신경쓰기 → mock, spyon 활용, 작은 단위로 테스트
- 더 깊게 사고하기 (클린코드, 더 나은 코드구조)
- PR 메세지 개선 (다이어그램 첨부, 리뷰 시 중점적으로 봐줬으면 하는 부분 추가, 회고 미리써서 추가)
- '어떻게' '왜'에 초점을 맞추어 과제를 수행하기
- 설계/구현/리팩/회고 시간 분배하기 → 매번 리팩과 회고는 쫓기듯 수행하게 됨. 모든 과정이 다 중요하므로 의식적인 시간 분배가 필요함
- Validation 처리방법 / 상수Scope 고민하고 학습하기
[우아한테크코스] 2주차 과제 코드 리뷰 돌아보기
많은 분들께서 정성스럽게 코드리뷰를 남겨주셨다.가지고 계신 지식을 링크까지 첨부해가며 공유해주시고, 칭찬도 아끼지 않는 모습에 너무 감사했고 감동도 많이 받았다.나도 그들에게 그런
ag-g.tistory.com