ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 함수야 거짓말 하지마라
    Programming🧑‍💻/Functional Programming 2025. 2. 9. 23:07

     

    함수 시그니쳐란? 

    함수 시그니쳐는 함수의 정체성을 나타내는 ID 카드와 같습니다. 

    함수의 이름, 매개변수 타입, 반환 타입을 포함하며,

    이를 통해 함수가 무엇을 받아서 무엇을 돌려주는지를 명확하게 알 수 있어야합니다. 

    func functionName(parameterName: ParameterType) -> ReturnType {
        // 함수 구현
    }

     

    예를 들어, 사용자의 나이를 받아서 성인 여부를 반환하는 함수의 시그니처는 이렇게 됩니다

    func isAdult(age: Int) -> Bool {
        return age >= 18
    }

     

    위와 같이 함수의 시그니쳐들을 통해 해당 함수가 어떤 "역할을 하는지?" ,"어떤 값들을 필요로 하는지?", "어떤 결과값이 나올 수 있는지?"등의 함수의 동작을 예측할 수 있어야합니다. 

     

    거짓말쟁이 함수들 

    작성한 코드속에는 거짓말을 하고 있는 함수들이 숨어있을 수 있습니다. 

    함수 시그니처가 약속한 것과 다른 행동을 하여 우리를 혼란스럽게 만듭니다. 

     

    1. 잘못된 이름 사용 

    • 함수명에서 양수값만 반환될 수 있음을 명시해두고 실제 호출시 음수값이 반환될 수 있습니다. 
    func getPositiveNumber(num: Int) -> Int {
        return num - 1  // 음수가 반환될 수 있음!
    }

     

    2. 부작용 숨기기 

     

    • 현재 함수 구현부에서는 값을 더할 뿐만아니라  UserDefaults를 이용해 값을 저장하고 있습니다. 
    • 함수 이름에서는 단순히 "계산"만 할 것이라고 암시하고 있습니다
    • 함수의 반환 타입은 단순히 Int로, 저장 작업이 일어난다는 것을 전혀 알려주지 않습니다
    • 이 저장 작업은 호출하는 쪽에서 예상하지 못한 상태 변경을 일으킵니다

     

    func calculateSum(numbers: [Int]) -> Int {
        UserDefaults.standard.set(numbers, forKey: "lastCalculation")  // 숨겨진 부작용!
        return numbers.reduce(0, +)
    }

     

    3. 실패 상황의 모호한 처리 

     

    • 데이터베이스에서 사용자를 찾지 못했을 때 빈 문자열("")을 반환합니다
    • 이것은 "사용자를 찾지 못함"이라는 실패 상황을 "이름이 빈 문자열인 사용자"와 구분할 수 없게 만듭니다
    • 함수의 반환 타입이 String이라는 것은 "항상 유효한 문자열을 반환하겠다"는 약속입니다
    • 하지만 실제로는 실패할 수 있는 작업인데, 이 가능성이 시그니처에 반영되어 있지 않습니다

     

    func fetchUserName(id: Int) -> String {
        guard let name = database.getName(id) else {
            return ""  // 빈 문자열 반환
        }
        return name
    }

     

     

    함수부터 제대로 

    1. 명확한 이름 사용

    // 좋은 예
    func findUserIfExists(id: Int) -> User? {
        // Optional 반환으로 실패 가능성을 명시
        return database.findUser(id)
    }

     

    2. 부작용 명시하기 

    func saveAndCalculateSum(numbers: [Int]) -> (sum: Int, savedSuccessfully: Bool) {
        let saved = UserDefaults.standard.set(numbers, forKey: "lastCalculation")
        let sum = numbers.reduce(0, +)
        return (sum, saved)
    }

     

    3. 실패 가능성 명확히 하기 

    // 좋은 예
    enum ValidationError: Error {
        case invalidAge
        case invalidName
    }
    
    func validateUser(age: Int, name: String) throws -> User {
        guard age >= 0 else {
            throw ValidationError.invalidAge
        }
        guard !name.isEmpty else {
            throw ValidationError.invalidName
        }
        return User(age: age, name: name)
    }

     

    개발자로서 성장하다 보면, 우리는 늘 새로운 것들을 배우고 적용하려 합니다. 최신 아키텍처 패턴, 멋진 디자인 패턴, 효율적인 상태 관리 도구..., 

     

    하지만 잠깐, 그 화려한 기술들을 도입하기 전에 우리가 놓치고 있는 것은 없을까요?

     

    함수는 우리 코드의 가장 기본적인 구성 요소입니다. 마치 집을 짓는 블록과 같죠.

    하나의 함수가 제대로 자신의 역할을 하지 못한다면, 그 위에 아무리 멋진 아키텍처를 쌓아올려도 소용이 없습니다.

    // MVVM 아키텍처를 사용하지만...
    class UserViewModel {
        func getUserData(id: Int) -> UserData {
            // 실패할 수 있는데 처리하지 않음
            // 부작용이 숨어있음
            // 이름과 다른 동작을 함
            // 책임이 너무 많음
            ...
        }
    }

    이런 기초적인 문제들이 있는 코드는, 아무리 좋은 아키텍처 패턴으로 감싸도 결국 문제를 일으킬 것입니다.

     

    새로운 기술을 배우고 적용하는 것은 분명 중요합니다. 하지만 그전에 우리는 이런 질문들을 해볼 필요가 있습니다

     

    "내가 작성한 이 함수는..."

    • 정직한가요? (이름이 약속한 일만 하나요?)
    • 믿을 수 있나요? (예외 상황을 제대로 처리하나요?)
    • 이해하기 쉬운가요? (다른 개발자가 봐도 의도를 파악할 수 있나요?)
    • 하나의 일만 하나요? (너무 많은 책임을 지고 있지는 않나요?)

    'Programming🧑‍💻 > Functional Programming' 카테고리의 다른 글

    순수 함수란?  (0) 2025.02.10

    댓글

Designed by Tistory.