-
Notifications
You must be signed in to change notification settings - Fork 107
[그리디] 이태규 로또 미션 1,2단계 제출합니다. #156
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
Changes from all commits
1da4dab
32a1dd2
8cf00d3
f791775
629917e
8d6011f
4f5d777
1236ad6
9c81c4d
8a9d1d1
3dbecca
79c2904
ab4816e
8369d8b
a658783
273a4e6
8c706f7
720e451
0d3ea4d
7eb99bb
0f388ea
b076b77
5639305
8ccef78
e2e4ced
1dd3892
97e0de4
650a6de
f6f090a
8c6bed1
69004b2
2d70068
28a6454
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # 1단계 - 로또 자동 구매 | ||
| ## 기능 요구사항 | ||
| - 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다. | ||
| - 로또 1장의 가격은 1000원이다. | ||
|
|
||
| ## 구현 목록 | ||
| ### Model | ||
| - `Lotto`: 6개의 숫자로 이루어진 개별 로또 객체입니다. | ||
| - `LottoBatch`: 구매한 여러 개의 로또들을 관리하는 일급 컬렉션입니다. | ||
| - `LottoFactory`: 설정된 규칙에 따라 유효한 숫자를 가진 `Lotto` 객체를 생성합니다. | ||
|
|
||
| ### View | ||
| - `InputView`: 사용자로부터 구입 금액 등의 입력을 받는 역할을 담당합니다. | ||
| - `OutputView`: 로또 구매 결과 및 번호 목록을 사용자에게 출력합니다. | ||
|
|
||
| ### Controller | ||
| - `LottoPurchaseController`: 로또 구매 프로세스의 전체적인 흐름을 제어하고 모델과 뷰를 연결합니다. | ||
|
|
||
| ### Common | ||
| - `NumberGenerator`: 숫자 생성을 위한 범용 인터페이스입니다. | ||
| - `LottoNumberGenerator`: 설정된 범위 내에서 임의의 숫자를 생성해 주는 로직을 구현한 클래스입니다. | ||
|
|
||
| ### Constants | ||
| - `LottoSettingsConstants`: 로또 관련 설정값들을 관리합니다. | ||
| - `ScriptConstants`: 사용자에게 보여줄 메시지를 관리합니다. | ||
|
|
||
| ### DTO | ||
| - `LottoDto`: 뷰와 컨트롤러 사이에서 로또 정보를 전달하기 위한 데이터 전송 객체입니다. | ||
|
|
||
| # 2단계 - 로또 당첨 | ||
| ## 기능 요구사항 | ||
| - 로또 당첨 번호를 받아 일치한 번호 수에 따라 당첨 결과를 보여준다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import model.LottoNumberGenerator; | ||
| import common.NumberGenerator; | ||
| import controller.LottoPurchaseController; | ||
| import controller.LottoResultCalculatorController; | ||
| import model.LottoBatch; | ||
| import model.LottoFactory; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| import java.util.Scanner; | ||
|
|
||
| public class Main { | ||
| public static void main(String[] args) { | ||
| LottoBatch lottoBatch = new LottoBatch(); | ||
| NumberGenerator numberGenerator = new LottoNumberGenerator(); | ||
| LottoFactory lottoFactory = new LottoFactory(numberGenerator); | ||
| InputView inputView = new InputView(new Scanner(System.in)); | ||
| OutputView outputView = new OutputView(); | ||
|
|
||
| LottoPurchaseController lottoPurchaseController = new LottoPurchaseController( | ||
| lottoBatch, lottoFactory, inputView, outputView | ||
| ); | ||
|
|
||
| lottoPurchaseController.purchase(); | ||
|
|
||
| LottoResultCalculatorController lottoResultCalculatorController = new LottoResultCalculatorController(lottoBatch, inputView, outputView); | ||
| lottoResultCalculatorController.calculate(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package common; | ||
|
|
||
| public interface NumberGenerator { | ||
| int generateNumber(); | ||
| } |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 검증 로직을 유틸클래스로 따로 분리하신 이유가 있을까요? 검증 로직은 따로 클래스로 가지고 있는 것이 아닌, 각 객체가 가지고있어야하는 책임인 것 같아요!
하나의 클래스에 모든 검증 로직을 넣어두면 검증 로직이 많아질 때 가독성이 떨어질 것 같기도하고,
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 검증 로직이 여러 곳에서 재사용되거나 객체 생성 전/외부 흐름에서도 필요해지는 경우가 있어, 특정 객체에 종속시키기보다는 독립적인 규칙으로 분리하는 것이 변경 대응이나 재사용 측면에서 유리하다고 판단했습니다. 또한 일부 검증은 상태를 가지지 않는 순수 규칙이라 static으로 두는 것도 딱히 큰 문제가 되지 않을 것 같다고 생각했어요. 물론 말씀해주신 것처럼 하나의 클래스에 모든 검증 로직을 넣어두면 검증 로직이 많아질 때 문제가 있겠지만, 적절한 묶음으로 나누면 괜찮을 것 갔아요! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package common; | ||
|
|
||
| import constants.ErrorMessageConstants; | ||
| import constants.LottoSettingsConstants; | ||
|
|
||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public class ValidateLotto { | ||
| public static void checkIfNumbersAreValid(List<Integer> numbers) { | ||
| checkIfDuplicateExist(numbers); | ||
| checkIfNotEnoughNumbers(numbers); | ||
| checkIfTooManyNumbers(numbers); | ||
| checkIfNumbersAreInRange(numbers); | ||
| } | ||
|
|
||
| private static void checkIfNumbersAreInRange(List<Integer> numbers){ | ||
| List<Integer> notInRange = numbers.stream().filter( | ||
| i-> i < LottoSettingsConstants.LOTTO_MINIMUM_NUMBER || i > LottoSettingsConstants.LOTTO_MAXIMUM_NUMBER | ||
| ).toList(); | ||
|
|
||
| if (!notInRange.isEmpty()) { | ||
| throw new IllegalArgumentException(ErrorMessageConstants.NUMBER_OUT_OF_RANGE); | ||
| } | ||
| } | ||
|
|
||
| private static void checkIfNotEnoughNumbers(List<Integer> numbers){ | ||
| if (numbers.size() < LottoSettingsConstants.LOTTO_SIZE) { | ||
| throw new IllegalArgumentException(ErrorMessageConstants.NUMBER_TOO_LITTLE); | ||
| } | ||
| } | ||
|
|
||
| private static void checkIfTooManyNumbers(List<Integer> numbers){ | ||
| if (numbers.size() > LottoSettingsConstants.LOTTO_SIZE) { | ||
| throw new IllegalArgumentException(ErrorMessageConstants.NUMBER_TOO_MANY); | ||
| } | ||
| } | ||
|
|
||
| protected static void checkIfDuplicateExist(List<Integer> numbers) { | ||
| Set<Integer> test = new HashSet<>(numbers); | ||
|
|
||
| if (test.size() != numbers.size()) { | ||
| throw new IllegalArgumentException(ErrorMessageConstants.NO_DUPLICATES_ALLOWED); | ||
| } | ||
| } | ||
|
|
||
| public static void checkPriceHigherThanSingleLottoPrice(int price) { | ||
| if (price < LottoSettingsConstants.LOTTO_PRICE){ | ||
| throw new IllegalArgumentException(ErrorMessageConstants.PRICE_TOO_LOW); | ||
| } | ||
| } | ||
| } |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (참고) 에러메세지를 enum으로 만들어 관리하기도합니다!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 신기하네요! 한 번 확안해 보겠습니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package constants; | ||
|
|
||
| public class ErrorMessageConstants { | ||
| public static String NOT_A_NUMBER = "입력값이 숫자여야 합니다."; | ||
| public static String NOT_A_SINGLE_NUMBER = "입력값은 1개의 정수여야 합니다."; | ||
| public static String NUMBER_OUT_OF_RANGE= "숫자가 로또 숫자 범위를 벗어납니다.."; | ||
| public static String NO_DUPLICATES_ALLOWED = "중복되는 로또 숫자를 넣으면 안됩니다."; | ||
| public static String NUMBER_TOO_LITTLE= "숫자 갯수가 1개의 로또 숫자 갯수보다 작습니다."; | ||
| public static String NUMBER_TOO_MANY= "숫자 갯수가 1개의 로또 숫자 갯수보다 큽니다."; | ||
| public static String PRICE_TOO_LOW= "최소한 1개의 로또를 구매할 수 있는 구입금액을 넣어주세요"; | ||
| public static String NO_MATCHING_RESULT= "불가능한 결과입니다."; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package constants; | ||
|
|
||
| import model.LottoResult; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| public class LottoSettingsConstants { | ||
| public static final int LOTTO_SIZE = 6; | ||
| public static final int LOTTO_MINIMUM_NUMBER = 1; | ||
| public static final int LOTTO_MAXIMUM_NUMBER = 45; | ||
| public static final int LOTTO_RANGE = LottoSettingsConstants.LOTTO_MAXIMUM_NUMBER - LottoSettingsConstants.LOTTO_SIZE + 1; | ||
| public static final int LOTTO_PRICE = 1000; | ||
|
|
||
|
|
||
| // result | ||
| public static final int NO_WIN = 0; | ||
| public static final int THREE_MATCH_PRICE = 5000; | ||
| public static final int FOUR_MATCH_PRICE= 50000; | ||
| public static final int FIVE_MATCH_PRICE = 1500000; | ||
| public static final int SIX_MATCH_PRICE = 2000000000; | ||
| public static final List<LottoResult> WINNING_LOTTO_RESULT_ASCENDING_ORDER = new ArrayList<>(List.of(LottoResult.THREE, LottoResult.FOUR, LottoResult.FIVE, LottoResult.SIX)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package constants; | ||
|
|
||
| public class ScriptConstants { | ||
| public static final String INPUT_CASH_SCRIPT = "구입금액을 입력해 주세요."; | ||
| public static final String OUTPUT_PURCHASE_SCRIPT = "%d개를 구매했습니다."; | ||
| public static final String INPUT_ENTER_WINNING_NUMBER_SCRIPT = "지난 주 당첨 번호를 입력해 주세요."; | ||
| public static final String OUTPUT_STAT_HEADER_SCRIPT = "당첨 통계\n---------"; | ||
| public static final String OUTPUT_STAT_SCRIPT = "%d개 일치 (%d원)- %d개"; | ||
| public static final String OUTPUT_RETURN_RATIO_SCRIPT = "총 수익률은 %.02f입니다."; | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package controller; | ||
|
|
||
| import dto.LottoDto; | ||
| import model.Lotto; | ||
| import model.LottoBatch; | ||
| import model.LottoFactory; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class LottoPurchaseController { | ||
| private final LottoBatch lottoBatch; | ||
| private final LottoFactory lottoFactory; | ||
| private final InputView inputView; | ||
| private final OutputView outputView; | ||
|
|
||
| public LottoPurchaseController( | ||
| LottoBatch lottoBatch, | ||
| LottoFactory lottoFactory, | ||
| InputView inputView, | ||
| OutputView outputView | ||
| ) { | ||
| this.lottoBatch = lottoBatch; | ||
| this.lottoFactory = lottoFactory; | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| } | ||
|
|
||
| public void purchase() { | ||
| int userCashInput = inputView.getUserCashInput(); | ||
|
|
||
| List<Lotto> boughtLottos = lottoFactory.generateLottoByPrice(userCashInput); | ||
| lottoBatch.addAll(boughtLottos); | ||
| List<LottoDto> lottoDtos = lottoBatch.getAllLotto().stream() | ||
| .map(this::wrapLottoIntoDto).toList(); | ||
|
|
||
| outputView.printPurchaseResult(lottoDtos); | ||
| } | ||
|
Comment on lines
+30
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 태규님이 생각하시는 controller의 역할은 무엇인가요? 저는 해당 컨트롤러가 도메인의 역할도 하고있는 것 같아요!
너무 많은 책임을 가지고 있는 것 같은데, 도메인 각 기능에 대해 해당 기능은 어느 도메인 혹은 계층이 가져야할지 고민해보시면 좋을 것 같아요!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 컨트롤러가 두 가지 역할을 해야 한다고 생각합니다 다만, 현재처럼 하나의 메소드가 그 모든 책임을 가지는 것은 문제가 있다고 생각했고, 이에 따라 로또를 반복 생성하는 부분은 별도의 메소드로 분리하여 책임을 나누었습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
태규님이 작성해주신 컨트롤러의 역할에 대해 잘 읽어봤습니다!
컨트롤러는 어플리케이션의 흐름을 관리하는 역할이기에 요 의견에 동의합니다.
하지만 입력/출력 로직의 호출이 아닌 데이터 정제 부분에 대해서는 저는 조금 다른 의견을 가지고있어요! 그전에 🤔💭 만일 로또의 가격이 1000원에서 500원으로 바뀌었다거나, 로또 가격 검증 조건이 추가 및 수정 되었다고 가정해보겠습니다! 현재 코드에서는 저는 이러한 이유로 검증로직을 도메인 부분으로 넘겨야한다고 생각하는데, 태규님은 요 의견에대해 어떻게 생각하시나요?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
이 부분이 저에게 컨트롤러가 어떤 역할을 해야하는지 잘 정리해준 것 같습니다... 확실히 특정 비즈니스 로직이 컨트롤러에 있는 설계는 것 자체가 객체지향적이지 않은 것 같네요. 해당 부분을 참고하여 |
||
|
|
||
| protected LottoDto wrapLottoIntoDto(Lotto lotto) { | ||
| return new LottoDto(lotto.getNumbers()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package controller; | ||
|
|
||
| import constants.LottoSettingsConstants; | ||
| import dto.LottoResultDto; | ||
| import model.Lotto; | ||
| import model.LottoBatch; | ||
| import model.LottoResult; | ||
| import common.ValidateLotto; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class LottoResultCalculatorController { | ||
| private final LottoBatch lottoBatch; | ||
| private final InputView inputView; | ||
| private final OutputView outputView; | ||
|
|
||
| public LottoResultCalculatorController(LottoBatch lottoBatch, InputView inputView, OutputView outputView) { | ||
| this.lottoBatch = lottoBatch; | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| } | ||
|
|
||
| public void calculate() { | ||
| List<Integer> winningNumbers = this.inputView.getWinningNumbers(); | ||
|
|
||
| List<LottoResult> matchCountPerLotto = this.getMatchCountPerLotto(winningNumbers); | ||
| this.outputView.printStats(wrapLottoIntoDto(matchCountPerLotto)); | ||
| this.outputView.printReturnRatio(getReturnRatio(winningNumbers)); | ||
| } | ||
|
|
||
| protected double getReturnRatio(List<Integer> winningNumbers) { | ||
| List<LottoResult> result = this.getMatchCountPerLotto(winningNumbers); | ||
|
|
||
| double earnResult = 0.0; | ||
| for (LottoResult lottoResult : result) { | ||
| earnResult += lottoResult.getReward(); | ||
| } | ||
|
|
||
| return earnResult / (LottoSettingsConstants.LOTTO_PRICE * lottoBatch.getLottoCount()); | ||
| } | ||
|
|
||
| protected List<LottoResult> getMatchCountPerLotto(List<Integer> winningNumbers) { | ||
| ValidateLotto.checkIfNumbersAreValid(winningNumbers); | ||
| List<LottoResult> result = new ArrayList<>(); | ||
|
|
||
| for (Lotto lotto : lottoBatch.getAllLotto()) { | ||
| result.add(lotto.compareWithWinningNumbers(winningNumbers)); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| protected LottoResultDto wrapLottoIntoDto (List<LottoResult> lottoResults) { | ||
| Map<LottoResult, Integer> result = new LinkedHashMap<>(); | ||
| for (LottoResult winningLotto: LottoSettingsConstants.WINNING_LOTTO_RESULT_ASCENDING_ORDER) { | ||
| result.put(winningLotto, 0); | ||
| } | ||
|
|
||
| for (LottoResult lottoResult : lottoResults) { | ||
| if(result.get(lottoResult) != null) { | ||
| result.put(lottoResult, result.get(lottoResult) + 1); | ||
| } | ||
| } | ||
|
|
||
| return new LottoResultDto(result); | ||
| } | ||
| } |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dto를 사용하는 이유는 무엇인가요?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dto는 모델 외 다른 부분들이 모델의 구현과 별개로 필요한 정보를 전달 받을 수 있도록 해줄 수 있는 포장지 같은 클래스로 이해하고 있습니다. 예를 들어 지금은 모델이 단순해 전달해야하는 정보가 적지만, 더 복잡한 모델을 가진 시스템에서는 뷰가 필요한 정보만을 컨트롤러를 통해 전달 받을 수 있게 해주어 편리하기에 확장성을 고려하여 dto를 적용해봤어요. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record LottoDto(List<Integer> numbers) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package dto; | ||
|
|
||
| import model.LottoResult; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| public record LottoResultDto (Map<LottoResult, Integer> lottoResults) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package model; | ||
|
|
||
| import common.ValidateLotto; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public class Lotto { | ||
| private final List<Integer> numbers; | ||
|
|
||
| public Lotto(List<Integer> numbers) { | ||
| ValidateLotto.checkIfNumbersAreValid(numbers); | ||
| this.numbers = new ArrayList<>(numbers); | ||
| } | ||
|
|
||
| public List<Integer> getNumbers() { | ||
| return List.copyOf(this.numbers); | ||
| } | ||
|
|
||
| public LottoResult compareWithWinningNumbers(List<Integer> winningNumbers) { | ||
| Set<Integer> lottoNumbers= new HashSet<>(this.numbers); | ||
| Set<Integer> winningNumberSet = new HashSet<>(winningNumbers); | ||
| lottoNumbers.retainAll(winningNumberSet); | ||
|
|
||
| return LottoResult.calculateLottoResult(lottoNumbers.size()); | ||
| } | ||
| } |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 태규님께서 정의하신 LottoBatch의 역할은 무엇인가요? Readme에는 구매한 여러 개의 로또들을 관리하는 일급 컬렉션이라고 정의해주셨는데, 당첨 결과까지 계산하고있어 조금 많은 책임을 가지고있는 것 같아요 🥲
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일급 컬랙션의 역할은 로또들을 관리하면서, 로또가 여러개 있을 때 유의미한 계산 (평균, 최댓값 등)을 계산하는 것이라고 생각을 했습니다. 하지만 코드를 읽어보니 당첨 결과를 계산하는 것은 외부의 입력값(당첨번호)의 개입이 필요하니, 이 계산은 컨트롤러가 하는 것이 더 좋겠다는 생각이 들어 수정하였습니다!. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package model; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| public class LottoBatch { | ||
| private final List<Lotto> lottos; | ||
|
|
||
| public LottoBatch() { | ||
| this.lottos = new ArrayList<>(); | ||
| } | ||
|
|
||
| public void add(Lotto lotto) { | ||
| this.lottos.add(lotto); | ||
| } | ||
|
|
||
| public void addAll(List<Lotto> lottos) { | ||
| this.lottos.addAll(lottos); | ||
| } | ||
|
|
||
| public List<Lotto> getAllLotto() { | ||
| return List.copyOf(this.lottos); | ||
| } | ||
|
|
||
| public int getLottoCount() { | ||
| return this.lottos.size(); | ||
| } | ||
| } |

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scanner는 View내부에서 선언하는 것이 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 이렇게 의존하는 객체는 최대한 외부로부터 받아오는게 저 좋을것 같아 위처럼 구현했어요. 아무래도 이 방식대로 구현하면 테스팅하기도 좋고, 의존하는 객체가 의존되는 객체의 생성 과정을 몰라도 되어 개발할 때 더 수월하다는 점들 때문에, 왠만하면 의존하는 객체는 외부로부터 받아오고 있도록 개발하는 방향이 더 바람직해 보입니다...!