코드를 잘 작성한다는 것은 무엇일까요? 이번에는 리액트 개발자로서 함수를 사용함에 있어서 함수를 재사용하는 리팩토링에 대해 알아보도록 하겠습니다.
단순히 코드를 작성하고 기능 구현에 그치는 것이 아니라 다른 개발자들이 봐도 잘 이해할 수 있는 코드, 유지보수가 쉬운 코드, 재사용이 가능한 코드 등을 작성하기 위해 열심히 고민해야하므로 이중의 일환으로 함수 재사용에 대해 살펴봅시다.
함수란, 한 가지의 일을 수행하는 코드가 블럭으로 묶여있는 것을 말하며, 간단한 명령만으로 동일한 코드를 필요한 곳마다 반복해서 사용하지 않을 수 있게 만들어줍니다.
프로젝트의 복잡도가 증가할수록 새로운 기능을 추가하는 것보다 기존 코드의 유지 보수에 더 많은 리소스를 투입하게 됩니다. 프로젝트의 대규모화에 따른 코드 유지 관리 비용 증가 자체를 막을 수는 없지만 코드의 복잡도를 낮추어 비용을 최소화 할 수는 있습니다.
중복 코드 줄이기 작업은 효율적인 코드 작성을 위한 주요한 방법 중 하나입니다. 우선, 중복되는 로직을 추출해 반복적으로 사용하게 되면 프로젝트의 코드량을 줄일 수 있습니다.
로직의 관리 포인트가 줄어든다
는 말은 하나의 수정사항을 반영하기 위해 여러 코드를 반복적으로 수정하지 않아도 된다는 뜻입니다. 코드를 예시로 들어 생각해보겠습니다.
// App.js
fetch("https://localhost:8000/proucts/1")
// src/config.js
export const API = "http://localhost:8000";
// App.js
fetch(`${API}/product/1`);
위와 같이 반복되는 상수들을 별도 파일로 분리하여 관리하는 것만으로도 한 군데만 바꾸더라도 모든 주소를 동일하게, 안전하게 수정할 수 있게 됩니다. 유지 보수 관점에서 훨씬 좋은 코드가 되겠죠?
그렇다고 중복되는 코드를 무작정 추출만 해서도 안됩니다. 로직을 추출할 때 '함수가 너무 복잡하지는 않은가?'하는 질문을 꼭 던져봐야 합니다.
각 함수는 가급적 단순하게 유지되어야 합니다. 하나의 함수는 가급적 하나의 일을 하는 것이 좋습니다. 한 함수의 너무 많은 역할이 뭉쳐있다면 오히려 제한적인 상황에섬나 쓰이게 되기도 합니다. 재사용하기가 어려워진다는 말이겠죠.
// DON'T
const fourRuleCalculate = (event, a, b) => {
const { innerText } = event.target;
if (innerText === "+") {
return a + b
} else if (innerText === "-") {
return a - b
} else if (innerText === "*") {
return a * b
} else if (innerText === "/") {
return a / b
}
}
#### After
// DO
const sum = (a, b) => a + b
const minus = (a, b) => a - b
const multiply = (a, b) => a * b
const divide = (a, b) => a / b
줄여 쓰겠다고 가독성을 과하게 희생시키지는 않았는지 살펴보세요. 때론 조금 장황한 코드가 유지보수하기 좋을 수도 있습니다.
아래 두 경우 중 어느 쪽이 중간에 로직을 바꿔넣기에 좋을지 생각해보세요!
// DON'T
width = container > 960 ? (growWithContainer ? (container * 0.8) : 960) : container;
// DO
if (container > 960) {
if (growWithContainer) {
width = container * 0.8;
} else {
width = 960;
}
} else {
width = container;
}
위 원칙들이 절대적으로 따라야 하는 황금률은 아닙니다. 경우에 따라 원칙을 벗어나야 더 좋은 코드가 될 수도 있습니다. 하지만 대부분의 경우 위 규칙들은 여러분의 코드를 보다 읽기 좋고, 쓰기 좋게 만들어 줄 것이라고 생각합니다.
fetch("http://localhost:3000", {
method: "POST",
body: JSON.stringify({
id: "jt",
pw: "1q2w3e4r!"
})
})
.then(res => res.json())
.then(res => console.log(res))
.catch(err => alert(err))
const onSucceed = name => {
if (name === "phoneNum') {
fetch(`${API}/users/identify`, {
method: 'POST',
headers: {'Content-type': 'application/json' },
body: JSON.stringify({
phoneNumber: form.phoneNum,
}),
})
.then(res => res.json())
.then(res => {
if(res.MESSAGE === 'SUCCESS'} {
alert('인증번호를 전송하였습니다.');
}
});
}
if (name === 'identNum') {
fetch(`${API}/users/identify`, {
method: 'PATCH',
headers: {'Content-type': 'application/json' },
body: JSON.stringify({
identifyNumber: form.identNum,
}),
})
.then(res => res.json())
.then(res => {
if(res.MESSAGE === 'SUCCESS'} {
alert('본인인증 완료');
history.push('/join/signup');
}
});
.catch(() => alert('인증번호를 확인해주세요.'));
}
};
...
<Btn onClick={() => onSucceed(data.btnName)} />
onSucceed
는 Btn
컴포넌트를 클릭했을 때 인자로 전달되는 name
값에 따라 서로 다른 동작을 하는 함수입니다. 하나의 함수로 줄이려는 의도가 보입니다.
하지만 기능을 살펴보면 1) 휴대폰 인증번호 전송, 2) 본인확인 두 가지 기능이 하나의 함수에 합쳐져 있음을 알 수 있습니다. 그러면 하나의 함수가 하나의 기능을 할 수 있도록 로직을 분리해보겠습니다. (Keep It Simple, Stupid)
requestCertificationNumber
: 휴대폰 인증번호 전송validateSelf
: 본인확인const onSucceed = (name) => {
if (name === 'phoneNum') {
// 휴대폰 인증번호 전송
requestCertificationNumber(form.phoneNum);
} else if (name === 'identNum') {
// 본인확인
validate(form.identNum);
}
};
const requestCertificationNumber = (phoneNumber) => {
fetch(`${API}/users/identify`, {
method: 'POST',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify({ phoneNumber }),
})
.then((res) => res.json())
.then((res) => {
alert('인증번호를 전송하였습니다.');
});
};
const validateSelf = (identifyNumber) => {
fetch(`${API}/users/identify`, {
method: 'PATCH',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify({ identifyNumber }),
})
.then((res) => res.json())
.then((res) => {
if (res.MESSAGE === 'SUCCESS') {
alert('본인인증 완료');
history.push('/join/signup');
}
})
.catch(() => alert('인증번호를 확인해주세요!'));
};
함수를 분리하고 나니 이전보다 onSucceed
함수의 목적이 분명해보입니다. 여기서 .json()
등 반복되는 fetch
관련 로직만 다시 한번 더 추출해보겠습니다! (Don't Repeat Yourself
)
import { API } from "../config";
import { customFetch } from "../utils/api";
const onSucceed = (name) => {
if (name === 'phoneNum') {
// 휴대폰 인증번호 전송
requestCertificationNumber(form.phoneNum);
} else if (name === 'identNum') {
// 본인확인
validateSelf(form.identNum);
}
};
const requestCertificationNumber = (phoneNumber) => {
customFetch(
API.VERIFICATION,
{
method: 'POST',
body: JSON.stringify({ phoneNumber }),
},
{
onSuccess: (res) => {
if (res.MESSAGE === 'SUCCESS') {
alert('본인인증 완료');
history.push('/join/signup');
}
},
onFail: () => alert('인증번호를 확인해주세요'),
}
);
};
const validateSelf = (identifyNumber) => {
customFetch(
API.VERIFY,
{
method: "PATCH",
body: JSON.stringify({ identifyNumber }),
},
{
onSuccess: (res) => {
if (res.MESSAGE === "SUCCESS") {
alert("본인인증 완료");
history.push("/join/signup");
}
},
onFail: () => alert("인증번호를 확인해주세요"),
}
);
};
// config.js
const BASE_URL = 'https://10.18.2.92'
const VERIFICATION = '/users/verification'
const VERIFY = '/users/verify'
const API = { VERIFICATION, VERIFY }
// utils/api.js
const customFetch = (
endpoint,
options = {},
{ onSuccess, onFail } = {}
) => {
const opts = {
method: 'GET',
headerds: { 'Content-type': 'application/json' },
...options
}
return fetch(BASE_URL + endpoint, opts)
.then(res => res.json())
.then(res => onSuccess && onSuccess(res))
.catch(err => onFail && onFail(err))
}
위와 같이 함수 재사용에 대해서 배웠습니다. 바로 위 코드는 실제로 자바스크립트의 객체, 함수 등의 문법을 잘 이해해야만 작성할 수 있는 코드라 생각합니다.
대부분의 코드는 이해되지만, 저 또한 아직까진 모두 완벽히 이해한 것이 아니라서 주말을 통해 함수 재사용에 대해서 한번 더 공부하려 합니다. 리액트에 조금 더 적응될 때까지 달려보겠습니다! 🏃♂️🏃♂️