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

[5기] 3주차 Wordle 과제 제출 - 호아 #39

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
f41c990
test: 통합 테스트 - 성공케이스 작성
jongmin4943 Jun 8, 2024
ecce462
docs(README): 게임진행순서, 용어사전 정리
hoa0217 Jun 8, 2024
68be191
docs(README): 모델링(클래스 다이어그램) 작성
hoa0217 Jun 8, 2024
9664220
feat(Position): Position 클래스 생성
hoa0217 Jun 8, 2024
d225a84
feat(Letter): Letter 클래스 생성
hoa0217 Jun 8, 2024
b8ece65
feat(Word): Word 클래스 생성
hoa0217 Jun 8, 2024
76fdedf
feat(Result): Result 클래스 생성
hoa0217 Jun 8, 2024
d97c0c2
refactor(Letter): isSameAlphabetDifferentPosition 메서드명 변경
hoa0217 Jun 9, 2024
846ec02
fix(Result): equals hashCode 구현
hoa0217 Jun 9, 2024
7fdb063
feat(Result): Results 클래스 생성
hoa0217 Jun 9, 2024
d03c4b4
test(Result): Result Fixture 분리
hoa0217 Jun 9, 2024
34c2333
feat(Word): compare 메서드 추가
hoa0217 Jun 9, 2024
60ca6a9
fix(Word): compare 메서드에 노랑타일로직 추가
hoa0217 Jun 9, 2024
e3a595d
test(Word): 글자와 위치가 일부가 같은 경우 테스트 추가
hoa0217 Jun 9, 2024
0a66559
test(Word): 글자와 위치가 전부 다른 경우 테스트 추가
hoa0217 Jun 9, 2024
aa72923
fix(Results): 필드 배열에서 List 로 변경
hoa0217 Jun 9, 2024
20cf464
refactor(Results): 필드 구현체 SortedSet 으로 변경
hoa0217 Jun 9, 2024
151e2ad
fix(Word): 같은 문자가 2개 입력되었을 때, 해당 문자가 정답에 하나만 존재하지만 위치가 틀린 경우 로직 추가
hoa0217 Jun 9, 2024
e14a085
test(Time): LocalDate 모킹 유틸 추가
hoa0217 Jun 12, 2024
b201c59
feat(AnswerFormula): AnswerFormula calculate 구현
hoa0217 Jun 12, 2024
f161544
feat(FileReader): FileReader 구현
hoa0217 Jun 12, 2024
5c08e85
feat(FileReader): FileReader 구현
hoa0217 Jun 12, 2024
bb6eb4f
fix(AnswerFormula): calculate 메서드 반환 타입 int로 변경
hoa0217 Jun 13, 2024
e52073e
fix(AnswerFormula): calculate 메서드 파라미터 예외처리 추가
hoa0217 Jun 13, 2024
b647035
feat(WordBook): 파일 기반 WordBook 클래스 생성
hoa0217 Jun 13, 2024
74acae5
feat(Alphabet): Alphabet 클래스 생성
hoa0217 Jun 13, 2024
7c56c0a
refactor(Letter): Alphabet을 값객체(VO)로 갖는다.
hoa0217 Jun 13, 2024
ee4a197
Merge remote-tracking branch 'origin/jamwa' into jamwa
hoa0217 Jun 15, 2024
db4f022
feat(Record): 단어 비교 기록 클래스 생성
hoa0217 Jun 15, 2024
553fefc
test(Result): 초록색 타일 여부 확인 테스트 추가
hoa0217 Jun 15, 2024
808df83
fix(Result): 초록색,회색 타일 여부 추가
hoa0217 Jun 15, 2024
65b95af
feat(InputView): 사용자 입력을 받는 클래스 생성
hoa0217 Jun 15, 2024
a9e9e76
feat(OutputView): 출력하는 클래스 생성
hoa0217 Jun 15, 2024
e848b10
feat(WordBook): 단어를 찾는 메서드 추가
hoa0217 Jun 15, 2024
6ca7944
test(WordBook): 시간관련 의존성 제거
hoa0217 Jun 15, 2024
4e50c35
fix(Position): 음수 예외처리 추가
hoa0217 Jun 15, 2024
bad65f2
fix(Alphabet): 알파벳은 대소문자를 구분하지 않는다
hoa0217 Jun 15, 2024
f861ed0
fix(ConsoleOutputView): 성공, 실패 종료문구 분리
hoa0217 Jun 15, 2024
1158a12
fix(Record): existAllGreen, isCountOver 메서드 추출
hoa0217 Jun 15, 2024
cc4afbf
feat(Wordle): 게임완성
hoa0217 Jun 15, 2024
8e397f0
fix(Exception): 예외 정리
hoa0217 Jun 15, 2024
5f37a11
feat(FileReader): FileReader 구현
hoa0217 Jun 15, 2024
609a398
refactor(Word): 코드 정리
hoa0217 Jun 17, 2024
7e58797
test: 테스트에 사용되는 리소스(word.text)및 케이스 변경
hoa0217 Jun 17, 2024
a678cd3
refactor: 코드 정리
hoa0217 Jun 17, 2024
d170631
test: 콘솔 통합테스트 시간 고정
hoa0217 Jun 17, 2024
261dbd5
docs: 리드미 정리
hoa0217 Jun 17, 2024
3d52104
docs: 게임 진행 순서 수정
hoa0217 Jun 29, 2024
f09f3c2
refactor(Record): existAllGreen -> existAnswer 메서드명
hoa0217 Jun 29, 2024
55f0609
refactor(Results): isAllGreen -> isAnswer 메서드명
hoa0217 Jun 29, 2024
f8f42c4
refactor(FileWordBook): find 메서드 반환 Optional로 수정
hoa0217 Jun 29, 2024
690cae5
refactor(Alphabet): 알파벳 예외 메시지 추가
hoa0217 Jun 30, 2024
fc871a1
refactor(AnswerFormula): 정답 공식 예외 메시지 추가
hoa0217 Jun 30, 2024
31cf9d0
refactor(FileReader): 파일 읽기 실패 예외 메시지 추가
hoa0217 Jun 30, 2024
5764cde
refactor(Position): 올바르지 않은 위치 예외 메시지 추가
hoa0217 Jun 30, 2024
0f04861
refactor(Wordle): 단어장에 단어가 없을 시 예외를 Wordle에서 던진다.
hoa0217 Jun 30, 2024
8c59d10
refactor(OutputView): 예외메세지를 받아서 뷰로 보여준다.
hoa0217 Jun 30, 2024
9593935
refactor(Wordle): 메서드 리팩토링
hoa0217 Jun 30, 2024
19e3484
refactor(Record): 안쓰는 메서드 isEnd 삭제
hoa0217 Jun 30, 2024
cdb969d
refactor(Word): word 파라미터명 변경
hoa0217 Jun 30, 2024
8b9b2b9
docs: 용어 사전 및 클래스 다이어그램 수정
hoa0217 Jun 30, 2024
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ repositories {
}

