-
함수야 거짓말 하지마라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