LALA's blog
[우아한테크코스 4기] 프리코스 3주차 회고 본문
벌써.. 3주차, 그리고 프리코스가 끝이 났다. 😱
이번 과제는 이전 과제들에 비해 난이도가 확실히 높아졌음을 느꼈고, 그만큼 더 많은 생각과 고민을 하며 과제를 진행하게 됐다.
과제는 난이도가 높아졌는데, 이번 과제 기간동안은 하필 시험, 병원, 일 등 때문에 너무 바빴어서 시간이 많이 부족해서 아쉬움이 너무 남는다. 그래도 과제가 끝나고 꼭 더 리팩토링하고 적용해봐야겠다!
어려웠던 점
- 설계 과정은 여전히 쉽지 않았다. 🙃
- 처음 보는 enum 클래스를 사용해야 했다.
- getter를 지양하고 객체에 메세지를 보내는 방식으로 구현했다.
- 비즈니스 로직과 UI 로직을 분리하려 노력했다.
enum 클래스
이번 과제에서는 enum 클래스를 사용해서 구현해야하는 요구사항이 있었다.
enum 은 단지 타입이 같은 값들을 모아두고 int 값으로 치환(?)해서 사용하는 느낌였는데, "추가 기능 구현" 이 나에게 수많은 물음표를 갖게 했다. enum은 단지 열거형일 뿐인데 저 Coin enum에 어떤 기능을 구현하라는거지...?! 처음보는 형태(?)의 Coin enum을 어떻게 사용해야 하는지 이해하고 삽질하는데만 하루 넘게 시간을 보냈었다고 한다. ㅎㅎㅎ
java의 enum 클래스를 학습해보니 내가 알던 단순한 enum 열거형과는 많이 달랐다.
만약 enum 클래스가 요구사항에 없었더라면 나는 당연하게 상수를 생각하고 이렇게 사용했을 것 같다.
public class CoinType {
public static final int COIN_500 = 500;
public static final int COIN_100 = 100;
public static final int COIN_50 = 50;
public static final int COIN_10 = 10;
}
public class Coin {
private int type;
public Coin(int type) {
this.type = type;
}
}
하지만 Coin Enum 클래스를 사용함으로써 Coin의 타입을 아무 int 형 값이 아닌 [500, 100, 50, 10] 으로 제한할 수 있고,
관계있는 메소드와 코드를 한 클래스에 표현할 수 있기 때문에 Coin의 상태와 행위를 한 곳에서 관리할 수 있게 됨을 느낄 수 있었다.
Enum의 values() 와 같은 기본 메소드를 통해 enum 값들을 배열로 뽑아낼 수도 있고, enum 은 클래스이기 때문에 행위를 하는 메소드를 구현하고 toString() 같은 기본 메소드를 Override 하여 사용할 수 있는 장점 또한 느낄 수 있었다.
Coin.java
public enum Coin {
COIN_500(500),
COIN_100(100),
COIN_50(50),
COIN_10(10);
private final int amount;
Coin(final int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public static List<Integer> toListWithLimit(int amountLimit) {
return Arrays.asList(Coin.values()).stream()
.map(Coin::getAmount)
.filter(amount -> amount <= amountLimit)
.collect(Collectors.toList());
}
public static Coin from(int amount) {
return Arrays.stream(Coin.values())
.filter(coin -> coin.amount == amount)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_COIN_MESSAGE));
}
@Override
public String toString() {
return amount + "원";
}
}
이번 과제에서는 적용하지 않았지만 Enum에 추상 메소드를 추가하면, 각 Enum 값별로 다른 행위를 하는 같은 메소드를 구현할 수 있는 장점이 있었다!! 이 점이 굉장히 매력적이었다. 많은 장점과 매력을 가지고 있는 Enum 클래스를 다양하게 적용해보고 연습해봐야겠다.
public enum Operator {
PLUS("+") {
@Override
public int calculate(int num1, int num2) {
return num1 + num2;
}
},
MINUS("-") {
@Override
public int calculate(int num1, int num2) {
return num1 - num2;
}
},
MULTIPLY("*") {
@Override
public int calculate(int num1, int num2) {
return num1 * num2;
}
},
DIVIDE("/") {
@Override
public int calculate(int num1, int num2) {
return num1 / num2;
}
};
private String symbol;
Operator(String symbol) {
this.symbol = symbol;
}
public abstract int calculate(int num1, int num2);
}
비즈니스 로직과 UI 로직 분리
이번 2주차 피드백에서 "비즈니스 로직과 UI 로직을 분리하라" 라는 피드백을 받았었다.
분명히 피드백을 신경쓰면서 구현을 했었는데.. 1차 기능 구현을 완료하고 코드를 리팩토링 하는 과정에서 해당 피드백에 위배되는 코드를 발견했다 !!
자판기의 동전들을 저장하고 있는 CoinBox 객체는 잔돈을 계산해주는 비즈니스 로직도 수행하는 것 뿐만 아니라 동전의 정보까지 출력해주는 로직까지 수행하고 있었다.
public class CoinBox {
private final EnumMap<Coin, Integer> coinEnumMap = new EnumMap<>(Coin.class);
// 보유한 Coin에서 잔돈을 반환해주는 비즈니스 로직
public void returnChanges(int amount) {
Coin[] coins = Coin.values();
for (Coin coin : coins) {
int number = Math.min(amount / coin.getAmount(), coinEnumMap.get(coin));
if (number > NUMBER_OF_NO_COIN) {
// 반환하는 잔돈을 출력하는 UI 로직
OutputView.printCoinInfo(coin.toString(), number);
amount -= coin.getAmount() * number;
}
}
}
// Coin 정보를 출력하는 UI 로직
public void showCoins() {
coinEnumMap.forEach((coin, numberOfCoin) -> OutputView.printCoinInfo(coin.toString(), numberOfCoin));
}
}
따라서 CoinBox 에서 Coin의 정보나 잔돈을 계산해주는 로직만을 수행하고, VendingMachineController 에서 Output 객체에 정보를 출력하도록 요청하는 방식으로 수정했다.
public List<String> getCoinInfoList() {
List<String> coinInfoList = new ArrayList<>();
coinEnumMap.forEach((coin, numberOfCoin) -> coinInfoList.add(coin.toString() + " - " + numberOfCoin + "개"));
return coinInfoList;
}
public List<String> getChangeInfoListForCustomer(int changeAmount) {
List<String> changeInfoList = new ArrayList<>();
for (Coin coin : Coin.values()) {
int numberOfCoin = Math.min(changeAmount / coin.getAmount(), coinEnumMap.get(coin));
if (numberOfCoin > NUMBER_OF_NO_COIN) {
changeInfoList.add(coin.toString() + " - " + numberOfCoin + "개");
changeAmount -= coin.getAmount() * numberOfCoin;
}
}
return changeInfoList;
}
3주차 마지막 날에 (ㅠㅠ) 피드백에서 작성된 "단일 책임의 원칙" 이라는 키워드로 검색해보고 더 많은 정보를 알게 됐다.
단순히 한 객체가 비즈니스 로직과 UI 로직을 분리시키면 된다고 생각했는데, 객체가 하나의 책임만을 갖도록 클래스를 분리시켜야 하는 것이었다. 책임을 분리할 때는 "해당 객체를 사용하는 액터를 생각하여 책임을 파악한다" 라는 말이 되게 와닿았다. 해당 원칙들을 살펴보니 VendingMachineController가 Model과 View의 중간 역할을 해주긴 하지만, 너무 많은 책임을 가지고 있는 것 같다..
그러다보니 이번 과제에서 "극단적으로 클래스를 쪼개보라"는 제안에 완벽하게 수행하지 못한 기분이 든다...ㅠㅠ 마지막 날 알게 되어 코드에 적용해보지 못하고 제출한게 너무 아쉽다...ㅠㅠ 꼭 최종 코테 전에 다시 리팩토링을 해볼 것이다!!!
객체와 메세지 주고 받기
"객체에 메세지를 주고 받아라"
2주차 피드백에서 굉장히 반가운 피드백을 발견했다! 지난 2주차 과제(관련글)에서 해당 내용을 적용했었기 때문이다. ㅎㅎ
이번 과제에서도 getter 사용을 지양하고 직접 해당 객체에게 메세지를 주고받는 방식으로 적용하려고 노력했다.
너 이 가격보다 비싸니 ?
Product.java
public boolean isChipperThanMoney(int money) {
return cost <= money;
}
너 판매할 물건 있니?
ProductRepository.java
public boolean canSellProduct(int customerMoney) {
long numberOfProduct = productHashMap.keySet().stream()
.filter(product -> product.isChipperThanMoney(customerMoney))
.count();
return numberOfProduct > NUMBER_OF_NO_PRODUCT;
}
마무리
3주라는 시간이 정말 빠르게 지나간 것 같다. 해당 기간동안 정말 내가 생각했던 것 이상으로 많은 것을 얻고 성장하는 시간이었다.
제일 크게 변한 것은 나만의 기준과 의도가 담긴 코드를 작성할 수 있게 된 부분인 것 같다.
내 코드의 문제점은 "왜 이런 방식으로 구현한 것인지 설명할 수 없다"는 점이었다. 부족한 JAVA 언어의 개념들, 객체지향 구조, 예외처리 등을 찾아보고 작은 것 하나일지라도 제 것으로 만들기 위해 학습하며 나만의 기준과 의미를 담아서 코드를 적용하려 노력했다. 역할별 클래스를 나누고, 자료구조를 선택하거나 네이밍을 정할 때도 "왜 이렇게 구현하는 게 더 좋은 방법일지? 어떤 의도를 가졌는지?"와 같은 많은 질문과 고민을 통해 정하고, 좀 더 확실한 개념을 습득하여 적용하도록 노력했다.
이후 매주 보내주시는 피드백을 통해 제가 선택한 방식들의 방향성을 더 확실하게 잡을 수 있었고, 내 코드가 정답이라고 100% 확신하진 못하지만, 이젠 어떤 기준과 의도를 가지고 코드를 작성했는지 자신 있게 말할 수 있게 되었다.
다음으로 변한 것은 개발을 할 때의 습관인 것 같다. 이전의 나는 설계를 하지 않고 당장 코드부터 짜고, 기능이 되면 "왜 이렇게 된 것인지" 깊게 알아보려 하지 않았은 안좋은 습관들이 많았다.
이제는 구현에 들어가기 전에 어떻게 하면 더 좋은 구조를 잡을 수 있을지 고민하는 시간에서 즐거움을 느끼고,
이제는 코드 한두줄을 짜더라도 깔끔한 코드, 네이밍, 객체의 역할에 대해 많은 고민을 하게 되고,
어떤게 더 좋을지 여러 경우를 비교해보며 결정하고,
해당 개념을 확실히 알게 됐을 때 사용하고,
더 좋은 방법은 없을지 계속해서 찾으려 노력하게 된 내 자신을 발견하게 됐다.
그리고 JAVA 개발을 처음하기 때문에 두려움이 컸었는데, 람다식과 Stream이나 Collection, java API들에 익숙해졌고, 조금은 자신 있게 코드를 작성해나갈 수 있게 된 것 같다!
프리코스의 3주라는 짧은 시간동안 많은 개발 지식, 그리고 나의 개발 습관까지 변화시키며 성장할 수 있었다. 그래서 더 우테코에 최종합격하고 싶다는 생각이 드는 것 같다. 10개월이라는 시간동안 얼마나 많은 것을 배우고 성장해나갈 수 있을지...!! 꼭 최종 코테까지 잘 마무리해서 내가 상상하지 못한 곳까지 성장해나가고 싶다. !!! 화이팅 ㅎ_ㅎ 💪🏻
참고 사이트
[OOP] 객체지향 5원칙(SOLID) - 단일 책임 원칙
'개발 > 우아한테크코스' 카테고리의 다른 글
[우테코 강의] 자바 List, Generic (0) | 2022.02.25 |
---|---|
[우테코 강의] 문자와 문자열 ( String , StringBuilder , StringBuffer ) (0) | 2022.02.25 |
DTO 란? 장점은? (0) | 2022.02.17 |
[우아한테크코스 4기] 프리코스 2주차 회고 (0) | 2021.12.07 |
[우아한테크코스 4기] 프리코스 1주차 회고 (0) | 2021.11.30 |