객체지향 생활 체조 원칙 9가지 (추가 내용)

smj_716·2025년 1월 24일

Java-study / live-study

목록 보기
5/16

✏️ 객체지향 생활 체조 원칙

  1. 한 메서드에 오직 한 단계의 들여쓰기만 한다.
  2. else 표현을 사용하지 않는다.
  3. 모든 원시 값과 문자열을 포장한다.
  4. 한 줄에 점을 하나만 사용한다.
  5. 이름을 줄여 쓰지 않는다.
  6. 모든 엔티티를 작게 유지한다.
  7. 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  8. 일급 컬렉션을 사용한다.
  9. getter/setter/property를 쓰지 않는다.

1. 한 메서드에 오직 한 단계의 들여쓰기만 한다

한 메서드에 들여쓰기를 여러번 하는 것은 해당 메서드가 여러가지 일을 하고 있다는 것이다. 메서드는 맡은 일이 적을수록 재사용성이 높고 디버깅도 용이하다. 즉, 하나의 메서드는 하나의 일만 해야한다는 것과 유사하다.

public void process(List<Order> orders) {
    for (Order o : orders) {
        if (o.isPaid()) {
            if (o.isShipped()) {
                System.out.println("Done");
            }
        }
    }
}

중첩된 반복문과 조건문은 한 단계 들여쓰기에 어긋나기 때문에 아래와 같이 메서드를 나눈다.

public void process(List<Order> orders) {
    for (Order o : orders) {
        if (isDone(o)) {
            System.out.println("Done");
        }
    }
}

private boolean isDone(Order o) {
    return o.isPaid() && o.isShipped();
}

2. else 표현을 사용하지 않는다

신규 기능이 추가되거나 수정사항이 발생하면 기존 코드를 리팩터링 하는 것보다 분기처리 하는 조건문을 추가하는 것이 쉽다. 그래서 early return을 통해 분기 처리를 없애준다. 이 규칙은 if/else 뿐만 아니라 switch/case 구문을 포함한 분기 구문을 지양하여 코드를 간단하게 만드는 것이다.

public String check(boolean success) {
    String status = "";
    if (success) {
        status = "Good";
    } else {
        status = "Bad";
    }
    return status;
}
public String check(boolean success) {
    if (success) {
        return "Good"; // early return 
    }
    return "Bad";  
}

3. 모든 원시 값과 문자열을 포장한다

컴파일러는 원시형 변수를 이용하여 의미적으로 맞는 프로그램 작성을 안내할 수 없다. 그러나 포장한 객체를 이용한다면 컴파일러와 개발자에게 해당 값이 어떤 값이며 왜 쓰는지 정보를 전달할 수 있다. LocalDateLocalTime 같은 포장 클래스를 활용하여 날짜 연산을 쉽게 처리하는 것도 이 규칙의 예시이다.

public void save(String email) {
    if (!email.contains("@")) {
        throw new IllegalArgumentException("Invalid");
    }
    System.out.println("Saved");
}

또한 아래 코드처럼 행위(메서드 get())를 함께 포함할 수 있도록 해준다.

public class Email {
    private final String value;

    public Email(String value) {
        if (!value.contains("@")) {
            throw new IllegalArgumentException("Invalid");
        }
        this.value = value;
    }

    public String get() {
        return value;
    }
}

public void save(Email email) {
    System.out.println("Saved: " + email.get());
}

4. 한 줄에 점을 하나만 사용한다.

스트림 등 체이닝하는 일부를 제외하고, 코드에서 점이 둘 이상 있다면 해당 부분을 리팩토링 해야한다.

  • 디미터(Demeter)의 법칙 : "친구하고만 대화하라" , 자신 소유의 객체, 자신이 생성한 객체, 그리고 누군가 준(파라미터로) 객체에만 메시지를 보낼 것!!
  • 최소한의 지식 원칙(The Principle of Least Knowledge) : 객체가 다른 객체의 내부 구조나 동작에 대해 알 필요가 없다!!

