본문 바로가기

개발(합니다)/컴퓨터 일반&CS

[CS] OOP-객체지향 생활 체조 9가지

반응형

객체지향 생활체조란

Object Calishenics(객체지향 생활체조)Jeff Bay가 발명한 9가지 규칙의 집합으로 공식화 된 프로그래밍 훈련입니다.
Object는 객체지향 프로그래밍을 의미하고
Calisthenics는 체조의 운동을 의미합니다.

객체지향 생활 체조는 규칙을 지키면 자연스럽게 코드 작성 방식이 변경되고
항상 모든 규칙을 지켜야 한다는 의미는 아니며,
규칙들과 균형을 맞추어 규칙과 유사하다고 느껴지는 경우에 일부를 적용합니다.

규칙들은 가독성 좋고, 테스트 가능하며, 이해하기 쉬운 코드를 만드는데 초점을 맞추고 있습니다.

아래 사이트에서 관련 내용을 확인할 수 있습니다.
https://williamdurand.fr/2013/06/03/object-calisthenics/

규칙

  1. 한 메서드에 오직 한 단계의 들여쓰라
  2. else 키워드를 사용하지 말라
  3. 모든 원시값과 문자열을 포장하라
  4. 일급 컬렉션을 사용하라
  5. 한 줄에 점은 오직 하나만 사용하라
  6. 축약하지 말라
  7. 클래스는 50줄 이하로 유지하라
  8. 인스턴스 변수는 2개 이하로 유지하라
  9. Getter, Setter를 사용하지 말라

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

들여쓰기가 깊어지게 되면 메서드들로 나누는 방법으로 가독성을 높일 수 있습니다.
마틴 파울러는 리팩토링 책에서 메소드 추출 패턴을 제시하기도 합니다.

// ===적용 전===
class Board {
    public String board() {
        StringBuilder buf = new StringBuilder();

        // 0
        for (int i = 0; i < 10; i++) {
            // 1
            for (int j = 0; j < 10; j++) {
                // 2
                buf.append(data[i][j]);
            }
            buf.append("\n");
        }

        return buf.toString();
    }
}

// ===적용 후===

class Board {
    public String board() {
        StringBuilder buf = new StringBuilder();

        collectRows(buf);

        return buf.toString();
    }

    private void collectRows(StringBuilder buf) {
        for (int i = 0; i < 10; i++) {
            collectRow(buf, i);
        }
    }

    private void collectRow(StringBuilder buf, int row) {
        for (int i = 0; i < 10; i++) {
            buf.append(data[row][i]);
        }

        buf.append("\n");
    }
}

2. else 키워드를 사용하지 마라

분기가 많아질수록 유지보수 하기 어려운 코드를 만들어 내게 됩니다.
else를 쉽게 없애는 방법은 early return 기법을 적용합니다.
예외처리를 해야 하는 경우에는 방어적 프로그램으로 예외를 먼저 처리하고 마지막에 실행시키 방식으로 합니다.

// === 적용 전===
public void login(String username, String password) {
    if (userRepository.isValid(username, password)) {
        redirect("homepage");
    } else {
        addFlash("error", "Bad credentials");

        redirect("login");
    }
}

// === 적용 후 ===
public void login(String username, String password) {
    if (userRepository.isValid(username, password)) {
        return redirect("homepage");
    }

    addFlash("error", "Bad credentials");

    return redirect("login");
}

// ===추가 개선 적용 후===
public void login(String username, String password) {
    String redirectRoute = "homepage";

    if (!userRepository.isValid(username, password)) {
        addFlash("error", "Bad credentials");
        redirectRoute = "login";
    }

    redirect(redirectRoute);
}

3. 모든 원시값과 문자열을 포장하라

Java에서 byte, short, int, long, float, double, boolean, char의 원시타입과String` 클래스까지 포장을 의미합니다.

값을 포장하는 의미는 소스 내에서 하드코딩 하지 말자는거 의미합니다.
의미가 있는 값을 객체로 포장하어 값객체를 만들어 사용합니다.

  • 의미있는 값은 상수화해서 필드로 만듭니다.
    • const int SELL_COUNT = 100;
  • 특정 기능이 있는 원시형은 값객체로 만듭니다.
    • class Person { Money money; }

4. 일급 컬렉션을 사용하라

모든 컬렉션을 일급 컬렉션으로 포장하는걸 의미하며 Object나 Array를 class로 포장함을 의미합니다.

일급 컬렉션을 적용하면 세 가지 이점을 얻을 수 있습니다.

  1. 비즈니스에 맞는 이름을 지을수 있고 종속적인 자료구조를 만들수 있습니다. : 예시로 Stack이 있습니다.
  2. 컬렉션을 불변성이 보장된 불변객체로 만들 수 있습니다 : private를 사용하고 setter를 노출하지 않아 기존 컬렉션 메서드를 이용해 임의로 컬렉션 내용을 변경할 수 없습니다.
  3. 상태와 행위를 한 곳에서 관리할 수 있습니다. : 응집도를 높이고 결합도를 낮출 수 있습니다.

5. 한 줄에 점은 오직 하나만 사용하라

객체에 접근하기 위해서는 .(점)을 이용하는데 너무 깊이 들어가게 되는 경우를 지양하라는 의미입니다.
디미터의 법칙처럼 자기 자신이나 1차 관계에 있는 친구에게만 이야기하라는 비유와 같습니다.

확정에는 열려 있고 수정에는 닫혀 있는 개방-폐쇄 원칙을 지킬 수 있다.

// ===적용 전===
class Location {
    public Piece current;
}
class Piece {
    public String representation;
}
class Board {
    public String boardRepresentation() {
        StringBuilder buf = new StringBuilder();

        for (Location loc : squares()) {
            buf.append(loc.current.representation.substring(0, 1)); // 문제
        }

        return buf.toString();
    }
}

// ===적용 후===
class Location {
    private Piece current;

    public void addTo(StringBuilder buf) {
        current.addTo(buf);
    }
}

class Piece {
    private String representation;

    public String character() {
        return representation.substring(0, 1);
    }

    public void addTo(StringBuilder buf) {
        buf.append(character());
    }
}
class Board {
    public String boardRepresentation() {
        StringBuilder buf = new StringBuilder();

        for (Location location : squares()) {
            location.addTo(buf); // 해결
        }

        return buf.toString();
    }
}

6. 축약하지 말라

줄여서 약어로 쓰지 말라는 의미입니다.
축약된 의미를 다시 풀어서 확인하는데 시간과 비용이 소요되는 문제를 방지하기 위함입니다.
그리고 메서드명이 너무 길어지지 않는지 의문이 든다면 단일 책임 원칙을 위반하지는 않는지 검토해봐야 합니다.
대부분 하나의 메서드는 하나의 기능을 한다면 메서드명이 짧은 메서드명을 가집니다.

7. 클래스는 50줄 이하로 유지하라

클래스는 50줄 이하로 유지
메서드는 10줄 이하로 유지
패키지는 10개 이하로 유지

공백을 포함하는 50줄이며 정말 공백이 필요한지를 생각해봐야 합니다.
OOP에서는 클래스든 메서드든 패키지든 단일 책임원칙을 위반하지 않았는지 확인해볼 필요가 있습니다.

8. 인스턴스 변수는 2개 이하로 유지하라

반응형