[클린코드 완독스터디] TIL (2022.02.14)

yourjin·2022년 2월 27일
0

read.log

목록 보기
24/37
post-thumbnail

TIL (2022.02.14)

DAY 1

🔖 오늘 읽은 범위 : 8장, 경계 (p. 144 ~p.152)


😃 책에서 기억하고 싶은 내용을 써보세요.

  • 어떤 식으로든 이 외부 코드를 우리 코드에 깔끔하게 통합해야만 한다. 이 장에서는 소프트웨어 경계를 깔끔하게 처리하는 기법과 기교를 살펴본다.

    여기서 말하는 경계는 외부 코드와 우리(나)의 코드 간의 경계를 의미한다.

  • 외부 코드 사용하기
    • 인터페이스 제공자와 인터페이스 사용자 사이에는 특유의 긴장이 존재한다. 이런 긴장으로 인해 시스템 경계에서 문제가 생길 소지가 많다.
      • 패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓴다. 더 많은
        환경에서 돌아가야 더 많은 고객이 구매하니까!
      • 반면, 사용자는 자신의 요구에 집중하는 인터페이스를 바란다.
    • java.util.Map
      • Map이 제공하는 기능성과 유연성은 확실히 유용하지만 그만큼 위험도 크다.
        • (원치 않은 삭제) Map 사용자라면 누구나 Map 내용을 지울 권한이 있다.
        • (원치 않은 데이터) Map은 객체 유형을 제한하지 않는다. 마음만 먹으면 사용자는 어떤 객체 유형도 추가할 수 있다.
      • Map을 사용하는 방법
        • Map 만 사용
          Map sensors = new HashMap();
          Sensor s = (Sensor)sensors.get(sensorid);
          • Map이 반환하는 Object를 올바른 유형으로 변환할 책임은 Map을 사용하는 클라이언트에 있다.
            → 하지만 깨끗한 코드라 보기는 어렵다.
        • 제네릭스(Generics) 사용
          Map<String, Sensor> sensors = new HashMap<Sensor>( );
          Sensor s = sensors.get(sensorid) ;
          • 프로그램에서 Map<String, Sensor> 인스턴스를 여기저기로 넘긴다면, Map
            인터페이스가 변할 경우, 수정할 코드가 상당히 많아진다.
        • Map을 클래스 안에 숨김 (Best!)
          public class Sensors {
          		private Map sensors = new HashMap();
          		public sensor getById(String id) {
          				return (Sensor) sensors.get(id) ;
          		}
          		// 이하 생략
          }
          • Sensors 사용자는 제내릭스가 사용되었는지 여부에 신경 쑬 필요가 없다. 아래에서 보듯, 제네릭스의 사용 여부는 Sensors 안에서 결정한다.
            → 경계 인터페이스인 Map을 Sensors 안으로 숨긴다.
          • 또한 Sensors 클래스는 프로그램에 필요한 인터페이스만 제공한다. 그래서 코드는 이해하기는 쉽지만 오용하기는 어렵다.
      • Map과 같은 경계 인터페이스를 이용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.
  • 경계 살피고 익히기
    • 외부 패키지 테스트가 우리 책임은 아니다. 하지만 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직하다.
    • 때로는 우리 버그인지 라이브러리 버그인지 찾아내느라 오랜 디버깅으로 골치를 앓는다.
      이런 상황은 그리 놀랍지도 않다.
    • 곧바로 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히면 어떨까? 짐 뉴커크(Jim Newkirk)는 이를 학습 테스트라 부른다.
    • 학습 테스트는 프로그램에서 사용하려는 방식(=목적)대로 외부 API를 호출한다. 통제된 환경에서 API를 제대로 이해하는 지를 확인하는 셈이다.
  • log4j 익히기
    • 로깅 기능을 직접 구현하는 대신 아파치의 log4j 패키지를 사용하려 한다고 가정하자.
    • 여러 시행 착오 끝에 저자가 작성한 가장 깨끗한 코드는 다음과 같다.
      • 독자적인 로거 클래스로 캡슐화한다. 그러면 나머지 프로그램은 log4j 경계 인터페이스를 몰라도 된다.

        public class LogTest {
        		private Logger logger;
        
        		@Before
        		public void initialize( ){
        				logger = Logger.getlogger("logger");
        				logger.removeAl1Appenders( );
        				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") ;
        		}
        }
  • 학습 테스트는 공짜 이상이다
    • (학습 테스트는) 투자하는 노력보다 얻는 성과가 더 크다. 패키지 새 버전이 나온다면 학습 테스트를 돌려 차이가 있는지 확인한다.
    • 패키지 새 버전이 나올 때마다 새로운 위험이 생긴다. 새 버전이 우리 코드와 호환되지 않으면 학습 테스트가 이 사실을 곧바로 밝혀낸다.
    • 학습 테스트를 이용한 학습이 필요하든 그렇지 않든, 실제 코드와 동일한 방식으로 인터페이스를 사용하는 테스트 케이스가 필요하다.
  • 아직 존재하지 않는 코드를 사용하기
    • 경계와 관련해서 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계다.
    • 우리 세상과 저쪽 세상이 만나는 경계가 어디쯤인지 대략 이 있었다. 일하면서 가끔은 그 경계에 부딪혔다.
    • 저쪽 팀이 아직 API를 설계하지 않았으므로 구체적인 방법은 몰랐다. 그래서 우리는 구현을 나중으로 미뤘다. 이쪽 코드를 진행하고자 우리는 자체적으로 인터페이스를 정의했다.
    • 우리가 바라는 인터페이스를 구현하면 우리가 인터페이스를 전적으로 통제한다는 장점이 생긴다. 또한 코드 가독성도 높아지고 코드 의도도 분명해진다.
  • 깨끗한 경계
    • 경계에는 흥미로운 일이 많이 벌어진다. 변경이 대표적인 예이다
    • 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요하지 않다.
    • 경계에 위치하는 코드는 깔끔히 분리한다. 또한 기대치를 정의하는 테스트 케이스도 작성한다.
    • 통제가 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 훨씬 좋다. 자칫하면 오히려 외부 코드에 휘둘리고 만다.
    • 외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자. Map에서 봤듯이, 새로운 클래스로 경계를 감싸거나 아니면 ADAPTER 패턴을 사용해 우리가 원하는 인터페이스 패키지가 제공하는 인터페이스로 변환하자.

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요

  • 이번 장에서 개인적으로 가장 중요한 단어를 뽑자면 인터페이스캡슐화인 것 같다. 인터페이스는 외부 코드와의 경계를 연결하는 일종의 추상적인 감이라면, 캡슐화는 이를 외부 코드에 의존적이지 않게 구현하는 방법이라고 생각한다. 책에서 이 단어들을 설명해주지 않았다면, 나는 외부 코드와 내 코드를 분리해야하는 이유조차 몰랐을 것 같다. 짧은 챕터였지만 여기서 배운 내용을 가장 많이 활용할 수 있을 것 같다.

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 제네릭스(Generics)
    • 제네릭(Generic)은 타입의 경계를 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다.
    • 한마디로 특정(Specific) 타입을 미리 지정해주는 것이 아닌 필요에 의해 지정할 수 있도록 하는 일반(Generic) 타입이라는 것이다.
    • 제네릭(Generic)의 장점
      1. 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
      2. 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다. 즉, 관리하기가 편하다.
      3. 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.
    • 참고 링크: 자바 [JAVA] - 제네릭(Generic)의 이해
  • 학습 테스트
    • 학습 테스트는 자신이 사용할 API나 프레임워크의 기능을 테스트하면서 사용법을 익히려는 것이다. 기능에 대한 검증이 목적이 아니다.
    • 책에서는 예시가 없어서 아쉬웠는데, 아래 블로그에는 예시들이 있어 이해하는 데 도움이 되었다.
    • 참고 링크: 학습 테스트

소감 3줄 요약

  • 실제 코드와 동일한 방식으로 인터페이스를 사용하는 테스트 케이스가 필요하다. 그래야 변경에 유연할 수 있다.
  • 경계에 위치하는 코드는 깔끔히 분리한다.
  • 통제가 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 훨씬 좋다.
profile
make it mine, make it yours

0개의 댓글