제가 저번 글에서 다음과 같이 말한적이 있습니다.

https://masiljangajji-coding.tistory.com/66

 

클린코드&테스트 - 추상과 구체

요즘은 코드를 잘 짠다 = 읽기가 좋다 = 가독성 있다 , 라는 말을 많이 하는 것 같습니다.저 또한 매우 동의하는 말인데요 하지만 개인적으로 "가독성 있다"라는 말 또한 추상적인 개념이라 생각

masiljangajji-coding.tistory.com

 

이러한 말을 한 이유는 클린코드, SOLID , DDD , TDD.... 등등의 것들은 전부 생산성을 위한 것이거든요
아~ 이렇게하면 생산성이 늘어나던데? 좋던데?와 같은 말인것이죠 , 또 그들은 일종의 "상품"으로서 작용되는 부분도 있습니다.
TDD라는 용어가 만들어지기 전까지는 Test코드를 작성을 안했을까요?? 그렇진 않을 것 입니다.


그저 테스트와 관련한 여러가지 산재돼있는 개념을 TDD라는 명칭으로 묶어 만든 일종의 "상품"이라고 생각합니다.
또 이런것들은 전부 생산성을 늘리는 Best Practice중 하나다 라고 볼 수 있습니다.


따라서 우리는 이것을 일종의 원칙이나 법처럼 적용시켜 개발할 이유는 없다고 생각합니다.
당장 내일 새로운 기능을 배포를 해야하는 상황에서 "클린코드를 위해서 리팩토링을 10시간 하겠습니다" 이런 말을 한다면
아마 팀장님한테 혼나겠죠


다른 예시로 "클린코드를 지향하니까 주석은 안답니다"같은 말씀을 하시는 분들이 계십니다.
근데 주석을 안단다고 진짜 코드가 클린해지나요? 조금 의문인 부분도 있거든요

 

강의에서도 비슷한 말을 합니다. "무조건"이 아니라는 것이죠
개인적으로는 "이런식으로 하면 좋던데??" 정도로 받아들이는 것이 가장 좋지 않을까 생각합니다.

 

객체를 만들 때 1개의 관심사로 책임이 정의돼있는지 확인하는 것은 굉장히 중요합니다.
저는 개인적으로는 "객체"라는 표현보다는 "타입"이라는 표현을 더 좋아합니다.


클래스를 만든다는 것과 타입을 만든다는 것은 동일한 것인데 어감에서 오는 차이가 크다고 생각하거든요
타입에 대해 말하는 것이 훨씬 더 명확하게 의도가 전달된다 생각합니다.
이 타입이 가져야 할 역할은 무엇이지? , 이 타입으로 나는 무엇을 하고싶지? , 이 타입이 해야만 하는 일은 뭐지? 와 같이 말이죠


int라는 타입을 생각해 봤을 때 마땅히 숫자형 데이터가 해야할 무언가가 떠오르지 않나요?
int 객체라고 하면 그런 부분에서 직관성이 떨어진다 생각합니다.

 

"setter를 자제하자" 이건 사실 당연한데요 setter로 열려있는 경우에는 누구나 해당 값을 임의로 변경할 수 있기 때문에
최대한 지양하는 것이 좋습니다.

특히 dto같은 경우에는 setter가 더욱 필요 없는데 사용자가 준 값을 내가 임의로 바꿀일이 많지는 않거든요
하지만 재밌는점은 "getter도 사용하지말자"입니다.

사실 getter의 경우에는 그냥 lombok으로 달아놓고 자유롭게 쓰거든요
setter의 경우만 고민을 합니다. 근데 getter도 사용하지말자? 이건 좀 재밌게 느껴졌습니다.


예시를 보면 바로 알 수 있는 부분인데요 1번코드와 2번코드 무엇이 더 직관적인가요?
당연히 2번코드지 않나 생각합니다.


또 관련된 설명을 하면서 이것이 person이라는 객체를 존중하지 않는 폭력적인 코드라는 설명하시는데
이 부분이 가장 흥미있고 재밌었습니다.


쉽게 말하면 객체는 캡슐화돼있는 것인데 getter를 남발하면 캡슐화를 왜 하냐?
getter를 쓸수도 있지만 안쓸수도 있지 않을까?? 라는 것 이죠

참 중요한 걸 얻고 가는데요

"캡슐화돼있는 데이터를 바깥에서 알고있다고 생각하지 말아라"
"우리는 알고있지만 바깥에있는 객체도 알고있을거라 생각하지마"
이것이 캡슐화의 전부인 것 같습니다.

"주니어를 위한 면접질문" 같은 곳에 가면 꼭 등장하는 SOLID입니다.

매우 간단한 코드로도 SOLID를 표현 가능하다 생각하는데요

public class ChefService {

    private final Chef chef; // Interface Chef라는 타입이 해야할 역할을 정의한 Specification

