포스트

2025-08-12-TIL

2025-08-06 멘토링 리뷰

과제 피드백

  • 최근 chatGPT로 코딩테스트가 검증이 어려워져서 과제 전형으로 많이 채용하는 추세
  • 의존성이 너무 불필요한 게 많은데, 보일러 플레이트로 보일 수 있음. 의존성이 무엇을 포함하고 왜 쓰는지 명확히 할 것
  • 답변을 달면서 레퍼런스를 다는 것은 좋은데, 레퍼런스를 달게 되면 그 내용이 반드시 포함되어야 한다.
  • MDC도 깊이 학습해볼 것
  • 손자병법에 졸속행진이라는 말이 있는데, 전쟁은 완전한 준비가 되고 나서 하는 게 아니라 행군하면서 전쟁 준비를 하는 것이다. 완벽한 준비는 없다. 일단 시작하는 것이 중요하고, 하면서 계속 완성해나가면 된다.

MDC

MDC(Mapped Diagnostic Context)는 로그에 요청 ID나 사용자 ID 같은 문맥 값을 붙이기 위한 스레드별 맵이다. Logback 문서에 따르면 MDC.put()과 get()은 현재 스레드의 MDC에만 영향을 주고, 패턴에서 %X{key}로 값을 꺼내 로그에 출력한다. 깊이 볼 부분은 스레드 경계다. 자식 스레드는 부모의 MDC를 자동으로 물려받지 않으므로, 스레드 풀에 작업을 넘길 때는 넘기는 쪽에서 MDC.getCopyOfContextMap()으로 복사해 두고 작업 스레드에서 MDC.setContextMap()으로 복원해야 한다.

1
2
3
4
5
6
7
8
9
Map<String, String> context = MDC.getCopyOfContextMap();
executor.submit(() -> {
    MDC.setContextMap(context);
    try {
        process();
    } finally {
        MDC.clear();
    }
});

멘토링 질문

캡슐화란?

구현을 숨기고, 클라이언트가 접근할 수 없도록 해야 한다. 즉, 접근제어자를 통해서 내부 구현을 숨기는 것이다.

캡슐화가 있어서 코드가 클린해질 수 있다.


상속의 단점은?

강한 결합: 자식 클래스가 부모 클래스의 내부 구조와 구현에 의존하게 된다. 부모 클래스 변경 시 줄줄이 수정이 필요하다. 변경에 취약

유연성 저하: 상속 구조가 한 번 깊어지면 설계를 바꾸기 어렵다. 객체 간 관계가 고정되어서 동적으로 다른 기능을 조합하기 어렵다.

다중 상속의 복잡성: 다이아몬드 문제를 포함해서 여러 부모 클래스를 상속하면 메서드나 속성이 충돌할 수 있다.

테스트와 유지보수에 부담: 부모와 자식 간 의존이 깊으면, 단위 테스트가 어려워진다. 상속 트리 변경 시 전체 테스트를 다시 해야 보장된다.


추상화란?

객체지향에서 추상화는 현실 세계를 모델링하는 것이다.

현실 -> 모델링 -> 패턴 발견 -> 묶기/분리

면접장에서 추상화를 물어보면, 면접장의 사물을 바탕으로 직접 설계를 해보면 좋다. 이런 내용에서 시간을 많이 끌 수 있어서 면접을 무난하게 마무리할 수 있다.

대수학에서 수의 체계를 분류할 때 추상화가 도움이 된다.


로컬 파일 업로드와 S3 업로드의 추상화

로컬 파일 업로드, AWS S3 파일 업로드하는 클래스가 있다고 했을 때, 이 둘을 어떻게 추상화할 수 있을까?

위의 “패턴 발견 -> 묶기/분리” 흐름을 적용하면, 두 클래스의 공통점은 파일을 받아 어딘가에 저장하고 나중에 다시 찾을 수 있는 식별자나 경로를 돌려준다는 것이다. 차이는 저장 위치와 그 위치에 접근하는 방법뿐이다. 그래서 공통 동작을 인터페이스로 묶고, 저장 위치별 구현을 분리하는 것이 출발점이 된다. 호출하는 쪽은 인터페이스에만 의존하므로 저장소를 바꿔도 코드가 바뀌지 않는다.

