Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2단계 - 출석 다시 구현하기] 코기(장재현) 미션 제출합니다. #109

Merged
merged 69 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
aa343c3
chore: 기존 코드 전부 삭제
jaehyeon2650 Feb 26, 2025
af6c59b
docs(readme): 구현할 기능 목록 정리
jaehyeon2650 Feb 26, 2025
6224c37
feat(domain): AttendanceHistory 도메인 생성
jaehyeon2650 Feb 26, 2025
8787435
feat(domain): AttendanceHistory 출석 결과 도출 기능 추가
jaehyeon2650 Feb 26, 2025
fd1be46
feat(domain): AttendanceHistories 도메인 생성
jaehyeon2650 Feb 26, 2025
0de98ac
feat(domain): AttendanceHistory 해당 날짜 존재 여부 확인 기능 추가
jaehyeon2650 Feb 26, 2025
8743ed9
feat(domain): AttendanceHistory 출석 추가 기능 검증 추가
jaehyeon2650 Feb 26, 2025
67c557a
feat(domain): Crew 도메인 생성
jaehyeon2650 Feb 26, 2025
1eb1c23
feat(domain): Crews 도메인 생성
jaehyeon2650 Feb 27, 2025
af1ade8
feat(view): InputView 생성
jaehyeon2650 Feb 27, 2025
67f2db9
feat(view): OutputView 생성
jaehyeon2650 Feb 27, 2025
4204fc6
feat(utils): FileReader 생성
jaehyeon2650 Feb 27, 2025
7f5f410
feat(domain): 생성자 변경
jaehyeon2650 Feb 27, 2025
39f26a6
feat(controller): AttendanceController 생성
jaehyeon2650 Feb 27, 2025
dd42979
refactor(AttendanceHistory): AttendanceHistory 맴버 변수 변경
jaehyeon2650 Feb 27, 2025
5374029
feat(AttendanceHistories): AttendanceHistories 도메인 로직 추가
jaehyeon2650 Feb 27, 2025
2b345cc
feat(AttendanceHistories): AttendanceHistories 도메인 로직 추가
jaehyeon2650 Feb 27, 2025
f6e3e15
feat(constants): Holiday enum 클래스 추가
jaehyeon2650 Feb 27, 2025
ed5605a
feat(domain): AttendanceHistories 생성자 변경
jaehyeon2650 Feb 27, 2025
3d24800
feat(domain): 출석 수정 기능 추가
jaehyeon2650 Feb 27, 2025
6dd302e
feat(domain): Crews 이전 기록 조회 기능 추가
jaehyeon2650 Feb 27, 2025
17e5606
feat(view): OutputView 수정 결과 출력 기능 추가
jaehyeon2650 Feb 27, 2025
1b4ff93
feat(view): InputView 기능 추가
jaehyeon2650 Feb 27, 2025
c97e0e5
feat(domain): 출석 검증 추가
jaehyeon2650 Feb 27, 2025
c1a7a6b
feat(controller): AttendanceController 수정 기능 추가
jaehyeon2650 Feb 27, 2025
dd961bb
feat(domain): AttendanceResult enum 타입 추가
jaehyeon2650 Feb 27, 2025
ead7eb9
feat(domain): AttendanceHistory 정렬 기능 추가
jaehyeon2650 Feb 27, 2025
e5c3702
feat(domain): AttendanceStatus enum 클래스 생성
jaehyeon2650 Feb 28, 2025
3665500
feat(domain): AttendanceAnalyze 도메인 추가
jaehyeon2650 Feb 28, 2025
ea52c5d
feat(domain): AttendanceHistory 로직 추가
jaehyeon2650 Feb 28, 2025
31b1991
feat(domain): AttendanceHistories 로직 추가
jaehyeon2650 Feb 28, 2025
739a5dc
feat(domain): AttendanceHistories 로직 추가
jaehyeon2650 Feb 28, 2025
769ca2f
feat(domain): Crews 로직 추가
jaehyeon2650 Feb 28, 2025
59e6805
feat(domain): Crews 로직 추가
jaehyeon2650 Feb 28, 2025
1cb4c8e
feat(dto): 출석 기록 관련 dto 생성
jaehyeon2650 Feb 28, 2025
32ff7a3
feat(view): 이달의 출석 기록 출력 기능 추가
jaehyeon2650 Feb 28, 2025
bf2900e
feat(controller): 이달의 출석 기록 출력 기능 추가
jaehyeon2650 Feb 28, 2025
70a27ab
feat(domain): AttendanceAnalyze 제적 대상자인지 판별하는 로직 추가
jaehyeon2650 Feb 28, 2025
5ebef02
feat(domain): Crew 제적 대상자인지 확인하는 메서드 추가
jaehyeon2650 Feb 28, 2025
0967d3f
feat(domain): Crews 기능 추가
jaehyeon2650 Feb 28, 2025
de1cf12
feat(dto): 제적 위험자 dto 추가
jaehyeon2650 Feb 28, 2025
6261b91
feat(view): 제적 위험자 출력 기능 추가
jaehyeon2650 Feb 28, 2025
9dd9a18
feat(controller): 제적 위험자 출력 기능 추가
jaehyeon2650 Feb 28, 2025
5a5026c
feat(view): 에러 메시지 출력 기능 추가
jaehyeon2650 Feb 28, 2025
43080e8
feat(controller): 재입력 기능 추가
jaehyeon2650 Feb 28, 2025
e6049c5
feat(util): DateFormatter 생성
jaehyeon2650 Feb 28, 2025
9278d6e
refactor(util): FileReader 메서드 정리
jaehyeon2650 Feb 28, 2025
f71010d
refactor(constant,controller): Selection enum 생성 및 컨트롤러 리펙토링
jaehyeon2650 Feb 28, 2025
4796a7f
feat(InputView): 검증 기능 추가
jaehyeon2650 Feb 28, 2025
55c4d4c
refactor(view): outputView 줄바꿈 추가
jaehyeon2650 Feb 28, 2025
6d5ff82
refactor(domain): AttendanceHistories 메서드 추출
jaehyeon2650 Feb 28, 2025
8b7e334
refactor: 메서드 위치 정리 및 공백 수정
jaehyeon2650 Feb 28, 2025
2bfd29d
refactor(domain): AttendanceResult 상수화
jaehyeon2650 Feb 28, 2025
709486f
refactor(domain): Username 포장 객체 생성
jaehyeon2650 Feb 28, 2025
e274851
feat(config): Config 객체 생성
jaehyeon2650 Feb 28, 2025
4878eb9
refactor: 코드 컨벤션 정리
jaehyeon2650 Feb 28, 2025
d269b0e
refactor(domain): 이름 최대 글자 상수화
jaehyeon2650 Mar 1, 2025
6a98fe7
feat(constants): 캠퍼스 운영 시간 상수화
jaehyeon2650 Mar 1, 2025
60c85b2
fix(constants): 운영 시간 로직 버그 수정
jaehyeon2650 Mar 1, 2025
c5c5c65
refactor(constants): Holiday 필드 수정
jaehyeon2650 Mar 1, 2025
553c73a
docs(readme): 기능 최종 정리
jaehyeon2650 Mar 1, 2025
b691be1
docs: 템플릿 작성
jaehyeon2650 Mar 1, 2025
92b8a0d
refactor(domain): CampusOpenTime 패키지 이동
jaehyeon2650 Mar 3, 2025
1b72865
test(domain): Crew 도메인 테스트 추가
jaehyeon2650 Mar 3, 2025
145267e
refactor(domain): AttendanceAnalyze 인스턴스 변수 변경
jaehyeon2650 Mar 3, 2025
978a560
refactor(domain): CampusOpenTime 메서드 이름 변경
jaehyeon2650 Mar 3, 2025
8f36339
refactor(domain): 정렬 인터페이스 구현 삭제
jaehyeon2650 Mar 3, 2025
4f07f7b
refactor(domain): AttendanceHistories getBeforeAttendanceHistories 메서…
jaehyeon2650 Mar 3, 2025
7875674
refactor: Optional 메서드 메개변수 삭제
jaehyeon2650 Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,36 @@
<!-- 리뷰어가 효과적으로 피드백할 수 있도록 중점적으로 피드백받고 싶은 내용을 공유해주세요.
예를 들어, 가장 고민했던 점이나 여전히 어려운 부분, 그리고 이에 대한 생각을 적을 수 있습니다. -->

