요구사항 정의 및 설계
- 요구사항 정의
- 어떤 계산기 기능이 필요한지 명확히 합니다.
- 예를 들어, 기본적인 사칙연산(덧셈, 뺄셈, 곱셈, 나눗셈), 괄호 사용, 제곱근, 제곱 등.
- 사용자 인터페이스 방식 결정 : 콘솔 기반인지, GUI(그래픽 사용자 인터페이스) 기반인지.
- 예외 처리 : 0으로 나누기와 같은 오류를 어떻게 처리할지 결정합니다.
- 설계
- 클래스 다이어그램: 계산기에 필요한 클래스를 설계합니다. 예를 들어, Calculator, Operation, Parser 등이 될 수 있습니다.
- 기능 분해: 주요 기능을 메소드로 분리합니다. 예를 들어, add(), subtract(), multiply(), divide(), evaluateExpression() 등.
- 데이터 흐름 설계: 데이터가 클래스 간에 어떻게 흐를지를 결정합니다. 입력, 처리, 출력의 흐름을 이해합니다.
요구사항 |
LV 1 1. 양의 정수(0 포함)를 입력받기 2. 사칙연산 기호(➕,➖,✖️,➗)를 입력받기 3. 위에서 입력받은 양의 정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과값을 출력하기 4. 반복문을 사용하되, 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지 무한으로 계산을 진행할 수 있도록 소스 코드를 수정하기 LV 2 1. App 클래스의 main 메서드에 Calculator 클래스가 활용될 수 있도록 수정 2. App 클래스의 main 메서드에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정 (캡슐화) LV 3 1. 계산된 결과 값들을 기록 2. 컬렉션의 가장 먼저 저장된 데이터를 삭제 3. 양의 정수만 받았지만 이제부터는 실수도 받을수 있게 수정 4. 결과가 저장되어 있는 컬렉션을 조회하는 기능을 만든다. 5. 그때 특정 값 보다 큰 결과 값을 출력 할 수 있도록. |
설계( 클래스 다이어그램 ) |
클래스 다이어그램 Calculator : 계산의 전반적인 부분을 담당 Main : 반복문으로 실제 순회와 화면 표시등, 기능의 통합과 출력을 담당 Folder : 데이터를 저장하는 공간을 창출 Option : 계산의 조건을 담당 설계 Option Calculator Folder Main |
설계( 기능 분해 ) |
Calculator : 계산의 전반적인 부분을 담당 - add(), subtract(), multiply(), divide() Main : 반복문으로 실제 순회와 화면 표시등, 기능의 통합과 출력을 담당 Folder : 데이터를 저장하는 공간을 창출 - save content (), removecontent() Operator : 계산의 조건을 담당 - caloption() |
설계( 데이터 흐름 설계 ) |
1. 입력 - 사용자 데이터 입력 -> Main에서 요청 2. 처리 - Calculator 클래스 객체 생성 -> 계산 -> Folder -> 데이터 저장, 수정, 출력 3. 출력 - Main 출력 |
### 1. **클래스 다이어그램**
각각의 클래스의 역할과 관계를 명확히 하고, 이를 바탕으로 클래스를 설계해 보겠습니다.
#### 주요 클래스 및 관계
1. **Option (부모 클래스)**
- **역할**: 계산기의 설정 및 옵션을 관리합니다.
- **속성**:
- `operator`: 계산에 사용할 연산자(예: +, -, *, /)
- **메소드**:
- `getOperator()`: 현재 연산자 반환
- `setOperator(String operator)`: 연산자 설정
2. **Calculator (자식 클래스, Option을 상속)**
- **역할**: 실제 계산을 담당하는 클래스입니다. `Option` 클래스를 상속받아 연산자와 계산을 처리합니다.
- **속성**:
- `result`: 계산된 결과값
- **메소드**:
- `add(double a, double b)`: 덧셈
- `subtract(double a, double b)`: 뺄셈
- `multiply(double a, double b)`: 곱셈
- `divide(double a, double b)`: 나눗셈
- `evaluateExpression(double a, double b)`: 두 숫자와 연산자로 계산을 처리
3. **Folder (데이터 저장 클래스)**
- **역할**: 계산된 결과를 저장하고 관리하는 클래스입니다.
- **속성**:
- `history`: 계산 결과를 저장하는 리스트나 컬렉션
- **메소드**:
- `storeResult(double result)`: 계산된 결과를 저장
- `getHistory()`: 저장된 결과 반환
- `removeOldestResult()`: 가장 오래된 결과를 삭제
4. **Main (메인 클래스)**
- **역할**: 프로그램의 실행과 사용자와의 상호작용을 담당합니다. 반복문을 사용해 계속해서 계산을 수행할 수 있게 합니다.
- **속성**:
- `calculator`: `Calculator` 객체
- `folder`: `Folder` 객체
- `parser`: `Parser` 객체
- **메소드**:
- `runCalculator()`: 계산기 실행
- `displayResults()`: 결과를 화면에 출력
- `exitProgram()`: 프로그램 종료
5. **Parser (입력 처리 클래스)**
- **역할**: 사용자 입력을 검증하고 파싱하는 역할을 합니다.
- **속성**: 없음
- **메소드**:
- `parseInput(String input)`: 사용자 입력을 분석하여 유효성 검사 및 필요한 데이터 반환
- `isValidOperator(String operator)`: 연산자가 유효한지 검사
- `isValidNumber(String number)`: 숫자 유효성 검사
---
### 2. **기능 분해**
각각의 클래스가 수행하는 주요 기능을 메소드로 분해하면 다음과 같습니다:
1. **Option 클래스**:
- `getOperator()`: 계산에 사용할 연산자 반환
- `setOperator(String operator)`: 연산자 설정
2. **Calculator 클래스** (Option을 상속받음):
- `add(double a, double b)`: 덧셈 수행
- `subtract(double a, double b)`: 뺄셈 수행
- `multiply(double a, double b)`: 곱셈 수행
- `divide(double a, double b)`: 나눗셈 수행 (0으로 나누기 예외 처리 포함)
- `evaluateExpression(double a, double b)`: 주어진 두 숫자와 연산자로 계산을 실행
3. **Folder 클래스**:
- `storeResult(double result)`: 계산된 결과를 저장
- `getHistory()`: 저장된 결과를 반환
- `removeOldestResult()`: 가장 오래된 계산 결과를 제거
4. **Main 클래스**:
- `runCalculator()`: 계산기 실행 (무한 루프)
- `displayResults()`: 저장된 결과 출력
- `exitProgram()`: 프로그램 종료
5. **Parser 클래스**:
- `parseInput(String input)`: 사용자 입력을 분석하여 유효한 숫자와 연산자 추출
- `isValidOperator(String operator)`: 연산자 검증 (예: `+`, `-`, `*`, `/`)
- `isValidNumber(String number)`: 숫자 유효성 검사 (예: 실수 또는 정수인지 확인)
---
### 3. **데이터 흐름 설계**
데이터 흐름 설계는 각 클래스 간에 데이터가 어떻게 이동하고 처리되는지 보여줍니다.
#### 데이터 흐름 단계
1. **사용자 입력**:
- 사용자는 `Main` 클래스에서 계산식을 입력합니다. 예: `5 + 3`.
2. **입력 파싱 및 검증**:
- `Main` 클래스는 입력값을 **Parser** 클래스에 전달합니다.
- **Parser** 클래스는 입력값을 분석하여 연산자(`+`, `-`, `*`, `/`)와 숫자 두 개를 분리합니다.
- 숫자와 연산자에 대한 유효성 검사를 진행합니다.
3. **연산 처리**:
- **Main** 클래스는 파싱된 입력을 **Calculator** 클래스에 전달합니다.
- **Calculator** 클래스는 연산자를 바탕으로 해당 연산을 실행하고, 결과를 반환합니다.
4. **결과 저장**:
- **Calculator** 클래스는 계산된 결과를 **Folder** 클래스에 저장합니다.
- **Folder** 클래스는 결과를 `history` 컬렉션에 저장하고, 이를 관리합니다.
5. **결과 출력**:
- **Main** 클래스는 `Folder` 클래스에서 계산된 결과를 조회하고, 출력합니다.
6. **계속 진행**:
- 사용자가 `"exit"`을 입력할 때까지 반복적으로 계산을 수행하고 결과를 출력합니다.
---
### **전체 흐름 요약**
1. **사용자 입력** → `Main` → **Parser** (입력 파싱 및 검증)
2. **Parser** → `Operation` 객체 생성 → **Calculator** (연산 처리)
3. **Calculator** → 결과 반환 → `Folder` (결과 저장)
4. **Folder** → 저장된 결과 조회 → `Main` (결과 출력)
5. **반복**: 사용자가 `"exit"`을 입력하기 전까지 계산 반복
---
위 설계를 바탕으로, 프로그램의 각 클래스가 어떻게 협력하여 계산을 수행하고 데이터를 처리하는지를 명확히 할 수 있습니다.
챗 GPT의 답인데 인간이 필요 없는거 아닌가...
< MAIN >
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
double result = 0.0;
String input1, input2, input3, operator;
Scanner scanner = new Scanner(System.in);
Calculator calc = new Calculator();
DataManager dataManager = new DataManager(); // 데이터 매니저 인스턴스 생성
while (true) {
try {
System.out.print("Enter first number or command (exit/delete data/find data): ");
input1 = scanner.nextLine();
// break 문을 통해 루프를 중지합니다.
if (input1.equalsIgnoreCase("exit")) {
System.out.println("Exiting the program.");
break;
}
if (input1.equalsIgnoreCase("delete data")) {
System.out.println("Deleting all data...");
dataManager.clearAllData(); // 모든 데이터 삭제
continue;
}
if (input1.equalsIgnoreCase("delete one data")) {
System.out.print("Deleting one data: ");
input2 = scanner.nextLine();
try {
double number2 = Double.parseDouble(input2);
System.out.println(dataManager.confirm(input1, number2));
} catch (NumberFormatException e) {
System.out.println("Invalid input. Please enter a valid number.");
}
continue;
}
// 각 상황별 대응을 코딩하였습니다.
// 추가로 클래스를 이용한 인스턴스를 통해 문제를 해결했습니다.
if (input1.equalsIgnoreCase("modified data")) { //데이터 수정
try {
System.out.print("Origin data: ");
input2 = scanner.nextLine();
double number2 = Double.parseDouble(input2);
System.out.print("Modified data: ");
input3 = scanner.nextLine();
double number3 = Double.parseDouble(input3);
System.out.println(dataManager.confirm(input1, number2, number3));
} catch (NumberFormatException e) {
System.out.println("Invalid input. Please enter valid numbers.");
}
continue;
}
if (input1.equalsIgnoreCase("add data")) {// 데이터 추가
System.out.print("Enter add number: ");
input2 = scanner.nextLine();
try {
double number2 = Double.parseDouble(input2);
System.out.println(dataManager.confirm(input1, number2));
} catch (NumberFormatException e) {
System.out.println("Invalid input. Please enter a valid number.");
}
continue;
}
if (input1.equalsIgnoreCase("find data")) {// 데이터 찾기
System.out.print("Enter comparison operator (e.g., '>'): ");
String findOption = scanner.nextLine();
System.out.print("Enter the number to compare with: ");
try {
double comparisonValue = Double.parseDouble(scanner.nextLine());
// 조건에 맞는 데이터 검색 후 출력
List<Double> foundData = dataManager.findDataByCondition(findOption, comparisonValue);
System.out.println("Found data: " + foundData);
} catch (NumberFormatException e) {
System.out.println("Invalid input. Please enter a valid number.");
}
continue;
}
// 사측연산 구현
double number1 = Double.parseDouble(input1);
System.out.print("Enter second number: ");
input2 = scanner.nextLine();
double number2 = Double.parseDouble(input2);
System.out.print("Enter an operator (+, -, *, /): ");
operator = scanner.nextLine();
try {
Operator operatorEnum = Operator.fromString(operator);
result = calc.calculate(number1, number2, operatorEnum);
System.out.println("Result: " + result);
// 결과를 DataManager에 저장
dataManager.addData(result);
// 아래는 예외처리입니다. 글자가 입력된 경우, 연산자 오류인경우, 잘못된 산술연산이 발생한 경우, 특정 예외 전부를 탐색하기 위해 Exception클래스를 사용한 전체 예외처리
} catch (IllegalArgumentException e) {
System.out.println("Invalid operator. Please enter one of +, -, *, /.");
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
} catch (NumberFormatException e) {
System.out.println("Invalid input. Please enter a valid number.");
} catch (Exception e) {
System.out.println("An unexpected error occurred: " + e.getMessage());
}
}
scanner.close();
}
}
< Calculator >
public class Calculator {
public <T extends Number> double calculate(T x, T y, Operator operator) {
double num1 = x.doubleValue(); // double형 변환
double num2 = y.doubleValue();
switch (operator) {
case ADD:
return num1 + num2;
case SUBTRACT:
return num1 - num2;
case MULTIPLY:
return num1 * num2;
case DIVIDE:
if (num2 == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return num1 / num2;
default:
throw new IllegalArgumentException("Unsupported operator: " + operator);
}
}
}
< DataManager >
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class DataManager {
private List<Double> dataList;
public DataManager() {
this.dataList = new ArrayList<>();
}
// 데이터 추가
public void addData(double data) {
dataList.add(data);
}
// 조건에 맞는 데이터를 찾기 (>, <, = 조건) using Streams
public List<Double> findDataByCondition(String condition, double value) {
return dataList.stream()
.filter(data -> {
switch (condition) {
case ">": return data > value;
case "<": return data < value;
case "=": return data == value;
default:
System.out.println("Invalid condition. Use >, <, or =.");
return false;
}
})
.collect(Collectors.toList());
}
// `add data` 및 `delete one data` 작업을 위한 메서드 (제네릭 사용)
public <T extends Number> String confirm(String condition, T value) {
switch (condition) {
case "add data":
dataList.add(value.doubleValue());
return "Data added: " + value.doubleValue();
case "delete one data":
// 람다, 데이터 삭제
boolean removed = dataList.removeIf(data -> data == value.doubleValue());
return removed ? "Complete delete one data" : "Data not found for deletion";
default:
return "Unknown condition: " + condition;
}
}
// `modify data` 작업을 위한 메서드 (제네릭 사용)
public <T extends Number> String confirm(String condition, T value, T value2) {
if ("modified data".equals(condition)) {
for (int i = 0; i < dataList.size(); i++) {
if (dataList.get(i) == value.doubleValue()) {
dataList.set(i, value2.doubleValue());
return "Complete data modify: " + value + " -> " + value2;
}
}
}
return "Condition not found for modification.";
}
// 모든 데이터 삭제
public void clearAllData() {
dataList.clear();
System.out.println("All data has been deleted.");
}
}
< Operator >
public enum Operator { // 스트림을 통한 사측연산 구현
ADD("+"),
SUBTRACT("-"),
MULTIPLY("*"),
DIVIDE("/");
private final String symbol;
Operator(String symbol) {
this.symbol = symbol;
}
public String getSymbol() {
return symbol;
}
public static Operator fromString(String symbol) {
for (Operator operator : Operator.values()) {
if (operator.getSymbol().equals(symbol)) {
return operator;
}
}
throw new IllegalArgumentException("Invalid operator: " + symbol);
}
}
구현
Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기
- [ ] 양의 정수(0 포함)를 입력받기
- [ ] Scanner를 사용하여 양의 정수 2개(0 포함)를 전달 받을 수 있습니다.
- [ ] 양의 정수는 각각 하나씩 전달 받습니다.
- [ ] 양의 정수는 적합한 타입으로 선언한 변수에 저장합니다.
- [ ] 사칙연산 기호(➕,➖,✖️,➗)를 입력받기
- [ ] Scanner를 사용하여 사칙연산 기호를 전달 받을 수 있습니다.
- [ ] 사칙연산 기호를 적합한 타입으로 선언한 변수에 저장합니다. (charAt(0))
- [ ] 위에서 입력받은 양의 정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과값을 출력하기
- [ ] 키워드 : if switch
- [ ] 사칙연산 기호에 맞는 연산자를 사용하여 연산을 진행합니다.
- [ ] 입력받은 연산 기호를 구분하기 위해 제어문을 사용합니다. (예를 들면 if, switch)
- [ ] 연산 오류가 발생할 경우 해당 오류에 대한 내용을 정제하여 출력합니다.
- [ ] ex) “나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.“
- [ ] 반복문을 사용하되, 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지 무한으로 계산을 진행할 수 있도록 소스 코드를 수정하기
- [ ] 키워드 : 무한으로 반복, 수정하기 (처음부터 무한 반복하는 것이 아니라, 위 스텝별로 진행하며 수정)
- [ ] 반복문을 사용합니다. (예를 들어, for, while…)
Lv 2. 클래스를 적용해 기본적인 연산을 수행할 수 있는 계산기 만들기
- [ ] 사칙연산을 수행 후, 결과값 반환 메서드 구현 & 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
- [ ] 사칙연산을 수행한 후, 결과값을 반환하는 메서드 구현
- [ ] 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
- [ ] 1) 양의 정수 2개(0 포함)와 연산 기호를 매개변수로 받아 사칙연산(➕,➖,✖️,➗) 기능을 수행한 후 2) 결과 값을 반환하는 메서드와 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성합니다.
- [ ] Lv 1에서 구현한 App 클래스의 main 메서드에 Calculator 클래스가 활용될 수 있도록 수정
- [ ] 연산 수행 역할은 Calculator 클래스가 담당
- [ ] 연산 결과는 Calculator 클래스의 연산 결과를 저장하는 필드에 저장
- [ ] 소스 코드 수정 후에도 수정 전의 기능들이 반드시 똑같이 동작해야합니다.
- [ ] 연산 수행 역할은 Calculator 클래스가 담당
- [ ] App 클래스의 main 메서드에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정 (캡슐화)
- [ ] 간접 접근을 통해 필드에 접근하여 가져올 수 있도록 구현합니다. (Getter 메서드)
- [ ] 간접 접근을 통해 필드에 접근하여 수정할 수 있도록 구현합니다. (Setter 메서드)
- [ ] 위 요구사항을 모두 구현 했다면 App 클래스의 main 메서드에서 위에서 구현한 메서드를 활용 해봅니다.
- [ ] Calculator 클래스에 저장된 연산 결과들 중 가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를 구현한 후 App 클래스의 main 메서드에 삭제 메서드가 활용될 수 있도록 수정
- [ ] 키워드 : 컬렉션
- [ ] 컬렉션에서 ‘값을 넣고 제거하는 방법을 이해한다.’가 중요합니다!
- [ ] 키워드 : 컬렉션
도전 기능 가이드
3. Enum, 제네릭, 람다 & 스트림을 이해한 계산기 만들기
- [ ] 현재 사칙연산 계산기는 (➕,➖,✖️,➗) 이렇게 총 4가지 연산 타입으로 구성되어 있습니다.
- [ ] Enum 타입을 활용하여 연산자 타입에 대한 정보를 관리하고 이를 사칙연산 계산기 ArithmeticCalculator 클래스에 활용 해봅니다.
- [ ] 실수, 즉 double 타입의 값을 전달 받아도 연산이 수행하도록 만들기
- [ ] 키워드 : 제네릭
- [ ] 단순히, 기존의 Int 타입을 double 타입으로 바꾸는 게 아닌 점에 주의하세요!
- [ ] 지금까지는 ArithmeticCalculator, 즉 사칙연산 계산기는 양의 정수(0 포함)를 매개변수로 전달받아 연산을 수행
- [ ] 피연산자를 여러 타입으로 받을 수 있도록 기능을 확장
- [ ] ArithmeticCalculator 클래스의 연산 메서드(calculate)
- [ ] 위 요구사항을 만족할 수 있도록 ArithmeticCalculator 클래스를 수정합니다. (제네릭)
- [ ] 추가적으로 수정이 필요한 다른 클래스나 메서드가 있다면 같이 수정 해주세요.
- [ ] 키워드 : 제네릭
- [ ] 저장된 연산 결과들 중 Scanner로 입력받은 값보다 큰 결과값 들을 출력
- [ ] ArithmeticCalculator 클래스에 위 요구사항을 만족하는 조회 메서드를 구현합니다.
- [ ] 단, 해당 메서드를 구현할 때 Lambda & Stream을 활용하여 구현합니다.
- [ ] Java 강의에서 람다 & 스트림을 학습 및 복습 하시고 적용 해보세요!
- [ ] 추가) 람다 & 스트림 학습을 위해 여러 가지 조회 조건들을 추가하여 구현 해보시면 학습에 많은 도움이 되실 수 있습니다.
하고 싶은대로 가이드
3. 오버로드, 제네릭 + 정형 타입 매개변수 사용
- 오버로드를 사용하여 여러 함수를 구현해본다.
- 제네릭 타입을 포함한 다양한 매개변수를 하나의 메소드에서 사용해 본다
- 특정 데이터의 추가, 삭제, 수정등의 추가 기능을 구현한다.
- 예외처리
트러블 슈팅
배경 : 계산기 과제를 받고 위의 내용을 기준으로 프로젝트 제작을 진행했다.
발단
- 오랜만에 자바 프로젝트를 진행해보니 파이썬과 너무 많은 차이가 있었다.
- 알고리즘 적인 요소는 잘 이해하고 있다보니 수월하게 해결할 수 있었다.
- 클래스 2개를 제작하여 하나는 계산, 정보 관리를 구현하였다.
- 다만 여기서 문제가 발생했는데
- 제니릭과 추상화된 타입의 연산자간의 문제가 발생했다.
전개
- 이 과제의 해결법은 크게 2가지였다.
- 하나, 제니릭 데이터의 반환값을 미리 정해두는 방식
- 둘, 제니릭한 데이터 매개변수의 타입을 for문으로 하나씩 다 확인하는 방법
- 이 과정에서 첫번째 방법을 택하게 되었는데 이유는 코드가 더 간결하게 짤 수 있어서였다.
- 조금 더 자세히 설명하면, Number 클래스를 상속받으면 해당 클래스 메소드인 doubleValue()를 사용해 매개변수 타입을 임의로 지정할 수 있었기 때문이었다.
class Calculator {
// 덧셈
public <T extends Number> double add(T x, T y) {
return x.doubleValue() + y.doubleValue();
}
// 뺄셈
public <T extends Number> double subtract(T x, T y) {
return x.doubleValue() - y.doubleValue();
}
// 곱셈
public <T extends Number> double multiply(T x, T y) {
return x.doubleValue() * y.doubleValue();
}
// 나눗셈
public <T extends Number> double divide(T x, T y) {
// 0으로 나누는 경우 예외 처리
if (y.doubleValue() == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return x.doubleValue() / y.doubleValue();
}
}
위기
- 다만 코드를 진행하면서 좀더 오려운 방식으로 코드를 전개해 보고 싶어졌다.
- 이번에는 제네릭을 사용하면서 모든 타입을 입력할 수 있도록하는 매개변수 타입을 만들어보고 싶어졌다.
- 다만 위 방식은 구현이 애매해졌는데
- public T void로 선언하면 확실히 모든 타입을 입력할 수 있게 되지만
- 이 경우 클래스 내부의 연산자를 사용하는 방식이 너무 복잡하고 길어진다는 문제점이 있었다.
절정 : 근본적인 해결을 위해 이런 방법으로 접근하였다.
- 그래서 아쉽지만 해당 방식은 구현을 포기하고 T와 정적 타입 매개변수를 같이 사용하는 것을 연습해 보기로 했다.
- 이번에는 함수에 특정 데이터 삭제와 수정을 추가할 예정이라 오버로드를 사용할 수 있었다.
- 코드는 단순히 double 데이터와 int 데이터를 받을 수 있도록 Number 클래스를 상속하는 타입을 만들었고
- 문자열을 받을 수 있는 String 타입의 매개변수를 받는 class confirm을 제작하였다.
- 그 외에는 다른 변수 없이 처리가 가능하였다.
- 조금 의외였던 것은 this.dataList를 안해도 제대로 dataList의 내용을 수정할 수 있었다.
- 사실 이 부분은 단순히 생각하면 되는데 객체의 dataList를 불러오는 dataList나 현재 객체라는 의미를 가진 this를 추가한 this.dataList나 다를게 없다..
- 보통 this는 매개 변수와 메서드 내의 변수명이 같을 때 구분하라고 사용하는 건데 현재 코드에는 딱히 맴버,로컬,매개 변수 중 이름이 같은 변수는 없었다.
- 사실 제네릭, 스트림, 람다 모두 기존의 파이썬에서는 사용하지 않은 기능이다보니 기능들이 필요한 적제 적소의 타이밍이 언제인지 감이 잘 오지 않았다.
결말 : 최종적으로 코드는 수월하게 완성이 되었다. 크게 막히는 부분은 없었지만, 몇번 gpt에게 물어보니 코드의 최적화와 클린 코드 작성에 있어서 큰 차이를 보였다. 기능을 구현하는 것도 문론 중요하지만, 어떠한 코드든 소프트웨어 생명주기 모델에 의거하여 유지보수가 거의 90%를 차지할 정도로 코드의 관리 용이성은 중요하다. 그 점에서 오늘 제작한 코드는 미흡한 점이 크다고 생각한다.
- 변수명이 이해가 되지 않는 부분은 변수 명을 수정하여 가독성을 높였다.
- 제네릭을 사용하여 다양한 형태의 데이터를 사용할 수 있게 수정했다.
- 스트림, 람다를 이용해 코드의 가독성을 높였다.
- 클래스를 나누어 적절히 기능별로 구현하였다.
- 상속을 구현하기보단 단순히 표현하기 위해 따로 클래스를 구현하였다.
- for, switch문을 적절히 사용하여 선택지를 구성할 수 있었다.
회고
- 사실 위의 방식은 배운 부분을 유기적으로 연동한 것이 끝이라 어렵지 않았지만 아래의 내용이 앞으로의 숙제일 것 같다.
- 코드의 변수, 객체, 생성자, 메서드, 매서드 내 변수, 함수 관리에 특정한 룰을 만드는 것이 좋아보인다.
- 코드가 짧으면 무조건 좋다고 생각했지만 그건 또 아닌 듯 하다.
- 대부분의 코드를 다른 팀원과 하게되면 항상 말하는 것이 "짜여있는 코드보다 차라리 처음부터 짜는게 빠르겠다" 라는 말을 많이한다. 그 점에 있어 타인과 함께 이해할 수 있는 코드 방식을 구사하는 것이 중요해 보인다.
- 복잡한 코드에는 주석을 통해 설명을 꼭 해줘야한다는 점도 중요한 포인트라고 생각이 든다.
- 특히 이번에 while, for, if, switch문을 보면서 생각나는게, 이 조건문 반복문은 꼭 필요하면서 코드를 길게 만드는 점이 좀 마음에 안든다...
- Map을 사용하는 방식으로 수정해 보는 것이 더 효율적이라는 생각이 든다.
- 다양한 기능을 배우고 구현을 해봤다고는 하지만, 실제로 정확히 어느 타이밍에 뭘 사용해야하는지, 이게 가장 어려운 것 같다. 적제 적소에 필요한 기능을 유지보수하기 쉽도록 개발하는 것의 어려움을 깨달았다.
- 스트림을 사용하는 방식으로 간단히 표현한 점에서는 유지보수에 용히하게 개선이 되었지만, 제네릭은 너무 코드를 이해하기 힘들게 만드는 것 같아서 기본 타입으로 구성하는 것이 좋을 것 같다.
- 상속의 개념은 최고 클래스에서 불러올 경우에는 많이 사용하는 느낌이지만, 실제로는 포함 관계를 더 많이 구현할 것 같다는 생각이 든다.
- 이 코드를 작성하면서 가장 힘들었던 부분은 코드의 최적화보다도, 유지보수에 유리하게 만드는 것이 어려웠다고 생각이 든다. 사실 상속으로 여러 코드를 나눠볼까 라는 생각을 했지만, 유지보수를 현업에서 다양한 사람과 함께 하는 만큼 이해가 쉬운 코드를 우선순위 1번으로 생각하고 2번을 최적화로 생각하면서 코드를 진행했다.
- 사실 어느쪽이 정답인지는 알 수 없으나, 가장 좋은 방법은 2가지를 다 챙기는 방법이라 생각한다.
- 중요한 숙제는 프리셋을 구성하는 것이라 생각한다. 나 스스로 메인, 각 클래스, 메소드, 변수 선언에 일정한 규칙을 가질 수 있는 개발 뼈대를 만드는 것이 앞으로의 능력에 중요한 기준이 될 것이라 생각한다.
- 다만, 그를 위해서는 깊은 수준의 CS와 JAVA 이해능력이 필요하다는 생각이 들었다. 추가적인 이론을 학습을 안할 수 는 없어보인다.
'Project > JAVA' 카테고리의 다른 글
[JAVA] 키오스크 (0) | 2024.11.26 |
---|