반응형

이번 챕터는 객체지향 개념의 챕터인거 같다.

사실 react의 함수형 컴포넌트를 사용하다보면 객체지향과 멀어지는 경향이 있는것 같다.

그래도 사고를 말랑말랑하게 하기위해 읽어보면 좋을듯 싶다.

 

외부 사용자가 클래스 내부 변수에 의존하지 않게 하고, 

외부에서의 조작으로 인해 의도치 않은 변경이나 버그를 막기 위해

흔히 private를 사용한다. (캡슐화, 정보 은닉)

 

이때 의무적으로? get, set 함수를 public 하게 외부에 노출시키는데 기계적으로 노출시키기보다는 

이러한 방식을 좀 더 깊게 고찰해보자는 챕터인것 같다.

 

객체/자료구조

  • 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개(내부 구조 은닉)
  • 자료 구조는 자료를 그대로 공개 (내부 구조 노출)
  •  

객체의 자료 추상화

구현을 감추기 위해선 추상화가 필요하다.

단순히 함수로 변수를 감춘다고 구현이 감춰지는것은 아니다. => 좀 더 고차원적인 사고가 필요

사용자가 어떻게 구현되었는지 몰라도(메소드 내부를 몰라도) 자료를 사용할수 있어야 좋은 코드 작성 방식이다. 

 

자바 코드로 생각해보자. (js는 interface 개념이 없어서 java로 보는게 편하..다?)

//구체적인 Point class이고 명시적 직교 좌표계라는것을 알 수 있다.
public class Point {
	public double x;
	public double y;
}

// 이게 직교 좌표계인지 무엇인지 알수는 없다(추상화 되어있다) 다만 사용하고 싶은
// 자료를 명백히 제공한다.
// 추상적인 Point interface이고 클래스 메서드로만 접근 가능하게 강제한다.
public interface Point {
	double getX();
	double getY();
	void setCartesian(double x, double y);
	double getR();
	double getTheta();
	void setPolar(double r, double theta);
}
//X,Y는 get할때 각각 구하는데 set은 한꺼번에 해야한다. => 개선 필요

위 코드의 public class는 구현을 외부로 노출하고

아래는 구현을 숨긴다.

 

일단 위의 point class는 private로 변수를 선언해도 getter, setter를 외부에 노출한다면 구현을 외부로 노출한다.

 

또 다음 예시를 보자.

//구체적 숫자 값으로 제공
public interface Vehicle {
	public getFuelThankCapacityInGallons();
	public getGallonsOfGasoline();
}

//연료 상태를 백분율이라는 추상적 값으로 제공
public interface Vehicle {
	double getPercentFuelRemaining();
}

위 구체적 인터페이스보다는 자료를 사용할때 추상적으로 제공하는 아래의 인터페이스가 더 좋다.

 

결론적으로, 

구현자는 사용자에게 '내부'를 알지 않아도 사용 가능하도록 추상화하는것이 좋다.

 

절차지향적 vs 객체지향적

본질적으로는 자료(구조)를 어떻게 사용할까에 대한 고민이다.

 

절차지향 

 

절차지향적인 코드는 기존 자료구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.

다만 새로운 자료구조를 추가하기 어렵다. (기존 함수를 전부 바꿔야함)

 

* 새로운 동작을 추가하는 유연성 -> 자료구조, 절차적인 코드

 

객체지향

 

객체지향적인 코드는 기존 함수는 그대로 놔두고 새로운 클래스를 추가하기 쉽다. (상속을 통해?)

다만 새로운 함수를 추가하기 어렵다. (그러기 위해서 모든 클래스를 고쳐야 한다.)

 

* 새로운 자료 타입을 추가하는 유연성 -> 객체 지향

 

서로 상호보완적인 관계이니 무조건 한쪽만 사용하는것은 지양하자.

(객체지향적 사고가 100% 정답은 아님) 

그런데 둘을 혼합하면 잡종 구조가 되니 이런 구조는 서로의 단점만 보이게된다. 그래서 비추천

 

