구현 코드 깃허브 주소
https://github.com/kim-hani/CalculatorProject/tree/main
Lv1과 Lv2는 간단한 구현이라 큰 어려움없이 진행되었다.
Lv3에서는 제네릭타입과 스트림,람다를 적용해야되는데
익숙하지 않은 부분이라 어려움이 있었다.
3개의 패키지에 나눠서 과제를 진행했다.

Calculator1 -> Lv1 구현
Calculator2 -> Lv2 구현
Calculator3 -> Lv3 구현
와 같이 패키지를 구분했다.
트러블 슈팅에 대한 내용은 Lv3, Calculator3 패키지에 있는 내용으로 다뤘다.
ArithmeticCalculator 클래스를 설계하면서, Integer와 Double을 동시에
처리하기 위해 제네릭 타입을 도입했다. 문제는 여기서 발생했다.
제네릭은 타입의 유연성을 확보해주지만 그 만큼 제약사항이 많아진다.
구현 과정에서 제네릭 타입의 숫자 연산과 타입캐스팅 문제를 발견했다.
1. 제네릭 타입 간 연산 불가
제네릭 타입 변수에는 모든 타입이 올 수 있기때문에 특정 타입에만 제공하는
메서드나 기능을 사용할 수 없다.
이러한 이유로 제네릭 타입 간 +,-,/,*와 같은 사칙연산을 사용하려고 하면
오류가 발생하였다.
2. 타입 캐스팅 문제
클래스를 제네릭으로 선언하고 클래스 내의 메소드내에서도 제네릭을 제대로 활용해보고싶어
모든 메서드의 리턴타입,매개 변수들을 제네릭 타입으로 작성하려고 했다.
이를 위해서는 제네릭 타입 T를 Double또는 Integer로 캐스팅을 해야 하지만
제네릭에서는 이를 자동으로 처리해주지 않는다.
이러한 과정에서 코드가 복잡해지고 오류가 많아지는 문제가 발생했다.
1. extends Number로 타입 제한
제네릭 타입 간 연산이 불가한 문제를 해결하기 위해 제네릭 클래스를 선언할때
public class ArithmeticCalculator<T extends Number>{
// 클래스 내용
}
와 같이 extends Number를 사용하여 T가 Integer와 Double과 같은 Number타입만
사용할 수 있도록 타입 제한을 하였다.
이를 통해 doubleValue()와 같은 메서드 사용이 가능했고 제네릭 타입을 숫자로
활용할 수 있었다.
T를 doubleValue()메소드로 타입 변환을 하여 연산을 수행하였고
OperatorType 에서 Enum을 사용하여 사칙연산을 처리했다.
제네릭 타입 간 연산은 어렵지 않게 해결했다.
이제 제네릭 타입을 클래스 내의 메소드 내에서도 적극 활용해보려고 했다.
연산 결과를 저장하는 result부터 제네릭 타입으로 선언하였다.
private ArrayList<T> result = new ArrayList<>();
이후 모든 메소드의 반환타입,매개변수들을 제네릭 타입 T로 바꾸었다.
모든 코드에 에러가 발생하였다.
제네릭은 T를 자동으로 Double,Integer로 변환해주는 기능이 없어
우선 T를 Double,Integer와 같은 타입정보를 명시적으로 전달하거나
변환 로직을 따로 구현해야 했었다.
변환 로직을 따로 구현하려고 했지만 이는 코드를 굉장히 복잡하게 만들었다.
변환 로직을 따로 구현하는 것은 유지보수,성능,가독성,비용면에서 너무 비효율적이라는
판단 하에 근본적인 해결 방안으로 접근했다.
result를 Double타입으로 제한하고 메서드의 반환 타입,매개변수를 Double로 명시하였다.
실제로 두 개의 숫자와 연산자를 매핑시켜 결과를 반환하는 calculate메서드에서는
매개변수로 받는 두 개의 숫자는 제네릭 타입으로 받게하였다.
제네릭 타입으로 받은 후 doubleValue() 메소드를 사용하여 double값으로 변환했다.
public double calculate(T n1,String op,T n2){
OperatorType operator = OperatorType.fromOperator(op);
double total = operator.apply(n1.doubleValue(),n2.doubleValue());
result.add(total);
return total;
}
연산은 항상 double로 수행하지만 입력값은 다양한 숫자 타입을 받을 수 있도록 설계하여
제네릭 타입의 장점도 살렸다.
제네릭 타입 간 연산 불가는 extends Number를 사용하여 타입 제한을 하여
해결하였지만 클래스 내 메소드에서 제네릭을 활용하는 부분에서
많은 어려움을 겪었다.
메소드에서 반환타입 및 매개변수들을 모두 제네릭 타입을 사용하려고 시도했지만
이를 해결하기 위해서는 제네릭 타입을 변환해주는 로직을 따로 구현해야 했다.
이는 코드를 너무 복잡하게 만들어 가독성도 안좋아지고
비용,성능,유지보수 측면에서도 비효율적이었다.
따라서 대부분의 메소드에서 타입을 명시적으로 작성했지만
calculate메소드 에서는 매개변수로 제네릭 타입을 받아
제네릭 타입의 장점도 활용하였다.
이를 통해 간결하고 유지보수하기도 좋은 코드가 작성되었다.
코드를 작성할 때 우리는 항상 더 좋은 코드를 작성하고싶은 욕심이 생기지만
때로는 그러한 욕심이 비효율적인 코드를 만들 수 있다는 것을 이해하게 되었다.
산의 정상에서 조금 더 나아가면 오르막길이 아닌 내리막길이듯
내가 작성한 코드가 최적의 상태일수도 있다.