그렇지 않으면 다른 객체와 강한 결합도를 가지게 되고 이것은 캡슐화를 어기는 것이다.

public String getCity(User u) {
    return u.getAddr().getCity().getName();
}
public String getCity(User u) {
    Addr addr = u.getAddr();
    City city = addr.getCity();
    return city.getName();
}

5. 이름을 줄여 쓰지 않는다(축약 금지)

과도한 축약은 코드의 가독성을 저해한다. 무조건 짧다고 좋은 것은 아니다.
애플 Programming API guidance 문서에도 "명확함이 간결함보다 중요하다"라고 축약을 지양한다.

func checkValidityAndSave() {
	// 유효성 검사
    // 저장
}

메서드의 이름이 너무 긴 경우는 책임을 너무 많이 가지고 있기 때문일 수도 있다. 그렇다면 책임을 나누어 각각의 메서드로 분리하는 방법도 있다.

func checkValidity() {
	// 유효성 검사
}

func save() {
	// 저장
}

6. 모든 엔티티를 작게 유지한다

  • 엔티티를 작성할 때 하나의 목적을 염두하고 설계하라는 것이다. SOLID 원칙중 단일 설계 원칙인 SRP와 연계되는 규칙이다. 엔티티가 하나의 책임만 가지고 있으면 작게 유지 된다.
  • 50줄 이상 되는 클래스 또는 10개 이상의 패키지는 지양해야한다. 만약 하나의 클래스가 100줄이 넘는다면 분명 분리할 수 있을 것이니 두려워하지 말자!
  • 패키지(하나의 목적을 달성하기 위한 연관된 클래스들의 모임)를 작게 유지하면 패키지가 진정한 정체성을 가지게 된다.

7. 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다

클래스의 인스턴스 변수를 제한하라는 것이다. 여기서 인스턴스 변수는 원시(primitive)타입이나 컬렉션과 같이 기본 또는 자료구조형의 변수를 의미한다.
도메인 요소들을 어떻게 묶고 구성할 것인지 설계에 대해 충분히 고민하여 클래스를 구성하고 리팩토링하라는 규칙이다.

원시값을 객체로 포장하여(3번 규칙) 도메인적 의미를 부여하라는 가이드라고 할 수 있다.

class Name {
    String firstName;
    String lastName;
    // ... 성과 이름에 대한 기능
}
public class Name {
    FirstName firstName;
    LastName lastName;
}
 
class FirstName {
    String name;
    // ... 이름에 대한 기능
}
 
class LastName {
    String name;
    // ... 성에 대한 기능
}

8. 일급 컬렉션을 사용한다

일급 컬렉션이란 컬렉션을 Wrapping하면서 Collection외 다른 필드를 가지고 있지 않은 클래스를 말한다.

List<String> emails = new ArrayList<>();
emails.add("test@example.com");
public class Emails {
    private final List<String> emails;

    public Emails(List<String> emails) {
        this.emails = emails;
    }

    public void add(String email) {
        emails.add(email);
    }
}

9. getter/setter/property를 쓰지 않는다

객체의 상태를 외부에 노출하지 않고 메시지를 통해 상호작용하도록 만들자는 의도이다. 객체지향 프로그래밍의 핵심 개념 중 캡슐화를 지키면서 객체에 메시지를 보내 스스로 상태에 대한 처리로직을 수행하도록 하라는 의미로 객체의 상태를 가져오는 접근자를 사용하는 것은 괜찮지만, 그 상태를 객체 바깥에서 변경하면 안된다는 것이다. 한 객체의 상태에 대한 결정은 반드시 객체 안에서 이루어져야한다.

⚠️ 주의 : 이 지침은 데이터 전달을 목적으로 하는 DTO나 프로세스 처리를 목적으로 하는 컨트롤러, 서비스 빈 클래스를 대상으로 하지 않는다.

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
public class User {
    private String name;

    public void rename(String newName) {
        this.name = newName;
    }
}

0개의 댓글