개발서적

[리뷰] 클린코드 Clean Code 6장 - 객체와 자료구조

코징 2022. 2. 16. 15:51
책 내용 - 객체와 자료구조

변수를 비공개 Private로 정의하는 이유가 있다.

남들이 변수에 의존하지 않게 만들고 싶어서다.

그렇다면 어째수 수많은 프로그래머가 조회 함수 get와 설정 set 함수를 당연하게 공개해 비공개 변수를 외부에 노출할까?

*자료 추상화

//목록6-1 구체적인 Point 클래스
public class point {
    public double x;
    public double y;
}

//목록6-2 추상적인 Point 클래스
public interface point {
    double getX();
    double getY();
    void setCartesian(double x, doube y);
    double getR();
    double getTheta();
    void setPolar(double r, double theta);
}

 구현을 감추려면 추상화가 필요하다. 그저조회 함수와 설정 함수로 변수를 다룬다고 클래스가 되지 않는다. 그보다는 추상 인터페이스 를 제공해 사용자가 구현을 모른 채 핵심을 조작할 수 있어야 진정한 의미 클래스이다.

// 구체적인 Vehicle
public interface Vehicle {
    double getFuelTankCapacityIngallons();
    double ettGallonsOfGasoline();
}

// 추상적인 Vehicle
public interface Vehicle {
    double getPercentFuelRemaining();
}

자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋다.

인터페이스나 조회/설정 함수만으로는 추상화가 이뤄지지 않는다. 개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 심각하게 고민해야 한다. 아무 생각 없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다.

 

*자료/객체 비대칭

객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.

자료 구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.

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

public class Rectangle {
	public Point topLeft;
    public double height;
    public double width;
}

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

public class Geometry {
	public final double PI = 3.141592653589793;
    
    public double area(Obeject shape) throws NoSuchShapeException {
		if (shape instanceof Square){
        	Square s = (Square)shape;
            return s.side * s.side;
        } else if (shape instanceof Square){
        	Square s = (Square)shape;
            return s.side * s.side;
        } else if (shape instanceof Square){
        	Square s = (Square)shape;
            return s.side * s.side;
        }
        
        throw new NoSuchShapeException();
    }
}

객체 지향 프로그래머가 위 코드를 본다면 코웃음을 칠지 모르겠다.

클래스가 절자척이라 비판한다면 맞는 말이다.

만약 Geometry 클래스에 둘레 길이를 구하는 perimeter() 함수를 추가하고 싶다면?

도형 클래스는 아무 영향도 받지 않는다.

도형 클래스에 의존하는 다른 클래스도 마찬가지다! 

반대로 새 도형을 추가하고 싶다면? Geometry 클래스에 속한 함수를 모두 고쳐야 한다.

두 조건은 완전히 정반대다.

 

public class Sqaure implements Shape {
	private Point topLeft;
    private double side;
    
    public double area() {
    	return side*side;
    }
}

public class Rectangle implements Shape {
	private Point topLeft;
    private double height;
    private double width;
    
    public double area() {
    	return height*width;
    }
}

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

객체 지향적인 도형 클래스다. 여기서 area()는 다형 메서드다. 

Geometry 클래스는 필요 없다. 그러므로 새 도형을 추가해도 기존 함수에 아무런 영향을 미치지 않는다. 반면 새 함수를 추가하고 싶다면 도형 클래스 전부를 고쳐야 한다.

 

자료 추상화 결론

복잡한 시스템을 짜다 보면 새로운 함수가 아니라 새로운 자료 타입이 필요한 경우가 생긴다. 이때는 클래스와 객체 지향 기법이 가장 적합하다.

새로운 함수가 필요한 경우도 생긴다. 이때는 절차적인 코드와 자료 구조가 좀 더 적합하다.

 

분별 있는 프로그래머는 모든 것이 객체라는 생각이 미신임을 잘 안다. 때로는 단순한 자료 구조와 절차적인 코드가 가장 적합한 상황도 있다.

 

 

*디미터 법칙

디미터 법칙은 잘 알려진 휴리스틱으로, 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다. 

 

기차 충돌

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

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

일반적으로 조잡하다 여겨지는 방식이므로 피하는 편이 좋다.

위와 같은 코드는 다음과 같이 나누는 편이 좋다.

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

위 코드가 객체라면 내부 구조를 숨겨야 하므로 확실히 디미터 법칙을 위반한다.

만약 ctxt, opts, scratchDir이 진짜 객체라면 

BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);

으로 내부 구조를 드러내지 않으며, 모듈에서 해당 함수는 자신이 몰라야 하는 여러 객체를 탐색할 필요가 없다.

 

 

결론

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

객체는 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기 쉬운 반면, 기족 객체에 새 동작을 추가하기는 어렵다.

자료구조는 별다른 동작 업이 자료를 노출한다. 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다.

시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 적합하다.

새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다.

 

객체와 자료구조의 사용법에 대해 명확하게 구분을 할 수 있는 개념을 갖고, 적절한 방법에 맞게 대응해야 된다. 항상 잊지 않을 것은 새로운 타입을 추가시키는 건 객체, 새 동작을 추가 하하는 것은 자료 구조와 절차