행동 매개변수화
조건이 늘 때마다 복제되는 컬렉션 순회를 함수형 계약으로 추출하고, 값 매개변수와 행동 매개변수의 차이·순수성·조합 가능성을 실행 예제로 익힙니다.
메서드 매개변수에는 숫자와 문자열만 전달하는 것이 아닙니다.
“어떤 상품을 선택할지”, “실패를 다시 시도할지”, “두 항목을 어떤 순서로 놓을지” 같은 행동도 객체로 표현해 전달할 수 있습니다.
알고리즘의 고정 부분은 컬렉션 순회와 결과 축적이고, 변하는 부분은 각 원소에 내릴 판단입니다.
변하는 판단을 매개변수로 받으면 새로운 조건이 생겨도 순회 코드를 복사하지 않습니다.
람다는 행동 객체를 짧게 만드는 문법입니다.
그러나 첫 설계 단계에서는 람다보다 경계를 먼저 찾아야 합니다.
입력과 출력이 무엇인지, 같은 입력에 같은 결과를 기대하는지, 외부 상태를 변경하는지 정해야 합니다.
계약이 흐리면 짧은 화살표 문법만 남고 테스트하기 어려운 콜백이 됩니다.
순회와 정책의 결합
아래 코드는 가격과 재고 조건마다 거의 같은 반복문을 가집니다.
세 번째 조건이 생기면 메서드를 하나 더 만들고, null 처리나 결과 불변화 개선도 모든 복사본에 적용해야 합니다.
boolean 플래그를 추가해 한 메서드로 합치면 조건 조합이 늘수록 분기표가 커집니다.
import java.util.ArrayList;
import java.util.List;
public final class DuplicatedProductFilters {
record Product(String name, int price, int stock) {
}
static List<Product> expensive(List<Product> products, int minimum) {
List<Product> result = new ArrayList<>();
for (Product product : products) {
if (product.price() >= minimum) {
result.add(product);
}
}
return result;
}
static List<Product> available(List<Product> products) {
List<Product> result = new ArrayList<>();
for (Product product : products) {
if (product.stock() > 0) {
result.add(product);
}
}
return result;
}
public static void main(String[] args) {
System.out.println("compile-only duplicated loop counterexample");
}
}값 매개변수는 알고리즘 안의 빈칸 하나를 채웁니다.
minimumPrice를 받으면 비교 기준 숫자는 바뀌지만 비교 방식은 그대로입니다.
행동 매개변수는 비교 방식 자체를 바꿉니다.
ProductRule이 boolean test(Product)를 선언하면 호출자는 가격, 재고, 이름, 복합 조건을 구현할 수 있고 선택 알고리즘은 수정되지 않습니다.
행동을 인터페이스 구현 객체로 받는 설계는 람다 이전에도 가능합니다.
람다는 함수형 인터페이스의 행동을 간결하게 표현합니다. 익명 클래스와 달리 this와 이름의 해석 방식에는 차이가 있으므로 단순한 문법 치환으로만 보지는 않습니다.
따라서 매개변수 이름과 메서드 시그니처가 도메인 의미를 잘 드러내야 합니다.
판단 계약과 선택 알고리즘
다음 예제는 ProductRule을 함수형 계약으로 정의합니다.
select()는 규칙이 무엇인지 모르고 각 상품을 시험합니다.
호출자는 이름 있는 규칙, 람다, 조합 규칙을 모두 전달할 수 있습니다.
반환 목록은 List.copyOf()로 고정해 선택 결과가 뒤에서 수정되지 않게 합니다.
import java.util.ArrayList;
import java.util.List;
public final class BehaviorParameterization {
record Product(String name, String category, int price, int stock) {
}
@FunctionalInterface
interface ProductRule {
boolean test(Product product);
default ProductRule and(ProductRule other) {
return product -> test(product) && other.test(product);
}
default ProductRule negate() {
return product -> !test(product);
}
}
static List<Product> select(List<Product> products, ProductRule rule) {
List<Product> selected = new ArrayList<>();
for (Product product : products) {
if (rule.test(product)) {
selected.add(product);
}
}
return List.copyOf(selected);
}
static final class CategoryRule implements ProductRule {
private final String category;
CategoryRule(String category) {
this.category = category;
}
@Override
public boolean test(Product product) {
return product.category().equals(category);
}
}
public static void main(String[] args) {
List<Product> products = List.of(
new Product("Keyboard", "device", 120, 3),
new Product("Cable", "device", 15, 0),
new Product("Book", "book", 35, 8));
ProductRule device = new CategoryRule("device");
ProductRule available = product -> product.stock() > 0;
ProductRule affordable = product -> product.price() <= 100;
System.out.println(select(products, device.and(available)));
System.out.println(select(products, available.and(affordable)));
System.out.println(select(products, available.negate()));
}
}첫 번째 select 호출을 상품별로 따라가면 오른쪽 규칙을 건너뛰는 경우가 드러납니다.
device.and(available)의 상품별 평가
device가 false이면 available을 호출하지 않고 두 조건을 통과한 상품만 결과에 추가합니다. 현재 상품·실행된 조건·누적 결과를 고정해 단락 평가를 보여 줄 수 있습니다.
device.and(available)의 상품별 평가
조건의 단락 평가
device.and(available)의 상품별 평가
device를 먼저 검사합니다. false인 Book은 available을 호출하지 않고 제외됩니다.
Keyboard
- device 결과
- true
- available 평가
- 호출 · true
- 선택
- 포함
Cable
- device 결과
- true
- available 평가
- 호출 · false
- 선택
- 제외
Book
- device 결과
- false
- available 평가
- 호출하지 않음
- 선택
- 제외
선택 결과에는 Keyboard만 포함됩니다. Cable은 available=false이므로 제외됩니다.
이 예의 조건
- device.and(available)은 상품별로 순차 단락 평가를 수행합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원 device.and(available)의 상품 한 개 처리 분기다. device=false는 available을 호출하지 않고 제외하며 device=true일 때만 available을 검사한다. true/false는 반환 불리언이다. Keyboard 포함, Cable·Book 제외라는 원 표는 보조로 유지한다. 모바일도 같은 5개 노드와 5개 관계이고 콜백을 병렬로 호출한다는 뜻이 아니다. |
| 구분 | 내용 |
|---|---|
| 설명 | 원 device.and(available)의 상품 한 개 처리 분기다. device=false는 available을 호출하지 않고 제외하며 device=true일 때만 available을 검사한다. true/false는 반환 불리언이다. Keyboard 포함, Cable·Book 제외라는 원 표는 보조로 유지한다. 모바일도 같은 5개 노드와 5개 관계이고 콜백을 병렬로 호출한다는 뜻이 아니다. |
| 전체 읽기 정보 | device.and(available)의 상품별 평가 device.and(available)의 상품별 평가 device.and(available)의 상품별 평가 조건의 단락 평가 device.and(available)의 상품별 평가 device를 먼저 검사합니다. false인 Book은 available을 호출하지 않고 제외됩니다. Keyboard device 결과 true available 평가 호출 · true 선택 포함 Cable device 결과 true available 평가 호출 · false 선택 제외 Book device 결과 false available 평가 호출하지 않음 선택 제외 선택 결과에는 Keyboard만 포함됩니다. Cable은 available=false이므로 제외됩니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 왼쪽 판단 상품의 device를 먼저 호출합니다. |
|
|
| 오른쪽 판단 device가 true인 상품만 available을 검사합니다. |
|
|
| 원 결과 Keyboard만 선택되고 Cable·Book은 제외됩니다. |
|
|
device.and(available)의 상품별 평가
device가 false이면 available을 호출하지 않고 두 조건을 통과한 상품만 결과에 추가합니다. 현재 상품·실행된 조건·누적 결과를 고정해 단락 평가를 보여 줄 수 있습니다.
device.and(available)의 상품별 평가
조건의 단락 평가
device.and(available)의 상품별 평가
device를 먼저 검사합니다. false인 Book은 available을 호출하지 않고 제외됩니다.
Keyboard
- device 결과
- true
- available 평가
- 호출 · true
- 선택
- 포함
Cable
- device 결과
- true
- available 평가
- 호출 · false
- 선택
- 제외
Book
- device 결과
- false
- available 평가
- 호출하지 않음
- 선택
- 제외
선택 결과에는 Keyboard만 포함됩니다. Cable은 available=false이므로 제외됩니다.
이 예의 조건
- device.and(available)은 상품별로 순차 단락 평가를 수행합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원 device.and(available)의 상품 한 개 처리 분기다. device=false는 available을 호출하지 않고 제외하며 device=true일 때만 available을 검사한다. true/false는 반환 불리언이다. Keyboard 포함, Cable·Book 제외라는 원 표는 보조로 유지한다. 모바일도 같은 5개 노드와 5개 관계이고 콜백을 병렬로 호출한다는 뜻이 아니다. |
| 구분 | 내용 |
|---|---|
| 설명 | 원 device.and(available)의 상품 한 개 처리 분기다. device=false는 available을 호출하지 않고 제외하며 device=true일 때만 available을 검사한다. true/false는 반환 불리언이다. Keyboard 포함, Cable·Book 제외라는 원 표는 보조로 유지한다. 모바일도 같은 5개 노드와 5개 관계이고 콜백을 병렬로 호출한다는 뜻이 아니다. |
| 전체 읽기 정보 | device.and(available)의 상품별 평가 device.and(available)의 상품별 평가 device.and(available)의 상품별 평가 조건의 단락 평가 device.and(available)의 상품별 평가 device를 먼저 검사합니다. false인 Book은 available을 호출하지 않고 제외됩니다. Keyboard device 결과 true available 평가 호출 · true 선택 포함 Cable device 결과 true available 평가 호출 · false 선택 제외 Book device 결과 false available 평가 호출하지 않음 선택 제외 선택 결과에는 Keyboard만 포함됩니다. Cable은 available=false이므로 제외됩니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 왼쪽 판단 상품의 device를 먼저 호출합니다. |
|
|
| 오른쪽 판단 device가 true인 상품만 available을 검사합니다. |
|
|
| 원 결과 Keyboard만 선택되고 Cable·Book은 제외됩니다. |
|
|
기본 메서드로 and와 negate를 제공하면 호출자가 조건 조합을 선언적으로 읽을 수 있습니다.
and는 왼쪽 조건이 false면 오른쪽을 평가하지 않는 단락 평가 의미를 가집니다.
오른쪽 규칙이 로그나 카운터를 변경한다면 평가 여부가 결과 외 상태에 영향을 줍니다.
예시 예제의 device.and(available)에서는 device를 세 상품 모두에 호출하고 available은 Keyboard·Cable 두 상품에만 호출합니다. Book은 왼쪽에서 false라 오른쪽 판단을 건너뜁니다. 반환값은 Keyboard 한 개이며, 함수가 순수하다는 조건에서 순서를 뒤집어도 선택 결과는 같습니다.
독립 확인: 같은 세 상품을 available.and(device)로 바꾸면 available과 device의 호출 수는 각각 얼마인가요? Book과 Cable에서 어떤 판단을 건너뛰나요?
풀이 확인
available은 3회, device는 Keyboard·Book에서 2회입니다. Cable은 available=false라 device를 건너뛰고 Book은 device=false까지 평가합니다. Book의 available을 건너뛰던 기존 순서와 호출 대상이 달라집니다. 조건에 부수 효과가 있다면 같은 선택 결과만으로 두 순서를 같은 동작이라고 볼 수 없습니다.
판단 함수는 가능하면 부수 효과 없이 두어 조합 순서가 예측 가능하게 합니다.
한 알고리즘이 규칙을 여러 번 호출할 수 있는지도 계약에 포함해야 합니다.
병렬 처리나 재시도가 추가되면 호출 횟수와 순서가 달라질 수 있습니다.
“정확히 한 번 실행”이 필요한 결제나 메시지 발행은 단순 조건식으로 전달하지 말고 별도의 명령 경계와 멱등성 정책을 둡니다.
Comparator 정책
선택만 행동 매개변수화의 사례가 아닙니다.
Java의 Comparator<T>는 두 값을 받아 음수, 0, 양수로 순서를 표현합니다.
sort 알고리즘은 비교 정책을 모르고 비교자만 호출합니다.
원본 목록을 직접 정렬할지 복사본을 만들지도 API 계약에서 분명히 해야 합니다.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public final class ComparatorPolicy {
record Ticket(String id, int severity, long openedAt) {
}
static List<Ticket> orderedCopy(List<Ticket> tickets, Comparator<Ticket> order) {
List<Ticket> copy = new ArrayList<>(tickets);
copy.sort(order);
return List.copyOf(copy);
}
public static void main(String[] args) {
List<Ticket> tickets = List.of(
new Ticket("T-3", 2, 300),
new Ticket("T-1", 1, 200),
new Ticket("T-2", 1, 100));
Comparator<Ticket> queueOrder = Comparator
.comparingInt(Ticket::severity)
.thenComparingLong(Ticket::openedAt)
.thenComparing(Ticket::id);
Comparator<Ticket> newestFirst = Comparator
.comparingLong(Ticket::openedAt)
.reversed();
System.out.println(orderedCopy(tickets, queueOrder));
System.out.println(orderedCopy(tickets, newestFirst));
System.out.println("original=" + tickets);
}
}비교자는 추이성을 지켜야 하고 compare(a, b) == 0의 의미를 의도적으로 정해야 합니다.
비교 계약이 깨지면 sort가 잘못된 순서를 만들거나 실행 시점 예외를 낼 수 있습니다.
숫자 차이를 left.price() - right.price()로 반환하면 오버플로가 생길 수 있으므로 Integer.compare()나 비교자 팩터리를 사용합니다.
행동 경계 선택
모든 한 줄 조건을 인터페이스로 추출할 필요는 없습니다.
호출자마다 정책이 달라지거나 새로운 정책 추가가 빈번하고, 공통 알고리즘이 안정적일 때 행동 매개변수가 효과적입니다.
한 곳에서만 쓰이며 변경 이유도 같은 코드를 억지로 분리하면 이름과 간접 호출만 늘어납니다.
함수 객체의 수명도 생각합니다.
람다가 큰 객체를 캡처한 채 장기 레지스트리에 저장되면 해당 객체가 가비지 컬렉션되지 않습니다.
요청 범위 문맥을 애플리케이션 싱글턴 콜백에 등록하는 패턴은 메모리 누수와 데이터 혼선을 만듭니다.
순수한 작은 정책은 오래 보관해도 안전하지만 가변 캡처는 소유자와 해제 시점을 정해야 합니다.
연습 문제
최대 횟수를 if 문에 고정하지 말고 RetryPolicy가 현재 시도와 실패를 받아 다음 지연을 반환하게 합니다.
음수는 중단, 0 이상은 재시도를 뜻합니다.
실행기는 연산을 호출하고 정책 결정 기록을 남깁니다.
예제에서는 실제 sleep을 하지 않습니다.
해답 보기
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public final class RetryDecisionSolution {
@FunctionalInterface
interface Operation<T> {
T run();
}
@FunctionalInterface
interface RetryPolicy {
Duration nextDelay(int failedAttempt, RuntimeException failure);
}
record Execution<T>(T value, List<String> decisions) {
Execution {
decisions = List.copyOf(decisions);
}
}
static <T> Execution<T> execute(Operation<T> operation, RetryPolicy policy) {
List<String> decisions = new ArrayList<>();
int attempt = 1;
while (true) {
try {
return new Execution<>(operation.run(), decisions);
} catch (RuntimeException failure) {
Duration delay = policy.nextDelay(attempt, failure);
decisions.add("attempt=" + attempt + ", delay=" + delay.toMillis());
if (delay.isNegative()) {
throw failure;
}
attempt += 1;
}
}
}
public static void main(String[] args) {
AtomicInteger calls = new AtomicInteger();
Operation<String> flaky = () -> {
int call = calls.incrementAndGet();
if (call < 3) {
throw new IllegalStateException("temporary " + call);
}
return "ok";
};
RetryPolicy twoRetries = (attempt, failure) -> attempt <= 2
? Duration.ofMillis(25L * attempt)
: Duration.ofMillis(-1);
System.out.println(execute(flaky, twoRetries));
System.out.println("calls=" + calls.get());
}
}원문 main의 연산 호출과 정책 기록을 함께 비교합니다.
연산 실패와 재시도 판단 기록
연산 실패 뒤 정책이 지연을 기록하고 음수면 원래의 실패를 던지며 아니면 시도 번호를 올려 다시 호출합니다. 실제 main은 잠들지 않으므로 기록된 지연과 대기를 구분하여 추적합니다.
연산 실패와 재시도 판단 기록
재시도 판단
연산 실패와 재시도 판단 기록
실패한 호출만 정책을 평가합니다. 세 번째 호출은 성공하므로 두 판단 기록과 ok를 반환합니다.
1회
- 연산 결과
- temporary 1 실패
- 정책과 기록
- 정책 호출 · delay=25 기록
2회
- 연산 결과
- temporary 2 실패
- 정책과 기록
- 정책 호출 · delay=50 기록
3회
- 연산 결과
- ok 반환
- 정책과 기록
- 정책 미호출 · 앞선 두 기록 반환
지연 25·50은 밀리초 값의 기록입니다. 이 실행기는 실제 대기나 sleep을 수행하지 않습니다.
이 예의 조건
- 지연 숫자를 기록하며 sleep하지 않습니다. 원 main은 세 번째 호출에서 성공합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 실패한 호출에서만 정책을 평가해 지연을 기록한다. 음수이면 원 실패를 던져 Execution은 반환하지 않고, 0 이상이면 시도 번호를 증가시켜 다시 연산을 호출한다. 화살표는 원 코드 제어 흐름이며 지연 숫자에 맞춰 sleep한다는 뜻이 아니다. 원 main은 세 번째 성공이므로 중단 경로는 별도 입력이다. 같은 7개 노드와 7개 관계를 모바일에서도 유지한다. |
| 구분 | 내용 |
|---|---|
| 설명 | 실패한 호출에서만 정책을 평가해 지연을 기록한다. 음수이면 원 실패를 던져 Execution은 반환하지 않고, 0 이상이면 시도 번호를 증가시켜 다시 연산을 호출한다. 화살표는 원 코드 제어 흐름이며 지연 숫자에 맞춰 sleep한다는 뜻이 아니다. 원 main은 세 번째 성공이므로 중단 경로는 별도 입력이다. 같은 7개 노드와 7개 관계를 모바일에서도 유지한다. |
| 전체 읽기 정보 | 연산 실패와 재시도 판단 기록 연산 실패와 재시도 판단 기록 연산 실패와 재시도 판단 기록 재시도 판단 연산 실패와 재시도 판단 기록 실패한 호출만 정책을 평가합니다. 세 번째 호출은 성공하므로 두 판단 기록과 ok를 반환합니다. 1회 연산 결과 temporary 1 실패 정책과 기록 정책 호출 · delay=25 기록 2회 연산 결과 temporary 2 실패 정책과 기록 정책 호출 · delay=50 기록 3회 연산 결과 ok 반환 정책과 기록 정책 미호출 · 앞선 두 기록 반환 지연 25·50은 밀리초 값의 기록입니다. 이 실행기는 실제 대기나 sleep을 수행하지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 실패와 정책 첫 실패의 attempt 1에서 정책이 반환한 25 ms를 기록합니다. |
|
|
| 다음 재시도 attempt 2도 실패해 50을 기록하고 attempt 3으로 넘어갑니다. |
|
|
| 성공 또는 중단 원 main의 세 번째 호출은 성공하여 값·판단 목록을 반환합니다. |
|
|
연산 실패와 재시도 판단 기록
연산 실패 뒤 정책이 지연을 기록하고 음수면 원래의 실패를 던지며 아니면 시도 번호를 올려 다시 호출합니다. 실제 main은 잠들지 않으므로 기록된 지연과 대기를 구분하여 추적합니다.
연산 실패와 재시도 판단 기록
재시도 판단
연산 실패와 재시도 판단 기록
실패한 호출만 정책을 평가합니다. 세 번째 호출은 성공하므로 두 판단 기록과 ok를 반환합니다.
1회
- 연산 결과
- temporary 1 실패
- 정책과 기록
- 정책 호출 · delay=25 기록
2회
- 연산 결과
- temporary 2 실패
- 정책과 기록
- 정책 호출 · delay=50 기록
3회
- 연산 결과
- ok 반환
- 정책과 기록
- 정책 미호출 · 앞선 두 기록 반환
지연 25·50은 밀리초 값의 기록입니다. 이 실행기는 실제 대기나 sleep을 수행하지 않습니다.
이 예의 조건
- 지연 숫자를 기록하며 sleep하지 않습니다. 원 main은 세 번째 호출에서 성공합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 실패한 호출에서만 정책을 평가해 지연을 기록한다. 음수이면 원 실패를 던져 Execution은 반환하지 않고, 0 이상이면 시도 번호를 증가시켜 다시 연산을 호출한다. 화살표는 원 코드 제어 흐름이며 지연 숫자에 맞춰 sleep한다는 뜻이 아니다. 원 main은 세 번째 성공이므로 중단 경로는 별도 입력이다. 같은 7개 노드와 7개 관계를 모바일에서도 유지한다. |
| 구분 | 내용 |
|---|---|
| 설명 | 실패한 호출에서만 정책을 평가해 지연을 기록한다. 음수이면 원 실패를 던져 Execution은 반환하지 않고, 0 이상이면 시도 번호를 증가시켜 다시 연산을 호출한다. 화살표는 원 코드 제어 흐름이며 지연 숫자에 맞춰 sleep한다는 뜻이 아니다. 원 main은 세 번째 성공이므로 중단 경로는 별도 입력이다. 같은 7개 노드와 7개 관계를 모바일에서도 유지한다. |
| 전체 읽기 정보 | 연산 실패와 재시도 판단 기록 연산 실패와 재시도 판단 기록 연산 실패와 재시도 판단 기록 재시도 판단 연산 실패와 재시도 판단 기록 실패한 호출만 정책을 평가합니다. 세 번째 호출은 성공하므로 두 판단 기록과 ok를 반환합니다. 1회 연산 결과 temporary 1 실패 정책과 기록 정책 호출 · delay=25 기록 2회 연산 결과 temporary 2 실패 정책과 기록 정책 호출 · delay=50 기록 3회 연산 결과 ok 반환 정책과 기록 정책 미호출 · 앞선 두 기록 반환 지연 25·50은 밀리초 값의 기록입니다. 이 실행기는 실제 대기나 sleep을 수행하지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 실패와 정책 첫 실패의 attempt 1에서 정책이 반환한 25 ms를 기록합니다. |
|
|
| 다음 재시도 attempt 2도 실패해 50을 기록하고 attempt 3으로 넘어갑니다. |
|
|
| 성공 또는 중단 원 main의 세 번째 호출은 성공하여 값·판단 목록을 반환합니다. |
|
|
이 main의 정상 경로는 성공으로 끝나므로 음수 지연의 중단 분기에는 도달하지 않습니다. 중단 분기에서는 원래 실패를 던지고 Execution을 반환하지 않아, 메모리에 쌓은 판단 목록도 결과로 돌려주지 않습니다.
독립 확인: 연산이 매번 같은 RuntimeException을 던진다고 바꾸고 예제의 twoRetries 정책을 그대로 사용합니다. 총 연산·정책 호출 수, 기록한 지연 값, 반환되는 Execution의 유무를 구하세요. 25와 50을 더한 75ms가 실제 경과 시간인가요?
풀이 확인
연산과 정책은 각각 3회입니다. attempt=1·2·3에서 기록은 25·50·-1이고 세 번째 음수 판단 뒤 원래의 실패를 던집니다. Execution은 반환되지 않아 판단 목록도 성공 결과로 받지 못합니다. 실행기에는 대기 코드가 없으므로 75ms를 실제 대기나 실측 경과 시간으로 해석하지 않습니다. 이는 허용한 재시도 두 번과 총 시도 세 번을 구분하는 조건입니다.
해답은 연산, 재시도 판단, 실제 대기 수단을 더 분리할 여지가 있습니다.
운영에서는 재시도 가능한 예외 분류, 전체 시간 예산, 지터, 인터럽트 보존, 멱등 연산 여부를 함께 다룹니다.