오늘 회사에서 코드를 작성하다가 리뷰를 받았습니다.
선배님이 제 코드를 보시고 이렇게 말씀하셨어요.
"함수형 스타일로 작성했으니 다른 부분도 일관성 있게 함수형 스타일로 작성하는 게 어떻겠냐"
엥? 함수형 스타일이 뭐지? 😅
내가 아는 함수형은 자바 stream 처럼 선언형으로 코드를 작성하는 건데... 대충은 알고 있었지만, 막상 코드와 비교해보니 조금 혼란스러웠습니다.
제가 개발한 부분은 코드1과 같이 가변 객체를 직접 수정하는 방식 이었는데, 코드2처럼 불변 객체를 생성하여 반환하는 방식 으로 바꾸었던 겁니다.
코드 1: 가변 객체 스타일
class BankAccount(var balance: Int) {
fun deposit(amount: Int) {
balance += amount
}
fun withdraw(amount: Int) {
if (balance >= amount) {
balance -= amount
} else {
throw IllegalArgumentException("잔액 부족")
}
}
}
코드 2: 불변 객체 기반 함수형 스타일
data class BankAccount(val balance: Int) {
fun deposit(amount: Int): BankAccount {
return copy(balance = balance + amount)
}
fun withdraw(amount: Int): BankAccount {
require(balance >= amount) { "잔액 부족" }
return copy(balance = balance - amount)
}
}
여기서 핵심은 기존 객체를 변경하지 않고, 새로운 객체를 반환한다는 점입니다.
그래서~ 궁금해서 찾아본 함수형 스타일이란!
함수형 스타일이란?
함수형 스타일은 단순히 Stream API나 람다식 같은 문법만을 의미하지 않습니다.
핵심은 부작용(side-effect)을 최소화하고, 데이터를 불변(immutable)하게 유지, 함수를 일급 객체처럼 다루는 프로그래밍 방식 전체입니다.
즉, 함수형 스타일은 크게 두 가지를 포함합니다.
- 함수 중심의 문법 활용
- 람다(Lambda), 스트림(Stream API), 고차 함수(Higher-order function) 등
- 예:
list.filter { it > 10 }.map { it * 2 }
- 불변성을 지향하는 설계
- 객체나 데이터를 변경하지 않고, 항상 새로운 객체를 반환
- 상태 공유를 최소화하여 예측 가능한 코드를 작성
1. 문법적 측면: 함수형 표현식 (람다, Stream API)
자바 8부터 본격적으로 도입된 함수형 프로그래밍 문법입니다.
// 기존 방식
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> upper = new ArrayList<>();
for (String n : names) {
upper.add(n.toUpperCase());
}
// 함수형 스타일
List<String> upper = names.stream()
.map(String::toUpperCase)
.toList();
Stream API를 활용하면 “무엇을 할지” 선언적으로 표현할 수 있어 코드가 더 간결하고 명확합니다.
2. 설계적 측면: 불변성(immutability)과 순수 함수
함수형 프로그래밍의 철학적 핵심은 “사이드 이펙트를 줄이고, 예측 가능한 동작”을 만드는 것입니다.
이를 위해 지향하는 설계 원칙은 다음과 같습니다.
불변 객체 (Immutable Object)
data class User(val name: String, val age: Int) {
fun withAge(newAge: Int): User = copy(age = newAge)
}
val user1 = User("Alice", 20)
val user2 = user1.withAge(21)
println(user1) // User(name=Alice, age=20)
println(user2) // User(name=Alice, age=21)
- 기존 객체(
user1)는 그대로 유지되고, 새 객체(user2)가 반환됩니다. - 덕분에 스레드 안전성과 디버깅 용이성이 높아집니다.
순수 함수 (Pure Function)
- 입력이 같으면 항상 같은 출력 반환
- 외부 상태나 전역 변수에 의존하지 않음
- 예:
deposit과withdraw가 항상 새로운BankAccount객체를 반환
3. 함수형 스타일은 결국?
- 코드 레벨 → 람다, Stream API, 메서드 참조 활용 (선언적/표현적 스타일)
- 설계 레벨 → 불변 객체, 순수 함수, side-effect 최소화 (구조적/설계적 스타일)
회사 코드 리뷰에서 “다른 메서드도 함수형 스타일로 작성하라”는 피드백은
“Stream API 등 선언적 코드를 활용해라” 뿐만 아니라,
“기존 객체를 직접 수정하지 말고, 새로운 객체를 반환해라” 도 가능하다는 것을 알게되었습니다.
4. 함수형 스타일이 유용한 경우
- 멀티스레드 환경에서 공유 상태 문제를 피하고 싶을 때 -> 공유된 상태를 변경하지 않으므로 동시성 문제 감소 (안정성)
- 테스트 코드에서 예측 가능한 결과를 원할 때 -> 입력이 같으면 결과가 항상 같음 (예측 가능성)
- 데이터 처리 로직을 간결하게 표현하고 싶을 때 -> 선언형(Declarative) 코드로 "무엇을 할지" 표현 가능 (가독성)
✅ 결론
- 함수형 스타일은 문법 + 설계를 아우르는 패러다임
- 핵심은 불변성 + 순수 함수 + 선언형 표현
- 단순히 Stream/람다를 쓰는 것보다 불변 객체 기반 설계까지 포함하는 것이 진짜 함수형 스타일입니다.
실무에서 코드 일관성을 위해, 작은 메서드라도 기존 객체를 바꾸는 대신 새로운 객체를 반환하도록 작성하면 함수형 스타일을 잘 적용한 것입니다.
이렇게 또 하나의 지식을 얻어간 하루! 👍
부록 (함수형 스타일의 핵심 원칙)
함수형 스타일(Functional Style)의 핵심 원칙은 크게 3가지로 요약할 수 있습니다.
1. 불변성 (Immutability)
* 객체나 데이터를 변경하지 않고, 새로운 객체를 반환
* 상태 공유를 최소화하여 부작용(side effect)을 줄이고, 예측 가능한 코드 작성 가능
* 장점: 스레드 안전, 디버깅 용이, 테스트 용이
data class User(val name: String, val age: Int) {
fun withAge(newAge: Int): User = copy(age = newAge)
}
val user1 = User("Alice", 20)
val user2 = user1.withAge(21) // user1은 그대로, user2가 새 객체
2. 순수 함수 (Pure Function)
* 입력이 같으면 항상 같은 출력 반환
* 외부 상태나 전역 변수에 의존하지 않음
* 부작용을 최소화 → 코드 예측 가능, 테스트 용이
fun add(a: Int, b: Int): Int = a + b
* add(2,3) 은 언제나 5를 반환
* 외부 상태 변경 없음
* 아래는 외부 상태를 변경하는 비순수 함수 예시 입니다.
var total = 0
fun addToTotal(a: Int): Int {
total += a // 외부 상태 변경
return total
}
3. 고차 함수 (Higher-Order Function) & 선언형 표현
* 함수를 인자로 받거나 함수를 반환하는 함수를 사용
* 반복문 대신 `map`, `filter`, `reduce` 등 선언적(Declarative) 연산으로 처리
val numbers = listOf(1, 2, 3, 4, 5)
val doubledEvens = numbers
.filter { it % 2 == 0 } // 조건에 맞는 값만 추출
.map { it * 2 } // 각 요소 변환
println(doubledEvens) // [4, 8]
✅ **결론**
함수형 스타일은 불변성 + 순수 함수 + 선언적/고차 함수 활용을 핵심 원칙으로 삼습니다.
단순히 람다나 Stream을 쓰는 것이 아니라,
| 원칙 | 설명 | 장점 |
| 불변성 | 객체/데이터를 변경하지 않고 새 객체 반환 | 스레드 안전, 테스트 용이 |
| 순수 함수 | 입력 → 출력이 항상 동일, 부작용 없음 | 예측 가능, 디버깅 용이 |
| 고차 함수 / 선언형 | 함수를 인자로 받거나 반환, 선언적 데이터 처리 | 간결, 가독성 높음, 재사용성 |
코드 설계 자체를 함수형 철학에 맞춰 작성하는 것이 진짜 함수형 스타일입니다.
'Life Style > 아무기록' 카테고리의 다른 글
| [나의 무지] 🤯 Spring RequestBody와 Static Inner Class: 오해와 진실 (1) | 2025.08.23 |
|---|---|
| 🐳 Docker Compose 구축하면서 부딪혔던 문제점들 (2) | 2025.08.23 |