이번주부터 부트캠프에서 배운 것들을 토대로 매주 후기를 작성하려고 한다.
이 글을 작성하는 목적은 매주 회고를 통해 내가 어떤 것들을 배웠고, 느꼈는지 기록으로 남기는 것이다.
머릿속에만 있던 것들을 한번 더 정리하며, 글을 작성하면서 내것으로 체화가 될 것이라 생각한다!!!
나중에 부트캠프가 다 끝나고 나를 돌아봤을 때 어떻게 성장해왔는지도 글을 통해 알 수 있을 것이다. 그럼 이제 시작해 보겠다.
이번주 과제
✅ 과제 주제
이번주 과제는 직접 유틸 함수를 구현해보는 것이었다.
보통 우리는 이미 완성되어 있는 유틸리티 함수를 아무 생각없이 편하게 쓴다.(나만 그런가??;;;;)
구현하는게 귀찮은 점도 있지만, 내가 구현한 것이 이미 구현한 것보다 완성도가 떨어지는 것을 알고 있어서 그런것일 수도 있다.
그래도 직접 유틸 함수를 구현해봄으로써 내가 지금까지 썼던 유틸 함수들이 내부적으로 어떻게 돌아가는지 더 와닿는 경험이 되었다.
✅ 사전에 알아야 하는 개념
과제에 들어가기 앞서, 다음과 같은 개념을 알고 있어야 이번 과제를 수행하는데 문제가 없었다.
Callback Function
콜백함수란 무엇일까?
MDN에서 정의된 의미는 다음과 같다.
A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.
여기서 2가지 중요한 키워드가 보인다.
- 다른 함수에 인수로 전달이 되어야 한다.
- 전달된 함수 내에서 실행이 되어야 한다.
이러한 2가지 조건을 모두 만족하는 함수를 콜백 함수라고 합니다.
그럼 아래 예시를 살펴보자.
function sayHello() {
return "Hello, ";
}
function greeting(helloMessage, name) {
console.log(helloMessage() + name); //2. helloMessage라는 파라미터로 받아서 실행 됐다.
}
// 1. `sayHello`라는 함수가 인수로 `greeting` 함수에 전달 됐다.
greeting(sayHello, "World!");
// Hello, World!
위 예시를 보면,
sayHello
라는 함수가 인수로greeting
함수에 전달이 됐고,greeting
함수 내에서helloMessage
(sayHello
) 파라미터가 실행이 됐다!
앞서 말한 2가지 조건이 만족했기 때문에 sayHello
함수는 콜백함수라고 볼 수 있다.
쉽게 표현해서 자신을 호출한(call) 곳에서(back) 실행된다고 생각하면 될 것 같다.
Recursive Function
함수를 구현하다 보면 큰 목표를 하나의 동일하면서 간단한 작업으로 쪼갤 경우가 필요할 수가 있다.
간단하게 말하면 함수가 자기 자신을 호출할 수도 있는데, 이를 재귀함수 라고 부른다.
재귀를 사용하면 코드 가독성이 올라가지만, 잘못 사용하면 무한 루프에 빠질 수 있기 때문에 꼭 기저사례(탈출 조건)를 명시해주어야 한다.
예시를 보는게 더 이해가 빠를 것 같다.
function recur(){
if(...){
//탈출 조건 명시!!!
}
... //비즈니스 로직 수행
recur(); //다시 자기 자신 호출!
}
recur(); //처음 호출
프로그래밍에 익숙하지 않는 사람이라면 재귀가 조금 어렵게 느껴질 수가 있다.(사실 나도 아직도 헷갈린다…)
그래도 디버깅을 통해 익숙해지다면 조금씩 이해가 가니 포기하지 말고 익숙해지자!!
✅ 도전이 되었던 부분
내가 구현했던 몇가지 함수를 예로 들어서 보여주는게 좋겠다.
reduce()
reduce
함수의 정의는 MDN에서 살펴보면 다음과 같다.
The
reduce()
method ofArray
instances executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
배열의 prototype 메서드로써, 배열의 각 요소에 대해 사용자가 제공하는 callback
함수를 순서대로 실행하여, 이전 요소에 대한 계산의 반환 값을 전달한다고 한다.
호출하는 형태의 경우는 다음과 같다.
reduce(callbackFn)
reduce(callbackFn, initialValue)
여기서 말하는 callbackFn
는 다음과 같은 인수를 받는다.
accumulator
: 배열을 돌면서 누산되는 값currentValue
: 현재 값currentIndex
: 현재 인덱스
🚨주의사항
A value to which
accumulator
is initialized the first time the callback is called. IfinitialValue
is specified,callbackFn
starts executing with the first value in the array ascurrentValue
. IfinitialValue
is not specified,accumulator
is initialized to the first value in the array, andcallbackFn
starts executing with the second value in the array ascurrentValue
. In this case, if the array is empty (so that there's no first value to return asaccumulator
), an error is thrown.
만약 initialValue
주어지지 않는 경우, accumulator
는 배열의 첫번째 값을 초기화되고, callbackFn
은 배열의 두 번째 값으로 currentValue
로 실행을 시작한다.
예를 보면서 한번 이해해보자.
const array = [1, 2, 3, 4, 5];
function reducer(acc, cur, index) {
const returnVal = acc + cur;
console.log(
`accumulator: ${acc}, currentValue: ${cur}, index: ${index}, returnVal: ${returnVal}`,
);
return returnVal;
}
array.reduce(reducer); //
위 함수의 동작 과정을 천천히 표를 통해서 살펴보자.
accumulator | currentValue | index | returnVal | |
---|---|---|---|---|
첫번째 호출 | 1 | 2 | 1 | 3 |
두번째 호출 | 3 | 3 | 2 | 6 |
세번째 호출 | 6 | 4 | 3 | 10 |
네번째 호출 | 10 | 5 | 4 | 15 |
initialValue
을 인수로 넘겨주지 않았기 때문에 배열의 첫번째 요소(index = 0
) 가 초깃값이 된다.- 배열의 반복문이 시작된다.
- 배열의 2번째 요소(
index=1
)부터 반복문이 시작된다. accumulator
와currentValue
더한 값을returnVal
에 넘겨준다.- ⇒ 1 + 2 = 3, 3을 리턴!!
- 배열의 마지막 요소까지 위의 로직을 반복한다.
- 최종
returnVal
값을 함수를 호출했던 곳으로 리턴한다.
위에 내가 순서대로 적었던 것이 의사코드(pseudocode)이다.
의사코드는 실제 로직을 작성하기 전에 각 단계를 논리적으로 표현하는 것을 말한다.
개발자마다 쓰는 용어나 이해하는 범위가 다르겠지만, 개발자의 연차와는 상관없이 누구라도 쉽게 이해할 수 있게 작성하는 것이 중요하다.
의사코드가 중요한 이유는, 내 생각이 논리적으로 정리가 안되면 코드를 작성하다가도 무엇을 놓치고 있는지, 로직에서는 허점이 없는지 찾기가 쉽지 않기 때문이다.
결국 프로그래밍은 나의 생각을 도구(프로그래밍)을 통해서 표현을 잘하는게 목적이기 때문에 앞으로도 나는 의사코드를 쉽고 이해하기 좋게 작성하도록 노력할 것이다.
실제 reduce() 구현
이제 내가 직접 reduce
라는 함수를 구현해볼 것이다.
다음과 같이 구현해야 하는 reduce
함수가 주어졌다.
// iterator 는 콜백함수이며, (accumulator, currentValue)를 인수로 받는다고 가정한다.
// array 는 배열만 들어온다고 가정한다.
reduce = function (array, iterator, initValue) {
...
}
의사코드를 먼저 작성해보자.
initValue
가 주어졌는지 확인한다.initValue
가 주어지지 않았다면,initValue
에 배열의 첫번째 값을 넣어준다.array
배열을 순회한다.- 만약
initValue
가 주어졌다면, 0부터 아니면 인덱스가 1부터 배열을 순회한다. iterator
콜백함수의 결과를initValue
에 누적한다.- 배열의 마지막까지 4번으로 돌아가 같은 로직을 반복한다.
- 마지막 누산된 값(
initValue
) 을 리턴한다.
그럼 이제 로직을 구현해보자.
reduce = function (array, iterator, initValue) {
const startIdx = (initValue !== undefined) ? 0 : 1;
initValue = (initValue !== undefined) ? initValue : array[0];
for (let i = startIdx; i < array.length; i++) {
initValue = iterator(initValue, array[i]);
}
return initValue;
};
// 테스트
const arr = [1,2,3,4,5];
const add = (acc, cur) => {return acc+cur};
console.log(reduce(arr, add)); //15
console.log(reduce(arr, add, 1)); //16
내가 예상한대로 결과가 나오는 것을 볼 수 있다.
이처럼 의사코드만 내가 문제를 제대로 이해하고 작성한다면 코드로 작성하는 것은 금방 할 수 있다.
멘토링 시간
✅ 멘토와 나눈 대화
일주일에 한번 체크인 시간을 가진다.
여기서는 오전 9시부터 오후 10시까지 정해진 스케줄 외에는 내가 직접 스케줄을 관리한다.
그래서 일주일 동안 내가 스케줄을 잘 지켰는지, 지키면서 어려운 점은 없었는지 확인해주는 시간이 바로 체크인 시간에 멘토님께서 해주신다.
(일종의 healthCheck? 괜찮니? → 200 return … 괜찮니? → 500 return?!?! … 큰일이군… 아직까지는 200을 리턴하고 있다 ㅎㅎ)
이번주 체크인 시간에는 스케줄 관리에 초점을 맞춰 이야기를 나누었다.
스케줄이 30분 단위로 작성을 해야 하기 때문에 정말 스케줄대로 내가 어떤 일을 하는게 쉽지 않았다.
그러면 내가 적어둔 일정이 밀리고 밀리고… 이렇게 가는게 맞는지 멘토님께 여쭤보았다.
멘토님께서는 시간이 지나면 지날수록 더 메타인지가 되어 잘지켜질 것이라고 위로를 해주셨다.(멘토님 정말 너무 좋으시다…)
앞으로도 고민이 생기면 얘기를 드려야 겠다.
✅ 멘토링이 학습에 준 영향
이번에 cloneDeep
을 직접 구현해보고, 테스트코드까지 작성해보았다.
나는 지금까지 자바로 개발을 계속 해왔기 때문에 아직까지 자바스크립트에 익숙하지가 않다.
그래도 테스트코드는 많이 작성을 해봤기 때문에 jest
를 통한 테스트코드 작성이 어렵지는 않았다.
멘토링 시간에 오신 멘토님께 내가 작성한 코드에 대해서 리뷰를 받아 보았다.
구현한 cloneDeep 함수
cloneDeep = function (input) {
//원시타입의 경우 그대로 리턴
if (typeof input !== 'object') {
return input;
}
if (Array.isArray(input)) {
//배열일 경우
const result = [];
for (const key in input) {
result.push(cloneDeep(input[key]));
}
return result;
}
//객체일 경우
const result = {};
for (const [key, value] of Object.entries(input)) {
result[key] = cloneDeep(value);
}
return result;
};
위의 코드는 멘토님의 피드백을 받아 리팩토링을 한 코드다. (원래는 이거보다 2배는 길었었다…)
확실히 코드가 깔끔해지고, 가독성이 좋아졌다!!
early return
과 early exit
을 통해 보는 사람이 더 편해졌을 것 같다.
앞으로도 더 좋은 코드 작성을 위해 노력을 해야겠다.
작성해본 테스트 코드
// 테스트 코드
describe("cloneDeep 함수는,", function () {
it("새로운 레퍼런스의 객체 혹은 배열을 반환해야 한다.", function () {
const inputArr = [];
const inputObj = {};
const result1 = _.cloneDeep(inputArr);
const result2 = _.cloneDeep(inputObj);
expect(result1).toEqual([]); // 원시값 비교(재귀적으로 안에 값을 다 돌면서 확인)
expect(result1).not.toBe(inputArr); // 참조값 비교
expect(result2).toEqual({}); // 원시값 비교
expect(result2).not.toBe(inputObj); // 참조값 비교
});
it("원시값이 들어올 경우, 그대로 반환해야 한다.", function () {
const str = "1";
const num = 1;
const result1 = _.cloneDeep(str);
const result2 = _.cloneDeep(num);
expect(result1).toEqual("1");
expect(result1).toBe(str);
expect(result2).toEqual(1);
expect(result2).toBe(num);
});
it("객체가 들어올 경우, 복사한 객체의 참조값을 수정해도 원본 객체에 영향이 가면 안된다.", function () {
const originObj = {
name : "John",
age : 25,
detail:
{
home: "Dagu"
}
};
const copyObj = _.cloneDeep(originObj);
copyObj.detail.home = "Seoul";
expect(originObj).not.toBe(copyObj); //다른 메모리 주소를 참조하는지 확인(참조값 비교)
expect(originObj.detail.home).equal("Dagu");
expect(copyObj.detail.home).equal("Seoul");
expect(originObj.detail).not.toBe(copyObj.detail); // 원시값 비교(재귀적으로 안에 값을 다 돌면서 확인)
});
it("배열이 들어올 경우, 복사한 배열의 참조값을 수정해도 원본 배열에 영향이 가면 안된다.", function () {
const originArr = [{a:1},{b:2},10];
const copyArr = _.cloneDeep(originArr);
copyArr[0]['a'] = 5;
expect(originArr).not.toBe(copyArr); //다른 메모리 주소를 참조하는지 확인(참조값 비교)
expect(originArr[0]).not.toBe(copyArr[0]);
expect(originArr[1]).not.toBe(copyArr[1]);
expect(originArr[0]['a']).equal(1);
expect(copyArr[0]['a']).equal(5);
expect(originArr.length).equal(3);
expect(copyArr.length).equal(3);
});
}
테스트 코드도 피드백을 주셨는데, 다른 객체 타입(ex) Date
) 도 되는지 테스트를 추가해보는게 좋겠다고 해주셨다. 그리고 한 깊이 더 들어가는 객체의 경우에도 잘 동작하는지도 확인해보면 좋겠다는 말도 덧붙여 주셨다.
한줄 후기
유틸리티 함수, 알고 쓰자!
참조
'바닐라 부트캠프' 카테고리의 다른 글
[WIL] 바닐라코딩 부트캠프 11주차 후기 - 그렇게 점점 여름이 찾아온다… (0) | 2025.04.25 |
---|---|
[WIL] 바닐라코딩 부트캠프 10주차 후기 - 코드에 당위성을 부여하자 (0) | 2025.04.18 |
[WIL] 바닐라코딩 부트캠프 8주차 후기 - redux, 전역으로 끌어올리기! (0) | 2025.04.06 |
[WIL] 바닐라코딩 부트캠프 6주차 후기 - Hook, 내뜻대로 안되네.. (0) | 2025.03.23 |
[WIL] 바닐라코딩 부트캠프 5주차 후기 - 리엑트, 첫 단추를 잘 끼우자! (0) | 2025.03.14 |