chp7. 신뢰할 수 없는 코드를 쓰면서 불변성 지키기
앞서 6장에서는 카피-온-라이트(Copy-on-Write) 를 통해 중첩된 데이터에서도 불변성을 지키는 방법을 살펴보았습니다. 새로운 기능을 새로 만들 때라면 이 방식을 적용하기 쉽습니다.
하지만 실제 프로젝트에서 이미 존재하는 레거시 코드와 함께 작업해야 하는 경우가 많습니다. 이때 사용할 수 있는 방어적 복사(Defensive Copy) 에 대해 알아봅니다.
1. 레거시 코드와 불변성
레거시 코드가 다루는 데이터는 불변성을 깨뜨릴 수 있습니다. 이런 상황에서 중요한 것은 “신뢰할 수 있는 코드”와 “신뢰할 수 없는 코드”의 경계를 명확히 하는 것입니다.
2. 안전지대 개념
불변성을 철저히 지키며 작성된 코드들의 영역을 안전지대라고 부를 수 있습니다. 반면 레거시 코드는 안전지대 바깥에 있습니다.
따라서 안전지대의 경계를 넘나드는 데이터는 잠재적으로 위험합니다. 이때 필요한 것이 바로 방어적 복사입니다.
3. 방어적 복사
방어적 복사는 데이터가
- 안전지대로 들어올 때
- 안전지대에서 나갈 때
깊은 복사본을 만드는 것을 의미합니다.
예를 들어, 장바구니에 담긴 상품 가격을 레거시 함수로 변경해야 한다고 할 때,
tsx
function add_item_to_cart(item, price) {
// 안전지대 밖으로 나갈 때 깊은 복사
var cart_copy = deepCopy(shopping_cart);
legacy_set_promotion_price(cart_copy);
// 안전지대로 들어올 때 다시 깊은 복사
shopping_cart = deepCopy(cart_copy);
}TIP
API 요청을 통해 서버에서 직렬화된 JSON 데이터를 받는 과정도, 일종의 방어적 복사라고 볼 수 있습니다. 데이터를 직렬화-역직렬화하는 순간, 원본과는 독립된 복사본이 만들어지기 때문입니다.
4. Copy-on-Write vs 방어적 복사
| 구분 | 카피-온-라이트 (Copy-on-Write) | 방어적 복사 (Defensive Copy) |
|---|---|---|
| 언제 쓰나요? | 통제할 수 있는 데이터를 바꿀 때 | 신뢰할 수 없는 코드/데이터와 주고받을 때 |
| 어디서 쓰나요? | 안전지대 내부 어디서든 | 안전지대의 경계 |
| 복사 방식 | 얕은 복사 (비용 적음) | 깊은 복사 (비용 큼) |
| 규칙 | 1. 얕은 복사본 생성 2. 복사본 변경 3. 복사본 반환 | 1. 안전지대 입장 시 깊은 복사 2. 안전지대 퇴장 시 깊은 복사 |
5. 얕은 복사 vs 깊은 복사
얕은 복사는 1단계만 복사하기 때문에 내부 참조는 여전히 공유됩니다.
tsx
let arr1 = [1, [2, 3]];
let arr2 = arr1.slice(); // 얕은 복사
arr2[0] = 99; // 원본은 영향 없음
arr2[1][0] = 88; // 내부 배열 요소 수정 → 원본도 변경됨
console.log(arr1); // [1, [88, 3]]깊은 복사는 내부 구조까지 모두 복제해 원본과 완전히 독립시킵니다.
tsx
let arr1 = [1, [2, 3]];
let arr2 = JSON.parse(JSON.stringify(arr1)); // 깊은 복사
arr2[1][0] = 88;
console.log(arr1); // [1, [2, 3]]
console.log(arr2); // [1, [88, 3]]정리하며
- 카피-온-라이트는 우리가 직접 통제할 수 있는 코드 내부에서 불변성을 지킬 때 유용합니다.
- 방어적 복사는 안전지대와 레거시 코드처럼 신뢰할 수 없는 코드 사이에서 데이터를 주고받을 때 필요합니다.
- 깊은 복사는 비용이 크기 때문에 꼭 필요한 순간, 즉 안전지대의 경계에서만 사용하는 것이 바람직합니다.