dependencies {
testImplementation 'org.mockito:mockito-inline:3.12.0'
Copy link

Choose a reason for hiding this comment

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

테스트하실때 mock을 활용하셨군요! ㅇ_ㅇ

Copy link
Author

Choose a reason for hiding this comment

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

네 맞습니다! staticMock을 사용하려고 의존성을 추가했습니다~~

testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testImplementation 'org.assertj:assertj-core:3.25.3'
}
Expand Down
163 changes: 163 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# 미션 - 워들

## 게임 진행 순서

- 단어장(`words.txt`)에 있는 단어를 읽어들인다.
- 읽어들인 단어들에서 정답인 단어를 정한다.
- 정답은 매일 바뀌며 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어이다.
- 문자 5개를 입력한다.
- 5개가 아닌 경우 재입력을 받는다.
- 단어장에 존재하지 않는 단어인 경우 재입력을 받는다.
- 알파벳이 아닌 경우 재입력을 받는다.
- 입력받은 문자와 정답을 비교한다.
- 비교 결과는 타일이 초록색/노란색/회색 중 하나로 바뀌면서 표현된다.
- 맞는 글자는 초록색, 위치가 틀리면 노란색, 없으면 회색
Copy link

Choose a reason for hiding this comment

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

리뷰를 다하고 돌아보니... 여기에 맞는글자는 초록색이라고 아예 설명을 정해두고 가셨던거였군요! ㅋㅋㅋㅋㅋ
컬러가 바뀌면 allBlue 요런 네이밍으로 전체이동...되는건가요 😉

Copy link
Author

Choose a reason for hiding this comment

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

아이쿠 ㅎㅎㅎㅎ 뷰에 대한 책임을 대부분 콘솔에 몰아 놓고 메서드명에 박아버렸네요 수정해야겠어요!!

- 같은 문자가 2개 입력되었을 때, 해당 문자가 정답에 하나만 존재하지만 위치가 틀린 경우 첫번 째 문자만 노란색으로 표시된다.
Copy link

Choose a reason for hiding this comment

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

같은 문자가 3개 입력되었을 때, 해당 문자가 정답에 하나만 존재하면 어떻게 되나요? 😄

