2차 프로젝트 회고 (1)

Sinf·2022년 2월 24일
0

생각정리

목록 보기
8/15

엘리스 SW 트랙 종료

엘리스 SW 트랙이 2월 20일, 2차 프로젝트 발표와 함께 종료되었습니다. 4개월의 시간이 눈 깜짝할 새 마무리 되었습니다. 트랙에 대한 회고에 앞 서 약 4주간 진행되었던 2차 프로젝트에 대한 회고를 작성하려고 합니다.

2차 프로젝트

2차 프로젝트는 1차와 다르게 React를 사용했습니다. 하지만 저는 이번에도 백엔드 파트를 담당해 맡은 일은 다른 것이 없었지만, 이번엔 홀로 백엔드 파트를 담당하게 되었습니다.

두번째로 진행되는 프로젝트여서 시작 전 임하는 자세가 조금 달랐습니다. 3가지 목표를 가지고 참여하게 되었습니다.

  1. 개발노트를 작성하자.
  2. 새로운 참여자가 생겼을 때도 쉽게 연착륙할 수 있는 코드를 작성하자.
  3. 좋은 테스트 코드를 작성하자.

3가지 목표가 어떻게 이루어졌는지 살펴보며 회고를 진행하도록 하겠습니다.

개발 노트를 작성하자.

1주차 중간회고에서 기록했던 것과 같이, 새로이 구현할 기능에 대해서 개발 노트를 작성하고 개발을 진행하는 것을 생각했습니다.

어떻게 작성했는가?

JWT 인증 방식 개발노트

위와 같이 전반적인 흐름과, 필요한 정보에 대해 정리하고 구현하기 시작했습니다.

모든 기능을 구현함에 있어 적용된 것은 아니지만, 좋아요 & 북마크 기능, 조회수 기능, 익명 게시판, 모임 글 수락 & 완료, 유저 인증 등 거의 모든 단계에서 개발 노트를 작성하고 진행하였습니다.

개발 노트를 사용하면서 좋았던 점

개발 노트를 작성하면서, 코드를 작성하기 이전에 더 좋은 방법에 대해 고민하고 검색하는 단계가 추가되었습니다. 무작정 코드를 C&P하는 것이 아니라 여러 방법에 대해 검색하고 비교하며 더 좋은 방법에 대해 고민하고 선택하게 되었습니다.

수 많은 코드 사이에서 길을 잃을 때, 지도와 같이 개발노트를 사용했습니다. 처음 구현하는 기능의 경우 어떻게 구현하는지, 어디를 구현하는지 지속적으로 확인하게 되는데, 다시 검색하는 과정이 없이 개발 노트를 통해 확인하고 진행할 수 있었습니다. 그리고 개발 노트를 작성하면서 머릿 속에 정리가 되어 더 쉽게 코드를 작성할 수 있었습니다.

앞으로도 사용할 것인가?

개발노트는 저의 자산이 될 것 같습니다. 이전에는 배우고 구현하고 작성한 뒤 정리하는 것만 생각했는데, 먼저 개념을 정리하고 구현에 들어갔을 때 조금 더 효율적인 개발 프로세스가 진행되었습니다.

클린 코드 (새로운 참여자에 대한 배려)

클린 코드에 대한 고민은 끝이 없을 것 같습니다.
이전 프로젝트와 다르게 프로젝트 구조에 대해서 고민하고, MVC 모델을 적용하려 고민했습니다. 그리고 어떤 코드를 사용해야 좋을 지 고민했습니다. 하나 하나 코드를 보며 고민한 것을 나누겠습니다.

프로젝트 구조

프로젝트 구조

1차와 같이 MVC 모델을 적용하고, 프로젝트 디렉터리 구조부터 최대한 단순화시키려 했습니다.

타입스크립트 사용하기

