nyximos.log

[Clean Code] 6장, 객체와 자료 구조 본문

Books

[Clean Code] 6장, 객체와 자료 구조

nyximos 2022. 2. 22. 13:16

 

클린코드,  애자일 소프트웨어 장인정신

Robert C. Martin

 

 

들어가며

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

 

자료 추상화

  • 구현을 감추려면 추상화가 필요하다.
  • 추상 인터페이스를 제공해 사용자가 구현을 모른 채 핵심을 조작할 수 있어야 진정한 의미의 클래스다.
  • 자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋다.
  • 아무 생각 없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다. 

 

구체적인 Point 클래스

  • 직교좌표계 사용
  • 계별적으로 좌표값을 읽고 설정하게 강제
  • 구현을 노출한다.
  • 변수를 private로 설정해도 각 값마다 get/set 함수를 제공한다면 구현을 외부로 노출하는 셈
public class Point{
    public double x;
    public double y;
}

 

추상적인 Point 클래스

 

  • 인터페이스는 자료 구조를 명백하게 표현한다.
  • 자료 구조 이상을 표현한다.
  • 클래스 메서드가 접근 정책을 강화한다.
  • 좌표를 읽을 때 → 각 값을 개별적으로 읽음
  • 좌표를 설정할 때 → 두 값을 한꺼번에 설정
public interface Point{
    double getX();
    double getY();
    void setCartesian(double x, double y);
    double getR();
    double getTheta();
    void setPolar(double r, double theta);
}

 

 

자료/객체 비대칭

  • 객체와 자료 구조 사이의 차이
    • 객체 : 추상화 뒤로 자료를 숨김, 자료를 다루는 함수만 공개
    • 자료 구조 : 자료를 그대로 공개, 별다른 함수 제공 🙅‍♀️

 

절차적인 도형 클래스

  • 각 도형 클래스 → 간단한 자료 구조 🙆‍♀️ 메서드 🙅‍♀️
  • 도형이 동작하는 방식 → Geometry 클래스에서 구현
  • 새 도형을 추가하고 싶다면 Geometry 클래스에 속한 함수를 모두 고쳐야 한다.
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(Object shape) throws NoSuchShapeException{
    	if(shape instanceof Square){
        	Square s = (Square)shape;
            return s.side * s.side;
        }
        else if(shape instanceof Rectangle){
        	Rectangle r = (Rectangle)shape;
            return r.height * r.width;
        }
        else if(shape instanceof Circle){
        	Circle c = (Circle)shape;
            return PI * c.radius * c.radius;
        }
        throw new NoSuchShapeException();
    }
}

 

객체 지향적인 도형 클래스

  • area() → 다형 메서드
  • 새 도형을 추가해도 기존 함수에 아무런 영향을 미치지 않는다.
  • 새 함수를 추가하고 싶다면 도형 클래스 전부를 고친다.
public class Square 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 sidth;
    
    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;
    }
}

 

객체와 자료 구조는 근본적으로 양분 된다.

  • 객체 지향 코드에서 어려운 변경 → 절차적인 코드에서 쉽다.
  • 절차적인 코드에서 어려운 변경 → 객체 지향 코드에서 쉽다.
  • 분별 있는 프로그래머는 모든 것이 객체라는 생각이 미신임을 잘 안다.
  • 때로는 단순한 자료 구조와 절차적인 코드가 가장 적합한 상황도 있다.

 

 

디미터 법칙

  • 잘 알려진 휴리스틱 heuristic
  • 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙
  • 객체 : 자료를 숨기고, 함수를 공개 → get 함수로 내부 구조를 공개하면 안된다는 의미

 

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

  • 클래스 C
  • f가 생성한 객체
  • f 인수가 넘어온 객체
  • C 인스턴스 변수에 저장된 객체

위 객체에서 허용된 메서드가 반환하는 객체의 메서드는 호출하면 안된다.

 

 

기차 충돌 train wreck

여러 객체가 한 줄로 이어진 기차처럼 보이기 때문에 피하는 편이 좋다.

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

 

다음과 같이 나누는 편이 좋다.

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

ctxt, Options, ScratchDir이 객체인지 아니면 자료구조인지에 따라 위반 여부가 결정된다.

  • 객체 → 내부 구조를 숨겨야 하므로 디미터 법칙 위반
  • 자료구조 → 내부 구조를 노출하므로 디미터 법칙 적용 🙅‍♀️

 