Copy link
Author

Choose a reason for hiding this comment

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

같은 문자가 n개 입력되었을 때, 하나만 존재하면 첫번 째 문자만 노란색으로 표시됩니다! 문서 수정할게요~

- 정답: lurid, 입력: hello, 결과: ⬜⬜🟨⬜⬜
- 6번 안에 맞추면 게임을 종료한다.
- 6번 안에 맞추지 못하면 그래도 종료한다.

## 용어 사전
Copy link

Choose a reason for hiding this comment

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

크 역시 ddd 영재 호아님 👍 정리하신거 멋집니다!

Copy link
Author

Choose a reason for hiding this comment

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

감사합니당!!


| 한글명 | 영문명 | 설명 |
|--------|----------------|------------------------------------------------|
| 워들 | Wordle | 5글자 영어 단어 맞추기 게임 |
| 단어장 | Word Book | 이 게임에서 사용될 수 있는 단어 모음 |
| 입력 단어 | Input Word | 플레이어가 입력하는 5글자 단어 |
| 정답 단어 | Answer Word | 오늘 게임의 정답인 5글자 단어 |
Copy link

Choose a reason for hiding this comment

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

클래스명중에 Word가 있는데 이 Word는 입력단어와 정답 단어를 포괄하는 개념인가요? 😄

Copy link
Author

@hoa0217 hoa0217 Jun 27, 2024

Choose a reason for hiding this comment

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

넵 초반에는 InputWord와 AnswerWord 클래스를 분리하려고 했으나, 굳이 그럴 필요가 없겠더라구요~ 개발하면서 모델링이 계속 변경되었는데 지속적 업데이트의 중요성을 느낍니다 ㅠㅠ

| 글자 | Letter | 단어를 구성하는 알파벳 |
| 영문 | Alphabet | 글자를 구성하는 최소단위 |
| 위치 | Position | 단어를 구성하는 글자의 위치 |
| 플레이어 | Player | 게임에 참여하는 사용자 |
| 결과 | Result | 입력단어와 정답단어를 비교해서 표현되는 타일모음 |
| 비교 | Compare | 입력단어와 정답단어의 글자와 위치를 비교하는 행위 |
| 초록색 타일 | Green Tile | 글자와 위치가 동일한 경우 표현되는 타일 |
| 노란색 타일 | Yellow Tile | 글자는 포함되지만 위치가 다른 경우 표현되는 타일 |
| 회색 타일 | Gray Tile | 글자와 위치가 모두 다른 경우 표현되는 타일 |
| 결과모음 | Results | 라운드가 진행될 때 마다 누적된 결과모음 |
| 기록모음 | Record | 누적된 결과모음의 기록 |
| 기준일 | Base Date | 오늘의 정답 단어를 계산하는 기준일(2021년 6월 19일) |
| 정답 공식 | Answer Formula | 오늘의 정답 단어를 계산하는 공식 `(현재 날짜 - 기준일) % 단어장의 단어 수` |
| 시작 | Start | 플레이어가 워들을 시작하는 행위 |
| 종료 | End | 워들이 종료되는 행위(라운드가 전부 끝났거나, 그 전에 정답을 맞추면 종료된다) |

## 모델링

### 클래스 다이어그램
Copy link

Choose a reason for hiding this comment

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

클래스 다이어그램까지 👍👍👍👍👍


```mermaid
---
title: Wordle
---
classDiagram
class Word {
-List<Letter> letters
+compare(Word inputWord) Results
+equals(Object o) boolean
}

class WordComparator {
-List<Letter> pendingLetters
-Results results
+WordComparator(letters: List<Letter>)
+compare(inputWord: Word): Results
-process(targetLetter: Letter, predicate: Predicate<Letter>, tile: Tile)
-fillEmptyToGray(targetLetter: Letter)
}

Word --|> WordComparator

class Letter {
-Alphabet alphabet
-Position position
+isSameAlphabet(Letter letter) boolean
+getPosition() Position
+equals(Object o) boolean
}

class Alphabet {
-char alphabet
+equals(Object o) boolean
}

class Position {
-char position
+compareTo(Position position) boolean
+equals(Object o) boolean
}

class Record {
-List<Results> record
+add(Results results) void
+isEnd() boolean
+isCountOver() boolean
+existAllGreen() boolean
+iterator() Iterator<Results>
+size() int
}
class Results {
-SortedSet<Result> results
+add(Result result) void
+isCheckedPosition(Position position) boolean
+isAllGreen() boolean
}
class Result {
-Tile tile
-Position poistion
+isSamePosition(Position position) boolean
+equals(Object o) boolean
+compareTo(Result result) int
+isGreen() boolean
+isYellow() boolean
+isGray() boolean
}
class Tile {
<<enumeration>>
+GREEN
+YELLOW
+GRAY
}
class AnswerFormula {
<<interface>>
+calculate(int wordCount) int
}
class BaseAnswerFormula {
+calculate(int wordCount) int
}
BaseAnswerFormula ..|> AnswerFormula
class Wordle {
-WordBook wordBook
-InputView inputView
-OutputView outputView
-Record record
-AnswerFormula answerFormula
+startGame() void
-runGame(Word answerWord) void
-processTurn(Word answerWord) void
-concludeGame() void
-handleWrongAnswer(Runnable runnable) void
}
class WordBook {
<<interface>>
+pick(AnswerFormula formula) Word
+exist(Word word) boolean
+find(String target) Word
}
class FileWordBook {
-List<Word> words
+pick(AnswerFormula formula) Word
+exist(Word word) boolean
+find(String target) Word
}
FileWordBook ..|> WordBook
```