1차와 다르게 2차는 타입스크립트를 사용해 프로젝트를 진행했습니다. 타입스크립트를 사용하는 장점은 타입 안정성타입 추론이었습니다. 자유도를 조금 낮춰 컴파일 단계에서 타입 체크가 이루어지면서 타입에 대한 안정성을 갖췄습니다. 그리고 개발 단계에서 타입스크립트의 타입 추론이 개발에 도움이 되었습니다.

가장 도움이 된 부분은 MongoDB의 ObjectId에 대한 타입입니다. 1차 프로젝트에서는 Object인지, String인지 구별하지 않고 사용하거나 toString()을 사용했습니다. 사용하지 않아도 되는 상황에서 사용하기도 했습니다. 타입스크립트를 사용하면서 어떤 때 String 타입이며, 어떤 때 Object 타입인지 정확히 인지하였고 코드를 작성할 수 있게 되었습니다.

새로운 참여자가 참여하기에도 타입스크립트가 도움이 될 것이라고 생각합니다. 변수의 타입이나 함수의 파라미터, 리턴에 대해서 정의된 타입으로 도움을 받을 수 있기 때문입니다.

서비스와 모델 분리

이전 1차 프로젝트의 코드를 살펴보면, DB를 참조하는 코드가 서비스 레이어에 여기저기 붙어 있었습니다.

export async function updatePost(
  postId: string,
  title: string,
  contents: string,
  authorId: object,
) {
  try {
    const post = await Post.findById(postId);
    if (post.author.toString() !== authorId.toString()) {
      throw new Error("권한이 없습니다.");
    }
    
    const newPost = await Post.findByIdAndUpdate(
      postId,
      {
        title: parseTitle(title),
        contents,
        photos: post.photos.map((photo) => {
          photo.text = parseTitle(title);
          return photo;
        }),
        updatedAt: Date.now(),
      },
      { new: true },
    );
    return newPost;
  } catch (err) {
    // throw new Error(err);
    throw new Error("존재하지 않는 글입니다.");
  }
}

1차 프로젝트 코드입니다. DB에 대한 로직은 서비스 레이어에서 모두 처리하고 있습니다. 프로젝트가 진행되면서 서비스 레이어 코드를 열어볼 때면 코드가 더 복잡하고 어려웠습니다.

// post.service.ts
async updatePost(
  postId: string,
  postDto: Partial<IPostDocument>,
  gatherDto: Partial<IPostDocument>
) {
  await this.PostModel.updatePost(postId, postDto, gatherDto);
}

// post.model.ts
PostSchema.statics.updatePost = async (
  postId: string,
  postDto: Partial<IPostDocument>,
  gatherDto: Partial<IPostDocument>
) => {
  const { subject, title, contents } = postDto;
  const dto = createDto(subject, { title, contents }, gatherDto);
  await Post.findByIdAndUpdate(postId, {
    ...dto,
  });
};

2차 프로젝트를 진행하면서 서비스 레이어와 모델 레이어로 분리하여 코드를 작성하였습니다. 물론 1차와 다르게 이미지를 처리하는 과정이 없어 코드가 더 짧은 것은 맞지만, 각각 기능에 맞게 코드를 분리하여 작성하게 되면서 양 쪽 코드의 가독성이 증가했습니다.

정말 클린한가?

이 외에도 여러 부분에서 협업하기 좋은 클린 코드에 대한 고민으로 코드를 작성하려고 노력했습니다.

하지만 다음에 협업할 사람을 위해 코드를 작성하는 것은 쉽지 않았습니다. 그럴 때면 1달 만에 이 코드를 본다면 이해할 수 있을까? 라는 생각으로 코드를 바라보곤 했습니다. 아직 부족한 점이 많았습니다. 더 좋은 코드를 작성하기 위해 고민할 필요가 있습니다.

좋은 테스트 코드를 작성하자.

테스트 코드 결과

2차 프로젝트에서 총 113개의 테스트 케이스를 작성하였습니다.

테스트 코드는 좋다.