코드에 대한 질문은 아니고 TDD에 대한 질문이 있습니다. 제가 TDD를 진행할 때 미리 도메인을 설계한 후에 테스트 작성을 하기
시작했습니다. 그런데 TDD에 대해 강의를 들을 때 코치님은 최소한의 설계만 하고 바로 테스트 코드를 작성한다고 하셨습니다.
TDD를 하면서 각 객체의 구성요소를 정하고 역할을 분배한다고 하셨습니다. 저는 여기서 최소한의 설계가 어느정도인지 감이 오지 않습니다.
이번에 TDD를 적용할 땐 미리 객체 구성요소를 설계 후 각 객체에 대해 TDD를 진행했는데 이렇게 하면 안되는건가요??
최소한의 설계가 어느 정도까지인지 궁금합니다!
안녕하세요 로키!😊 step1에서 로키 덕분에 제 코드에 대해 의구심을 가지고 생각하는 기회를 얻을 수 있었습니다. 감사합니다 👍
step1에서 머지된 후 마지막에 코멘트 받은 부분에 대해서도 적용했습니다!
#### 👨‍💻 피드백 적용
- [X] 월, 일은 MonthDay 를 활용하기 -> Holiday 객체
- [X] DTO record 클래스 활용
- [X] LocalDateTime을 LocalDate, LocalTime 으로 분리하여 관리 -> AttendanceHistory
- [X] csv 파일에 없는 기록을 23:59 임의 시간으로 결석을 처리하지 않기 -> LocalDateTime을 LocalDate, LocalTime으로 나눈후 만약 csv 파일에 기록이 없다면 LocalTime을 null로 받는다. null은 위험성이 크기에 Optional을 사용하였고 로직에서 null 포인터 예외가 터지지 않도록 사용 자제
- [X] 상수와 상태를 적절히 분리하기
#### 🤔 궁금한 점
- csv 파일에 없는 기록 즉, 하루 기록이 없는 경우 처리를 예전에 23:59 시간을 정해서 해당 시간은 결석으로 처리했습니다.
하지만 로키의 말대로 현재는 08:00 ~ 23:00만 출석 가능하지만 추후에 해당 요구사항이 변경이 되면 큰 문제가 발생할거라 생각하고 수정을 했습니다.
수정한 방법으로는 LocalDate와 LocalTime 으로 분리 후 LocalTime이 null인 경우 결석으로 간주하도록 했습니다.
하지만 null을 이용한 방법은 null 포인트 예외와 메서드를 사용하는 입장에서 위험하다고 판단했습니다. 그래서 LocalTime을 리턴하는 메서드인 경우 Optional을 통해 null이 있을 수 있음을 알렸습니다. 또한 LocalTime을 이용한 로직은 최소화하고 만약 사용한다면 null 체크를 통해 예외를 방지했습니다.
현재는 결석을 처리하는 방법이 이정도 밖에 생각나지 않네요 ㅠㅠ 사실 이렇게 null로 처리하는 것도 문제가 있다고 생각합니다..
현재 선택한 방법에 대해 로키의 생각과 혹시 추천하시는 다른 방법이 있을지 피드백을 받고 싶습니다!
- 테스트와 관련된 질문입니다!
```text
@Test
@DisplayName("특정 크루 출석 기록 추가하는 테스트")
void addAttendanceHistory() {
// given
String username = "a";
LocalDateTime time = LocalDateTime.of(2024, 12, 24, 10, 31);
// when
AttendanceResult result = crews.addAttendanceHistory(username, time);
// then
assertThat(result).isEqualTo(AttendanceResult.ABSENCE);
}
```
TDD를 진행하면서 crews의 addAttendanceHistory(특정 크루의 출석 기록을 추가하는 메서드) 테스트 만드려고 했습니다(RED 단계). 하지만 // then 에서 검증을 할 때 정상적으로 잘 들어갔는지 확인하는 로직이 필요했습니다.
그래서 이때 테스트를 위한 메서드(특정 크루의 출석 기록을 가져오는 기능)를 추가해서 검증을 해야할지 고민이였습니다. 물론 운이 좋게도 특정 크루의 출석 기록을 가져오는 기능은 추후에 사용되기에 상관없지만 만약 테스트를 위한 메서드가 추후 도메인 로직에서 사용하지 않으면 테스트를 위한 메서드가 존재해 좋지 않다고 생각이 들었습니다.
해당 상황에서 그렇다면 getter를 통해 확인하는 것이 맞는지 혹은 그 외에 테스트 방법이 있을지 궁금합니다!
또한 코치에게 여쭤봤을 땐 테스트를 위한 생성자를 만들어서 equal 메서드로 해결할거 같다고 하셨습니다. 그런데 의문이 든 점이 테스트를 위한 메서드는 지양해야하는데 테스트를 위한 생성자는 괜찮은건지 의문입니다. 로키의 의견은 어떨지 궁금합니다!
150 changes: 74 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
# java-attendance

