본문 바로가기
책/클린코드

6. 객체와 자료 구조

by 히포파타마스 2023. 6. 18.

객체와 자료 구조

 

(어떤) 시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다. 다른 경우로 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다. 우수한 소프트웨어 개발자는 편견 없이 이 사실을 이해해 직면한 문제에 최적인 해결책을 선택한다.

 

-요약-

어설픈 캡슐화를 지양하고 추상화를 활용해라.

객체와 자료 구조를 명확히 구분하고 사용해라.

 

1. 객체 지향

1.1 모든 필드에 get과 set을 만들지 마라

모든 필드에 get()과 set()을 만든다면 이 객체는 자료 구조와 다를 바 없다.

형식적인 캡슐화를 하지말고 추상화를 통해 객체 내부의 값을 숨기고 역할을 부여하자

이런 방식은 더 유연하고 확장 가능한 코드를 작성하는데 도움이 된다.

 

1.2 디미터 법칙

디미터 법칙에 의하면 "모듈은 자신이 조작하는 객체의 내부 값을 몰라야 한다."

 

더 정확한 법칙의 정의는 다음과 같다.

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

 

- 클래스 C

- f가 생성한 객체

- f인수로 넘어온 객체

- C 인스턴스 변수에 저장된 객체

 

즉, 특정 모듈은 자신이 조작하는 객체의 내부값을 이용한 메서드를 호출하지 말아야 한다는 말이다.

 

이에 연동되는 말으로 "묻지 말고 시켜라" 라는 말이있다.

모듈은 자신이 조작하는 객체의 내부값을 묻지 말고, 무엇을 해야하는지 알려주어야 한다는 말이다.

 

위의 내용들은 전부 "객체간의 결합도를 낮추고 자율성을 높여 서로 협력하기 쉽도록 설계하는 것"이 목적이다

 

"운전자"라는 객체가 "자동차"라는 객체를 사용한다고 하자.

"운전자"가 "자동차"를 움직이기 위해서 자동차 내부의 엔진, 연료, 기어 등과 같은 내부값을 가져와서 조립하고

"자동차"를 움직일 수도 있다.

하지만 "자동차"에 move()라는 메서드가 추상화 되어있다면 "운전자"는 "자동차"를 움직이기 위해 move()를 사용하면 될 뿐이다.

"운전자"는 "자동차" 내부의 값을 전혀 알 필요가 없다. 단지 move()를 사용하기만 하면 "자동차"를 움직이는 것이 약속된것이다. 

당연히 후자의 방식으로 설계된 코드가 더 보기 좋고 유연할 것이다.

 

[디미터 법칙 위반 예시]

@Transactional
@CacheEvict(key = "#boardId", value = {"findBoard", "boardView"})
public void removeBoard(Long loginAccountId, Long boardId) {

    Board board = boardRepository.findByIdWithBoardTagsAndAccount(boardId)
            .orElseThrow(() -> new BusinessLogicException(ExceptionCode.NOT_FOUND_BOARD));

    Account account = board.getAccount();

    if (!loginAccountId.equals(account.getId())) {
        throw new BusinessLogicException(ExceptionCode.FORBIDDEN);
    }

    account.getBoards().remove(board);
    boardTagRepository.deleteByBoardId(boardId);
    boardPhotoRepository.deleteByBoardId(boardId);
    commentRepository.deleteByBoardId(boardId);
    likesRepository.deleteByBoardId(boardId);
    boardRepository.deleteById(boardId);
}

 

board 내부의 Account를 가져와서 사용하며 Account 내부의 boards를 또 가져와서 조작한다.

account를 가져와서 예외처리를 하는 부분도 디미터 법칙을 위반하지만 account.getBoards().remove(board)와 같이 연속적으로 내부값을 조작하는 부분은 가독성이 떨어 질 수 있다.

 

[디미터 법칙 준수 예시]

@Transactional
@CacheEvict(key = "#boardId", value = {"findBoard", "boardView"})
public void removeBoard(Long loginAccountId, Long boardId) {

    Board board = boardRepository.findByIdWithBoardTagsAndAccount(boardId)
            .orElseThrow(() -> new BusinessLogicException(ExceptionCode.NOT_FOUND_BOARD));

    Long accountId = board.getAccountId();

    if (!loginAccountId.equals(accountId) {
        throw new BusinessLogicException(ExceptionCode.FORBIDDEN);
    }

    board.deleteInAccount();
    boardTagRepository.deleteByBoardId(boardId);
    boardPhotoRepository.deleteByBoardId(boardId);
    commentRepository.deleteByBoardId(boardId);
    likesRepository.deleteByBoardId(boardId);
    boardRepository.deleteById(boardId);
}

 

board에서 Account를 가져와서 조작하지 않고 board에게 필요한 행위를 시키는 방향으로 코드를 개선하였다.

board를 만든 Account에서 자기 자신을 지운다는 행위를 추상화하여 deleteInAccount()라는 메서드를 만들었다.

 

코드를 더 직관적으로 파악할 수 있으며, 추상화를 통해 유연성이 더 높아졌다.

 

사용자는 board가 어떤 방식으로 account에서 자기 자신을 지우는지 알 필요가 없다.

단지 추상화된 deleteInAccount()라는 메서드를 사용하기만 하면된다.

' > 클린코드' 카테고리의 다른 글

9. 단위 테스트  (0) 2023.06.23
5. 형식 맞추기  (0) 2023.06.18
4. 주석  (0) 2023.06.18

댓글