Post

실용주의 프로그래머 5장 구부러지거나 부러지거나

실용주의 프로그래머 5장 구부러지거나 부러지거나

내용

5장. 구부러지거나 부러지거나

항목 28. 결합도 줄이기

  • 연쇄 메서드 호출 문제
  • 글로벌화 - 정적인 것의 위험함
  • 상속 - 왜 클래스 상속이 위험한가?

연쇄 메서드 호출

연쇄 메서드 호출(chained method calls, 책에서 열차 사고(train wreck)라고 부른 것)은 결합도를 불필요하게 높여서 변경하기 어려운 코드 구조를 만든다는 것이 핵심이다.

  • ❌ 연쇄 메서드 호출 문제점
  1. 중간 객체들의 구조에 강하게 결합됨
  2. 객체 내부 구현이 바뀌면 호출 코드가 다 깨짐
  3. 유지보수 어려움
  4. 작은 변화에도 전체가 흔들리는 열차 사고(train wreck) 발생
  • ✔ 해결책
  1. 📌 단계를 독립된 함수로 분리 (변환 방식)
  2. 📌 “.” 한 번만 사용하라는 원칙
  3. 📌 객체 내부 구조를 숨기고 명확한 API 제공
  4. 📌 데이터 처리 파이프라인을 명시적으로 작성

항목 29. 실세계를 갖고 저글링하기

  • 유한 상태 기계 Finite State Machine. FSM

  • 감시자 패턴 Observer Pattern

    • 이벤트 발생시키는 감시대상과 이벤트에 관심있는 클라이언트 감시자로 이뤄진다 감시자가 감시대상에 등록해야하는 결함발생.
    • 감시대상이 콜백 직접호출하므로, 동기적 특성상 감시대상이 계속 기다려야한다 이 문제는 3. 게시 구독으로 해결한다
    • 스프링의 event listener가 이패턴으로 구현됨.
  • 게시 구독 pubsub

    • 감시자 패턴을 일반화한 것 + 높은 결합도, 성능 문제 해결
    • 게시자publishe와 구독자subscribe가 채널channel로 연결 채널은 별도의 코드(라이브러리, 프로세스, 분산 인프라 등)로 구현된다 게시자와 구독자 사이의 통신은 코드 밖에서 이뤄지고 비동기적일 것이다
    • 대부분 클라우드 서비스가 pubsub서비스 제공한다 추가적인 결합 없이 비동기 이벤트 처리하기 좋은 기술이다 기존코드의 수정없이 이벤트 처리 코드를 추가, 교체할 수 있다 <단점> 해당 시스템에서 현재 어떤 일이 발생하는 지 파악하기 힘들다
  • 반응형 프로그래밍과 스트림 그리고 이벤트

    • 이벤트는 발생한 개별 사건입니다.
    • 이벤트가 시간순으로 모인 것이 스트림입니다.
    • 이러한 스트림의 변화에 반응하여 코드를 작성하는 것이 반응형 프로그래밍입니다.
    • ex)
    • React (리액트) / Vue.js (뷰) / Svelte (스벨트)
      • 이러한 모던 웹 프레임워크들은 기본적으로 선언적(Declarative)이고 컴포넌트 기반의 반응형 패러다임을 채택하고 있습니다.
      • 핵심: 개발자는 데이터 상태만 변경하면 되고, 프레임워크가 알아서 변경된 부분만 효율적으로 DOM(문서 객체 모델)에 반영합니다. 이는 뷰(View)를 데이터의 함수로 간주하는 방식입니다.
      • 예시: React의 useState나 Vue의 ref, reactive 같은 기능들이 상태 변화를 감지하고 자동으로 UI를 업데이트하는 반응형 메커니즘의 핵심입니다.
    • Spring webFlux
      • Spring Framework의 반응형 웹 스택입니다. 전통적인 Spring Web MVC가 스레드 블로킹 방식인 것과 달리, WebFlux는 Non-blocking 방식으로 동작하여 적은 수의 스레드로도 많은 동시 요청을 처리할 수 있게 해줍니다.
      • 강조. 적은 수의 스레드로도 많은 동시 요청을 처리할 수 있게 해줍니다.