출석 미션 저장소

## 구현해야할 기능

### 출석 확인

- [x] 닉네임을 입력한다.
- [x] 등교 시간을 입력한다.
- [x] 출석한 시간을 기준과 비교해 출석 결과를 도출한다.(5분 초과 지각, 30분 초과 결석)
- [x] 월요일은 13:00 ~ 18:00
- [x] 화~금요일은 10:00 ~ 18:00
- [x] 캠퍼스 운영 시간에만 출석 가능하다.(08:00~23:00)

#### 입출력 예시

```plain
## 👨‍💻출석 구현

2024년 12월 한 달 동안 시범적으로 최소한의 기능을 갖춘 출석 시스템을 개발하여 출석을 체계적으로 관리하기로 하였습니다.
이 시스템은 코치가 사용하게 됩니다. 아래 요구 사항을 충족하는 출석 시스템을 개발해 주시기를 바랍니다.

### 구현할 기능
#### 전체 프로그램 공통 기능
- [X] 교육 시간은 월요일은 13:00~18:00, 화요일~금요일은 10:00~18:00이다.
- [X] 시작 시간으로부터 5분 초과는 지각
- [X] 시작 시간으로부터 30분 초과는 결석
- [X] 출석 기록이 없으면 결석으로 간주
- [X] 캠퍼스 운영 시간은 매일 08:00 ~ 23:00 이다.
- [X] 프로그램은 사용자가 종료 할 때까지 종료되지 않는다.
- [X] 예외 발생 시 메뉴로 이동한다.
---
#### 출석 확인
- [X] 이름을 입력 받는다.(이름은 최대 5글자)
- [X] 등교 시간을 입력 받는다.(00:00 형태로 입력)
- [X] 출석을 이미 한 경우, 다시 출석할 수 없고 수정 기능으로 안내한다.
- [X] 등교일이 아닌 경우(공휴일) 에러 메시지를 바로 출력한다.
- [x] 08:00 ~ 23:00 이외의 시간을 입력한 경우 에러 메시지를 출력한다.
- [X] 출석 시간과 출석 결과를 출력한다.

##### 세부 로직 기능
- [X] 이름을 통해 크루를 찾는다.
- [X] 해당 크루에 오늘 날짜에 기록이 있는지 확인한다.
- [x] 해당 크루에 출석 기록을 남긴다.
- [x] 해당 크루의 출석 기록을 반환한다.
- [x] 등교 시간이 08:00 ~ 23:00 이외의 시간을 입력한 경우 예외 발생한다.
- [X] 공휴일인 경우 바로 예외 발생한다.
```text
닉네임을 입력해 주세요.
이든
등교 시간을 입력해 주세요.
09:59

