Skip to content

스프린트 미션 5 제출#65

Open
kangyeon0413 wants to merge 28 commits into
codeit-sprint-fullstack:basicfrom
kangyeon0413:react-이강연

Hidden character warning

The head ref may contain hidden characters: "react-\uc774\uac15\uc5f0"
Open

스프린트 미션 5 제출#65
kangyeon0413 wants to merge 28 commits into
codeit-sprint-fullstack:basicfrom
kangyeon0413:react-이강연

Conversation

@kangyeon0413

Copy link
Copy Markdown
Collaborator

미션 4때 브랜치 옮기는걸 잊어서 현재 미션에서 'react-이강연'으로 옮겼습니다

@tenenger7125 tenenger7125 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 이강연님!

미션 4 PR 요청하신 내용 잘 확인했습니다.

인상 깊었던 부분

이번 미션에서는 백엔드 API 구현부터 프론트엔드 화면 구성까지 전체적인 흐름을 직접 구현하시면서 코드 구조를 개선하려는 고민이 많이 느껴졌습니다.

특히 라우터 분리, Early Return 활용, 응답 파싱 유틸 함수 작성 등 단순히 기능 구현에 그치지 않고 유지보수성과 재사용성을 고려한 부분이 인상적이었습니다. 🔥


라우터 분리

app.use('/products', productRouter);

라우터를 별도 파일로 분리하고 /products 경로를 중앙에서 관리하도록 구성하신 점이 좋았습니다. 🔥

덕분에 각 라우터 파일에서는 중복되는 경로를 반복해서 작성하지 않아도 되고, 프로젝트 규모가 커졌을 때도 구조를 파악하기 쉬운 형태가 될 수 있습니다.


조회 조건 분리

const filter = keyword

keyword 존재 여부에 따라 DB 조회 조건을 생성하는 로직을 별도의 변수로 분리하신 점이 좋았습니다. 🔥

조회 조건이 복잡해질수록 이런 방식이 가독성과 유지보수성에 큰 도움이 됩니다.

여기서 한 단계 더 나아가면 조회 조건을 생성하는 규칙 자체를 별도 파일로 분리하는 방법도 있습니다.

예를 들어 productRule.js 파일을 만들어 아래와 같이 관리할 수 있습니다.

export const productRule = {
  productPagination(query) {
    return {
      offset: Number(query.offset) || 0,
      limit: Number(query.limit) || 20,
      keyword: query.keyword,
      sort: query.sort,
    };
  },
};

추후 Express와 TypeScript를 함께 사용하거나 zod, yup 같은 유효성 검증 라이브러리를 사용하게 되면 이러한 구조가 더욱 유용해질 수 있습니다. 지금은 개념 정도만 알아두셔도 좋을 것 같습니다. 😀


에러 처리

res.status(500).json({ message: '서버 오류가 발생했습니다.' });

백엔드 API 구현 시 try-catch를 활용하여 예외 상황을 처리하신 점 좋았습니다.

다만 500 Internal Server Error는 서버에서 문제가 발생했다는 사실만 알려줄 뿐, 실제 원인을 파악하기에는 범위가 매우 넓습니다.

나중에 디버깅이 필요한 상황을 고려한다면 에러 객체의 정보를 함께 활용하는 방법도 있습니다.

예를 들면 다음과 같은 형태입니다.

catch (e) {
  console.error(e);

  return res.status(500).json({
    message: e.message,
  });
}

물론 실제 운영 환경에서는 내부 오류를 그대로 노출하지 않는 경우도 많지만, 개발 단계에서는 문제를 빠르게 파악하는 데 도움이 될 수 있습니다. 😀


응답 형식 통일

res.json({ list, totalCount: total });

현재도 충분히 깔끔하게 작성되어 있습니다.

다만 프로젝트 규모가 커질 경우 응답 형식을 통일하기 위해 별도의 Mapper 또는 Response Utility를 사용하는 경우도 있습니다.

예를 들면 다음과 같은 형태입니다.

export const responseMapper = (data) => ({
  success: true,
  data,
});

지금 단계에서는 반드시 필요한 구조는 아니지만, 실무에서는 응답 형식을 일관성 있게 유지하기 위해 활용하는 경우가 있다는 점 정도만 알아두시면 좋을 것 같습니다. 😀


HTTP 상태 코드 관리

res.status(201).json(article);

상태 코드를 명시적으로 작성해 API 응답 결과를 쉽게 파악할 수 있도록 구현하신 점 좋았습니다.