1
2
3
4
5
6
7
public interface FileStorage {
    String upload(String fileName, InputStream content);
    InputStream download(String key);
}

public class LocalFileStorage implements FileStorage { /* 로컬 디스크에 저장 */ }
public class S3FileStorage implements FileStorage { /* S3 버킷에 저장 */ }

인터페이스를 깊이 고민하기

인터페이스 하나만 가지고 한 시간, 두 시간 이상 이야기할 수 있도록 깊이 생각해보고 고민해보기. 그리고 정리해보기


TDD의 효과

TDD의 효과

Martin Fowler는 TDD를 다음 기능에 대한 테스트를 먼저 쓰고, 테스트가 통과할 때까지 코드를 쓰고, 새 코드와 기존 코드를 리팩터링하는 세 단계의 반복으로 설명한다. 그가 드는 효과는 두 가지다. 테스트가 요구할 때만 기능 코드를 추가하므로 자연스럽게 스스로를 검증하는 코드(self-testing code)가 남고, 테스트를 먼저 생각하면 구현보다 인터페이스를 먼저 고민하게 된다. 가장 흔한 실패는 세 번째 단계인 리팩터링을 건너뛰는 것이다. 인터페이스를 먼저 고민하게 된다는 점은 바로 위의 인터페이스 질문과도 이어진다.


추상 클래스와 인터페이스의 차이

항목인터페이스 (Interface)추상 클래스 (Abstract Class)
목적기능 명세 제공 (계약)공통 기능 정의 (상속 기반 구현 공유)
다중 상속가능 (여러 개 구현)불가 (단일 상속만 허용)
메서드추상 메서드 위주 (default/static 허용)추상 메서드 + 일반 메서드 모두 가능
필드public static final만 가능 (상수)인스턴스 변수, 상태 보관 가능
생성자없음있음
접근 제어자모든 메서드는 public메서드/필드에 접근 제어자 사용 가능
사용 예전략, 규약, DI, 콜백 등공통 기능 묶기, 기본 구현 제공

ISP가 인터페이스와 밀접하고, LSP가 추상 클래스와 밀접하다.

다중 상속은 다이아몬드 문제가 있다.


final을 선언하면?

final은 마지막이라는 뜻 그대로 ‘더 이상 변경을 금지한다’는 뜻이다.

필드에 붙이면 값을 변경하지 못하므로 상수화, 클래스에 붙이면 하위 클래스에서의 변경을 금지하므로 상속 불가, 메소드에 붙이면 메소드를 재작성하는 것을 막으므로 오버라이딩 불가이다.

특히 클래스에 final은 버그 발생 가능성을 줄여준다. 상속을 통해서 비정상적인 하위클래스를 밀어넣어서 해킹을 할 수도 있다. 따라서 안전한 코드를 위해서 명확히 상속하지 않는 클래스는 final로 막아두는 것이 좋다. 특히 외부로 배포하는 라이브러리 성격의 코드는 더욱 이 내용이 중요시된다. 예를 들어, 자바의 String 클래스도 final로 선언되어 있다.


SRP를 잘 지켰다고 말하기 위한 기준은?

응집도가 높고, 결합도가 낮게 설계하는 게 SRP를 잘 지키는 방법 중 하나이다.

  • 클래스가 담당하는 변경 이유가 하나뿐인가?
  • 이 클래스에 영향을 미치는 이해관계자가 하나인가?
  • 모든 메서드가 같은 목적을 위해 협력하는가?
  • 기능 변경 시 수정 범위가 작고 한정적인가?
  • 클래스 설명을 한 문장으로, 접속사 없이 말할 수 있는가?

체크리스트의 두 번째 항목은 Robert C. Martin이 SRP를 다시 설명한 글과 같은 관점이다. 그는 “변경의 이유”를 결국 변경을 요청하는 사람, 즉 하나의 좁은 업무 기능을 대표하는 집단으로 보고, 같은 이유로 바뀌는 것은 모으고 다른 이유로 바뀌는 것은 떼어 놓으라고 정리한다.

참고한 자료외부 출처 3

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다