12월 05일 화요일 09:59 (출석)
```

#### 예외 사항

- [x] 닉네임은 최대 5글자
- [x] 기존에 등록된 크루여야한다.
- [x] 등교 시간 입력 포맷 HH:MM
- [x] 주말 및 공휴일에는 출석을 받지 않는다.

```plain
[ERROR] 12월 14일 토요일은 등교일이 아닙니다.
```

### 출석 수정

- [x] 출석 수정 크루의 닉네임을 입력한다.
- [x] 수정하려는 날짜를 입력한다.
- [x] 수정 등교 시간을 입력한다.

#### 입출력 예시

```plain
---
#### 출석 수정
- [X] 이름을 입력받는다.
- [X] 수정하려는 날짜를 입력받는다.
- [X] 바꾼 시간을 입력받는다.
- [X] 수정 결과를 출력한다.
##### 세부 로직 기능
- [X] 이름을 통해 크루를 찾는다.
- [X] 특정 날짜의 기록을 가져온다.
- [X] 특정 날짜의 기록을 수정한다.
- [X] 기록 수정 결과를 반환한다.
```text
출석을 수정하려는 크루의 닉네임을 입력해 주세요.
빙티
수정하려는 날짜(일)를 입력해 주세요.
Expand All @@ -53,60 +57,54 @@

12월 03일 화요일 10:07 (지각) -> 09:58 (출석) 수정 완료!
```