    public ChefService(Chef chef) { // subTyping 이용 , Chef타입의 서브타입을 주입 가능
        this.chef = chef;           // 나는 Korean,American 선택해서 넣을 수 있음
    }                               // 후에 다른 Chef가 추가되어도 생성할때 넣는 인자만 변경하면 됨 OCP , DIP 

    public void makeFood(){
        chef.cook(); // korean,american의 코드가 변경되도 ChefService가 신경 쓸 부분은 아님
        // cook이라는 행위를 Chef에 정의해 둠으로써 통일화된 추상화를 제공함
        // Korean,American의 cook메서드의 내부 구현이 바뀌어도 신경 안써도 됨

    }

}

이 코드로 SOLID의 모든 것을 표현 가능합니다.

DIP = 의존성 역전 원칙 -> 추상화에 의존해라
Chef라는 추상화 정도가 높은 Interface 타입을 받음으로써 ChefService는 추상화에 의존하고있습니다.

그로인해 각각의 Chef를 하나의 모듈처럼 사용가능합니다.
Korean을 넣었다가...American넣었다가... 아니면 다른 Chef을 만들어 주입해도 문제가없습니다.
따라서 OCP 원칙을 지키는 길이기도 합니다.

OCP = 개방 폐쇠원칙 -> 확장은 열려 있고 수정은 닫혀 있다.
Chef 타입 1000개 만들어서 사용해도 생성자 주입할때 들어가는 코드만 변경해주면 문제없음.

public interface Chef {

    void cook(); // 요리하세요
}
public class AmericanChef implements Chef{
    @Override
    public void cook() {
        // 양식요리
    }
}
public class KoreanChef implements Chef{
    @Override
    public void cook() {
        // 한식요리
    }
}

LSP = 리스코프 치환 원칙 -> Super Type이 정의한 근본적인 역할을 따라야 한다.
SuperType인 Chef에 cook이라는 기능이 명시돼 있습니다.
마땅히 요리하는 행위를 생각하겠죠??


근런데 갑자기 KoreanChef의 cook() 메서드를 음식을 먹는 기능으로 변경한다고 해보겠습니다.
이러면 위에서 정의내린 Chef타입의 근본적인 기능이 변경되는 것 입니다.
하지만 우리의 코드는 SuperType이 정의한 "요리" 행위를 잘 하는것으로 보이니 LSP를 지키는 것으로 보입니다.


ISP , SRP = 인터페이스 분리 원칙 , 단일 책임 원칙 -> 하나의 책임만 가져라 
Chef는 요리하는 행위만 신경쓰면 됩니다. 그것이 Chef이라는 타입이 해야할 책임입니다.


그런데 갑자기 Chef에게 drive() 기능을 주면 어떤가요? 요리사라는 타입이 해야할 일인가요??
이경우 SRP가 깨진다고 볼 수 있습니다. ISP도 일맥상통한다 생각합니다.


SOLID 개념은 각각의 것들이 유기적으로 결합되어 있어 SOLID중에 3가지만 만족한다, 4가지만 만족한다, 이런 건 없다고 생각합니다.
하나가 안지켜지면 전체적으로 안지켜지고 하나만 잘 지켜도 전체적으로 완성이되는 개념이라 봅니다.

 

마지막으로 간단하게 코드를 변경해 보았는데요
개인적으로는, [직관적인 코드 = 읽기 쉬운 코드 = 좋은 코드 = 클린코드] 라는 생각이 있습니다.
그래서 사람에게 말하는듯이 이름을 작성하는 것을 좋아합니다.

특히 boolean 타입의 경우에는 isXXX~ 식으로 많이 작성하는 것 같습니다.

public boolean validateOrder(Order order) {
    if (order.getItems().size() == 0) {
        log.info("주문 항목이 없습니다.");
        return false;
    } else {
        if (order.getTotalPrice() > 0) {
            if (!order.hasCustomerInfo()) {
                log.info("사용자 정보가 없습니다.");
                return false;
            } else {
                return true;
            }
        } else if (!(order.getTotalPrice() > 0)) {
            log.info("올바르지 않은 총 가격입니다.");
            return false;
        }
    }
    return true;
}

읽기쉽게 , 내가 생각하는 clean code로 변경

    public boolean validateOrder(Order order) {

        if (order.isEmpty()) {
            log.info("주문 항목이 없습니다.");
            return false;
        }

        if (order.isTotalPriceNegative()) {
            log.info("올바르지 않은 총 가격입니다.");
            return false;
        }

        if (order.isCustomerInfoMissing()) {
            log.info("사용자 정보가 없습니다.");
            return false;
        }

        return true;
    }

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

그래서 클린코드가 뭐냐  (0) 2024.10.06
클린코드&테스트 - 추상과 구체  (1) 2024.10.01

+ Recent posts