이렇게 하면 문제가 간단해진다.

  • 객체 → 비공개 변수와 공개 함수 포함
  • 자료구조 → 무조건 함수 없이 공개 변수만 포함

그러나 단순한 자료 구조에도 조회 함수와 설정 함수를 정의하라 요구하는 프레임워크와 표준이 존재한다.

 

 

잡종 구조

  • 절반은 객체, 절반은 자료구조
  • 새로운 함수나 새로운 자료 구조를 추가하기 어렵다.
  • 프로그래머가 함수나 타입을 보호할지 공개할지 확신하지 못해(더 나쁘게는 무지해) 어중간하게 내놓은 설계에 불과하다.

 

구조체 감추기

ctxt, options, scratchDir이 객체라면 → 내부 구조를 감춰야 한다.

 

임시 디렉터리의 절대 경로를 얻는 방법 1

ctxt.getAbsolutePathOfScratchDirectoryOption();
  • ctxt객체에 공개해야 하는 메서드가 너무 많아진다.

 

임시 디렉터리의 절대 경로를 얻는 방법 2

ctxt.getScratchDirectoryOption().getAbsolutePath();
  • getScratchDirectoryOption()이 객체가 아니라 자료 구조를 반환한다고 가정한다.

 

ctxt가 객체라면 → 뭔가를 하라고 말해야된다.

절대 경로가 필요한 이유가 임시 파일을 생성하기 위해서라면

ctxt에 임시 파일을 생성하라고 시키자.

BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
  • ctxt는 내부 구조를 드러내지 않는다.
  • 모듈에서 해당 함수는 자신이 몰라야 하는 여러 객체를 탐색할 필요가 없다.
  • 디미터 법칙을 위반하지 않는다.

 

자료 전달 객체

Data Transfer Object, DTO

  • 공개 변수만 있고 함수가 없는 클래스
  • 데이터베이스와 통신하거나 소켓에서 받은 메세지의 구문을 분석할 때 유용하다.
  • DTO는 데이터베이스에 저장된 가공되지 않은 정보를 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체
public class Address{
    private String street;
    private String streetExtra;
    private String city;
    private String state;
    private String zip;
    
    public Address(String street, String streetExtra, String city, String state, String zip){
     this.street = street;
     this.streetExtra = streetExtra;
     this.city = city;
     this.state = state;
     this.zip = zip;
    }
    
    public String getStreet(){
    	return street;
    }
    
    public String getStreetExtra(){
    	return streetExtra;
    }
    
    public String getCity(){
    	return city;
    }
    
    public String getState(){
    	return state;
    }
    
    public String getZip(){
    	return zip;
    }
    
}

 

활성 레코드

  • DTO의 특수한 형태
  • 공개 변수가 있거나 비공개 변수에 조회/설정 함수가 있는 자료구조
  • save나 find 같은 탐색 함수도 제공한다
  • 데이터베이스 테이블이나 다른 소스에서 자료를 직접 변환한다.
  • 비즈니스 규칙을 담으면서 내부 자료를 숨기는 객체는 따로 생성한다.

 

결론

객체

  • 동작 공개 , 자료 숨김
  • 기존 동작 변경 🙅‍♀️ 새 객체 타입 추가 → 쉬움
  • 기존 객체에 새 동작 추가 → 어려움

 

자료 구조

  • 별다른 동작 없이 자료 노출
  • 기존 자료에 새 동작 추가 → 쉬움
  • 기존 함수에 새 자료 구조 추가 → 어려움

 

시스템을 구현할 때

  • 새로운 자료 타입을 추가하는 유연성이 필요하다. → 객체
  • 새로운 동작을 추가하는 유연성이 필요하다. → 자료 구조 & 절차적인 코드

 

'Books' 카테고리의 다른 글

[Clean Code] 8장, 경계  (0) 2022.08.02
[Clean Code] 7장, 오류 처리  (0) 2022.02.24
[Clean Code] 5장, 형식 맞추기  (0) 2022.02.18
[Clean Code] 4장, 주석  (0) 2022.02.16
[Clean Code] 3장, 함수  (0) 2022.02.12