함수형 프로그래밍이란 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나이다. 해당 패러다임은 람다 계산법에 기반을 둔다.
명령형 프로그래밍을 기반으로 개발했던 개발자들은 개발하는 SW의 크기가 커짐에 따라, 복잡하게 얽혀있는 코드를 유지 보수하는 것이 매우 힘들다는 것을 알게 되었다. 함수형 프로그래밍은 거의 모든 것을 순수 함수로 나누어 문제를 해결하는 기법으로, 함수를 작성해 가독성을 높이고 유지 보수를 용이하게 해준다.
경합 조건, 교착 상태, 동시 업데이트 등의 문제는 모두 가변 변수로 인해 발생한다. 어떠한 변수도 갱신되지 않는다면 동시성 애플리케이션에서 마주치는 경합 조건이나 동시 업데이트 문제가 발생하지 않는다.
두 개 이상의 프로세스가 공통 자원을 동시에 읽거나 쓰는 동작을 할 때, 공용 데이터에 대한 접근이 어떤 순서에 따라 동작되었는지에 따라 실행 결과가 같지 않고 달라지는 것을 말한다.
두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태
여러 데이터베이스 세션이 동시에 동일한 데이터를 업데이트하도록 허용될 때 발생하는 문제 → Data 무결성 문제!
→ 외부에 영향을 주거나, 영향을 받지 않고 입력 값이 같으면 늘 반환 값도 일치함을 보장한다.
// Pure function example
private String name = "ssafy";
// Not pure
public String greeting(){
return "Hello" + name;
}
// Pure
public String greeting(String name){
return "Hello" + name;
}
→ 1급 함수의 한 부분으로 다음 조건을 만족하는 함수
// Higher order function example
public static Integer compute(Functional<Integer, Integer> function,
Integer value){
return function.apply(value);
}
public class AwesomeClass {
private static Integer invert(Integer value){
return -value;
}
private static integer invertTheNumber(){
Integer toInvert = 5;
Function<Integer, Integer> InvertFunction = AwesomeClass::invert;
return compute(invertFunction, toInvert);
}
}
함수형 프로그래밍은 객체지향 프로그래밍과 대척하는 관계가 아니다. 오히려 Java8에서는 함수형 프로그래밍을 이용하여 효율성을 증가시켰다. 예를 들면 Lambda, Stream 등이 있다.
코드를 작성하면서 좋은 방향으로 개선할 수 있다면 적극 이용해서 작성해보자!
// Simple stream example
import java.util.Arrays;
import java.util.List;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.filter(n -> n % 2 == 0) // 짝수만 필터링
.map(n -> n * n) // 제곱값으로 변환
.forEach(System.out::println); // 결과 출력
}
}
# 결과값
4
16
36
64
100
상태 변경의 예측 어려움: 메서드 호출 시 객체의 상태가 변경되므로, 메서드 호출 순서나 외부 상태에 따라 결과가 달라질 수 있다. 이는 코드의 동작을 예측하기 어렵게 만들 수 있다.
데이터 불변성 부족: 상태 변경이 자주 일어날 경우 예상치 못한 결과가 발생할 수 있으며, 디버깅이 어려울 수 있다.
경합 조건(Race Condition): 멀티스레드 환경에서 동시에 여러 스레드가 객체의 상태를 변경하면 데이터 불일치나 잘못된 결과가 발생할 수 있다.
예외 처리의 복잡성: 예외 처리를 통해 부작용이 발생할 수 있는 상황을 처리하는 것은 코드를 복잡하게 만들 수 있다.
디버깅의 어려움: 메서드 호출 사이에 다양한 상태 변경이 발생하므로 코드의 흐름을 디버깅하기 어려울 수 있다.
의존성 증가: 메서드가 외부 상태를 변경하면 다른 코드나 모듈이 예상치 못한 영향을 받을 수 있다.
예측 가능한 동작: 객체의 상태가 불변성을 유지하고, 객체를 변경하는 대신 새로운 객체를 생성하는 FP 스타일은 예측 가능한 동작을 제공한다. 이로 인해 코드의 동작을 더 쉽게 예측하고 디버깅할 수 있다.
다중 스레드 환경에서의 안정성: FP는 부작용을 최소화하기 때문에 멀티스레드 환경에서 안전하게 동작하는 코드를 작성하기가 더 쉽다. 객체 상태를 변경하지 않고 새로운 객체를 생성하면, 스레드 간의 경합 조건과 같은 문제를 피할 수 있다.
데이터 불변성: 객체 상태를 변경하는 것이 아니라 새로운 객체를 생성하면, 원래의 데이터 불변성을 유지할 수 있다. 이는 복잡한 시스템에서 예측할 수 없는 데이터 변경으로 인한 버그 발생 가능성을 줄여준다.
코드 테스트와 유지보수: 불변성과 예측 가능한 동작으로 인해 코드 테스트가 용이해지며, 코드 유지보수성이 향상된다. 변경된 객체가 아닌 새로운 객체를 생성하므로, 기존 코드를 변경하지 않고도 새로운 동작을 추가하거나 수정할 수 있다.
부작용의 격리: 객체의 상태 변경이 함수 내부에서만 발생하도록 함으로써 부작용을 격리시킬 수 있다. 이는 코드의 예측 가능성을 높이고 다른 부분에 영향을 미치지 않는 변경을 수행할 수 있도록 한다.
많은 것을 배웠습니다, 감사합니다.