TotT: 관심사 분리? 끝!
원문: https://testing.googleblog.com/2020/12/testing-on-toilet-separation-of.html (Translated by Google Gemini)
다음 함수는 SpeedyImg라는 API를 사용하여 바이트 배열을 이미지로 디코딩합니다. 다른 팀이 소유한 API를 참조할 때 어떤 유지보수 문제가 발생할 수 있을까요?
SpeedyImgImage decodeImage(List<SpeedyImgDecoder> decoders, byte[] data) {
SpeedyImgOptions options = getDefaultConvertOptions();
for (SpeedyImgDecoder decoder : decoders) {
SpeedyImgResult decodeResult = decoder.decode(decoder.formatBytes(data));
SpeedyImgImage image = decodeResult.getImage(options);
if (validateGoodImage(image)) { return image; }
}
throw new RuntimeException();
}
API 호출에 대한 세부 정보가 도메인 로직과 혼합되어 있어 코드를 이해하기 어렵게 만들 수 있습니다. 예를 들어, decoder.formatBytes()
호출은 API에서 필요하지만 바이트가 어떻게 포맷되는지는 도메인 로직과 관련이 없습니다.
또한 이 API가 코드베이스의 여러 곳에서 사용되는 경우, API 사용 방식이 변경되면 모든 사용처를 변경해야 할 수 있습니다. 예를 들어, 이 함수의 반환 유형이 더 일반적인 SpeedyImgResult
유형으로 변경되면 SpeedyImgImage
의 사용처를 업데이트해야 합니다.
이러한 유지보수 문제를 피하려면, API 세부 정보를 추상화 뒤에 숨기기 위해 래퍼 (wrapper) 타입을 만드세요:
Image decodeImage(List<ImageDecoder> decoders, byte[] data) {
for (ImageDecoder decoder : decoders) {
Image decodedImage = decoder.decode(data);
if (validateGoodImage(decodedImage)) { return decodedImage; }
}
throw new RuntimeException();
}
외부 API를 래핑하는 것은 관심사 분리 원칙을 따릅니다. API 호출 로직이 도메인 로직과 분리되기 때문입니다. 이는 다음과 같은 많은 이점을 제공합니다.
- API 사용 방식이 변경되더라도 래퍼에 API를 캡슐화하면 변경 사항이 코드베이스 전체에 전파되는 것을 막을 수 있습니다.
- 소유한 타입의 인터페이스나 구현은 수정할 수 있지만, API 타입은 수정할 수 없습니다.
- 도입된 타입(예:
ImageDecoder
/Image
)으로 여전히 표현될 수 있으므로 다른 API로 전환하거나 추가하기가 더 쉽습니다. - 핵심 로직을 이해하기 위해 API 코드를 자세히 살펴볼 필요가 없으므로 가독성이 향상될 수 있습니다.
모든 외부 API를 래핑할 필요는 없습니다. 예를 들어, API를 분리하는 데 엄청난 노력이 필요하거나 코드베이스를 오염시키지 않을 만큼 간단하다면 래퍼 타입을 도입하지 않는 것이 더 나을 수 있습니다(예: Java의 List
또는 C++의 std::vector
와 같은 라이브러리 타입). 확실하지 않을 때는 래퍼가 코드를 명확하게 개선할 경우에만 추가해야 한다는 점을 기억하세요(YAGNI 원칙 참조).