## 🚀 세부 요구 사항

- 6x5 격자를 통해서 5글자 단어를 6번 만에 추측한다.
- 플레이어가 답안을 제출하면 프로그램이 정답과 제출된 단어의 각 알파벳 종류와 위치를 비교해 판별한다.
- 판별 결과는 흰색의 타일이 세 가지 색(초록색/노란색/회색) 중 하나로 바뀌면서 표현된다.
- 맞는 글자는 초록색, 위치가 틀리면 노란색, 없으면 회색
- 두 개의 동일한 문자를 입력하고 그중 하나가 회색으로 표시되면 해당 문자 중 하나만 최종 단어에 나타난다.
- 정답과 답안은 `words.txt`에 존재하는 단어여야 한다.
- 정답은 매일 바뀌며 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어이다.
21 changes: 21 additions & 0 deletions src/main/java/wordle/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package wordle;

import wordle.application.Wordle;
import wordle.domain.FileWordBook;
import wordle.domain.WordBook;
import wordle.infra.FileReader;
import wordle.ui.ConsoleInputView;
import wordle.ui.ConsoleOutputView;
import wordle.ui.InputView;
import wordle.ui.OutputView;

public class Application {

public static void main(String[] args) {
WordBook wordBook = new FileWordBook(new FileReader());
InputView inputView = new ConsoleInputView();
OutputView outputView = new ConsoleOutputView();
Wordle wordle = new Wordle(wordBook, inputView, outputView);
wordle.startGame();
}
}
72 changes: 72 additions & 0 deletions src/main/java/wordle/application/Wordle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package wordle.application;

import wordle.domain.AnswerFormula;
import wordle.domain.BaseAnswerFormula;
import wordle.domain.Record;
import wordle.domain.Results;
import wordle.domain.Word;
import wordle.domain.WordBook;
import wordle.exception.WordleException;
import wordle.exception.WordleInvalidInputException;
import wordle.ui.InputView;
import wordle.ui.OutputView;

public class Wordle {

private final WordBook wordBook;

private final InputView inputView;

private final OutputView outputView;

private final Record record;

private final AnswerFormula answerFormula;

public Wordle(WordBook wordBook, InputView inputView, OutputView outputView) {
this.wordBook = wordBook;
this.inputView = inputView;
this.outputView = outputView;
this.record = new Record();
this.answerFormula = new BaseAnswerFormula();
}

public void startGame() {
Word answerWord = wordBook.pick(answerFormula);
outputView.welcome();
runGame(answerWord);
concludeGame();
}

private void runGame(Word answerWord) {
while (!record.isEnd()) {
outputView.showRecord(record);
handleWrongAnswer(() -> processTurn(answerWord));
}
}

private void processTurn(Word answerWord) {
Copy link

Choose a reason for hiding this comment

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

각 단계의 명칭이 Turn이군요! 😄
Results의 용어사전에는 '라운드가 진행될 때 마다 누적된 결과모음'라고 적혀있는데 요기서 말하는 라운드와 턴은 동일한 단어일까요?

Copy link
Author

@hoa0217 hoa0217 Jun 29, 2024

Choose a reason for hiding this comment

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

ㅎㅎㅎㅎㅎ 메서드명을 processRound로 변경해야겠네요

outputView.askAnswer();
Word inputWord = wordBook.find(inputView.input());
Results results = answerWord.compare(inputWord);
record.add(results);
}

private void concludeGame() {
if (record.existAllGreen()) {
outputView.successEnd(record);
return;
}
outputView.failEnd(record);
}
Copy link

Choose a reason for hiding this comment

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

record에는 종료되었는지 여부를 isEnd() 조건을 잘 만들어주셨는데요 👍
정답을 맞췄을때 종료와 실패했을때 종료의 분기처리가 필요해지는군요!
혹시 record에서 existAllGreen라는 rule을 물어보기보다는 정말로 정답을 맞췄는지 물어보는건 어떨까요? 😄


private void handleWrongAnswer(Runnable runnable) {
try {
runnable.run();
} catch (WordleInvalidInputException e) {
outputView.wrongAnswer();
} catch (WordleException e) {
outputView.unexpectedEnd();
}
Copy link

Choose a reason for hiding this comment

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

크 exception과 각각의 화면출력요소를 남기는 센스 👍👍
outputView에서 wrongAnswer와 unexpectedEnd는 서로 출력 메시지만 다르더라고요!
exception에서 message를 담아서 출력해주는 방식은 혹시 어떻게 생각하시나요 😄

Copy link
Author

Choose a reason for hiding this comment

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

아예 예외메세지를 담아서 outputView에 넘겨주는 방법도 있겠네요~

}
}
37 changes: 37 additions & 0 deletions src/main/java/wordle/domain/Alphabet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package wordle.domain;

