Search
Duplicate
🎆

8장. 경계

: 애플리케이션을 개발할 때, 모든 코드를 직접 개발하는 경우는 드물다. 우리는 이미 안정성과 성능에서 입증된 라이브러리들을 애용한다. 때로는 사내 다른 팀이 제공하는 라이브러리를 사용할 수도 있다.
: 어떤 식으로든 외부 코드들은 우리의 코드들과 함께하기 마련이다. 우리는 이런 외부 코드들과 내부 코드 간의 경계를 잘 설정하고 안정적으로 동작할 수 있게 해야한다.

외부 코드 사용하기

: 인터페이스(패키지, 프레임워크) 제공자와 사용자 간에는 간극이 존재할 수 밖에 없다.
: 인터페이스 제공자는 사용량을 높이기 위해 범용성 있게 개발하려하고 사용자는 본인의 프로젝트에 맞게 사용하려하기 때문이다.
: 이런 이유로 시스템 경계(외부 코드와 내부 코드)에서 문제가 생길 소지가 있다.
⇒ 아마 외부 코드의 변경이 내부 코드를 변경시키는 등의 영향이리라
: 저자는 여기서 하나의 예를 들어주었다.
java.util.Map
: 프로그램에서 빈번히 return new HashMap<User>();doSomething(Map users);와 같은 작업들이 이뤄진다고 생각해보자.
: 2번째의 경우, users 객체에서 특정 User를 꺼내려면 다음과 같이 작성해주어야 한다. User user = (User) users.get(userId);
: 이론 코드는 한 번이 아니라 여러 차례 나온다. 즉, Map이 반환하는 객체의 타입을 우리가 사용하고자 하는 타입으로 변경할 책임이 Map 객체를 사용하는 코드에 있다는 것이다.
1.
대신 다음과 같이 제네릭스를 사용하면 코드 가독성이 크게 높아진다. Map<String, User> users = new HashMap<User>();
2.
더 좋은 해결책은 외부 코드를 한번 래핑하는 것이다.
public class Sensors { private Map sensors = new HashMap(); public Sensor getById(String id) { return (Sensor) sensors.get(id); } } Sensors sensors = new Sensors(); Sensor s = sensors.getById(sensorId)
Java
복사
기능을 제한할 수 있으므로 오용하기 어렵고 비즈니스 규칙만 따를 수 있게 한다.
모든 기능들을 캡슐화하라는 것이 아닌, 반환값, 파라미터로 사용되는 객체들을 캡슐화하라는 것이다.

경계 살피고 익히기

: 라이브러리의 도움을 받으면 우리는 실제로 구현해야할 코드의 양이 줄어들고, 더 빠르게 출시에 가까워질 수 있다.
: 외부에서 가져온 패키지나 라이브러리를 사용하고 싶다면 어떻게 해야할까?
: 공식문서를 뒤지거나 남들의 사용 예를 훑어보면서 익히는 것도 좋지만 외부 패키지나 라이브러리가 어떻게 동작하는 지 테스트해보고 사용하자.
: 곧바로 이를 내부 코드에 삽입하기보다 간단한 테스트 케이스를 작성해서 외부 코드를 익히란 뜻이다. 이를 짐 뉴커크는 학습 테스트라고 부른다.

log4j 익히기

: 로깅 기능을 직접 구현하는 대신, 아파치가 제공해주는 log4j를 사용해보자.
: 다음은 hello를 출력해주는 학습 테스트 케이스다.
1.
먼저 다음과 같은 임의의 구조로 실행을 해보자.
@Test public void testLogCreate() { Logger logger = Logger.getLogger("MyLogger"); logger.info("hello"); }
Java
복사
: 테스트를 실행해보니 Appender라는 뭔가가 필요하다는 오류가 발생한다.
2.
ConsoleAppender를 생성한 후 테스트 케이스를 다시 돌려보자.
@Test public void testLogAddAppender() { Logger logger = Logger.getLogger("MyLogger"); ConsoleAppender appender = new ConsoleAppender(); logger.addAppender(appender); logger.info("hello"); }
Java
복사
: 이번엔 Appender에 출력 스트림이 없다는 오류가 발생한다. 구글을 검색해 해결방안을 확인한 후 다음과 같이 시도해본다.
3.
AppenderStream을 넘겨준다.
@Test public void testLogAddAppender() { Logger logger = Logger.getLogger("MyLogger"); logger.removeAllAppenders(); logger.addAppender(new ConsoleAppender(new PatternLayout("%p %t %m%n"), ConsoleAppender.SYSTEM_OUT)); logger.info("hello"); }
Java
복사
: 이제 제대로 돌아간다.
: 여기서 만족하지 말고 뭔가 이해가지 않는 것들을 확인해보자.
ConsoleAppender.SYSTEM_OUT 인수를 제거했더니 문제 없이 돌아간다.
PatternLayout을 제거했더니 또 다시 출력 스트림이 없다는 오류가 발생한다. 뭔가 이상하다.
문서를 찾아보니 기본 ConsoleAppender 생성자는 설정되지 않은 상태라고 한다. 뭔가 이상하다.
: 좀 더 구글을 뒤지고 문서를 읽어서 다음과 같은 코드를 얻게 되었다.
4.
완성
public class LogTest { private Logger logger; @Before public void initialize() { logger = Logger.getLogger("logger"); logger.removeAllAppenders(); Logger.getRootLogger().removeAllAppenders(); } @Test public void basicLogger() { BasicConfigurator.configure(); logger.info("basicLogger"); } @Test public void addAppenderWithStream() { logger.addAppender(new ConsoleAppender(new PatternLayOut("%p %t %m%n"), ConsoleAppender.SYSTEM_OUT)); logger.info("addAppenderWithStream"); } @Test public void addAppenderWithoutStream() { logger.addAppender(new ConsoleAppender(new PatternLayOut("%p %t %m%n"))); logger.info("addAppenderWithoutStream"); } }
Java
복사

학습 테스트는 공짜보다 더 좋다.

: 외부 라이브러리를 사용하려면 해당 라이브러리에 대한 학습은 필수적이다. 또한 학습 테스트는 필요한 지식만 효과적으로 파악할 수 있게 하는 방법이다.
: 새로운 버전으로 업데이트를 진행했을 때, 여전히 호환되는 지 확인하기 좋다. → 테스트코드의 장점..!

아직 존재하지 않는 코드를 의존하기

: 다른 팀에서 구현하거나 인터페이스가 정의되지 않은 기능의 경우, 자체적 또는 논의 하에 인터페이스를 정의해서 사용한다.
: 추상적이던 코드가 구체적으로 변경되면 어댑터 패턴을 사용해서 간극을 메운다.
→ 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환하는 어댑터를 사용하는 패턴

깨끗한 경계

: 경계에선 변경이 일어날 확률이 높다. → 내부 코드가 변경되든 외부 코드가 변경되는 변경사항에 따른 변경이다.
: 변경 가능성이 높은 코드에 너무 많은 비용을 쏟거나 구조를 대충 설계하는 것은 좋지 않은 행위
: 의존성이나 결합도를 높여야할 대상은 우리 코드다.
: 특정한 기능을 기대하는 바에 대해서는 테스트 코드로 설명하거나 인터페이스로 설명하자.