클린코드와 리팩토링/클린코드

클린코드 - 객체와 자료구조

infitry 2024. 4. 14. 14:51
반응형

본 글은 클린코드 6장을 읽고 난 후 개인적인 생각입니다.

 

자료 추상화

변수를 비공개로 정의하는 이유는 변수에 의존하지 않게 만들기 위해서입니다.

하지만 수많은 프로그래머가 getter 혹은 setter 함수를 당연하게 공개해 비공개 변수를 외부에 노출합니다.

변수를 private으로 선언하더라도 각 값마다 getter 함수와 setter 함수로 제공한다면 구현을 외부로 노출하는 셈입니다.

이렇게 자료를 세세하게 공개하기보다는 추상적인 개념의 함수를 만드는 것이 좋다고 합니다.

저는 setter에 대해서는 이 내용에 동의하지만 getter까지 전부 추상적인 개념(클래스 내부의 어떤 비공개 인스턴스 변수를 가져오는지 모르도록 추상화)으로 일일이 변경해야 한다는 부분에서는 클래스 용도마다 차이가 있을 것으로 생각합니다.

 

자료/객체 비대칭

절차적인 코드

기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.

새로운 자료 구조를 추가하기 어렵다.

 

객체 지향 코드

기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다고 합니다.

추상화 인터페이스에 새로운 함수를 추가하기 어렵다. (모든 클래스를 고쳐야 하기 때문에)

 

예를 들어 다음과 같은 코드가 있다고 하면

절차적인 코드

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.141592653589793;
    
    public double area(Object shape) {
    	if (shape instanceof Square) {
        	Square square = (Square) shape;
            return square.side * square.side;
        } else if (shape instanceOf Circle) {
        	Circle c = (Circle) shape;
            return PI * c.radius * c.radius;
        }
    }
}

 

객체지향 코드

public interface Shape {
	double area();
}

public class Square implements Shape {
	public Point topLeft;
    public double side;
    
    @Override
    public double area() {
    	return side * side;
    }
}

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

 

절차적인 코드에서는 새 함수를 추가하여도 다른 클래스들이 영향받지 않습니다.

객체 지향 코드에서는 Shape에 함수를 추가한다면 구현체인 Circle, Square 모두가 영향을 받습니다.

개인적인 생각으로는 추상화한 Shape 에 함수가 추가되어야 하는 경우는 구현체들에 모두 추가되어야 할 때인 것 같은데 딱히 단점으로 느껴지진 않고 영향을 받는 게 당연한 것 같다고 생각이 듭니다.

특정 하나의 구현체만 적용되어야 한다면 다른 방법을 사용하는 게 좋아 보이네요.

객체지향 코드는 새 클래스를 추가하기 쉽습니다. 위 코드에서 Shape를 구현하는 클래스만 추가하여 함수를 구현하면 됩니다.

 

디미터 법칙

"클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다."라고 합니다.

- 클래스 C

- f 가 생성한 객체

- f 인수로 넘어온 객체

- C 인스턴스 변수에 저장된 객체

 

다음과 같은 코드가 있습니다.

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

 

흔히 위와 같은 코드를 기차 충돌이라고 부른다고 합니다.

여러 객차가 한 줄로 이어진 기차처럼 보이기 때문인데 이러한 코드는 다음과 같이 바꾸는 게 좋은 것 같습니다.

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

 

자료 전달 객체

활성 레코드

활성 레코드는 데이터베이스 테이블이나 다른 소스에서 자료를 직접 반환한 결과입니다.

활성 레코드에 비즈니스 규칙 메서드를 추가해 이런 자료 구조를 객체로 취급하는 개발자가 흔하다고 합니다.

개인적인 생각으로는 상황에 따라 다를 것 같기는 합니다. 프로젝트 초반 단계에서 빠르게 속도를 내기 위해서는 3 계층 아키텍처 + JPA Entity(책에서 말하는 활성레코드로 추정)에 비즈니스 로직을 추가할 수도 있을 것 같습니다. 다만 점점 도메인 로직이 거대해진다면 활성 레코드 객체와 도메인 로직을 구현하는 객체를 별개로 두는 게 더 좋을 것 같습니다.

 

결론

객체는 동작을 공개하고 자료를 숨긴다.

절차적인 코드, 객체 지향 코드는 상황에 따라 활용될 수 있다.

반응형