Skip to content

TDD, 클린코드를 적용하여 레거시 코드의 학습 관리 시스템 리팩토링

Notifications You must be signed in to change notification settings

Perhona/java-lms

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

학습 관리 시스템(Learning Management System)


미션을 완수하고 배운 내용 by Perhona

  • 클래스의 복잡도가 올라가면 클래스를 분리하고 메서드의 크기를 줄여 유지보수, 테스트 하기 좋은 코드로 개선
  • 실무와 비슷한 환경의 레거시 코드를 TDD, 클린코드를 적용하여 구조 개선
  • 데이터가 이미 존재하는 상태에서 요구사항 변경 시 리팩토링 방안
  • DB 테이블보다 먼저 도메인 관점에서의 설계 및 개발
  • 의존의 가장 마지막 단계에 있는 기능에서 시작하는 테스트
  • public 메서드를 제공할 때, 사용자가 메서드 호출 순서를 몰라도 사용함에 오류가 없도록 설계

진행 방법

  • 학습 관리 시스템의 수강신청 요구사항을 파악한다.
  • 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
  • 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
  • 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

온라인 코드 리뷰 과정


🚀 1단계 - 레거시 코드 리팩터링

<질문 삭제하기> 요구사항

  • 질문 데이터의 삭제 상태를 변경한다. (deleted = true)
  • 로그인 사용자 == 질문글 작성자인 경우 삭제 가능
  • 질문글에 답변글이 없는 경우 삭제 가능
  • 질문을 삭제하면 답변 또한 삭제한다.
    • 답변의 삭제 상태를 변경한다. (deleted = true)
    • 답변글 작성자 != 로그인 사용자인 경우 답변글을 삭제할 수 없다.
    • 질문글 작성자 != 답변글 작성자인 경우 답변글을 삭제할 수 없다.

리팩터링 요구사항

  • QnaService의 deleteQuestion() 메서드의 핵심 비지니스 로직을 도메인 모델 객체에 구현
    • TDD로 구현
  • 리팩토링 후에도 QnaServiceTest의 모든 테스트는 통과해야 한다.

Todo

  • 답변을 삭제한다. (deleted = true)
  • 질문을 삭제한다. (deleted = true)
  • 답변과 질문의 삭제 이력을 가져온다.

Feedback 23.11.26

  • Wrapper 클래스에서 변수명은 values, value, 값을 가져오는 메서드명은 value(), get()으로 사용해 보기
  • delete 작업 후에 deleteHistories를 즉시 반환하도록 변경
  • Answer의 delete()가 답변만 삭제하고 싶을 경우에도 대응할 수 있도록 변경

🚀 2단계 - 수강신청(도메인 모델)

<수강신청> 기능 요구사항

  • 과정(Course)은 기수 단위로 운영되며, 여러개의 강의(Session)을 가질 수 있다.
  • 강의(Session)
    • 시작일과 종료일을 가진다.
    • 강의 커버 이미지를 가진다.
      • 이미지 크기는 1MB 이하이다.
      • 이미지 타입은 gif, jpg(jpeg 포함), png, svg만 허용한다.
      • 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다.
    • 무료강의와 유료강의로 나뉜다.
      • 무료 강의는 최대 수강 인원의 제한이 없다.
      • 유료 강의는 최대 수강 인원을 초과할 수 없다.
      • 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다.
      • 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다.
        • 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다.
    • 준비중, 모집중, 종료 3가지 상태를 가진다.
  • 수강신청
    • 로그인 유저는 강의를 대상으로 수강신청을 할 수 있다.
    • 수강신청은 모집중 상태인 경우에만 가능하다.

프로그래밍 요구사항

  • DB 테이블 설계 없이 도메인 모델부터 구현한다.
  • 도메인 모델은 TDD로 구현한다. 단, Service 클래스는 단위 테스트가 없어도 된다.

Feedback 23.12.15

  • 이미지 타입을 관리할 수 있는 enum을 사용해보기
  • 항상 인자가 필요한 경우 기본 생성자를 삭제하기
  • 이미지 가로, 세로 비율을 구할 때 부동소수점인 Double 외에 곱셈 혹은 BigDecimal을 사용해보기
    • 코드 가독성과 속도를 고려하여 BigDecimal이 아닌 곱셈으로 수정
  • 강의의 기간을 관리하는 클래스를 만들어서 유효성 검사를 진행해보기
  • 강의에 필수 값이 있다면 생성 시 검증을 진행해보기
  • 클래스 네임에 Info, Data는 사용하지 않고, 의미를 보다 명확히 하기
  • 수강 신청이 완료되면 수강 인원이 늘어야 할 것
  • 파일 마지막의 newLine에 대해 공부해 보고, 누락된 부분을 추가하기

Feedback 23.12.07

  • userNumber를 가져오는 방식에 대한 고민
  • 값이 필요한 객체의 경우 기본생성자를 제공하지 않고, 추가적인 validate를 진행

🚀 3단계 - 수강신청(DB 적용)

