클린코드 TIL : 6장. 객체와 자료구조

Devil😈·2024년 2월 4일
1

CleanCode

목록 보기
6/10
post-thumbnail

자료 추상화

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

abstract class Vehicle {
  double getFuelTankCapacityInGallons();
  double getGallonsOfGaoline();
}

이렇게 연료탱크의 값과 남은 연료의 값을 가져오는 것보다는 아래와 같이 정보가 어디서 오는지 전혀 드러나지 않게 추상적 개념으로 표현하는 편이 좋다.

abstract class Vehicle {
  double getPercentFuelRemaining();
}

자료/객체 비대칭

아래 코드는 Class가 절차적이다. 절차적 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 반면, 새로운 자료 구조를 추가하기 어렵다.
-> 새 도형을 추가하려면 Geometry 클래스를 뜯어 고쳐야 한다.

class Square {
  Point topLeft;
  double side;
  Square(this.topLeft, this.side);
}

class Rectangle {
  Point topLeft;
  double height, width;
  Rectangle(this.topLeft, this.height, this.width);
}

class Circle {
  Point center;
  double radius;
  Circle(this.center, this.radius);
}

class NoSuchShapeException implements Exception {
  final String message;
  NoSuchShapeException([this.message = ""]);
}

class Geometry {
  static final double PI = 3.141592653589793;

  double area(dynamic shape) {
    if (shape is Square) {
      return shape.side * shape.side;
    } else if (shape is Rectangle) {
      return shape.height * shape.width;
    } else if (shape is Circle) {
      return PI * shape.radius * shape.radius;
    }
    throw NoSuchShapeException("No such shape");
  }
}

아래 코드는 객체 지향적인 도형 클래스이다. area()가 다형메서드가 되고 Geometry 클래스가 필요없어진다. 따라서 새 도형을 추가해도 기존 함수에 아무런 영향이 없다. 다만, 둘레 길이를 구하는 새 함수를 추가하고 싶다면 모든 도형 클래스를 수정해야 한다. 즉, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽지만 새로운 함수를 추가하기는 어렵다.


abstract class Shape {
  double area();
}

class Square implements Shape {
  Point topLeft;
  double side;

  Square(this.topLeft, this.side);

  @override
  double area() => side * side;
}

class Rectangle implements Shape {
  Point topLeft;
  double height, width;

  Rectangle(this.topLeft, this.height, this.width);

  @override
  double area() => height * width;
}

class Circle implements Shape {
  Point center;
  double radius;
  static const double PI = 3.141592653589793; // final 대신 const 사용

  Circle(this.center, this.radius);

  @override
  double area() => PI * radius * radius;
}

새로운 자료 타입이 필요한 경우 클래스와 객체 지향 기법이 적합하고, 새로운 함수가 필요한 경우에는 절차적인 코드와 자료 구조가 더 적합하다.

디미터 법칙

디미터 법칙(The Law of Demeter) = 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙

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

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

아래 코드는 디미터 법칙을 어기는 것처럼 보이는데, getOptions() 함수가 반환하는 객체의 getScratchDir() 함수를 호출한 후 다시 이 함수가 반환하는 객체의 getAbsolutePath()함수를 호출하기 때문이다.

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

이와 같은 코드를 기차 충돌(train wreck)이라고 부른다. 이 코드는 다음과 같이 나누는 편이 좋다.

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

잡종구조

자료구조에 조회 함수와 설정함수를 정의하면서 절반은 객체, 절반은 자료 구조인 잡종 구조가 나타나는데 이는 새로운 함수는 물론이고 새로운 자료 구조도 추가하기 어려운 단점만 모인 구조가 될 수 있다.

자료 전달 객체

자료 전달 객체 (Data Transfer Object, DTO) : 공개 변수만 있고 함수가 없는 클래스

데이터베이스에서 정보를 읽어왔을 때 처음 사용하는 구조체다. Java bean이 일반적인 형태인데, Dart에서는 Data Class 형태가 되겠다.

class Address {
  String _street;
  String _streetExtra;
  String _city;
  String _state;
  String _zip;

  Address(this._street, this._streetExtra, this._city, this._state, this._zip);

  String get street => _street;
  String get streetExtra => _streetExtra;
  String get city => _city;
  String get state => _state;
  String get zip => _zip;
}

DTO의 특수한 형태로 활성 레코드(Active Record)가 있는데 공개 변수가 있거나 비공개 변수에 조회/설정 함수가 있는 자료 구조지만, save나 find와 같은 탐색 함수도 제공한다. 활성 레코드는 DB테이블이나 다른 소스에서 자료를 직접 변환한 결과다.

활성 레코드는 자료 구조로 취급해야 하고, 비즈니스 규칙 메서드를 추가하지 않아야 한다. 비즈니스 규칙을 담는 객체는 따로 생성한다.

profile
얼굴셋 손여섯

0개의 댓글