[개발 도서 공부 - 📗 Clean Code] 6장: 객체와 자료 구조

Hyunjoon Choi·2023년 10월 21일
0
post-thumbnail

서론

서론부터 찔린다..

변수를 비공개로 정의하는 이유가 있다. 남들이 변수에 의존하지 않게 만들고 싶어서다. 충돌이든 변덕이든, 변수 타입이나 구현을 맘대로 바꾸고 싶어서다. 그렇다면 어째서 수많은 프로그래머가 조회 (get) 함수와 설정 (set) 함수를 당연하게 공개해 비공개 변수를 외부에 노출할까?

자료 추상화

  • 변수를 private으로 선언하더라도 각 값마다 조회 (함수)와 설정 (set) 함수를 제공한다면 구현을 외부로 노출하는 셈이다.
  • 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.
  • 인터페이스나 조회/설정 함수만으로는 추상화가 이뤄지지 않는다. 개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 심각하게 고민해야 한다.

자료/객체 비대칭

절차지향적인 코드와 객체지향적인 코드는 서로 각각의 장단점이 있다.

예를 들어, 다음과 같은 코드가 있다고 해 보자.

public class Square {
    public Point topLeft;
    public double side;
}

public class Circle {
    public Point center;
    public double radius;
}

public class Geometry {
    public final double PI = 3.141592;
}

public double area(Object shape) throws NoSuchShapeException {
    if (shape instanceof Square) {
        Square s = (Square) shape;
        return s.side * s.side;
    } else if (shape instanceof Circle) {
        Circle c = (Circle) shape;
        return PI * c.radius * c.radius;
    }
}

위의 Square, Circle 클래스는 단순히 자료만을 담고 있는 자료 구조 형태의 절차적인 클래스이며, 각 도형이 동작하는 방식은 Geometry 클래스에서 구현된다.

이때, Geometry 클래스에 둘레 길이를 구하는 perimeter() 함수를 추가한다고 해도 각 도형 클래스들은 영향을 받지 않는다.

자료 구조를 사용하는 절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 그러나 새 자료 구조를 추가할 경우, Geometry 클래스에 속한 함수를 모두 고쳐야 하는 등 번거롭다.

다음은 객체지향적으로 나타낸 코드다.

public class Circle implements Shape {
    private Point center;
    private double radius;
    public final double PI = 3.141592;
    
    public double area() {
        return PI * radius * radius;
    }
}

만약 이때, 새 함수를 작성한다면 어떻게 될까? 인터페이스의 원리에 따라 모든 구현체에 해당 함수를 전부 작성해야 한다. 반면, 새 구현 클래스를 추가하는 것은 쉽다.

객체지향적인 코드는 새로운 함수를 추가하기 어려우나 새로운 구현체를 작성하는 건 쉽다.

객체지향적인 접근법이 무조건 100% 유용한 것은 아니다. 상황에 따라 절차적인 코드가 더 적합한 경우도 있다!

디미터 법칙

낯선 사람은 경계하고 친구랑만 놀아라.

기차 충돌

아래와 같은 코드를 기차 충돌이라 한다.

final String outputDir = ctxt.getOptions().getScrachDir().getAbsolutePath();

반면 다음과 같은 경우는 충돌되지 않는다. 자료 구조 형태로 사용했기 때문이다.

final String outputDir = ctxt.options.scratchDir.absolutePath;

완벽하게 기차 충돌을 피하려면 자료 구조는 무조건 함수 없이 공개 변수만 포함, 객체는 비공개 변수와 공개 함수를 포함하도록 하면 된다. (하지만 빈과 같이 단순한 자료 구조에도 조회 함수와 설정 함수를 정의하라 요구하는 표준이 존재하기도 한다.)

잡종 구조

잡종 구조란 절반은 객체, 절반은 자료 구조인 형태를 뜻한다.

이러한 잡종 구조는 새로운 함수는 물론이고 새로운 자료 구조도 추가하기 어렵다. 만약 아래의 DTO와 같은 형태를 사용할 것이라면, 중요한 비즈니스 로직은 작성하지 않도록 하자!

구조체 감추기

객체는 자신의 내부 구조를 감춰야 한다.

객체에게는 뭔가를 하라고 말해야지 속을 드러내라고 말하면 안된다.

흔히 묻지 말고 시켜라 원칙이 이것에 해당된다.

자료 전달 객체

자료 전달 객체를 바로 표현하면 흔히 말하는 DTO (Data Transfer Object)를 뜻한다. (테코톡 공부 - DTO vs VO)

DTO는 자료 구조체로 취급한다.

자료 구조체는 일반적으로 공개 변수만 있고 함수가 없어야 하지만, 간혹 빈 (bean)은 비공개 변수를 조회/설정 함수로 조작하기도 한다.

활성 레코드

활성 레코드란, DTO 형태이면서 save나 find같은 탐색 함수도 제공하는 것을 뜻한다.

만약 활성 레코드에 비즈니스 규칙 메서드를 추가한다면 자료 구조도 아니고 객체도 아닌 잡종 구조가 나온다. 따라서 정확히 하기 위해, 활성 레코드는 자료 구조로 취급한다. 비즈니스 규칙을 담으면서 내부 자료를 숨기는 객체는 따로 생성한다!

결론

  • 시스템을 구현할 때 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다.
  • 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다.

객체지향과 절차지향을 유연하게 생각하는 훈련이 중요함을 알게 되었다. 특히, getter/setter를 쓰지 말아야겠다고 생각하면서 궁금했던 부분은 "그럼 어떻게 DTO에도 쓰지 않을 수 있는 것일까? 라는 생각이 들었었는데, 이렇게 DTO를 사용할 경우에는 자료 구조로써 취급하면 된다는 것을 배웠다.

profile
개발을 좋아하는 워커홀릭

0개의 댓글