항목 30. 변환 프로그래밍

  • 의미 없는 중간 상태의 변수를 자꾸 만들지 말라.
  • 상태를 쌓아 놓지 말고 전달하라.
  • java 에선 스트림
    • A. 스트림의 특징
      • 선언적(Declarative): “어떻게(How)”가 아닌, “무엇을(What)” 할지 명시합니다.
      • 지연 평가(Lazy Evaluation): 최종 연산이 호출되기 전까지는 중간 연산이 실행되지 않습니다.
      • Non-destructive: 원본 데이터를 변경하지 않고 새로운 결과를 생성합니다 (불변성 유지).
1
2
3
4
5
6
7
8
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sumOfDoubledEvens = numbers.stream()  // 1. 소스
    .filter(n -> n % 2 == 0)             // 2. 중간 연산 (짝수만 필터링)
    .map(n -> n * 2)                     // 2. 중간 연산 (각 숫자를 2배로 변환)
    .reduce(0, Integer::sum);            // 3. 최종 연산 (모든 요소를 더하여 합계 도출)

// 결과: 4 + 8 + 12 + 16 + 20 = 60

항목 31. 상속세(맡은 부분)

  • 상속의 문제
    • 코드를 공유하기 위해 상속을 쓸 때의 문제
      • 부모 클래스의 변수명을 바꿔서 생기는 문제
      • 서로 다른 모듈에서 그렇게 사용한다면 문제가 생길듯, 하지만 동일한 모듈 내에서는 요즘 IDE에서는 변수명 정도는 쉽게 리팩토링 가능하다.
    • 타입을 정의하기 위해 상속을 쓸 때의 문제
      • 디테일하게 정의하다보면 트리가 엄청 상세해지는 거대해지는 문제
    • 예기치 못한 사이드 이펙트 들.
  • 상속의 대안
    • 인터페이스와 프로토콜
      • 클래스 상속대신 구현을
      • 인터페이스는 내부 변수를 정의하지 않아 위에 언급한 문제보다 훨씬 안전
    • 위임
      • 의존할 클래스를 상속이아닌 구성(내부 private 변수로 관리하고, 생성자에서 초기화)으로 하는 방안
        • 상속은 모두 의존(노출)했지만, 구성으로 바꿀 경우 필요한 부분만 의존하면 된다.
      • 서비스에 위임하라.Has-A 가 Is-A보다 낮다
    • 믹스인과 트레이트
      • 여러가지 기능을 동시에 적용해야하는 경우는? 믹스인을 쓰자.
        • 믹스인은 클래스나 객체에 공통 기능을 혼합하여 재사용성을 높이는 디자인 패턴입니다. 믹스인은 상속을 사용하지 않고도 여러 클래스에 공통 기능을 추가할 수 있게 해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface LoggingMixin {
    default void log(String message) {
        System.out.println("[LOG] " + message);
    }
}
public interface StampMixin {
    default long timestamp() {
        return System.currentTimeMillis();
    }
}

// default 메서드의 혼합으로 만드는 방식
public class LoggingStamp implements LoggingMixin, StampMixin {

    public void logWithStamp(String message) {
        log("[TS: " + timestamp() + "] " + message);
    }
}

// 구성 composition으로 만드는 방식
public class LoggingStamp {

    private final LoggingMixin logging = new LoggingMixin() {};
    private final StampMixin stamp = new StampMixin() {};

    public void log(String message) {
        logging.log(message);
    }

    public void logWithStamp(String message) {
        logging.log("[TS: " + stamp.timestamp() + "] " + message);
    }
}


  • 의도를 잘 드러내는 기법을 쓰자.
  • 정글 전체를 끌어오지 않도록 하자.

항목 32. 설정

  • 외부 설정으로 애플리케이션을 조정할 수 있게 하라.
    • 로그 레벨과 로그 저장위치
      • 신선했다. 하지만 불가능한 방식이 아니다.
      • 프로그램 실행시 로그백 설정 파일을 외부 주입.
    • 설정 정보가 바뀌기 위해서는 코드 빌드가 필요해서는 안된다
      • 음, 자주바뀌냐가 핵심일듯.
    • 지나치지 마라.
This post is licensed under CC BY 4.0 by the author.