1. 개요
개인 과제인 콘솔 쇼핑몰을 개발하면서 오류가 발생했던 부분과 고민했던 내용에 대해 다뤘습니다.
2. 트러블 슈팅
1) 객체가 초기화 되는 문제
이 문제는 상품의 총 가격 totalPrice 값을 출력하는 곳에서 발생했습니다.
잠시 코드에 대한 설명을 하겠습니다.
먼저 판매중인 상품의 목록을 저장하는 productList와
장바구니에 담긴 상품들의 총 가격 totalPrice를 인스턴스 변수로 선언하였습니다.
그 후, 상품을 장바구니에 담는 과정에서 상품의 가격을 기존의 totalPrice에 더했습니다.
import '../data/product.dart';
import 'dart:io';
// 전체적인 쇼핑몰을 관리하는 클래스
class ShoppingMall {
// 상품 목록
List<Product> productList = [
Product("shirts", 45000),
Product("onepiece", 30000),
Product("short sleeves", 35000),
Product("short pants", 38000),
Product("socks", 5000),
];
// 장바구니에 담긴 상품들의 총 가격
int totalPrice = 0;
// 상품을 장바구니에 담기
void addToCart() {
try {
stdout.write('상품 이름을 입력해 주세요 !\n');
String? product = stdin.readLineSync();
final item = productList.where((e) => e.productName == product);
if (item.isEmpty) {
throw Exception('입력값이 올바르지 않아요 !\n');
}
stdout.write('상품 개수를 입력해 주세요 !\n');
int? itemCount = int.parse(stdin.readLineSync()!);
if (itemCount < 1) {
throw Exception('0개보다 많은 개수의 상품만 담을 수 있어요 !\n');
}
productInCart.add(item.first.productName);
totalPrice += item.first.productPrice * itemCount;
print('장바구니에 상품이 담겼어요 !\n');
} catch (e) {
print('입력값이 올바르지 않아요 !\n');
}
}
// 장바구니에 담은 상품의 총 가격을 출력
void showTotal() {
if (productInCart.isEmpty) {
print('장바구니에 담긴 상품이 없습니다.');
return;
}
print('장바구니에 ${productInCart.join(',')}(이)가 담겨있네요. 총 $totalPrice원 입니다 !\n');
}
}
문제는 main.dart 파일에서 이를 실행해야 하는데 아무리 장바구니에 담아도 totalPrice가 0원이 된다는 점이었습니다.
하지만, addToCart()에서 장바구니에 담은 후, totalPrice를 출력하면 0이 아닌 정상적인 값이 대입 됩니다.
그렇기 때문에 addToCart() 메서드의 문제는 아니라고 판단했습니다.
그렇다면 총 가격을 출력하는 showTotal()메서드가 문제일까요?
showTotal() 메서드 내부를 보시면 그저 다른 로직 없이 totalPrice를 출력하고 있습니다.
그래서 showTotal()도 문제가 아닐 것이라 생각했습니다.
이제 마지막 확인해야 할 코드가 남아있습니다.
바로 main.dart코드를 확인해보겠습니다.
main 함수의 내부는 while문으로 이루어져있는데요.
이는 사용자가 번호를 입력하면 프로그래밍이 끝나지 않기 위함입니다.
import 'dart:io';
import 'dart:convert';
import '../bin/domain/shopping_mall.dart';
void main() {
while (true) {
stdout.write(
'[1] 상품 목록 보기 / [2] 장바구니에 담기 / [3] 장바구니에 담긴 상품의 총 가격 보기 / [4] 프로그램 종료\n',
);
String? input = stdin.readLineSync();
var emart = ShoppingMall();
switch (input) {
case '1':
emart.showProducts();
break;
case '2':
emart.addToCart();
break;
case '3':
emart.showTotal();
break;
case '4':
print('정말 종료하시겠습니까?');
String? isEnd = stdin.readLineSync();
if (isEnd == '5') {
print('이용해 주셔서 감사합니다 ! 안녕히 가세요!');
return;
} else {
print('종료하지 않습니다.');
break;
}
default:
print('지원하지 않는 기능입니다 ! 다시 시도해 주세요 ..');
}
}
}
눈썰미가 좋으신 분이라면 눈치 채실 수도 있을 것이라 생각합니다.
문제는 ShoppingMall 객체를 만드는 과정을 while문 안에 넣은 것이죠.
while 문 안에 객체 생성 과정을 넣게 되면 한 번 while 문을 돌 때마다 객체가 재생성됩니다.
쉽게 말해서 한 번의 입력이 끝날 때마다 객체가 생성되어 기존의 만들어진 객체에는 더 이상 접근할 수 없게 된 것입니다.
그래서 기존에 담았던 상품들의 totalPrice가 0으로 초기화된 것이었죠.
import 'dart:io';
import '../bin/domain/shopping_mall.dart';
void main() {
var emart = ShoppingMall();
while (true) {
stdout.write(
'[1] 상품 목록 보기 / [2] 장바구니에 담기 / [3] 장바구니에 담긴 상품의 총 가격 보기 / [4] 프로그램 종료\n',
);
String? input = stdin.readLineSync();
switch (input) {
case '1':
emart.showProducts();
break;
case '2':
emart.addToCart();
break;
case '3':
emart.showTotal();
break;
default:
print('지원하지 않는 기능입니다 ! 다시 시도해 주세요 ..');
break;
}
}
}
ShoppingMall 객체를 생성하는 코드를 while 문 바깥으로 옮기니 정상적으로 작동 했습니다.
3. 프로젝트 소개
제 프로젝트는 총 4개의 파일로 이루어져있습니다.
main.dart, domain폴더의 entities에 있는 product.dart, domain폴더의 usecase에 있는 shopping_mall.dart, utils 폴더의 input.dart 파일입니다.
제가 여기서 domain 폴더의 entities와 usecase로 나눈 이유는 다음과 같습니다.
entities는 비즈니스의 데이터 모델을 정의하는 폴더로,
상품의 이름과 가격을 정의하는 Product 클래스는 데이터를 표현하기 때문에 entities로 분리하였습니다.
usecase는 비즈니스 로직을 구현하는 계층으로,
핵심 기능(판매 상품 표시, 장바구니에 담기 등)을 처리하기 때문에 usecase로 분리하였습니다.
이 코드는 domain/entities/product.dart 파일입니다.
판매하는 상품 객체를 만들기 위한 Product class가 정의되어 있습니다.
별다른 로직없이 Product 객체를 생성하기 위한 데이터이기 때문에 domain/entities 폴더로 나누었습니다.
// 판매하는 상품을 정의하기 위한 클래스
class Product {
String productName = '';
int productPrice = 0;
Product(this.productName, this.productPrice);
}
이 코드는 domain/usecase/shopping_mall.dart입니다.
제가 생각했을 때 ShoppingMall 클래스는 사용자의 비즈니스 로직을 처리한다고 생각하여 domain/usecase로 나누었습니다.
import '../entities/product.dart';
import '../../utils/input.dart';
import 'dart:io';
// 전체적인 쇼핑몰을 관리하는 클래스
class ShoppingMall {
// 상품 목록
List<Product> productList = [
Product("shirts", 45000),
Product("onepiece", 30000),
Product("short sleeves", 35000),
Product("short pants", 38000),
Product("socks", 5000),
];
// 장바구니에 담긴 상품들의 총 가격
int totalPrice = 0;
List<String> productInCart = [];
// 상품 목록 출력
void showProducts() {
for (Product product in productList) {
print('${product.productName} / ${product.productPrice}원');
}
}
// 상품을 장바구니에 담기
void addToCart() {
try {
stdout.write('상품 이름을 입력해 주세요 !\n');
String? product = getUserInput();
final item = productList.where((e) => e.productName == product);
if (item.isEmpty) {
throw Exception('입력값이 올바르지 않아요 !\n');
}
stdout.write('상품 개수를 입력해 주세요 !\n');
int? itemCount = int.parse(getUserInput()!);
if (itemCount < 1) {
throw Exception('0개보다 많은 개수의 상품만 담을 수 있어요 !\n');
}
productInCart.add(item.first.productName);
totalPrice += item.first.productPrice * itemCount;
print('장바구니에 상품이 담겼어요 !\n');
} catch (e) {
print('입력값이 올바르지 않아요 !\n');
}
}
// 장바구니에 담은 상품의 총 가격을 출력
void showTotal() {
if (productInCart.isEmpty) {
print('장바구니에 담긴 상품이 없습니다.');
return;
}
print('장바구니에 ${productInCart.join(',')}(이)가 담겨있네요. 총 $totalPrice원 입니다 !\n');
}
// 장바구니 초기화
void resetCart() {
if (productInCart.isEmpty) {
print('이미 장바구니가 비어있습니다.');
return;
}
print('장바구니를 초기화합니다.');
productInCart = [];
totalPrice = 0;
}
}
세 번째로 utils/input.dart 파일입니다.
이 파일은 사용자의 입력을 받는 부분만 구현되어 있습니다.
사용자의 입력을 받는 부분이 중복되어 따로 함수로 분리하면 좋지 않을까해서 분리해보았습니다.
물론 현재 프로젝트에서 사용자 입력을 받는 부분은 코드의 규모가 작아 분리를 하지 않아도 되지만,
점차 프로젝트 규모가 커지면 이렇게 함수를 분리하는 것이 효율적이라고 생각했습니다.
import 'dart:io';
String? getUserInput() {
return stdin.readLineSync();
}
마지막으로 main.dart 파일입니다.
먼저 ShoppingMall 객체를 생성한 후, while문으로 사용자의 입력을 받습니다.
사용자의 입력에 따라 각 분기별로 처리한 후, 4번을 누른 후, 5번을 누른다면 프로그램이 종료됩니다.
import 'dart:io';
import '../bin/domain/shopping_mall.dart';
void main() {
var emart = ShoppingMall();
while (true) {
stdout.write(
'[1] 상품 목록 보기 / [2] 장바구니에 담기 / [3] 장바구니에 담긴 상품의 총 가격 보기 / [4] 프로그램 종료 / [6] 장바구니 초기화\n',
);
String? input = stdin.readLineSync();
switch (input) {
case '1':
emart.showProducts();
break;
case '2':
emart.addToCart();
break;
case '3':
emart.showTotal();
break;
case '4':
print('정말 종료하시겠습니까?');
String? isEnd = stdin.readLineSync();
if (isEnd == '5') {
print('이용해 주셔서 감사합니다 ~ 안녕히 가세요 !');
return;
} else {
print('종료하지 않습니다.');
break;
}
case '6':
emart.resetCart();
break;
default:
print('지원하지 않는 기능입니다 ! 다시 시도해 주세요 ..');
break;
}
}
}
4. 마무리
트러블 슈팅은 처음 작성해 보는건데 제 코드에 대해 많이 생각하게 되는 계기가 된 것 같습니다.
특히, 객체 생성 위치가 중요한 영향을 미친다는 점을 코드 실행을 통해 확인할 수 있었습니다.
또한, 프로젝트를 폴더 구조로 나누면서 비즈니스 로직와 데이터 분리에 대한 고민도 하게 되었고,
사용자 입력을 어디서 처리해야 하는지에 대해서도 많은 생각을 하게 되었습니다.
비즈니스 로직이 과하게 커지는 것을 방지하기 위해 별도의 입력 처리 클래스를 도입하는 것도 좋은 방법이라는 생각이 들었습니다.
마지막으로 과제를 늦게 시작했었는데 조금 일찍 했었으면 어땠을까 하는 아쉬움도 남습니다.
빠른 분들은 월요일부터 과제를 시작하신 분도 있더라구요.
구현 과제는 빨리 시작하면 시작할수록 좋다는 생각이 들었습니다.
'TIL' 카테고리의 다른 글
[TIL] 250318 LeetCode 풀이, 주석 정리, 개인 과제 (0) | 2025.03.18 |
---|---|
[TIL] 250317 LeetCode 27번, 예외처리 (2) | 2025.03.17 |
[TIL] 250313 클래스와 과제 시작 (0) | 2025.03.14 |
[TIL] 250312 제네릭, 함수형 프로그래밍 (feat. Dart) (0) | 2025.03.12 |
[TIL] 250311 Collections, Enum, Function (feat. Dart) (1) | 2025.03.11 |
1. 개요
개인 과제인 콘솔 쇼핑몰을 개발하면서 오류가 발생했던 부분과 고민했던 내용에 대해 다뤘습니다.
2. 트러블 슈팅
1) 객체가 초기화 되는 문제
이 문제는 상품의 총 가격 totalPrice 값을 출력하는 곳에서 발생했습니다.
잠시 코드에 대한 설명을 하겠습니다.
먼저 판매중인 상품의 목록을 저장하는 productList와
장바구니에 담긴 상품들의 총 가격 totalPrice를 인스턴스 변수로 선언하였습니다.
그 후, 상품을 장바구니에 담는 과정에서 상품의 가격을 기존의 totalPrice에 더했습니다.
import '../data/product.dart';
import 'dart:io';
// 전체적인 쇼핑몰을 관리하는 클래스
class ShoppingMall {
// 상품 목록
List<Product> productList = [
Product("shirts", 45000),
Product("onepiece", 30000),
Product("short sleeves", 35000),
Product("short pants", 38000),
Product("socks", 5000),
];
// 장바구니에 담긴 상품들의 총 가격
int totalPrice = 0;
// 상품을 장바구니에 담기
void addToCart() {
try {
stdout.write('상품 이름을 입력해 주세요 !\n');
String? product = stdin.readLineSync();
final item = productList.where((e) => e.productName == product);
if (item.isEmpty) {
throw Exception('입력값이 올바르지 않아요 !\n');
}
stdout.write('상품 개수를 입력해 주세요 !\n');
int? itemCount = int.parse(stdin.readLineSync()!);
if (itemCount < 1) {
throw Exception('0개보다 많은 개수의 상품만 담을 수 있어요 !\n');
}
productInCart.add(item.first.productName);
totalPrice += item.first.productPrice * itemCount;
print('장바구니에 상품이 담겼어요 !\n');
} catch (e) {
print('입력값이 올바르지 않아요 !\n');
}
}
// 장바구니에 담은 상품의 총 가격을 출력
void showTotal() {
if (productInCart.isEmpty) {
print('장바구니에 담긴 상품이 없습니다.');
return;
}
print('장바구니에 ${productInCart.join(',')}(이)가 담겨있네요. 총 $totalPrice원 입니다 !\n');
}
}
문제는 main.dart 파일에서 이를 실행해야 하는데 아무리 장바구니에 담아도 totalPrice가 0원이 된다는 점이었습니다.
하지만, addToCart()에서 장바구니에 담은 후, totalPrice를 출력하면 0이 아닌 정상적인 값이 대입 됩니다.
그렇기 때문에 addToCart() 메서드의 문제는 아니라고 판단했습니다.
그렇다면 총 가격을 출력하는 showTotal()메서드가 문제일까요?
showTotal() 메서드 내부를 보시면 그저 다른 로직 없이 totalPrice를 출력하고 있습니다.
그래서 showTotal()도 문제가 아닐 것이라 생각했습니다.
이제 마지막 확인해야 할 코드가 남아있습니다.
바로 main.dart코드를 확인해보겠습니다.
main 함수의 내부는 while문으로 이루어져있는데요.
이는 사용자가 번호를 입력하면 프로그래밍이 끝나지 않기 위함입니다.
import 'dart:io';
import 'dart:convert';
import '../bin/domain/shopping_mall.dart';
void main() {
while (true) {
stdout.write(
'[1] 상품 목록 보기 / [2] 장바구니에 담기 / [3] 장바구니에 담긴 상품의 총 가격 보기 / [4] 프로그램 종료\n',
);
String? input = stdin.readLineSync();
var emart = ShoppingMall();
switch (input) {
case '1':
emart.showProducts();
break;
case '2':
emart.addToCart();
break;
case '3':
emart.showTotal();
break;
case '4':
print('정말 종료하시겠습니까?');
String? isEnd = stdin.readLineSync();
if (isEnd == '5') {
print('이용해 주셔서 감사합니다 ! 안녕히 가세요!');
return;
} else {
print('종료하지 않습니다.');
break;
}
default:
print('지원하지 않는 기능입니다 ! 다시 시도해 주세요 ..');
}
}
}
눈썰미가 좋으신 분이라면 눈치 채실 수도 있을 것이라 생각합니다.
문제는 ShoppingMall 객체를 만드는 과정을 while문 안에 넣은 것이죠.
while 문 안에 객체 생성 과정을 넣게 되면 한 번 while 문을 돌 때마다 객체가 재생성됩니다.
쉽게 말해서 한 번의 입력이 끝날 때마다 객체가 생성되어 기존의 만들어진 객체에는 더 이상 접근할 수 없게 된 것입니다.
그래서 기존에 담았던 상품들의 totalPrice가 0으로 초기화된 것이었죠.
import 'dart:io';
import '../bin/domain/shopping_mall.dart';
void main() {
var emart = ShoppingMall();
while (true) {
stdout.write(
'[1] 상품 목록 보기 / [2] 장바구니에 담기 / [3] 장바구니에 담긴 상품의 총 가격 보기 / [4] 프로그램 종료\n',
);
String? input = stdin.readLineSync();
switch (input) {
case '1':
emart.showProducts();
break;
case '2':
emart.addToCart();
break;
case '3':
emart.showTotal();
break;
default:
print('지원하지 않는 기능입니다 ! 다시 시도해 주세요 ..');
break;
}
}
}
ShoppingMall 객체를 생성하는 코드를 while 문 바깥으로 옮기니 정상적으로 작동 했습니다.
3. 프로젝트 소개
제 프로젝트는 총 4개의 파일로 이루어져있습니다.
main.dart, domain폴더의 entities에 있는 product.dart, domain폴더의 usecase에 있는 shopping_mall.dart, utils 폴더의 input.dart 파일입니다.
제가 여기서 domain 폴더의 entities와 usecase로 나눈 이유는 다음과 같습니다.
entities는 비즈니스의 데이터 모델을 정의하는 폴더로,
상품의 이름과 가격을 정의하는 Product 클래스는 데이터를 표현하기 때문에 entities로 분리하였습니다.
usecase는 비즈니스 로직을 구현하는 계층으로,
핵심 기능(판매 상품 표시, 장바구니에 담기 등)을 처리하기 때문에 usecase로 분리하였습니다.
이 코드는 domain/entities/product.dart 파일입니다.
판매하는 상품 객체를 만들기 위한 Product class가 정의되어 있습니다.
별다른 로직없이 Product 객체를 생성하기 위한 데이터이기 때문에 domain/entities 폴더로 나누었습니다.
// 판매하는 상품을 정의하기 위한 클래스
class Product {
String productName = '';
int productPrice = 0;
Product(this.productName, this.productPrice);
}
이 코드는 domain/usecase/shopping_mall.dart입니다.
제가 생각했을 때 ShoppingMall 클래스는 사용자의 비즈니스 로직을 처리한다고 생각하여 domain/usecase로 나누었습니다.
import '../entities/product.dart';
import '../../utils/input.dart';
import 'dart:io';
// 전체적인 쇼핑몰을 관리하는 클래스
class ShoppingMall {
// 상품 목록
List<Product> productList = [
Product("shirts", 45000),
Product("onepiece", 30000),
Product("short sleeves", 35000),
Product("short pants", 38000),
Product("socks", 5000),
];
// 장바구니에 담긴 상품들의 총 가격
int totalPrice = 0;
List<String> productInCart = [];
// 상품 목록 출력
void showProducts() {
for (Product product in productList) {
print('${product.productName} / ${product.productPrice}원');
}
}
// 상품을 장바구니에 담기
void addToCart() {
try {
stdout.write('상품 이름을 입력해 주세요 !\n');
String? product = getUserInput();
final item = productList.where((e) => e.productName == product);
if (item.isEmpty) {
throw Exception('입력값이 올바르지 않아요 !\n');
}
stdout.write('상품 개수를 입력해 주세요 !\n');
int? itemCount = int.parse(getUserInput()!);
if (itemCount < 1) {
throw Exception('0개보다 많은 개수의 상품만 담을 수 있어요 !\n');
}
productInCart.add(item.first.productName);
totalPrice += item.first.productPrice * itemCount;
print('장바구니에 상품이 담겼어요 !\n');
} catch (e) {
print('입력값이 올바르지 않아요 !\n');
}
}
// 장바구니에 담은 상품의 총 가격을 출력
void showTotal() {
if (productInCart.isEmpty) {
print('장바구니에 담긴 상품이 없습니다.');
return;
}
print('장바구니에 ${productInCart.join(',')}(이)가 담겨있네요. 총 $totalPrice원 입니다 !\n');
}
// 장바구니 초기화
void resetCart() {
if (productInCart.isEmpty) {
print('이미 장바구니가 비어있습니다.');
return;
}
print('장바구니를 초기화합니다.');
productInCart = [];
totalPrice = 0;
}
}
세 번째로 utils/input.dart 파일입니다.
이 파일은 사용자의 입력을 받는 부분만 구현되어 있습니다.
사용자의 입력을 받는 부분이 중복되어 따로 함수로 분리하면 좋지 않을까해서 분리해보았습니다.
물론 현재 프로젝트에서 사용자 입력을 받는 부분은 코드의 규모가 작아 분리를 하지 않아도 되지만,
점차 프로젝트 규모가 커지면 이렇게 함수를 분리하는 것이 효율적이라고 생각했습니다.
import 'dart:io';
String? getUserInput() {
return stdin.readLineSync();
}
마지막으로 main.dart 파일입니다.
먼저 ShoppingMall 객체를 생성한 후, while문으로 사용자의 입력을 받습니다.
사용자의 입력에 따라 각 분기별로 처리한 후, 4번을 누른 후, 5번을 누른다면 프로그램이 종료됩니다.
import 'dart:io';
import '../bin/domain/shopping_mall.dart';
void main() {
var emart = ShoppingMall();
while (true) {
stdout.write(
'[1] 상품 목록 보기 / [2] 장바구니에 담기 / [3] 장바구니에 담긴 상품의 총 가격 보기 / [4] 프로그램 종료 / [6] 장바구니 초기화\n',
);
String? input = stdin.readLineSync();
switch (input) {
case '1':
emart.showProducts();
break;
case '2':
emart.addToCart();
break;
case '3':
emart.showTotal();
break;
case '4':
print('정말 종료하시겠습니까?');
String? isEnd = stdin.readLineSync();
if (isEnd == '5') {
print('이용해 주셔서 감사합니다 ~ 안녕히 가세요 !');
return;
} else {
print('종료하지 않습니다.');
break;
}
case '6':
emart.resetCart();
break;
default:
print('지원하지 않는 기능입니다 ! 다시 시도해 주세요 ..');
break;
}
}
}
4. 마무리
트러블 슈팅은 처음 작성해 보는건데 제 코드에 대해 많이 생각하게 되는 계기가 된 것 같습니다.
특히, 객체 생성 위치가 중요한 영향을 미친다는 점을 코드 실행을 통해 확인할 수 있었습니다.
또한, 프로젝트를 폴더 구조로 나누면서 비즈니스 로직와 데이터 분리에 대한 고민도 하게 되었고,
사용자 입력을 어디서 처리해야 하는지에 대해서도 많은 생각을 하게 되었습니다.
비즈니스 로직이 과하게 커지는 것을 방지하기 위해 별도의 입력 처리 클래스를 도입하는 것도 좋은 방법이라는 생각이 들었습니다.
마지막으로 과제를 늦게 시작했었는데 조금 일찍 했었으면 어땠을까 하는 아쉬움도 남습니다.
빠른 분들은 월요일부터 과제를 시작하신 분도 있더라구요.
구현 과제는 빨리 시작하면 시작할수록 좋다는 생각이 들었습니다.
'TIL' 카테고리의 다른 글
[TIL] 250318 LeetCode 풀이, 주석 정리, 개인 과제 (0) | 2025.03.18 |
---|---|
[TIL] 250317 LeetCode 27번, 예외처리 (2) | 2025.03.17 |
[TIL] 250313 클래스와 과제 시작 (0) | 2025.03.14 |
[TIL] 250312 제네릭, 함수형 프로그래밍 (feat. Dart) (0) | 2025.03.12 |
[TIL] 250311 Collections, Enum, Function (feat. Dart) (1) | 2025.03.11 |