1차와 마찬가지로 테스트 코드의 장점을 느낄 수 있는 프로젝트였습니다. 1차와 다르게 처음부터 테스트 코드를 작성하면서 코드의 기능에 대한 신뢰를 가지고 작성하게 되었습니다.모든 코드는 아니지만, 테스트 케이스를 먼저 작성하고 코드를 작성하는 시도도 했습니다.

이번 프로젝트에서 테스트 코드는 개발 효율이라는 결과를 가져왔습니다.

1차의 경우 API 서버의 여러 작은 버그들이 발생하여 서버를 올리고 난 뒤 수 많은 버그들과 싸우며 마지막까지 버그를 고치고 있었습니다.

2차의 경우 API 서버의 버그들이 있었지만, 1차와 다르게 훨씬 적었습니다. 테스트 코드를 통한 기능의 신뢰성을 갖고 추가적인 기능 개발을 시도할 수 있었습니다.

좋은 테스트 코드였는가?

사실 이번 테스트 코드도 익숙한대로 작성한 부분이 많습니다. 단위 테스트를 적용하는 것이 목표였지만, 그렇지 못했습니다.

제 테스트 코드는 테스트 간의 종속성이 존재했습니다.

it("일반 포스트 생성 테스트", async () => {
  // given

  // when
  const res = await request(server)
  .post("/api/posts")
  .set("authorization", token)
  .send({ title, contents, subject, category });
  // then
  expect(res.statusCode).toEqual(201);
  expect(res.body.isOk).toEqual(true);
  expect(Object.keys(res.body)).toEqual(
    expect.arrayContaining(["isOk", "postId"])
  );

  postId = res.body.postId;
});

it("포스트 삭제 로직", async () => {
  const res = await request(server)
  .delete(`/api/posts/${postId}`)
  .set("authorization", token)
  .send();

  expect(res.statusCode).toEqual(200);
  expect(res.body.isOk).toEqual(true);
});

위 코드는 포스트 생성과 삭제를 위한 테스트 코드입니다. supertest를 통해 테스트를 진행하였는데, 포스트 생성 때 사용한 postId를 삭제 때도 사용하고 있습니다. 그 결과 삭제 로직에서 문제가 없더라도 생성 로직에 문제가 있으면 삭제 테스트도 통과하지 못합니다.

테스트 코드 간의 독립성을 위해 하루 빨리 Mocking에 대해 학습하고 작성하도록 해야할 것 같습니다.

그래도 1차와 다르게 더 다양한 테스트 케이스를 적용하려고 노력하고 작성했습니다.

마지막 정리

2차 프로젝트 또한 많은 것을 배우는 시간이 되었습니다.
1차보다 더 긴 시간을 쏟았고, 더 좋은 코드를 작성하기 위한 고통을 감내했습니다. 하지만 앞으로도 배울 것이 많다고 생각합니다.

NestJS

Express 프레임워크로 개발을 진행하면서 겪은 어려움은 높은 자유도에 따르는 책임이었습니다. 프로젝트 구조부터 코드를 사용하는 방식까지 모두 계획하고 작성하다보니, 제 습관이 들어가고 다른 사람과 협업 하기 어렵다라는 결론이었습니다.

NestJS 프레임워크를 공부하고 적용해보기로 했습니다. 위에서 겪은 Express의 어려움을 해결할 수 있는 프레임워크라고 판단했습니다.

RDBS

NoSQL인 MongoDB를 사용하면서 쉽고 편하게 작성한 코드들이 많습니다. 하지만 RDB에 너무 멀어진 감이 있습니다. 무조건 RDBS가 좋은 것은 아니지만 현재 취업을 위해서는 RDBS를 공부할 필요가 있습니다.

Mocking

1차와 마찬가지로 부족한 점입니다. 더 좋은 테스트 코드를 작성할 수 있어야 합니다.

profile
주니어 개발자입니다. 🚀

0개의 댓글