import java.util.Objects;
import wordle.exception.InvalidAlphabetException;

public class Alphabet {

private static final char MIN_ALPHABET = 'a';
private static final char MAX_ALPHABET = 'z';
private final char alphabet;

public Alphabet(char alphabet) {
char lowerAlphabet = Character.toLowerCase(alphabet);
if (lowerAlphabet < MIN_ALPHABET || lowerAlphabet > MAX_ALPHABET) {
Copy link

Choose a reason for hiding this comment

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

알파벳에 min과 max라는 표현을 사용할수도 있군요! 뭔가 숫자 표기만 보다가 문자표기는 신선하네요 👍

throw new InvalidAlphabetException();
}

this.alphabet = lowerAlphabet;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Alphabet alphabet1 = (Alphabet) o;
return alphabet == alphabet1.alphabet;
}

@Override
public int hashCode() {
return Objects.hash(alphabet);
}
}
7 changes: 7 additions & 0 deletions src/main/java/wordle/domain/AnswerFormula.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package wordle.domain;

@FunctionalInterface
Copy link

Choose a reason for hiding this comment

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

오 functional interace까지 👍

public interface AnswerFormula {

int calculate(int wordCount);
}
19 changes: 19 additions & 0 deletions src/main/java/wordle/domain/BaseAnswerFormula.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package wordle.domain;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import wordle.exception.AnswerFormulaException;

public class BaseAnswerFormula implements AnswerFormula {

private static final LocalDate BASE = LocalDate.of(2021, 6, 19);
private static final int MIN_WORD_COUNT = 1;

public int calculate(int wordCount) {
if (wordCount < MIN_WORD_COUNT) {
throw new AnswerFormulaException();
}

return (int) ChronoUnit.DAYS.between(BASE, LocalDate.now()) % wordCount;
}
}
38 changes: 38 additions & 0 deletions src/main/java/wordle/domain/FileWordBook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package wordle.domain;

import java.util.List;
import wordle.exception.WordNotExistException;
import wordle.infra.FileReader;

public class FileWordBook implements WordBook {

public static final String FILE_PATH = "words.txt";
private final List<Word> words;

public FileWordBook(FileReader fileReader) {
this.words = fileReader.readByLine(FILE_PATH)
.stream()
.map(Word::new)
.toList();
}

@Override
public Word pick(AnswerFormula answerFormula) {
int index = answerFormula.calculate(words.size());
return words.get(index);
}

@Override
public boolean exist(Word word) {
return words.contains(word);
}

@Override
public Word find(String target) {
Word targetWord = new Word(target);
return words.stream()
.filter(targetWord::equals)
.findFirst()
.orElseThrow(WordNotExistException::new);
}
Copy link

Choose a reason for hiding this comment

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

오... 단어가 없을 때 대한 예외처리도 WordBook이 직접 하는군요? ㅋㅋㅋㅋ
FileWordBook이 인프라적인 요소라고 생각해보면, 예를들어 db에서 조회해오는 repository라고 생각해보면.... repo에서 직접 예외를 던지시는 편이신가욧? 😄

Copy link
Author

@hoa0217 hoa0217 Jun 27, 2024

Choose a reason for hiding this comment

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

헉...... WordBook이 레포라는 관점으로 보면 어플리케이션 서비스에서 던지는게 맞겠네요.. 역시 👍

}
Loading