짧고도 긴 프로젝트가 끝나고 다시 코드르 들여다 보면서 리펙토링을 해보려고 합니다.
"코드를 작성하는 동안 시간이 부족해서 기능 구현에만 집중하다 보니, 코드의 재사용성을 고려하지 못한 컴포넌트를 만들거나, 파일에 적합한 역할에 맞는 코드를 작성하지 못한 경우가 있었습니다. 이 문제를 해결하기 위해 코드를 재구성하고 개선하는 작업을 진행하려고 합니다 같이 보시죠
// 회원가입 API 로직
export async function signUp(data: object) {
try {
const response = await client.post("/users/", data);
if (response.status === 201) {
toast.success("회원가입 되었습니다.");
const result = response.data;
return result;
}
const result = response.data;
return result;
} catch (e: any) {
toast.error(e.response.data.message);
throw new Error(e);
}
}
// 로그인 API 로직
export async function logIn(data: object) {
try {
const response = await client.post("/auth/login", data);
if (response.status === 201) {
toast.success("로그인 되었습니다.");
const result = response.data;
return result;
}
const result = response.data;
return result;
} catch (e: any) {
toast.error(e.response.data.message);
throw new Error(e);
}
}
코드를 보시면 Axios를 사용해 서버와 API 통신을 하고 있는 로직인 걸 알 수 있을 실 겁니다. 만약 요청 성공시 Toast 라이브러리가 실행이 되어 위와 같이 화면에 Toast UI가 보이게 될 것 입니다.
지금 이 프로젝트에서 작성한 API 통신 코드는 저런 식으로 View 로직과 데이터 통신 로직이 섞여 작성되어 있습니다. 이 부분이 적절하지 않다는 생각이 들어 아래와 같이 코드를 개선해 보았습니다.
export async function signUp(data: object) {
try {
const response = await client.post("/users/", data);
const result = response.data;
return result;
const result = response.data;
return result;
} catch (e: any) {
console.log(e);
return e.response.data.message;
}
}
export async function logIn(data: object) {
try {
const response = await client.post("/auth/login", data);
const result = response.data;
return result;
}
const result = response.data;
return result;
} catch (e: any) {
console.log(e);
return e.response.data.message;
}
}
async function contextLogin(data: { email: string; password: string }) {
const result = await logIn(data);
if (typeof result === "string") {
toast.error(result);
}
localStorage.setItem("user", JSON.stringify(result.user));
localStorage.setItem("token", result.accessToken);
toast.success(result.user.nickname + "님 반갑습니다");
}
async function contextLogout() {
localStorage.removeItem("user");
localStorage.removeItem("token");
}
기존에 있던 toast 관련 코드를 제거했고, 사용자에게 기능 수행 결과를 알려주기 위해 AuthProvider에서 API 통신 여부에 따라 Toast를 표시하는 기능을 추가했습니다. 이로써 View와 데이터 통신 로직이 분리되어 더 깔끔한 구조가 되었습니다.
const client = axios.create({
baseURL: API_BASE_URL,
});
// 500에러 발생시 센트리로 에러 전송
client.interceptors.response.use(
(res) => res,
(err) => {
if (err.response && err.response.status === 500) {
Sentry.withScope((scope) => {
scope.setTag("api", "Network500Error"); // 태그 설정
scope.setLevel("fatal"); // 레벨 설정
Sentry.captureException(new Error("서버 500 에러 발생")); // 센트리로 보낼 에러 메세지
});
return Promise.reject(err);
}
return Promise.reject(err);
}
);
멘토님께서 Sentry라는 오류 모니터링 서비스를 소개해 주셔서 이번 프로젝트에 간단하게 적용시켜 보았다
위 코드는 API 통신 중 500에러 발생시 Sentry로 오류를 전달해주는 코드입니다.
500에러 말고도 다른 오류들도 수집해서 어떤 기능에서 오류가 자주 나오는지 파악하기 위해 위에서 봤던 데이터 통신 로직에 Sentry 로직을 추가 연결시켜주었습니다.
export async function signUp(data: object) {
try {
const response = await client.post("/users/", data);
const result = response.data;
return result;
} catch (e: any) {
console.log(e);
const { method, url } = e.config;
const { status } = e.response;
Sentry.withScope((scope) => {
scope.setTag("api", "signUp"); // 태그 설정
scope.setLevel("warning"); // 레벨 설정
scope.setFingerprint([method, status, url]);
Sentry.captureException(new Error(e.response.data.message));
});
return e.response.data.message;
}
}
태그를 설정하여 보다 쉽게 원하는 오류를 찾을 수 있도록 하였고, 심각한 오류들은 나오지 않을 거 같아 에러 레벨은 warning으로 주었습니다.
Sentry는 HTTP status가 다르다 하더라도 같은 이슈로 그룹화합니다. 이렇게 되면 각 HTTP status별로 발생하는 이벤트 분포 확인, 이벤트 검색, 이슈 분석이 어려워집니다.
다음과 같이 HTTP method(GET, POST), status(400, 404, 500), url을 fingerprint 조건으로 설정해주면 해당 조건에 부합하는 이벤트들을 하나의 이슈로 그룹화할 수 있습니다.
다음 리펙토링 2부로 돌아오겠습니다.
📌 Taskify 깃허브 링크
https://github.com/taskify-team7/taskify