📕 목차
1. 외부 코드 사용하기
2. 경계 살피고 익히기
3. log4j 익히기
4. 학습 테스트는 공짜 이상이다
5. 아직 존재하지 않는 코드를 사용하기
6. 깨끗한 경계
1. 외부 코드 사용하기
📌 인터페이스 제공자와 사용자
- 인터페이스 제공자는 적용성을 최대한 넓히려 애쓴다. (그래야 팔리니까)
- 인터페이스 사용자는 자신의 요구에 집중하는 인터페이스를 바란다.
이러한 괴리감이 시스템 경계에서 문제가 생길 소지가 많다.
📌 java.util.Map
- 다양한 인터페이스로 수많은 기능을 제공하여 기능성과 유연성을 높이는 것은 위험도 크다.
- Map 사용자라면 누구나 Map 내용을 지울 권한이 있어, 프로그램에서 공유할 때 문제가 생길 수 있다.
- Map은 객체 유형을 제한하지 않으므로 마음만 먹으면 어떤 객체 유형이든 추가할 수 있다.
Map sensors = new HashMap();
Sensor s = (Sensor)sensors.get(sensorId);
위 방식은 Map이 반환하는 Object를 올바른 유형으로 변환할 책임을 Map을 사용하는 Client에게 떠넘기고 있다.
Map<String, Sensor> sensors = new HashMap<Sensor>();
Sensor s = sensors.get(sensorId);
Generics를 사용하면 코드 가독성을 높이면서, 의도를 분명히 전달할 수 있다.
하지만 여전히 Map 인터페이스가 사용자에게 필요하지 않은 기능까지 제공한다는 문제를 해결할 수 없다.
✨ Wrapper class
public class Sensors {
private Map sensors = new HashMap();
public Sensor getById(String id) {
return (Sensor) sensors.get(id);
}
}
- 가변 인스턴스 Map을 Sensors 안으로 숨겨버리면 문제가 해결된다.
- Sensors가 관리하므로 Generics를 쓰든 말든 문제가 되지 않는다.
- Sensors 클래스는 설계 규칙과 비지니스 규칙을 따르도록 강제성을 부여하고, 필요한 인터페이스만 제공할 수 있다.
🤚 오해하지 말자!
Map 클래스를 사용할 때마다 Wrapper class로 감싸서 캡슐화 하라는 것은 아니다.
다만 Map 인스턴스와 같은 경계 인터페이스들을 여기저기 넘기지 말라는 의미다.
공개 API의 인수로 넘기거나 반환값으로 사용하지 않고 내부적으로만 사용한다면 문제될 게 없다.
2. 경계 살피고 익히기
외부 패키지 테스트가 우리 책임은 아니지만, 우리 자신을 위해 테스트 하는 것이 바람직하다.
하지만 외부 코드를 익히기란 어렵고, 외부 코드를 통합하기도 어려우며, 두 가지를 동시에 하는 것은 두배로 어렵다.
이럴 때는 학습 테스트(TDD)를 이용해 외부 API를 호출하는 방법을 사용하라.
(요새 TDD 공부 중인데 진짜 재밌긴 하다.)
3. log4j
책에서는 log4j에 대한 코드를 몇 줄 적고, 디버깅 과정을 보여주고 있다.
나는 좀 더 실전에 맞게 스프링 부트에서 사용하는 logger, log4j, log4j2, slf4j에 대해 조사했다.
📌 Logger
private static final Logger logger = Logger.getLogger(MyApplication.class.getName());
자바에서 제공하는 라이브러리를 사용하기 때문에 외부 라이브러리 없이 사용 가능하다.
그런데 단점이 너무 많다.
- Java 1.4 시점에 이미 잘 만들어진 log4j가 있었고 널리 쓰이고 있었는데, 굳이 프로그래머들이 갈아탈 이유가 없었다.
- 다른 라이브러리에 비해 퍼포먼스가 느리다.
- custome 레벨을 만들면 메모리 누수가 발생한다.
- 타라이브러리에 비해 기능은 부족한데, 유연성도 떨어진다.
📌 Log4j
public static final Logger logger = LogManager.getLogger(MyApplication.class);
특징은 Log4j2에서 설명하는 걸로..별다른 이유가 없다면 새로운 프로젝트에선 Log4j2를 쓰면 된다.
🟡 구성
요소 | 설명 |
Logger | 출력할 메시지를 Appender에 전달 |
Appender | 전달된 로그 출력할 위치 지정 |
Layout | 로그 출력 형식 지정 |
🟡 로그 레벨
로그 레벨 | 설명 |
FATAL | 아주 심각한 에러가 발생 |
ERROR | 요청 처리 중 문제가 발생 |
WARN | 실행에는 문제가 없지만, 잠재적인 위험성 요소를 알려주는 경고 메시지 |
INFO | 상태 변경 등의 정보성 메시지 |
DEBUG | 개발 시 디버그 용도로 사용하는 메시지 |
TRACE | 디버그 레벨보다 상세한 이벤트를 나타내는 메시디 |
📌 Log4j2
- Logback처럼 필터링 기능과 자동 리로딩 지원
- thread safe 하며, 퍼포먼스 최적화, 여러 종류의 appender 지원하며, 명확한 기준의 레벨을 가진다.
- Mutli Thread 환경에서 Async Logger의 경우 다른 Loggin Framework에 비해 처리량이 훨씬 많고, 대기 시간이 짧다.
- Java 8부터 람다식을 지원하고 Lazy Evalutation을 지원한다.
이외에도 뭐가 엄청 많다.
logger.info("new transaction has been created");
logger.debug("processing {0} entries in loop", list.size());
사용은 대충 이런 식으로 👀
📌 Slf4j (Simple Logging Faced for Java)
- 자체 Logging Framework가 아니라 logger의 추상체
- 즉, 설정에 따라 다른 Logging Framework의 Interface 역할을 수행하는 wrapper 역할을 한다.
- Application code → slf4j(wrapper) → Logging Framework(log4j2, logback, log4j 등)
- 여러 Logging Framework를 쉽게 갈아탈 수 있어서 요새 많이 쓴다고 한다.
4. 학습 테스트는 공짜 이상이다
학습 테스트는 투자하는 노력보다 얻는 성과가 크다.
일단 프로그램이 '무엇'을 해야하는지 초점을 맞추어 테스트를 돌린 후, 결과에 차이가 있는지 비교한다.
설령 새 버전이 나오더라도 작성한 테스트 케이스가 이전 케이스들을 다시 빠르게 확인해준다.
5. 아직 존재하지 않는 코드를 사용하기
지식이 경계를 너머 미치지 못하는 코드 영역도 있을 수 있다.
즉, 아는 코드와 모르는 코드를 분리하는 경계 유형이 존재할 수도 있다는 뜻이다.
(책에 나온 예시를 나름 풀어서 설명해보자면)
무선통신 시스템에 들어갈 소프트웨어를 개발해야 하는데, Transmitter(송신기) 시스템에 대한 지식도 없었고, Transmitter 시스템을 책임진 사람들은 인터페이스도 정의하지 못했다.
그래서 Transmitter가 필요로 하는 기능을 먼저 정의했다.
"지정한 주파수(freq)를 이용해 해당 스트림(stream)에서 들어오는 자료를 아날로그 신호(signal)로 전송하라"
<interface> Transmitter에는 transmit(freq, stream)이라는 메서드가 필요할 것이다.
그리고 아직 상대측에서 정의해주지 않고, 이해하지도 못하는 Transmitter 인터페이스와 해당 인터페이스가 필요로 하는 기능을 제공하는 CommunicationsController를 분리해낼 수 있다.
CommunicationsController의 기능은 너무나 명확하기에 이를 구현하는 동안 Transmitter 인터페이스에 대한 구현 작업이 끝나면, Apdater 패턴으로 API 사용을 캡슐화할 수 있다.
또한 FakeTransmitter 클래스를 사용하여 CommunicationsController 클래스 테스트에도 용이하다.
6. 깨끗한 경계
외부 패키지 의존성이 높은 건 별로 좋은 일이 아니다.
아니 툭하면 Deprecated 걸어버리는 양아치들 패키지를 대체 왜
볼 때마다 웃긴 스택 오버 플로우 답변 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
여튼 웬만하면 외부 패키지 호출을 줄이고, 새로운 클래스로 감싸거나 Adapter 패턴을 사용하는 게 좋다.
어느 방법이든 가독성이 높아지며, 경계 인터페이스를 사용하는 일관성도 높아지고, 외부 패키지가 변해도 변경할 코드가 줄어든다.