#### 예외 사항

- [x] 크루 멤버에 입력된 닉네임이 없는 경우

### 출석 기록 확인

- [x] 닉네임을 입력한다.
- [x] 해당 크루의 전날까지의 출석 기록 전부 출력
- [x] 출석, 지각, 결석 통계 내기
- [x] 통계를 기반으로 대상자 판별
- 누적 지각 및 결석 횟수에 따라 경고 또는 면담을 시행한다. 또한 결석 횟수가 5회를 초과할 때 제적을 시행한다.
- 지각 3회는 결석 1회로 간주한다.
- 경고 대상자: 결석 2회 이상
- 면담 대상자: 결석 3회 이상
- 제적 대상자: 결석 5회 초과

### 출석 기록 확인

```plain

---
#### 크루별 출석 기록 확인
- [X] 이름을 입력받는다.
- [X] 전날까지의 크루 출석 기록을 출력한다.
- [X] 크루 출석 기록 요약 결과를 출력한다.
- [X] 제적 대상자 결과를 출력한다.
##### 세부 로직 기능
- [X] 이름을 통해 크루를 찾는다.
- [X] 기록이 없으면 결석으로 처리한다.
- [X] 특정 날짜에서 전날까지 기록만 조회한다.
- [X] 모든 출석 기록을 요약한다.
- [X] 제적 대상자 결과를 도출한다.
```text
닉네임을 입력해 주세요.
빙티

이번 달 빙티의 출석 기록입니다.

12월 02일 월요일 13:00 (출석)
12월 03일 화요일 09:58 (출석)
12월 03일 화요일 10:07 (지각)
12월 04일 수요일 10:02 (출석)
12월 05일 목요일 10:06 (지각)
12월 06일 금요일 10:01 (출석)
12월 09일 월요일 --:-- (결석)
12월 10일 화요일 10:08 (지각)
12월 10일 화요일 10:03 (출석)
12월 11일 수요일 --:-- (결석)
12월 12일 목요일 --:-- (결석)
12월 13일 금요일 10:02 (출석)

