Skip to content
dnwls16071 edited this page Sep 6, 2025 · 14 revisions

📚 추상(抽象)

  • 가독성(Readability) = 글이 잘 읽힌다 = 이해가 잘 된다 = 유지보수 하기 수월하다 = 시간과 자원이 절약된다.

추상(abstract) vs 구체(concrete)

  • 추상 = 특정 측면을 가려내고 불필요한 부분을 버린다.

네이밍(naming)🤔

  • ✅️이름을 짓는다는 행위는 추상적 사고를 기반으로 한다.
    • 표현하고자 하는 구체에서 정말 중요한 핵심 개념만을 추출해 잘 드러내는 표현
    • 우리 도메인의 문맥 안에서 이해되는 용어
  • ✅️단수와 복수를 구분하자.
    • 끝에 -(e)s를 붙여 어떤 데이터가 단수인지 복수인지를 나타내는 것만으로도 읽는 이에게 중요한 정보를 같이 전달할 수 있다.
  • ✅️이름을 줄이지 않는다.
    • 가독성을 희생해 얻는 부분으로 잃는 것에 비해 얻는 것이 적다.
  • ✅️은어/방언 사용하지 않는다.
    • 도메인 용어를 먼저 정의하는 과정이 먼저 필요할 수도 있다. 모두가 아는 것으로 사용하는 것이 좋다.

메서드 추상화(method abstraction)🤔

  • ✅️한 메서드의 주제는 반드시 하나다.
    • 메서드의 이름으로 구체적인 내용을 추상화한다.
  • ✅️생략할 정보와 의미를 부여하고 드러낼 정보를 구분한다.

메서드 선언부🤔

  • ✅️void 대신 반환할 만한 값이 있는지 고민해본다. -> 반환값이 있다면 테스트가 용이하기 때문에

매직 넘버, 매직 스트링🤔

  • ✅️매직 넘버/매직 스트링 : 의미를 갖고 있으나 상수로 추출되지 않은 숫자, 문자열 등
    • 이런 상수 추출을 통해 이름을 짓고 의미를 부여함으로써 기독성과 유지보수성을 높인다.

📚 논리/사고의 흐름

Early Return

  • Early Return으로 else의 사용을 지양하자.
// bad❌
int get_string_type(char* string) {
    int ret = 0;
    if (is_rule_1(string)) {
        ret = 1;
    } else {
        if (is_rule_2(string)) {
            ret = 2;
        } else {
            ret = 0;
        }
    }

    return ret;
}

// good⭕
int is_valid(char* string) {
    if (is_rule_1(string))
        return 1;
    if (is_rule_2(string))
        return 2;

    return 0;
}

공백 라인의 의미

  • 복잡한 로직의 끊어 읽기가 필요한 경우

부정어를 대하는 자세

// bad❌ -> 2번의 사고를 필요로 한다.
if (!isLeftDirection()) {
   doSomething();
}

// good⭕ -> 1번이면 끝
if (isNotLeftDirection()) {
  doSomething();
}
  • 부정어구를 쓰지 않아도 되는 상황인가를 체크한다.
  • 부정어 의미를 담고 있는 다른 단어가 존재하는지 고민하고 직접 부정어구로 메서드명을 구성한다.
    • 부정 연산자(!)의 가독성 저해

해피 케이스와 예외 처리

  • 예외가 발생할 가능성을 낮추는 것에 초점을 맞춰야 한다.
  • 의도한 예외와 의도치않은 예외를 구분한다.
    • 사용자에게 보여줄 예외와 개발자가 보고 처리해야 할 예외를 구분한다.

Null

  • NPE를 방지하는 방향으로 경각심을 가져야 한다.
  • 메서드 설계 시 return null을 자제한다. 만약 어렵다면 Optional 사용을 고려해본다.
  • Optional
    • Optional은 비싼 객체이다. 꼭 필요한 상황에서 반환 타입에 사용한다.
    • Optional은 절대로 파라미터로 받지 않도록 한다. 분기 케이스를 따져야하기 때문이다.
    • Optional을 반환받았다면 빠르게 해소한다.
    • orElse(), orElseGet(), orElseThrow() 차이를 숙지하고 사용하도록 한다.

1. orElseThrow() : 값이 없는 것을 '예외'로 처리할 때

  • Optional이 비어있을 경우, 지정된 예외(Exception)를 발생시킨다.

2. orElseGet() : 값이 없을 때 '대체 값을 동적으로 생성'해야 할 때

  • Optional이 비어있을 경우에만, 파라미터로 받은 Supplier (람다식 또는 메서드 레퍼런스)를 실행하여 그 결과값을 반환한다.
    • 대체할 기본값이 새로운 객체 생성을 필요로 할 때
    • 대체할 값을 가져오기 위해 다른 메서드를 호출하거나 복잡한 연산이 필요할 때

3. orElse() : 값이 없을 때 '이미 만들어진 상수 값'을 사용할 때

  • Optional이 비어있을 경우, 파라미터로 받은 값을 그대로 반환한다.
  • 불필요한 연산 : orElse() 안에 객체 생성이나 메서드 호출 코드를 넣으면, Optional에 값이 존재해서 그 결과가 필요 없음에도 불구하고 무조건 실행되어 성능 저하의 원인이 될 수 있다.

