아래 두 코드 예시를 보자
public class Point {
public double x;
public double y;
}
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
두 번째 코드는 자료 구조 이상을 표현한다. 접근 정책을 강제하고 추상적이지만 자료구조를 명백하게 표현한다.
첫 번째 코드는 getter, setter가 노출되고 개별적으로 변수 값을 읽고 설정하게 강제한다. 변수가 private으로 정의되고 getter, setter가 있다면 별 차이가 없다. 이런 코드는 객체가 아닌 데이터를 표현하는 자료구조에 가깝다.
즉, 변수 사이에 함수라는 계층을 넣는다고 구현이 저절로 감춰지는 것은 아니다. 구현을 감추려면 추상화가 필요하며, 조회 함수와 설정 함수로 변수를 다룬다고 클래스가 되는 것이 아니다. 그보다는 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.
이때, 자료를 세세하게 공개하기 보다는 추상적인 개념으로 표현하는 것이 중요하다. 따라서 개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 심각하게 고민해야 한다.
오늘도 아무 생각 없이 getter, setter를 추가하지는 않았는지 되돌아보자.
객체와 자료구조
습관적 getter, setter에서 얻은 인사이트를 토대로 우리는 객체와 자료구조의 차이를 느낄 수 있다. 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다. 자료구조는 자료를 그대로 공개하며 getter, setter 외에 별다른 함수를 제공하지 않는다.
그래서 두 개념은 사실상 정반대의 성질을 가진다. 아래 Geometry 클래스를 살펴보자. (Geometry 클래스가 다루는 세 가지의 도형 클래스는 단순한 자료구조이며, 별다른 함수를 제공하지 않는다)
public class Geometry {
public final double PI = 3.141592...
public double area(Object shape) throws NoSuchShapeException {
if(shape instanceof Square) {
// do something
}
else if(shape instanceof Rectangle) {
// do something
}
else if(shape instanceof Circle) {
// do something
}
throw new NoSuchShapeException();
}
}
객체지향적인 관점에서 바라본다면 예상되는 문제점은 새 도형이 추가되었을 때 Geometry 클래스에 속한 함수를 모두 고쳐야 한다는 것이다. 그렇지만 Geometry 클래스에 둘레 길이를 구하는 perimeter() 함수를 추가하고 싶다면? 도형 클래스는 아무 영향도 받지 않는다. 그럼 아래 예제를 살펴보자.
public class Square implements Shape {
...
public double area() {
return side * side;
}
}
public class Rectangle implements Shape {
...
public double area() {
return height * height;
}
}
public class Circle implements Shape {
...
public double area() {
return PI * radius * radius;
}
}
위 예제는 객체지향적이다. 도형이 추가되어도 다른 코드에 영향을 주지 않고 area 함수를 각 도형 객체가 구현하여 도형의 내부 자료를 알지 못하더라도 도형의 너비를 계산할 수 있다. 그러나 Shape 인터페이스에 새로운 함수가 추가된다면, 모든 도형 객체가 새로운 함수를 구현해야하는 단점이 있다.
따라서 앞서 두 가지 예제를 통해 우리는 객체와 자료구조가 근본적으로 상반되는 성질을 가진다는 것을 알 수 있다.
💡 (자료 구조를 사용하는) 절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 반면, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.
💡 절차적인 코드는 새로운 자료 구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다. 객체 지향 코드는 새로운 함수를 추가하기 어렵다. 그러려면 모든 클래스를 고쳐야 한다.
복잡한 시스템을 짜다 보면 새로운 함수가 아니라 새로운 자료 타입이 필요한 경우가 생긴다. 이때는 클래스와 객체 지향 기법이 가장 적합하다. 반면, 새로운 자료 타입이 아니라 새로운 함수가 필요한 경우도 생긴다. 이때는 절차적인 코드와 자료 구조가 좀 더 적합하다.
분별있는 프로그래머는 “모든 것이 객체”라는 생각이 미신임을 잘 안다.
결론
대부분의 개발자들이 변수를 비공개로 정의하는 이유가 있다. 남들이 변수에 의존하지 않게 만들고 싶기 때문이다. 그런데 많은 개발자가 getter, setter를 당연하다는 듯이 노출시킨다. 이것은 객체의 구현을 외부로 노출시키는 행위와 같아 모순적이다.
'책 > 클린 코드' 카테고리의 다른 글
클린 코드: 디미터 법칙 (0) | 2023.06.28 |
---|---|
클린 코드 1~2장 (0) | 2022.02.11 |