애그리거트 경계 표준은 자식 컬렉션을 이렇게 다루기로 했다.
생성자에서 List.copyOf()로 복사하고, 변경은 루트의 의도 메서드로만 한다.
앞 절반(copyOf로 복사한다)은 수단이고 뒤 절반(변경은 루트를 거친다)이 목적이다. 수단 하나로 목적이 지켜지는지를 재봤다.
주장 — copyOf로 받으면 밖에서 내부 상태를 바꿀 수 없다
생성자에서 List.copyOf로 받은 컬렉션은 애그리거트 밖에서 내부 상태를 바꿀 수 없다.
근거 종류는 1(기술 사실)이다. 프레임워크를 쓰지 않았다 — 자바 컬렉션의 성질이라 Spring이 끼면 변수만 는다.
반증 조건 — 세 경로 중 하나라도 안쪽 값이 바뀌면 반증이다
- refuted — 세 경우 중 하나라도 애그리거트 안에서 읽은 값이 밖에서 바꾼 대로 바뀐다
- supported — (가)와 (나)가 막히고 (다)에서도 안 바뀐다
- inconclusive — 대조군(리스트를 그대로 보관한 애그리거트)에서 (다)가 바뀌지 않는다
대조군을 둔 이유가 (다) 때문이다. 실험이 「값이 바뀌는 것」을 애초에 관측할 수 있는지 먼저 보여야 한다.
설계 — 밖에서 건드리는 방법을 셋으로 나눈다
(가) 꺼내서 더한다 root.getItems().add(...)
(나) 넘긴 원본을 바꾼다 생성자에 넘겼던 리스트에 add
(다) 요소를 바꾼다 원본이 갖고 있던 자식 객체의 값을 바꾼다
자식은 가변 객체로 두되 setter가 아니라 의도 메서드(changeQuantity)로 바꾼다. 표준이 Domain에 쓰라고 한 모양 그대로다. 경우마다 애그리거트를 새로 만들어 앞 경우가 뒤 경우를 오염시키지 않게 했다.
결과 — (가)(나)는 막혔고 (다)는 대조군과 같았다
PROBE_ENV java=21.0.9 copyOfClass=List12 rawClass=ArrayList
copyOf (가) UnsupportedOperationException size 2→2 안 바뀜
copyOf (나) 예외없음 size 2→2 안 바뀜
copyOf (다) 예외없음 quantity 7→99 바뀜
대조군 (가) 예외없음 size 2→3 바뀜
대조군 (나) 예외없음 size 2→3 바뀜
대조군 (다) 예외없음 quantity 7→99 바뀜
판정은 refuted다. (다)에서 copyOf 쪽과 대조군의 결과가 완전히 같다.
예상과 달랐던 점 — copyOf가 막는 것은 구조뿐이다
List.copyOf는 얕은 복사라 자식 객체의 참조를 그대로 넘긴다. 그래서 막히는 것과 안 막히는 것이 이렇게 갈린다.
| 밖에서 하는 일 | copyOf가 막나 |
|---|---|
| 목록에 더하거나 뺀다 | 막는다 |
| 생성자에 넘겼던 원본 목록을 바꾼다 | 막는다 |
| 목록 안 객체의 값을 바꾼다 | 못 막는다 |
표준 글의 「변경은 루트의 의도 메서드로만」은 자식이 불변일 때만 copyOf 하나로 지켜진다. 자식이 가변이면 밖에서 잡고 있던 참조로 그냥 바꿀 수 있다.
이 실험은 자식을 어떻게 해야 하는지는 재지 않았다. 자식까지 복사할지, 자식을 불변으로 만들지, 꺼낼 때 방어 복사를 할지는 각각 다른 대가가 있고 이 실험 밖이다.
이 결과가 표준에 남기는 것 — 수단을 하나 더 정해야 한다
규칙(변경은 루트를 거친다)은 이 실험이 판정하지 않는다. 깨진 것은 그 규칙을 copyOf 하나로 강제할 수 있다는 설명이다.
규칙은 바로 고치지 않는다. 표준 글에 > 반증: 줄을 달고 kind: 모순 이슈에서 자식 객체를 무엇으로 막을지를 정한다.
정리
copyOf는 목록의 구조만 막는다. 추가·제거는 막히고 원본 목록의 변경도 안 비친다- 원소의 상태는 못 막는다. 자식의 의도 메서드로 값을 바꾸면 안쪽 값이 그대로 바뀐다 — 대조군과 같은 결과다
- 자식이 불변일 때만 지금 문장이 성립한다
- 재지 않은 것 — 자식까지 복사하거나 불변으로 만들었을 때의 비용
자신만의 철학을 만들어가는 중입니다.
댓글남기기