여기서 더 나아가면 숫자를 직접 사용하는 대신 상수나 라이브러리를 활용하는 방법도 있습니다.

실무에서는 http-status 또는 http-status-codes 패키지를 사용하는 경우가 많습니다.

import { StatusCodes } from 'http-status-codes';

res.status(StatusCodes.CREATED).json(article);

코드만 보더라도 의미를 바로 파악할 수 있다는 장점이 있습니다. 😀


Early Return 활용

if (!article) return res.status(404).json({ message: '게시글을 찾을 수 없습니다.' });

ㅎㅎ Early Return을 적극적으로 활용하신 점이 좋았습니다. 🔥

중첩된 조건문을 줄이고 예외 상황을 먼저 처리할 수 있어 전체적인 코드 가독성이 훨씬 좋아졌습니다.

실무에서도 매우 자주 사용하는 패턴이기 때문에 앞으로도 계속 활용해보시면 좋을 것 같습니다.


응답 파싱 유틸 함수

function normalizeFetchResult(response) {

응답값을 정규화하는 유틸 함수를 작성해 반복적인 코드를 줄이고, 일관된 데이터를 활용할 수 있도록 구현하신 점이 인상 깊었습니다.

특히 함수명도 역할이 명확하게 드러나서 코드를 읽는 입장에서 이해하기 쉬웠습니다. 🔥


유틸 함수 활용

if (error.response) {

앞서 normalizeFetchResult 유틸 함수를 만들어두셨는데, 실제 코드에서는 활용되지 않는 부분이 조금 아쉬웠습니다. 🥲

좋은 유틸 함수를 작성한 만큼 실제 API 응답 처리 로직에서도 적극적으로 사용해보시면 더욱 일관된 구조를 만들 수 있을 것 같습니다.


컴포넌트 분리

<div className="footerRight">

이제 React를 활용하고 있으니 반복되는 UI는 컴포넌트로 분리하는 습관을 함께 가져가시면 좋을 것 같습니다.

예를 들어 SNS 링크 영역은 URL과 아이콘만 변경되는 구조이기 때문에 별도 컴포넌트로 분리하면 코드 중복을 줄일 수 있습니다.


폼 UI 컴포넌트화

<div className="formGroup">

폼 영역도 비슷한 형태가 반복되고 있다면 컴포넌트 분리를 고려해보시면 좋을 것 같습니다.

예를 들어 InputField, FormGroup 같은 형태로 추상화하면 재사용성과 유지보수성이 더욱 좋아질 수 있습니다.

React의 가장 큰 장점 중 하나가 컴포넌트 기반 설계인 만큼 앞으로 점점 더 활용해보시면 좋겠습니다. 👍


PR 브랜치 확인

추가로 PR 요청 시 병합 대상 브랜치를 한 번 더 확인해주시면 좋을 것 같습니다.

현재는 병합 대상 브랜치 설정이 다르게 되어 있는 것으로 보이며,

codeit-sprint-fullstack:react-이강연

브랜치로 요청해주시면 됩니다.


마무리

전체적으로 이번 미션에서는 백엔드 API 구현부터 프론트엔드 컴포넌트 구성까지 다양한 영역을 직접 구현하시면서 코드 구조를 개선하려는 고민이 많이 느껴졌습니다.

특히 Early Return, 라우터 분리, 응답 파싱 유틸 함수 작성 등은 유지보수성을 고려한 좋은 시도였다고 생각합니다.

이번 미션도 정말 고생 많으셨습니다! 강연님은 가끔씩 깜짝놀라는 번뜩이는 아이디어를 코드에 녹여내는 게 있어서 항상 놀라곤 합니다 😄. 여기에 "어떻게 하면 컴포넌트로 분리해서 더 재사용할 수 있을까?", "어떻게 하면 더 읽기 쉬운 코드가 될까?"를 추가한다면 더욱 완벽해질거 같습니다. 👍

Comment thread server/app.js
app.use(cors());
app.use(express.json());

app.use('/products', productRouter);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

라우터 파일로 분리함과 동시에 /product 반복적인 코드를 작성하지 않도록 코드를 짠 것 좋습니다 🔥

Comment thread server/routes/articles.js
try {
const { page = 1, pageSize = 10, keyword = '' } = req.query;

const filter = keyword

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keyword에 따라 DB 쿼리 요청을 보내는 조건식을 변수로 분리하는 모습 좋습니다 🔥
여기에 더 나아가는 방법은 이러한 규칙을 또다른 파일로 분리해서 import 해서 하는 방법이 있겠습니다.

아예 처음부터 req.query를 인수로 전달받는 rule 함수를 만드는 것이라고 보시면 될거 같습니다.

예를 들어 productRule.js 파일을 만들어서 아래와 같은 방법이 있다는 것 참고해주시면 좋겠습니다

export const productRule = {
  productPagination(query) {
    
    return { offset: Number(offset) || 0, limit: Number(limit) || 20, keyword: ..., sort: ... }
  }
}

추후에 별도의 프레임워크 없이 express에 typescript을 이용해 구현하는 시기가 온다면, query 자체를 유효성 검사 도구(zod, yup 등)을 이용해서 분리하게 될텐데 미리 경험하신다고 생각하시면 좋습니다 😀

Comment thread server/routes/articles.js

res.json({ list, totalCount: total });
} catch (e) {
res.status(500).json({ message: '서버 오류가 발생했습니다.' });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

백엔드에서 api 호출시 에러 핸들링을 위해 try-catch 구문을 활용하신점 좋습니다.

다만, 500 에러는 서버 에러코드가 맞으나, 확인 가능한 범위가 광범위하기 때문에 적절한 오류 핸들링라고 보기가 어렵습니다.
나중에는 디버깅을 해서 오류를 해결해야하는 경우가 있는데, 현재 방법보다는 에러 객체의 값을 활용하는 방법(예: e.message) 응답값을 반환하는 형태로 만드는 것도 있다는 것도 참고하면 좋을거 같습니다 😀

Comment thread server/routes/articles.js
.skip((page - 1) * pageSize)
.limit(Number(pageSize));

res.json({ list, totalCount: total });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

약간 더 리팩토링을 할 수 있다면, mapper라는 별도의 유틸함수를 만들어서 반환하는 값을 동일하게 적용시키는 방법이 있습니다.

mapper 함수를 적용시키는 이유는 단순하게 동일한 값을 반환하는 목적에 있습니다.
지금은 당장은 필요없습니다. 다만, 나중에 실무에서는 해당 방법을 사용하여 동일한 응답값을 전달해주도록 활용하는 경우가 생기니 현재는 개념만 알아두시면 좋을거 같습니다. 😀

Comment thread server/routes/articles.js
return res.status(400).json({ message: 'title, content는 필수입니다.' });
}
const article = await Article.create({ title, content, image });
res.status(201).json(article);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상태코드를 작성하여 api 응답을 유추할 수 있게 만들도록 한 점 좋습니다.
여기에 약간 더 리팩토링을 할 수 있다면, 숫자를 별도의 상수로 분리하여 import 하여 참고하는 방법이 있습니다.
추가적으로 실무에서는 별도의 파일을 만드는 것 보다, 상태코드 관련된 패키지(http-status)를 설치해서 import해서 사용하는 경우가 많다는 점도 알아두시면 좋을거 같습니다. 😀

Comment thread server/routes/articles.js
router.patch('/:id', async (req, res) => {
try {
const article = await Article.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!article) return res.status(404).json({ message: '게시글을 찾을 수 없습니다.' });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅎㅎ early return 활용하시니 가독성이 향상되니 좋습니다. 🔥

* - result.status : 응답상태 코드
* - result.payload : 응답결과 또는 에러사유
*/
function normalizeFetchResult(response) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

응답값을 파싱하는 유틸 함수로 만들어서 반복적인 코드와 일관적인 데이터를 활용할 수 있도록 구현한 점 인상 깊습니다. 또한 함수명도 적절하네요! 🔥

});
return response.data;
} catch (error) {
if (error.response) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유틸함수를 만들었지만, 활용하지 않은점은 아쉽네요... 🥲

Comment thread src/pages/Home.jsx
<a className="footerLink" href="/privacy">Privacy Policy</a>
<a className="footerLink" href="/faq">FAQ</a>
</div>
<div className="footerRight">

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이제 리액트를 활용하고 있으니, 컴포넌트를 만들어서 반복적인 코드를 개선하는 방향으로 코드를 작성하면 더 좋을거 같습니다!

<button className="submitBtn" onClick={handleSubmit}>등록</button>
</div>

<div className="formGroup">

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트로 분리해서 반복적인 코드 작성을 줄이는 방법을 고려하면 더 좋을거 같습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants