seaking110 님의 블로그
키오스크 과제 트러블 슈팅 본문
트러블 슈팅: 장바구니 구현 방식에 대한 고민
장바구니를 구현하는 과정에서, 데이터를 어떻게 구조화할지에 대해 고민이 많았습니다. 리스트 기반 방식(List<BasketItem>)과 맵 기반 방식(Map<MenuItem, Integer>) 중 어떤 것이 더 적합한지에 대해 깊이 고민한 끝에, 최종적으로 리스트 방식을 채택했습니다. 아래는 각 방식에 대한 비교와 선택 배경을 정리한 내용입니다.
대안 1: Map<MenuItem, Integer> 방식
구현 개요
- BasketItem 클래스를 제거하고, Basket 클래스에서 Map<MenuItem, Integer>를 사용하여 장바구니 항목을 관리.
- Key: MenuItem (제품 정보), Value: Integer (수량).
- 동일한 제품 추가 시 Key를 기준으로 Value(수량)를 업데이트.
장점
- 수량 관리 효율성:
동일한 제품이 장바구니에 존재하는지 확인하고 수량을 업데이트하는 작업이 Map의 Key 기반 탐색으로 매우 빠르게 수행(O(1)). - 간결한 데이터 구조:
수량을 별도의 클래스(BasketItem)로 관리하지 않고, MenuItem과 매핑된 Integer로 관리하므로 불필요한 객체 생성을 줄일 수 있음. - 코드 간결성:
수량 증가나 감소 등의 작업이 Key를 통해 Value를 업데이트하는 간단한 방식으로 처리 가능.
단점
- 확장성 부족:
장바구니 항목별로 수량 외의 데이터를 추가로 관리해야 하는 경우(예: 사용자 요청사항, 추가 옵션 등), Map<MenuItem, Integer> 구조에서는 이를 처리하기 어렵고 비효율적. - 캡슐화 약화:
수량(Integer)이 제품 정보(MenuItem)와 분리되어 있어, 데이터 간의 관계가 명확하지 않고 코드의 직관성이 떨어질 가능성이 있음.
대안 2: List<BasketItem> 방식
구현 개요
- BasketItem 클래스를 도입해, 제품 정보(MenuItem)와 수량(count)을 캡슐화.
- Basket 클래스에서 List<BasketItem>을 사용하여 장바구니를 관리.
- 동일한 제품 추가 시 리스트에서 검색 후 수량을 업데이트.
장점
- 객체 지향적 설계:
BasketItem 객체를 통해 제품 정보와 수량을 하나로 묶어 강력한 캡슐화를 제공.
데이터 간 관계가 명확하며, 유지보수 및 확장성이 뛰어남. - 확장성 우수:
장바구니 항목별로 추가 데이터를 저장해야 할 경우(옵션, 주문 시각 등), BasketItem에 필드를 추가하거나 메서드를 확장하여 손쉽게 관리 가능. - 명확한 데이터 구조:
제품 정보와 수량을 하나의 객체로 묶어 장바구니 상태를 명확히 표현할 수 있음. - 검색 로직의 단순화:
제품 추가 시 동일 제품이 리스트에 존재하는지 확인 후 수량을 증가시키는 로직을 간단히 구현 가능.
단점
- 검색 성능 저하:
동일 제품이 존재하는지 확인하기 위해 리스트를 탐색해야 하므로 O(n)의 시간 복잡도가 발생. - 메모리 사용 증가:
동일한 제품이라도 별도의 BasketItem 객체를 생성하므로, Map 방식에 비해 약간의 메모리 오버헤드가 발생.
최종 선택: List<BasketItem> 방식
선택 이유
- 객체 지향적 설계 선호:
프로젝트 요구사항에서 장바구니 항목별로 수량 외에도 다양한 데이터를 관리할 가능성이 있었음.
BasketItem 객체를 통해 제품 정보와 수량을 강결합하여 데이터의 일관성과 안전성을 보장. - 확장성 고려:
BasketItem 방식은 추가적인 데이터나 기능을 손쉽게 확장 가능.
반면, Map 방식은 수량 외의 데이터를 추가로 관리하려면 복잡한 변경이 필요. - 가독성과 유지보수성:
BasketItem 방식은 데이터 구조가 명확하며, 메서드 단위로 역할이 잘 분리되어 코드의 가독성과 유지보수성이 뛰어남. - 요구사항 적합성:
Map 방식은 수량 관리에는 효과적이지만, 추가적인 데이터 확장을 고려할 때 적합하지 않다고 판단.
트러블 슈팅의 교훈
장바구니 데이터를 효율적이고 직관적으로 관리하기 위해 두 가지 방식(List<BasketItem>, Map<MenuItem, Integer>)을 고민했으며, 각각의 장단점을 분석했습니다. 최종적으로는 확장성, 가독성, 객체 지향적 설계를 중시하여 리스트 방식을 채택했습니다.
이번 고민을 통해, 단순히 성능이나 효율성만을 따지는 것이 아니라 프로젝트의 요구사항과 확장 가능성에 맞는 설계가 얼마나 중요한지를 배울 수 있었습니다.
트러블 슈팅: 입력 메서드 구현 시 초기값 설정 개선
프로젝트 진행 중 입력 메서드의 구현에서 불필요한 초기값 설정 문제를 발견했습니다. 초기값을 -1로 설정했을 때, 해당 값이 실제 입력 데이터로 오해될 여지가 있음을 확인했습니다. 이를 해결하기 위해 초기값 설정을 없애는 방향으로 메서드를 개선하기로 결정했으며, 이에 대해 여러 가지 접근 방식을 조사하고 실험했습니다.
문제점 분석
변경 전 코드는 다음과 같습니다.
// 사용자의 입력을 받는 메서드
public int input() {
int n = -1;
while (true) {
try {
n = sc.nextInt();
} catch (InputMismatchException e) {
System.out.println("문자가 아닌 숫자를 입력해주세요.\n");
sc.nextLine();
continue;
}
return n;
}
}
이 코드에서 int n = -1;과 같은 초기값은 입력받지 않은 상태를 나타내기 위한 용도였지만, 실제로 프로그램이 입력값으로 -1을 허용하지 않는다는 보장이 없으면 잠재적인 오류를 유발할 가능성이 있습니다. 이로 인해 초기값 설정 없이 입력을 처리할 수 있는 구조로 리팩토링이 필요하다는 결론에 도달했습니다.
해결 방안 조사 및 비교
- 문제 해결을 위해 다음 3가지 방법 고려
- 선언과 동시에 사용자 입력을 처리
- 특징 : 초기값 설정 없이 입력값이 유효할 때 바로 반환하는 방식
- 장점 :
- 코드가 간결하고 가독성이 뛰어남
- 불필요한 변수를 선언하지 않아 메모리 낭비를 줄임
- 단점:
- 코드 확장성이 부족하며, 입력 처리 외의 부가 로직 추가 시 재작성 필요.
- 코드 예시:
public int input() {
while (true) {
try {
return sc.nextInt(); // 유효한 입력이면 바로 반환
} catch (InputMismatchException e) {
System.out.println("문자가 아닌 숫자를 입력해주세요.\n");
sc.nextLine(); // 잘못된 입력 제거
}
}
}
2. Optional 사용
- 특징: Optional 객체를 활용하여 값이 있을 수도, 없을 수도 있음을 명시적으로 표현합니다.
- 장점:
- 값의 존재 여부를 명확히 표현 가능.
- Optional API를 활용해 입력값을 유연하게 처리할 수 있음.
- 단점:
- Java 8 이상에서만 사용 가능.
- 추가적인 Optional 처리 코드로 인해 간단한 입력 처리 로직에 복잡성이 증가.
- 코드 예시
public Optional<Integer> input() {
while (true) {
try {
return Optional.of(sc.nextInt());
} catch (InputMismatchException e) {
System.out.println("문자가 아닌 숫자를 입력해주세요.\n");
sc.nextLine();
}
}
}
3. do-while 루프 사용
- 특징: 루프를 최소 한 번 실행해야 할 때 적합한 구조로, 입력값이 유효하지 않을 경우 반복적으로 실행됩니다.
- 장점:
- 최소 한 번 실행을 보장하며 코드 흐름이 명확.
- 단점:
- 초기값 선언을 피할 수 있으나, do-while 문 자체가 가독성이 좋지 않다는 평가를 받음.
- 실무에서는 while이나 for 루프보다 사용 빈도가 낮음.
- 코드 예시:
public int input() {
int n;
do {
try {
n = sc.nextInt();
break;
} catch (InputMismatchException e) {
System.out.println("문자가 아닌 숫자를 입력해주세요.\n");
sc.nextLine();
}
} while (true);
return n;
}
최종 결정
이번 프로젝트에서는 가독성과 실무적 요구 사항을 고려하여 선언과 동시에 입력을 처리하는 방식을 채택했습니다. 주요 이유는 다음과 같습니다:
- 단순성과 효율성: 입력 처리가 간결하게 구현되어 코드의 가독성이 높아짐.
- 불필요한 변수 제거: 로컬 변수를 줄여 메모리 사용을 최적화.
- 프로젝트 요구사항 부합: 입력 검증 로직이 간단하며, 확장 가능성이 낮은 경우 이 방식이 적합.
Optional 방식은 복잡한 입력 검증과 추가적인 유연성이 필요한 경우 적합하지만, 이번 프로젝트의 경우 단순한 입력 처리 로직에 비해 과도한 설계로 판단되었습니다.
마찬가지로, do-while 방식은 가독성 문제로 인해 배제되었습니다.
트러블 슈팅의 교훈
이번 트러블 슈팅 과정을 통해 단순히 코드를 작성하는 것뿐만 아니라, 코드의 가독성과 실무적 요구 사항을 고려한 설계의 중요성을 배웠습니다. 작은 변경이라도 프로젝트 전반의 품질과 유지보수성에 큰 영향을 미칠 수 있음을 실감했으며, 앞으로도 단순하면서도 명확한 코드 구현을 위해 고민을 이어가겠습니다.
'Today I Learned' 카테고리의 다른 글
| Spring 입문! (0) | 2025.01.22 |
|---|---|
| 백엔드 개발자 필수 지식 (0) | 2025.01.20 |
| Enum과 Stream (0) | 2025.01.17 |
| 분할 정복을 이용한 거듭제곱 (0) | 2025.01.16 |
| 키오스크 과제 3일차 (0) | 2025.01.15 |