```

#### 예외 사항

- [x] 크루 멤버에 입력된 닉네임이 없는 경우

### 제적 위험자 확인

- [x] 결석 횟수를 지각 3회로 간주하여 가장 지각 횟수가 많은 순서대로 출력
- [x] 출석 상태가 같으면 닉네임으로 오름차순 정리
출석: 5회
지각: 2회
결석: 3회

#### 입출력 예시

```plain
면담 대상자입니다.
```
---
#### 재적 위험자 확인
- [X] 제적 위험자 결과를 출력한다.
- [X] 결석, 지각 횟수, 제적 위험자 결과를 출력한다.
##### 세부 로직 기능
- [X] 전날까지 출석 기록을 바탕으로 제적 위험자를 확인한다.
- [X] 결석 1회 -> 지각 3회로 계산하여 지각이 많은 순으로 정렬한다.
- [X] 만약 같으면 이름 순으로 정렬한다.
```text
제적 위험자 조회 결과
- 빙티: 결석 3회, 지각 4회 (면담)
- 이든: 결석 2회, 지각 5회 (면담)
- 빙봉: 결석 1회, 지각 6회 (면담)
- 쿠키: 결석 2회, 지각 3회 (면담)
- 짱수: 결석 0회, 지각 6회 (경고)
```
```
6 changes: 3 additions & 3 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import configure.Configure;
import config.Config;
import controller.AttendanceController;

public class Main {
public static void main(String[] args) {
Configure configure = new Configure();
AttendanceController attendanceController = configure.attendanceController();
Config config = new Config();
AttendanceController attendanceController = config.attendanceController();
attendanceController.start();
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
package configure;
package config;

import controller.AttendanceController;
import view.InputView;
import view.OutputView;

public class Configure {
private static AttendanceController attendanceController;
private static OutputView outputVIew;
public class Config {
private static OutputView outputView;
private static InputView inputView;
private static AttendanceController attendanceController;

public AttendanceController attendanceController() {
if (attendanceController == null) {
return new AttendanceController(outputVIew(), inputView());
return new AttendanceController(outputView(), inputView());
}
return attendanceController;
}

private OutputView outputVIew() {
if (outputVIew == null) {
outputVIew = new OutputView();
private OutputView outputView() {
if (outputView == null) {
return new OutputView();
}
return outputVIew;
return outputView;
}

private InputView inputView() {
if (inputView == null) {
inputView = new InputView();
return new InputView();
}
return inputView;
}

}
28 changes: 28 additions & 0 deletions src/main/java/constant/Holiday.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package constant;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.MonthDay;
import java.util.Arrays;

public enum Holiday {
CHRISTMAS(MonthDay.of(12, 25));

private final MonthDay monthDay;

Holiday(MonthDay monthDay) {
this.monthDay = monthDay;
}

public static boolean isHoliday(LocalDate localDate) {
boolean isHoliday = Arrays.stream(Holiday.values())
.anyMatch(holiday -> holiday.monthDay.getMonthValue() == localDate.getMonthValue()
&& holiday.monthDay.getDayOfMonth() == localDate.getDayOfMonth());
return isHoliday || isWeekend(localDate);
}

private static boolean isWeekend(LocalDate localDate) {
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
}
}
23 changes: 23 additions & 0 deletions src/main/java/constant/Selection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package constant;

import java.util.Arrays;

public enum Selection {
ADD("1"),
EDIT("2"),
FIND_HISTORY("3"),
FIND_EXPULSION("4"),
QUIT("Q");

private final String value;

Selection(String value) {
this.value = value;
}

public static Selection findSelection(String selection) {
return Arrays.stream(Selection.values()).filter(select -> select.value.equals(selection))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("[ERROR] 1,2,3,4,Q 중에서 선택하세요!"));
}
}
25 changes: 0 additions & 25 deletions src/main/java/constants/SelectionOption.java

This file was deleted.

Loading