[클린코드] 6장 객체와 자료 구조

wlsh44·2022년 10월 3일
0

클린코드

목록 보기
8/8

왜 이 주제가 여기 있지라는 생각을 할 수도 있지만 이 장 이후로도 계속 객체 지향과 관련된 코드 작성법이 나오기 때문에 간단하게 다루는 것 같다.

자료 추상화

@Getter
@AllArgsConstructor
public class Geometry {

	private GeometryType type;
    private double height;
    private double width;
}

public interface Geometry {
	double getArea();
	double getRound();
    ...
}

도형에 대한 구체 클래스와 인터페이스가 존재한다.

구체 클래스의 어떤 도형인지 알기 위해 항상 객체 생성시에 선언을 해줘야 하고 추후에 어떤 도형인지 알기 위해 getType()을 호출해야 한다.
또한 넓이나 둘래를 구하기 위해서는 heightwidth를 가져와 외부에서 처리해야 한다.
즉, 구현이 외부에 노출된다.

하지만 인터페이스로 추상화를 하고 구체 클래스에서 넓이와 둘래를 계산해서 외부에 제공한다면 사용자는 구현을 몰라도 원하는 데이터를 얻을 수 있다.

추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.

아무리 인터페이스로 추상화 했다고 하더라도 인터페이스 자체가 추상적이지 않다면 추상화에 실패했다고 볼 수 있다.

다음 예를 보자.

public interface Geometry {
	double getHeight();
    double getWidth();
    double getArea();
	double getPerimeter();
    ...
}

얼핏 보면 크게 문제 없어 보이기도 한다.
하지만 구체 클래스로 원을 구현하려고 하면 문제가 생긴다.
원을 구현할 때 필요한 반지름은 height도 아니고 width도 아니기 때문이다.
그 외에 타원이나 다른 모양의 도형도 구현할 수 없게 된다.

그렇기 때문에 추상화 과정에서 조회/설정 함수는 추상화가 이루어지면 안 된다.
이 외에도 특정 클래스의 구체적인 개념이 들어가서는 안 된다.

자료/객체 비대칭

객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.
자료 구조는 자료를 그대로 공개하며 별다른 함수를 제공하지 않는다.

책에서 나온 객체와 자료 구조의 정의다.

객체에 대해서는 위의 Geometry를 통해 간단하게 다뤘다.
자료 구조는 Dto를 예로 들 수 있다.

@Getter
@AllArgsConstructor
public class SomeDto {
	private String data1;
    private String data2;
}

Dto는 기본적으로 데이터를 전달하는 클래스이므로 특별한 함수를 제공하지 않으며 조회의 기능만 제공한다.

이 외에도 객체와 자료 구조는 반대의 특징을 갖고 있다.

(자료 구조를 사용하는) 절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.
반면, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.

새 함수를 추가하고 싶을 때 만약 절차적인 코드를 사용하는 클래스가 있다면 단순히 함수를 추가하면 된다.
하지만 객체 지향 코드는 interface부터 모든 구체 클래스를 전부 고쳐야 한다.
만약 모든 구체 클래스에게 필요한 함수라면 상관 없지만 특정 클래스에서만 필요한 함수라면 곤란한 상황을 겪게 된다.

디미터 법칙

디미터 법칙이란 객체는 자료를 숨기고 함수를 공개해야 한다 라는 의미를 갖고 있다.
객체 지향에서 결합도를 낮춰야 한다는 의미로도 볼 수 있다.

기차 충돌

이 디미터 법칙에서 가장 잘 알려진 부분이 기차 충돌 이다.

우선 기차 충돌이란 아래 예시처럼 .가 계속 나오는 코드를 말한다.

String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

이런 코드는 조잡하게 여겨지기 때문에 피해야 한다.


디미터 법칙과 기차 충돌에 관해 하고 싶은 말이 많은데 줄인 이유는 기차 충돌과 디미터 법칙은 이제 다른 개념으로 분리해야 된다고 생각하기 때문이다.

우선 위의 코드를 기차 코드 관점에서 해결하면 다음과 같은 코드가 나온다.

Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
String outputDir = scratchDir.getAbsolutePath();

이 코드는 분명 기차 코드는 아니다.
하지만 이 코드가 디미터 법칙을 해결한 코드일까?

다시 한 번 디미터 법칙을 상기해보자.

객체는 자료를 숨기고 함수를 공개해야 한다

현재 이 코드를 실행하고 있는 객체와 위 객체들은 아주 높은 결합도를 갖고 있다.
그렇기 때문에 디미터 법칙을 해결한 코드가 아니라고 할 수 있다.

ctxt.createScratchFileStream(classFileName);

public class Context {
	
    private Options opts;

	public BufferedOutputStream createScratchFileStream(String classFileName) {
    	String outputDir = getOutputDir();
        ...
        return bufferedOutputStream;
    }
    
    private String getOutputDir() {
    	return opts.getScratchDir().getAbsolutePath();
    }
}

디미터 법칙을 지키는 코드는 위와 같은 형태가 될 수 있다.
Context 객체의 내부 데이터를 외부에 노출시키지 않고, outputDir이 필요한 목적을 파악하고 그 목적에 해당되는 객체를 반환한다.
즉, '객체 지향의 사실과 오해' 책에서 말하는 메세지를 전달하는, 책에서 말하는 구조체를 감싸는 방식이다.

그렇게 되면 결합도를 높이던 코드는 private 메서드로 감싸져 내부에서만 사용되고 외부 객체와 결합도를 낮출 수 있다.

하지만 여전히 getOutputDir()에서 기차 코드는 존재함을 볼 수 있다.
그렇기 때문에 기차 코드와 디미터 법칙은 분리되는 개념이라고 생각된다.

그리고 기차 코드에 대한 인식이 점점 바뀌고 있다는 생각이 든다.
현재 각광받고 있는 함수형 패러다임도 기차 코드임을 알 수 있다.
Java의 Stream 또한 마찬가지이다.

기차 코드가 나쁘게 인식되는 경우는 디미터 법칙을 지키지 않은 기차 코드이며, 기차 코드의 각 함수들이 자신의 메세지, 의도를 정확하게 표현하는 함수라면 좋은 코드라고 생각한다.

그렇기에 책에서 디미터 법칙의 소주제로 기차 코드를 설명한 부분이 좀 아쉬웠다. (개인적으로 처음 알게 된 개념이라 읽을 때 헷갈렸다.😂)

https://mangkyu.tistory.com/147

디미터 법칙에 대해 잘 설명 해놓은 블로그다.
이 글을 보고 이런 생각을 하게 되어서 추가했다.

느낀 점

다른 장에 비해 짧길래 가볍게 넘어갈 줄 알았는데 의외로 읽기 힘들었던 부분이었다.
처음 읽을 때 객체와 자료 구조에 대해 좀 헷갈렸는데, 이번에 블로그 정리하면서 조금 알 것 같아서 적으려다가 다시 헷갈려서 못 적었다...
결론 부분에서 기존 자료 구조에 새 동작을 추가하기는 쉽다고 나와있는데 어떤 동작을 말하는 걸까? 자료 구조가 단순 getter외에 다른 동작을 가질 수 있을까?
스터디 중 다른 분께서 Java의 MapisEmpty() size() 와 같은 추상 메서드를 제공하는데 객체인지 자료 구조인지 헷갈린다는 질문을 한 적이 있다.
이에 대한 다른 분의 답을 왜 그랬는지는 모르겠지만 적어놓지를 않았다...
하다못해 블로그 정리라도 일찍 했으면 기억했을 것 같은데 참 후회되는 순간이다. 😭

profile
정리정리

0개의 댓글