디미터 법칙

잘 알려진 휴리스틱(경험에 기반하여 문제를 해결하거나 학습하거나 발견해 내는 방법, 최고가 아닌 최적의 방식)

모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙

 

정확히는 클래스 C의 메서드 f는 아래 객체의 메서드만 호출해야 한다.

  1. 클래스 C
  2. f가 생성한 객체
  3. f 인수로 넘어온 객체
  4. C 인스턴스 변수에 저장된 객체

이때 객체가 반환하는 객체의 메소드를 바로 호출하면 안된다. (잘 모르는것은 경계)

 

기차 충돌

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

객체가 한 줄로 이어져서 사용하는 방식으로 디미터 법칙을 무시한다.

일반적으로 조잡하다고 느껴질수 있기 때문에 가능하면 피하는것이 좋다.

 

해당 방식은 다음처럼 고치면 어떻게 될까?

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

 

이렇게 나눠도 ctext 객체가 opt를 가지고, scratchDir를 가지고 있다는 '내부 구조'를 알 수 있기 때문에

디미터 법칙을 위반할 수 있는 가능성이 존재한다.

그리고 이 위반 가능성은 ctxt가 '자료구조'인지 '객체' 인지에 따라 달라진다.

 

자료구조의 경우 

final String outputDir = ctxt.options.scratchDir.absolutePath;

처럼 사용해도 디미터 법칙의 대상이 되지 않는다.

(자료구조는 내부 구조를 노출해도 되므로 상관X)

 

그럼 자료구조가 아닌, 객체라면 코드를 어떻게 개선해야할까?

'내부 방식'을 몰라도 작동할 수 있도록 개선하면 된다.

 

final String outputDir = ctxt.getScratchAbsolutePath();

 

자료 전달 객체(DTO)

전형적인 자료구조

공개 변수만 있고 함수가 없는 클래스(get, set까진 있다)

 

- 데이터 저장용(데이터베이스와 통신, 소켓에서 받은 메세지 구문 분석시 유용)

- 가공되지 않은 정보를 받아와 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 사용 

(raw data)

 

class Address {

	private final String street;
	private final String streetExtra;
	private final String city;
    ...
}

활성 레코드

활성 레코드는 DTO의 특수한 형태

 

공개 변수, 비공계 변수에 조회/설정 함수 제공

save, find 같은 탐색 함수 제공

 

데이터베이스, 다른 소스에서 자료를 직접 변환한 '결과'

비즈니스 로직 메서드를 활성 레코드에 넣는것은 옳지 않다. 자료구조로 취급하자.

 

결론 

 

나는 잡종 구조?를 좀 많이 쓴편이긴 한데 객체와 자료구조를 분리해서 생각해야겠다

또한, 훌륭한 개발자는 편견에 빠지지 않는다. 알맞은 상황에 알맞게 쓰자

* 새로운 동작을 추가하는 유연성 -> 자료구조, 절차적인 코드

* 새로운 자료 타입을 추가하는 유연성 -> 객체 지향

 

 

 

 

반응형

'독서 > cleancode' 카테고리의 다른 글

cleancode - unit test  (0) 2022.01.25
clean code - 경계  (0) 2022.01.24
cleancode - 오류처리  (0) 2022.01.22
clean code - 주석 & 형식 맞추기  (0) 2022.01.20
clean code - 함수  (0) 2022.01.19
반응형

책을 읽고 내용을 메모해두려고 합니다.

1. 함수는 작게

여러 역할을 하는 함수는 역할별로 분리해서 쪼갠다

특히, 함수는 한가지만 잘해야 한다, 한가지만 해야한다.

ex) 이미지가 들어왔을때 이미지 형식이 알맞은지 검사하고, 알맞지 않다면 이미지 정보를 삭제하고 맞다면 저장소에 저장하는 함수

(글로도 벌써 이해하기 힘들다ㅋㅋ)

위 예시 함수는 아래의 3가지 함수로 분리 가능하다.