요구사항

  • 2단계에서 구현한 도메인 모델을 DB 테이블과 매핑하고, 데이터를 저장한다.
    • schema.sql 에 DB 테이블 추가
      • Session
      • CoverImage
      • NsUserSession
    • CRUD 코드 추가
      • sessionRepository
      • coverImageRepository
    • CRUD 코드에 대한 테스트 코드 추가
      • sessionRepository

Feedback 23.12.12

  • CoverImage와 Sesssion의 관계 고민
    • Session은 CoverImage를 가진다.
    • 수강신청 시 CoverImage의 정보는 필요하지 않다.
    • CoverImage는 sessionId를 반드시 안다.
  • Repository 와 DAO의 차이를 알아보고 설계를 수정
    • 현재의 설계는 DAO에 가깝다. Repository는 DAO를 이용해서 서비스단에서 사용하고자 하는 객체를 만드는데 집중한다.
  • CoverImage의 중복된 validation 제거
  • Wrapper 타입과 Primitive 타입의 쓰임새 확인 후 수정
  • SessionPaymentCondition 내 메서드의 적합한 명명 고민
  • 수강신청 로직 수정(검증)
  • 객체 저장 후 id값을 반환하도록 하기
  • Class 레벨의 @Transactional 활용

🚀 4단계 - 수강신청(요구사항 변경)

변경된 요구사항

  • (기존) 강의가 모집중일 때만 수강 신청이 가능하다.
    • (변경) 강의가 진행 중인 상태에서도 수강신청이 가능하다.
      • 강의 진행 상태 (준비중, 진행중, 종료)와 모집 상태(모집중, 비모집중)으로 분리한다.
      • 강의가 '진행중, 준비중'이고 '모집중'인 상태에서만 수강신청이 가능하다.
  • (기존) 강의는 강의 커버 이미지를 가진다.
    • (변경) 강의는 하나 이상의 강의 커버 이미지를 가질 수 있다.
  • (기존) 수강 신청은 별도의 승인이 필요 없다.
    • (변경) '선발된' 인원만 수강이 가능해야 한다.
    • 강사는 수강신청한 사람 중 선발된 인원에 대해서만 수강 승인이 가능해야 한다.
    • 강사는 수강신청한 사람 중 선발되지 않은 사람은 수강을 취소할 수 있어야 한다.
    • 수강 인원은 미리 정해져있다.
    • 수강신청은 아무나 가능하지만, 강사는 선발된 인원을 확인해서 수강 승인 혹은 취소를 진행한다.

프로그래밍 요구사항

  • DB상에 이미 데이터가 있다는 전제 하에 진행한다. (기존에 쌓인 데이터를 지우지 않아야 한다.)
    • Session 컬럼 session_status를 session_recruitment_status로 변경, session_progress_status 추가
    • CoverImage에 id pk로 추가
    • NsUserSession에 registered 필드 추가, 기존 인원은 ture로 업데이트 하는 시나리오

Feedback 23.12.14

  • create table DDL문을 수정하지 않고 Alter table 을 이용해본다.
    • session
    • ns_user_session
  • sessionStatus를 분류하되, 기존 데이터의로도 수강 신청이 가능하도록 구현
    • 강의 진행 컬럼을 추가하되, 해당 값이 존재하지 않는 기존 강의의 경우 EMPTY 값을 갖도록 함
  • 수강 신청 기능과 강사의 신청 승인은 각각의 기능으로 구현된다.
  • NsUserSession이 3가지 상태(신청, 승인, 취소)를 갖도록 구현해본다.
  • 수강 승인 기능이 생긴 시점을 기준으로 수강 승인 기능을 구현한다.
    • session 테이블에 approval_required 컬럼 추가, default value false
    • ns_user_session 테이블에 enrollment_status 컬럼 추가, default value 'APPROVED'
  • Session에 강사 정보를 추가 및 검증한다.
  • 수강 승인을 할 수 있는 인원이 최대 수강 인원으로 제한되도록 해본다.
    • 추가로, 수강 인원만큼 승인이 되었다면 추가로 수강 승인이 되지 않도록 제한한다.

Feedback 24.01.03

  • NsUserSession 과 같은 클래스 네임은 개발자의 관점으로, 도메인 입장에서 더욱 적합한 이름을 고민해야 함
    • Student, Students
  • enum에게도 역할을 위임할 수 있다. 단, 작은 메서드일 경우 과하게 느껴질 수 있음
  • 수강 승인/취소 역할을 NsUserSession이 담당하도록 해본다
  • 값을 변경할 수 없는 필드에 대해서는 final을 선언하는 습관을 들인다
  • NsUserSessions(Students) 에 새로운 수강자를 추가한 뒤 id 혹은 이름으로 찾는 메서드의 필요성
  • Session 클래스의 필드 수가 많아지며 복잡도는 증가한다. 클래스 분리를 해 볼 것
    • Enrollment 클래스를 추가하고 enroll에 대한 테스트 분리
  • 현재 수강중인 인원 수를 찾기 위해 매번 수강생 전체 리스트를 갖고 오는 것은 성능상 이슈가 생길 수 있다.
    • session 테이블 역정규화, 수강 인원 직접 관리

About

TDD, 클린코드를 적용하여 레거시 코드의 학습 관리 시스템 리팩토링

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%