📚 객체 지향 패러다임

  • 객체(Object) = 데이터 + 코드
  • 객체 추상화 필요 단계
    • 비공개 필드(데이터) + 비공개 로직(코드)
    • 공개 메서드 선언부를 통해 외부 세계와 소통
    • 객체 책임이 나뉨에 따라 객체 간 협력이 발생

객체를 만들 때 주의할 점

  • ✅️1개의 관심사로 명확하게 책임이 정의되었는가?
  • ✅️생성자 혹은 정적 팩토리 메서드에서 유효성 검증이 가능하다.
    • 도메인에 특화된 검증 로직이 들어갈 수 있다.
class Money {
    private long value;

    public Money(long value) {
        if (value < 0) {
            throw new IllegalArgumentException("돈은 0원 이상이어야 합니다.");
        }
        this.value = value;
    }
}
  • ✅️setter 사용은 자제한다.
    • 데이터는 불변이 최고다. 변하는 데이터라도 객체가 핸들링할 수 있어야 한다.
    • 객체 내부에서 외부 세계의 개입 없이 자체적인 변경/가공으로 처리할 수 있는가를 확인한다.
    • 만약 외부에서 가지고 있는 데이터로 데이터 변경 요청을 하는 경우 set~보다는 update~와 같이 의도를 드러내는 네이밍을 고려한다.
  • ✅️getter도 처음엔 사용을 자제한다. 필요한 경우에만 추가하도록 한다.
    • 외부에서 객체 내의 데이터가 필요하다고 getter를 남발하는 것은 무례한 행동이다. 객체에 메시지를 요청한다.
Person person = new Person();

// bad❌
if (person.getWallet().getID().findAge() >= 19) {
  pass();
}

// good⭕
if (person.isAgeGraterThanOrEqualTo(19) {
  pass();
}
  • ✅️객체의 필드 수를 줄여라.
    • 불필요한 데이터가 많을수록 복잡도가 높아지고 대응할 변화가 많아진다.
    • 메서드 기능으로도 제공이 가능하면 OK, 단 미리 가공하는 것이 성능 상의 이점이 있다면 필드로 가지고 있는 것도 좋을 순 있다.
class Bill {
    private final List<Menu> menus;
    private final long totalPrice;  // 미리 가지고 있을수도 있는 값

    // 메서드 기능으로 충분히 계산할 수 있는 값
    public long calculateTotalPrice() {
        return this.menus.stream()
                   .mapToLong(Menu::getPrice)
                   .sum();
    }
}

SOLID - SRP(단일 책임 원칙)

  • ✅️하나의 클래스는 단 한 가지 변경 이유만을 가져야 한다.
    • 객체가 가진 공개 메서드, 필드, 상수 등은 해당 객체의 단일 책임에 의해서만 변경이 되어야 한다.

SOLID - OCP(개방 폐쇄 원칙)

  • ✅️확장에는 열려 있고, 수정에는 닫혀 있어야 한다.
    • 기존 코드 변경없이, 시스템 기능을 확장할 수 있어야 한다.
    • 추상화와 다형성을 활용해 OCP를 지킬 수 있다. 구체에 의존하기보다 인터페이스로 분리해야 한다.

SOLID - LSP(리스코프 치환 원칙)

  • **✅️ **

SOLID - ISP(인터페이스 분리 원칙)

  • **✅️ **

SOLID - DIP(의존 관계 주입 원칙)

  • **✅️ **

📚 객체 지향 적용하기

📚 코드를 다듬어보자

📚 소소한 내용을 정리하자

📖 Java

📖 Kotlin

📖 Coroutine

📖 Spring

📖 Spring Security

📖 Spring Batch

📖 Reactive Programming

📖 Database

📖 MySQL

📖 Redis

📖 JPA

📖 QueryDsl

📖 MSA

📖 Kafka

📖 Apache Flink

  • [Apache Flink - Apache Flink Architecture]
  • [Apache Flink - Stream Processing]
  • [Apache Flink - Data Stream API & Window]
  • [Apache Flink - State Management]

📖 HTTP

📖 AWS

📖 Docker

📖 Kubernetes

📖 CI/CD

📖 Nginx

📖 Monitoring🥈

  • [Monitoring - Log Concept]
  • [Monitoring - Log Level & Filter]
  • [Monitoring - Logback]
  • [Monitoring - Log Collection with ELK Stack]
  • [Monitoring - Log Monitoring with Kibana]
  • [Monitoring - Building a Monitoring System with Spring Boot Actuator]
  • [Monitoring - Server Monitoring with Prometheus and Grafana with Discord Alerts]

📖 Test

📖 Effective Java 3/E

📖 Kotlin Academy - Effective Kotlin

📖 Kotlin Academy - 핵심편

📖 스프링으로 시작하는 리액티브 프로그래밍

📖 가상 면접 사례로 배우는 대규모 시스템 설계 기초 1

📖 가상 면접 사례로 배우는 대규모 시스템 설계 기초 2

📖 Clean Code

📖 리팩토링 2판

📖 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식

📖 GraphQL

Clone this wiki locally