☄️ 경계도 설계다 – 내 프로젝트에서 수립한 경계 처리
경계, 즉 내 코드와 외부 코드(서드파티 라이브러리, 오픈소스, 타 시스템) 사이의 접점은 생각보다 많은 위험을 내포하고 있었습니다.
그동안은 RedisTemplate, RestTemplate, Logger, 외부 API, Map 같은 것들을 아무 생각 없이 바로 사용하는 경우가 많았죠.
하지만 이 장을 읽고 난 후, 저는 다음과 같은 목표를 세웠습니다.
✔️ 외부 시스템과의 접점을 깔끔하게 감싸고
✔️ 그 변경에 우리 시스템이 휘둘리지 않도록 보호하고
✔️ 테스트 가능한 단위로 쪼개기
서드파티 라이브러리나 외부 API (Map, log4j, 결제 모듈) 등은 직접 사용하면 변경에 취약해지고 테스트도 어렵습니다.
그래서 저는 다음과 같은 리팩토링을 적용했습니다:
🔧 Before
redisTemplate.opsForValue().get("time_deal:stock:" + dealId);
✅ After
public class TimeDealCache {
public Integer getStock(Long id) { ... } // 내부적으로 redisTemplate 사용
}
→ TimeDealCache로 레디스 캐싱 클래스를 분리해, 나중에 Redis 대신 다른 캐시 시스템으로 바꾸더라도, 내부만 바꾸면 외부는 그대로 사용 가능하도록 리팩토링했습니다.
또한 아직 "결제"기능 구현 전인데, 테스트할 땐 FakePaymentService 같은 걸 만들어서 실서비스 대신 사용 가능하도록 해보려고 합니다. 바로 아래처럼요!
✅ 적용
public interface PaymentService {
PaymentResponse pay(PaymentRequest req);
}
@Component
public class PgPaymentAdapter implements PaymentService {
private final RestTemplate restTemplate = new RestTemplate();
public PaymentResponse pay(PaymentRequest req) {
return restTemplate.postForObject("https://pg.com/pay", req, PaymentResponse.class);
}
}
@Component
public class LogService {
private final Logger logger = LoggerFactory.getLogger("TimeDealLogger");
public void info(String msg) {
logger.info("[TimeDeal] " + msg);
}
public void error(String msg, Throwable t) {
logger.error("[TimeDeal ERROR] " + msg, t);
}
}
🌊 느낀 점: 경계는 위험하다. 그래서 더 신중하게 다뤄야 한다.
이 장을 읽기 전에는 "그냥 갖다 쓰면 되지, 감싸는 건 오버엔지니어링 아닌가?" 생각했는데,
막상 실무에 적용해 보니 바뀌는 건 외부 라이브러리인데, 고통받는 건 내 코드라는 걸 뼈저리게 느꼈습니다.
이제, 클린코드 핵심내용을 정리해보도록 하겠습니다.
1. 서드파티 코드 사용하기: "Map vs 감싸기(Wrapping)"
서드파티 클래스(Map, List 등)를 직접 쓰지 말고, 감싸서 사용해야합니다. 그래야 변경에 유연하게 대처할 수 있습니다.
❌ 직접 사용 (문제 발생 가능성 높음)
Map<String, Sensor> sensors = new HashMap<>();
Sensor s = sensors.get("A123"); // 사용하는 곳마다 Map을 알아야 함
✅ 감싸기 (변경에 유리함)
public class Sensors {
private Map<String, Sensor> sensors = new HashMap<>();
public Sensor getById(String id) {
return sensors.get(id); // Map은 내부에서만 사용됨
}
public void addSensor(String id, Sensor sensor) {
sensors.put(id, sensor);
}
}
2. 경계를 탐험하고 공부하기: Learning Test
서드파티 코드를 바로 쓰지 말고, 테스트로 어떻게 동작하는지 먼저 실험합시다.
@Test
public void testLog4jBasic() {
Logger logger = Logger.getLogger("MyLogger");
BasicConfigurator.configure(); // 콘솔 출력 설정
logger.info("Hello, log4j!");
}
이런 실험을 통해 "log4j가 어떻게 동작하는지"를 실제로 익힙니다.
그리고 나중에 log4j를 감싸는 래퍼 클래스를 만들어 시스템 전체가 log4j에 종속되지 않게 해야합니다.
3. "공부를 위한 테스트"는 값어치를 한다
Learning Test는 위험이 없습니다 (실제 코드에 영향 없음).
향후 라이브러리가 바뀌었을 때 우리가 쓰는 기능이 여전히 잘 작동하는지 확인 가능합니다.
@Test
public void testNewLibraryBehavior() {
ThirdPartyLibrary lib = new ThirdPartyLibrary();
assertEquals("expectedValue", lib.someFunction("input"));
}
라이브러리가 버전업되었을 때, 이 테스트가 깨지면 우리가 쓰던 기능이 바뀐 걸 바로 알 수 있습니다!
(ㄴ개인적으로 매우 좋은 방법이라고생각)
4. 아직 존재하지 않는 코드 사용하기 (Fake + Adapter)
아직 개발되지 않은 외부 시스템을 써야 할 때, 그냥 인터페이스를 먼저 만들고 가짜(Fake)로 작업 진행을 할 수 있습니다.
public interface Transmitter {
void transmit(String freq, String stream);
}
public class FakeTransmitter implements Transmitter {
public void transmit(String freq, String stream) {
System.out.println("Fake transmit: " + freq + ", " + stream);
}
}
✅ Adapter로 실제 구현이 나중에 들어옴
public class RealTransmitter {
public void realSend(String freq, String data) {
// 실제 전송 코드
}
}
public class TransmitterAdapter implements Transmitter {
private RealTransmitter real = new RealTransmitter();
public void transmit(String freq, String stream) {
real.realSend(freq, stream);
}
}
초기에는 FakeTransmitter로 개발하고, 실제 구현이 나오면 TransmitterAdapter로 교체하면 끝입니다.
5. Clean한 경계란?
경계란 "내 코드"와 "외부 코드" 사이의 접점이고, 이 경계를 잘 설계하면 외부의 변경에도 내 코드가 영향을 적게 받습니다.
✅ 좋은 예: 외부 API를 Adapter로 감싸기
// 외부 API
class ExternalMailer {
public void sendEmail(String address, String content) { /* ... */ }
}
// 내부에서 사용하는 인터페이스
interface Mailer {
void send(String to, String message);
}
// Adapter
class MailerAdapter implements Mailer {
private ExternalMailer externalMailer = new ExternalMailer();
public void send(String to, String message) {
externalMailer.sendEmail(to, message);
}
}
🧭 앞으로의 기준
- 외부 라이브러리는 되도록 래퍼나 어댑터로 감쌀 것
- 인터페이스를 먼저 설계하고, 외부 구현체를 뒤에 붙일 것
- 외부 시스템 테스트는 Learning Test로 사전 실험 후 사용
클린 코드의 “경계” 장은 시스템을 바깥으로부터 "보호"하는 방법을 고민하게 해준 챕터였습니다.
읽고, 프로젝트에 방식을 녹여가며 그 가치를 체감할 수 있는 내용이었습니다.
'개발프로젝트' 카테고리의 다른 글
[Clean Code] 복잡한 조건문은 어떻게 사라졌을까?— 클린 코드의 점진적 개선 원칙으로 할인 정책 리팩터링하기 (2) | 2025.06.16 |
---|---|
[Clean Code] 프로젝트에 '창발성' 원칙을 녹여낸 할인 정책 설계 이야기 (2) | 2025.06.12 |
[CleanCode] 실제 프로젝트에서 Clean Code 원칙을 적용한 예외 처리 리팩토링 경험기 (1) | 2025.05.26 |
[개발프로젝트] 스프링 API 예외 처리, 이제 try-catch는 그만! (1) | 2025.05.26 |
[CleanCode] 형식 맞추기 – 형식 규칙에 대한 깊은 고찰,클린코드를 읽고... (1) | 2025.05.22 |