2024. 4. 30. 16:13ㆍ개발자 과정/Kotlin
- 기능완성도는 뛰어나지만, 가독성을 떨어집니다. 옆동료도 설명없이 잘 이해할 수 있는 코드를 만들 수 있도록 애써보세요.
- 기능에 따라(연산자, utils) 파일 폴더링하는 것을 권해드립니다. 복잡도를 관리하기에 좋은 방법입니다.
- 연산부호와 연산기능은 enum으로 구현하는 방법도 고민해보세요
- Error클래스를 사용하셨는데, 상황에 맞는 Exception을 구현해서 사용하는 것을 권합니다.
- else-if와 else의 사용을 줄이려해보시고, 객체지향적으로 해결할 방법을 고민해보세요
- readme파일 아주 잘 작성해주셨습니다.
수고하셨습니다. 빠르게 성장할 것 같아요!
개발자가 죽으면 먼저 가있던 트래쉬 코드가 마중을 나온다는 얘기가 있다.
난 이 이야기를 무척 싫어한다.
뭐랄까..면접보는 기분을 오랜만에 느꼈다.
날카로운 질문! 이게 왜 있냐는데에 납득가능한 답변을 해야하는 부담!
나 자신을 돌아보게 만드는 피드백..!
그래도 튜터님이 나의 부족한 점을 인지하고 있는 지금이 가장 좋은 때라고 생각하여
완성했던 계산기 코드를 뜯어 고쳤다.
1.기능완성도는 뛰어나지만, 가독성은 떨어집니다. &
else-if와 else의 사용을 줄이려해보시고, 객체지향적으로 해결할 방법을 고민해보세요
가독성이 떨어지는 것과 많은 분기처리는 어느정도 상통한다고 생각해서 둘을 묶었다.
코드를 깔끔하고 수준있게 잘 짠다는 평소의 평가와는 달리,
코드가 복잡해지기 시작하면 나만 알아먹는 로직을 쓰는 경우가 종종 있었는데,
이번이 이런 경우라고 생각이 들었다.
그 부분을 어떻게 해소하기 위해 다듬었다.
BEFORE
if (it == '.' &&
(i - 1 in calc.indices && calc[i - 1].isDigit()) &&
(i + 1 in calc.indices && calc[i + 1].isDigit())) {str += it}
else if (it in operators) {str += " "
if (i == 0 && it == '-') str += it
else if (it == '-' && calc[i - 1] in operators) str += it
else str += "$it "
}
else if (it.isDigit()) {str += it}
AFTER
if (it == '.' &&//소수점 판별. 소수의 기본 형태는 0.0 이다.
(i - 1 in calc.indices && calc[i - 1].isDigit()) &&
(i + 1 in calc.indices && calc[i + 1].isDigit())) {
str += it
}
else if (it in "(+-*/%)") {//연산자 판별
str+= " "+
if ((it == '-') && ((i == 0) || (calc[i - 1] in ("+-*/%")))) it
else "$it "
}
else if (it.isDigit()) {//숫자 판별
str += it
}
대표적으로 뽑아온 개선 사항이다.
아무래도 문자열index를 마구 헤집어 다니면서 파싱을 하는 코드라, 조건문 만으로는 로직을 다 설명하기 어렵겠다 생각이 들었다.
그래서 각각의 조건문에 간단한 주석으로 이해를 돕도록 하였고, if문 처리를 합쳤다.
보면 소수점 판별과 숫자판별 조건문도 합칠 수 있는데, 굵직한 조건문은 그대로 두는 것이 로직의 의사를 더 잘 전달 할 수 있을거 같아
그 부분은 그대로 두었다.
2. 기능에 따라(연산자, utils) 파일 폴더링하는 것을 권해드립니다. 복잡도를 관리하기에 좋은 방법입니다.
처음에는 모든 클래스 파일을 class폴더안에 때려박아 놓았는데, 폴더 트리로 프로그램의 복잡도를 줄이는 것이
다른 사람이 이해하기에 수월할 거라고 하셨다. 사실 폴더트리는 귀찮아서 안했었는데...
기왕하는거 제대로 하는게 맞겠다는 생각이 들어 이 부분도 수정했다.
BEFORE
AFTER
클래스 파일을 기능과 상속관계에 따라 폴더 트리로 구분지었다.
3. 연산부호와 연산기능은 enum으로 구현하는 방법도 고민해보세요
계산기 프로그램의 연산기능을 if나 when함수 써서 처리하지 말고,
enum class를 통해 구현해 보라고 하셨다.
그와 함께 받은 피드백이 객체 지향성이 떨어진다는 것이 었는데,
객체지향은 배웠어도 개체지향적 프로그래밍은 잘 모르는 터라 이번 기회에 한 번 찍먹이라도 해보자 했다.
그래서 디자인 패턴에 대해 개념정도만 빠르게 훑고 있었는데 "전략 패턴"이라는 것이 눈에 띄었다.
어떠한 인터페이스가 있고, 그것을 상속받은 구현체들이 여러개 있을 때, 이것을 객체지향적으로 풀어내기 좋은 방법
이라는 것에서 똑같은 로직을 연산자만 바꿔서 해야하는 이 프로그램에 잘 어울리겠다고 생각이 들었다.
그리고 이것을 enum class에 녹여내어 클라이언트에서 컨텍스트에게 연산자만 넘기면,
그곳에서 분류와 리턴을 수행하는 것이 최종적 목표였다.
BEFORE
private fun calculate(x: Double, y: Double, oper: String):Double
{
var result =0.0
var result=0.0
try {
result=when (oper) {
"+" -> Summation().calculateResult(x, y)
"-" -> Subtraction().calculateResult(x, y)
"*" -> Multiplication().calculateResult(x, y)
"/" -> Division().calculateResult(x, y)
"%" -> Remainder().calculateResult(x, y)
else -> {
throw Error("올바르지 않은 연산자 입니다.")
}
}
val operation = Strategy.fromSymbol(oper)
?: throw IllegalArgumentException("올바르지 않은 연산자 입니다.")
result = operation.running(x,y)
}
catch (e:NumberFormatException) {
println("올바르지 않은 숫자형식 입니다.")
}
}
AFTER
컨텍스트
enum class Strategy(private val symbol: String, private val strategy: Calculator){
SUM("+", Summation()),
SUB("-", Subtraction()),
MUL("*", Multiplication()),
DIV("/", Division()),
REM("%", Remainder());
companion object{
fun fromSymbol(symbol: String): Strategy? =
Strategy.entries.find { it.symbol == symbol }
}
fun running(x:Double, y:Double):Double=strategy.calculateResult(x,y)
}
클라이언트
private fun calculate(y: Double, x: Double, oper: String): Double {
val operation = Strategy.fromSymbol(oper)
?: throw IllegalArgumentException("올바르지 않은 연산자 입니다.")
return operation.running(x, y)
}
구현체들을 enum calss에서 핸들링하고, calculate가 연산자를 넘겨주면 적절한 전략을 받고, 계산을 수행하도록 한다.
4. Error클래스를 사용하셨는데, 상황에 맞는 Exception을 구현해서 사용하는 것을 권합니다.
처음엔 예외처리를 Error로 전부 통일하여 썼는데, 이렇게 쓰면 안된다고 하셨다.
그래서 적절한 예외처리는 무엇인지 알아보고, 그에 맞게 적용였다.
Exception 종류
- NullPointerException: null을 참조하여 발생하는 예외로, 코틀린은 이를 방지하기 위해 널 안전성을 강조합니다.
- IllegalArgumentException: 메서드에 잘못된 인수가 전달되었을 때 발생하는 예외입니다.
- IllegalStateException: 객체의 상태가 메서드 호출에 부적절한 경우에 발생하는 예외입니다.
- IndexOutOfBoundsException: 배열이나 리스트 등의 컬렉션에서 유효하지 않은 인덱스에 접근하려고 할 때 발생하는 예외입니다.
- NoSuchElementException: 요소가 없는 컬렉션에서 요소를 찾으려고 할 때 발생하는 예외입니다.
- ArithmeticException: 산술 연산에서 오류가 발생할 때 발생하는 예외입니다. 예를 들어, 0으로 나누기 시도할 때 발생할 수 있습니다.
- NumberFormatException: 숫자로 변환할 수 없는 문자열을 변환하려고 할 때 발생하는 예외입니다.
- UnsupportedOperationException: 특정한 연산이 지원되지 않을 때 발생하는 예외입니다. 주로 읽기 전용 컬렉션에서 변경 연산을 시도했을 때 발생합니다.
- ConcurrentModificationException: 컬렉션을 수정하고 있는 동안 반복자를 사용하려고 할 때 발생하는 예외입니다.
- ClassCastException: 타입 변환을 할 수 없는 경우 발생하는 예외입니다.
- FileNotFoundException: 파일을 찾을 수 없는 경우 발생하는 예외입니다.
- IOException: 입출력 작업 중에 발생하는 예외의 기본 클래스입니다. 파일 입출력과 관련된 여러 가지 예외들이 여기에 속합니다.
- SecurityException: 보안 관련 문제로 인해 발생하는 예외입니다.
- IllegalAccessError: 메서드나 필드에 접근할 권한이 없을 때 발생하는 예외입니다.
BEFORE
try {
for (token in postfix) {
if (token in "(+-*/%)") {
if (stack.size < 2) throw Error("올바르지 않은 입력입니다.")
val result = calculate(stack.pop(), stack.pop(), token)
if (result.isNaN() || result.isInfinite())
throw Error("올바르지 않은 식입니다.")
stack.add(result)
continue
}
stack.add(token.toDouble())
}
if (stack.size != 1) throw Error("잘못된 계산입니다.")
println("계산 결과: ${stack.first()}")
} catch (e: NumberFormatException) {
println("올바르지 않은 숫자형식 입니다.")
} catch (e: Error) {
println(e.message)
}
AFTER
try {
for (token in postfix) {
if (token in "(+-*/%)") {
if (stack.size < 2) throw IllegalArgumentException("올바르지 않은 입력입니다.")
val result = calculate(stack.pop(), stack.pop(), token)
if (result.isNaN() || result.isInfinite())
throw ArithmeticException("올바르지 않은 식입니다.")
stack.add(result)
continue
}
stack.add(token.toDouble())
}
if (stack.size != 1) throw ArithmeticException("잘못된 계산입니다.")
println("계산 결과: ${stack.first()}")
} catch (e: NumberFormatException) {
println("올바르지 않은 숫자형식 입니다.")
} catch (e: RuntimeException) {
println(e.message)
}
NumberFormatException은 계산중에 나오는 예외니까 따로 처리하고, 이외의 예외는 한곳에서 처리하고,
예외상황을 뭉게지 않고 구분하였다.
지금은 프로그램이 단순하기에 catch (e: RuntimeException)를 통해 한번에 받지만,
다음에 제대로 된 프로그램을 만들때엔 이 부분도 세분화 해야할 것이다.
여담
이렇게 수정 후 다시 튜터님에게 가서 평가를 받았을때, 객체지향적이고, 예외처리를 잘 구분한 프로그램이 되었다고 평가를 받았다.
아직 많이 부족하다는 것이 다시금 느껴진다. 혼자하는데에 습관이 들어버려 기본기 부터 갈아 엎어야 겠다는 생각이 들었다.
그동안의 노력과 배움은 대체 무엇이었나 하는 마음과, 무의미함의 감정이 스스로를 옥죄어 온다.
더군다나 실패의 허망함을 지겹도록 맛보았으니 말이다.
그래도 하다보면 도달하지 않을까. 오늘도 그렇게 생각하고 해야 할 일을 해나아가 본다.
결과적으로 나의 마음가짐은 수많은 실패의 전후로 나뉘는 것은 없다만,
이것밖에 내가 할 수 있는건 없으니까.
'개발자 과정 > Kotlin' 카테고리의 다른 글
야구게임 최종 (0) | 2024.05.02 |
---|---|
야구게임 전략패턴 (0) | 2024.05.01 |
(코딩테스트) 숫자 짝꿍 (StringBuilder) (0) | 2024.04.25 |
계산기 프로젝트 최종 (0) | 2024.04.24 |
연쇄수식 계산기 (0) | 2024.04.23 |