[TIL] Swift 계산기 과제: 문제 해결 이야기

Eden·2024년 10월 31일
1

TIL

목록 보기
27/41
post-thumbnail

swift로 계산기 만들다가 죽을 뻔한 썰 푼다.


🌱Lv.1: 기본적인 계산기 만들기

더하기, 빼기, 곱하기, 나누기 연산을 수행할 수 있는 Calculator 클래스를 만들고, 이 클래스를 이용해 연산을 출력한다.

구현 과정

import Foundation

class Calculator {
    func add(_ a: Double, _ b: Double) -> Double{
        return a + b
    }
    func substract(_ a: Double,_ b: Double) -> Double {
        return a - b
    }
    func multiply(_ a: Double, _ b: Double) -> Double {
        return a * b
    }
    func divide(_ a: Double, _ b: Double) -> Double? {
        return b != 0 ? a / b : nil
    }
}

let calculator = Calculator()

let addResult = calculator.add(5, 3)
print(addResult)

let substractResult = calculator.substract(5, 3)
print(substractResult)

let multiplyResult = calculator.multiply(5, 3)
print(multiplyResult)

if let divideResult = calculator.divide(5, 3) {
    print(divideResult)
} else {
    print("0으로 나눔")
}

처음에는 모든 연산(더하기, 빼기, 곱하기, 나누기)을 하나의 큰 Calculator 클래스 안에서 모두 구현했다. 이 클래스는 각각의 연산을 위한 메서드(add, subtract, multiply, divide)를 가지고 있었다.

코드의 복잡성

모든 연산을 한 클래스에 넣다 보니, 코드가 너무 복잡해지고 수정하기가 어려웠다. 특히 나중에 새로운 기능을 추가할 때 기존 코드에 영향을 줄 가능성이 높았다.

코드의 개선

그래서 연산을 독립적인 클래스로 나눠야 했다. 이렇게 하면 각 클래스가 오직 하나의 역할만 하게 되어, 나중에 수정하거나 문제가 생겼을 때 더 쉽게 관리할 수 있을 것 같았다.



🌿 레벨 2: 나머지 연산 추가하기

Calculator 클래스에 나머지 연산을 추가하고, 이를 통해 연산을 출력한다.

구현 과정

RemainderOperation이라는 클래스를 추가했다. 이 클래스는 두 숫자를 나눌 때의 나머지를 계산한다.

class RemainderOperation {
    func calculate(_ a: Double, _ b: Double) -> Double? {
        return b != 0 ? a.truncatingRemainder(dividingBy: b) : nil
    }
}

Calculator 클래스에 remainder 메서드를 추가하여 이 클래스를 사용하도록 했다.

truncatingRemainder(dividingBy:)를 사용한 이유

나머지 연산을 구현할 때, Swift에서는 truncatingRemainder(dividingBy:)를 사용했다. 이 메서드는 정수뿐만 아니라 소수점이 있는 실수(Double 타입)에 대해서도 정확하게 나머지를 구할 수 있다.

일반적으로 정수에서는 % 연산자를 사용하여 나머지를 구할 수 있지만, 실수 연산에서는 이 연산자를 사용할 수 없다. 그래서 실수에서도 나머지를 계산할 수 있도록 truncatingRemainder(dividingBy:)를 사용했다.

예를 들어 1.56 % 2 같은 연산을 수행할 때, truncatingRemainder(dividingBy:)는 소수점까지 정확한 나머지 값을 계산해준다. 또한, 나누는 값이 음수일 때도 올바르게 결과를 반환해줘서 다양한 경우의 수를 처리하는 데 적합하다.

이 메서드를 사용함으로써 나머지 연산이 필요할 때 안전하고 정확하게 계산할 수 있었다.

코드의 개선 과정

0으로 나누기 문제가 여전히 있었다. 나누기와 마찬가지로, 나머지 연산에서도 0으로 나누는 경우를 처리해야 했다. 이를 위해 nil을 반환하도록 하고, 호출하는 쪽에서 안전하게 처리하게 했다.

if let remainderResult = calculator.remainder(1.56, 0) {
    print(remainderResult)
} else {
    print("0으로 나눌 수 없습니다.")
}

이렇게 하면 0으로 나눌 때 nil이 반환되고, 적절한 메시지를 출력할 수 있게 되었다.

🪴 레벨 3: 각 연산을 개별 클래스로 분리하기

각 연산을 담당하는 클래스를 만들고, 이 클래스들과 Calculator 클래스 간의 관계를 설정한다.

구현 과정

각 연산을 독립적인 클래스로 나누었다. 예를 들어, 더하기는 AddOperation 클래스, 빼기는 SubtractOperation 클래스에서 담당하도록 했다. 이렇게 하면 각 클래스의 역할이 분명해졌다.

class AddOperation {
    func calculate(_ a: Double, _ b: Double) -> Double {
        return a + b
    }
}

class SubtractOperation {
    func calculate(_ a: Double, _ b: Double) -> Double {
        return a - b
    }
}

그리고 Calculator 클래스는 각 연산 클래스를 멤버 변수로 가지고 있으며, 이 클래스를 사용해 실제 연산을 수행하게 했다.

class Calculator {
    private let addOperation = AddOperation()
    private let subtractOperation = SubtractOperation()
    private let multiplyOperation = MultiplyOperation()
    private let divideOperation = DivideOperation()
    private let remainderOperation = RemainderOperation()
    
    func add(_ a: Double, _ b: Double) -> Double {
        return addOperation.calculate(a, b)
    }
    // 나머지 메서드들도 동일하게 각각의 클래스를 호출함
}

문제점과 해결 방법

처음에는 모든 연산을 한 클래스에 넣으려고 했지만, 이렇게 하면 코드가 너무 복잡해지고 수정하기 어려웠다. 그래서 각 연산을 별도의 클래스로 나누고 Calculator에서 이들을 사용하는 방식으로 책임을 분리했다. 이로 인해 유지보수성이 크게 향상되었고, 새로운 연산이 필요할 때 더 쉽게 추가할 수 있게 되었다.





그 뒤는 죽을 뻔 해서 못했다.

객체지향 프로그래밍을 배워야 겠다고 느꼈다.....

사용 예시

마지막으로 Calculator 클래스를 사용해 각 연산을 수행해 보겠다.

let calculator = Calculator()

let addResult = calculator.add(5, 3)
print(addResult) // 출력: 8.0

let subtractResult = calculator.subtract(5, 3)
print(subtractResult) // 출력: 2.0

let multiplyResult = calculator.multiply(5, 3)
print(multiplyResult) // 출력: 15.0

if let divideResult = calculator.divide(5, 3) {
    print(divideResult) // 출력: 1.6666666666666667
} else {
    print("0으로 나눔")
}

if let remainderResult = calculator.remainder(1.56, 2) {
    print(remainderResult) // 출력: 1.56
} else {
    print("0으로 나눔")
}

이렇게 각 연산을 수행하고, 0으로 나누는 경우에는 예외적으로 처리하여 프로그램이 안전하게 동작하도록 했다.

profile
Just living the daydream, one moment at a time.
post-custom-banner

2개의 댓글

comment-user-thumbnail
2024년 10월 31일

레벨 2에 글이 반복 돼요!
그리고 연산 메서드의 파라미터도 사용자가 구분하기 쉽게 firstOperand같은 이름을 짓는 건 어떨까요
계산기 내부에 직접 객체를 생성하셨는데 객체를 외부에서 전달받는 방법과 비교했을 때 어떤 차이가 잇을까여

1개의 답글