1. 이미지 형식 검사

2. 이미지 정보 삭제

3. 이미지 저장소에 저장

function yourfuncExample(image){

    if(isVaildImageExt(image)){
        saveImage(image);
    }
    else{
        deleteImage(image);
    }
}

isVaildImageExt(image){
    ... // 형식 확인하는 함수
}

saveImage(image){
    .... // 이미지 저장하는 함수
}

deleteImage(image){
    ... // 이미지 삭제
}

내 경험상 한번 쓴 함수가 다시 뒤에 재사용되는 경우가 많았던걸로 기억한다.

그래서 최대한 잘게 쪼개놓으면 나중 재사용 할 수 있어서 시간절약 및 유지보수에 매우 도움이 된다.

 

2. 서술적인 이름으로

추상화 수준은 동일히

이름을 붙일땐 일관성이 있고 명확해야 한다.

보통 함수는 동작이므로 동사 형태의 이름을 지니며

함수는 같은 문구, 명사, 동사 순서를 지닌다

ex) includeTeardownPages, includeSuiteTearDownPage

 

3. 함수 인수

인수가 많아지면 많아질수록 코드를 이해하는데 어려움이 생긴다. 또한, 테스트할때 매우 복잡해진다.

3항부터는 조금 생각해보자

좀 많아진다고 생각되면 객체로 한번 감싸자

function notGood(a,b,c){
    //이거보다는 
}

function good({a,b,c}){
     // 객체로 한번 감싸서 구조 분해 할당을 활용
}

개발을 하면서 a,b,c의 순서를 외워야할 필요가 없기 때문에 객체(객체 리터럴이나 클래스)로 감싸는거 추천

 

4. side Effect를 일으키지 마라

함수형 프로그래밍의 side Effect 느낌보다는 함수가 자신의 역할 이외에 다른 역할을 추가적으로 한다든지

의도치않은 부수 효과를 일으키는 경우를 말하는것 같다.

예를 들어 dom tree의 element를 여러 함수가 공유해서 같이 조작한다든지 매개변수로 넘어온 객체의 내부 값을 변경한다든지 예상치 못한 결과를 내는 경우를 뜻한다.

사실 1번을 잘 지키면 4번은 저절로 따라온다.

모듈, 함수간 결합도(의존도)를 줄이고 응집도를 높이자.

 

5. 명령과 조회 분리

뭔가 수행하거나, 뭔가 답하거나 여러 종류중 하나만 하는것을 추천

조건이 맞나? isExist ~~

수행? do~~

설정? set~~

 

6. 오류 처리엔 예외처리 이용

function ex(code){

    if(isVaild(code)){ 
        console.log('error');
        return;  //이거보다는 
    }
}

function ex2(code){
    try{
     isVaild(code); // 여기서 throw error
    }
    catch(e){
        console.log(e);
    }

}

예시가 좀 엉성하긴 한데.. 에러처리에는 예외처리를 애용하자. (역할분리)

try catch문 자체도 따로 함수로 뽑아내는것이 좋다.

추가적으로 발생가능한 Error들을 정의하는 Error set을 만들자.

 

7. 반복하지 마라

(중요해서 또나옴..?)

동일한 로직을 함수로 분리하고 재사용하자.

유지보수 및 시간 절약에 큰 도움이 된다.

만약 동일한 로직인 코드를 4개를 만들었다면 뭔가 변경사항이 있을때 4곳을 각각 수정해야한다.

그런데 함수로 분리하고 변경사항을 해당 함수에만 적용한다면 한곳만 고치면 되니 매우 편리해진다.

반응형

'독서 > cleancode' 카테고리의 다른 글

cleancode - unit test  (0) 2022.01.25
clean code - 경계  (0) 2022.01.24
cleancode - 오류처리  (0) 2022.01.22
cleancode - 객체와 자료구조  (0) 2022.01.21
clean code - 주석 & 형식 맞추기  (0) 2022.01.20

+ Recent posts