클린 코드는 무엇일까요? 보통 "읽기 좋은 코드"라는 말을 하곤 합니다.
그런데 "읽기 좋다"는 무엇을 의미할까요?

주석을 아예 달지 않으면 읽기 좋은 코드일까요? 이건 좀 아닌 것 같고...
변수의 이름을 잘 짓는 것? 이것만으로는 좀 부족한 것 같고...

이런 고민을 해결하고자 클린 코드 & 테스트 스터디에 참여해 활동한 지 1주 차가 됐습니다.

첫 주차는 객체 지향의 가장 기본이 되는 "추상화"에 대해서 말하는 시간이었습니다.
물리적으로 나뉜 세션은 여러 가지였지만, 다룬 내용들이 대부분 "추상화"를 뒷받침하는 요소였기 때문에 1주 동안 추상화에 대해서 말했다 해도 괜찮을 것 같습니다.

그리고 이 부분은 매우 중요하다고 생각하는 것이, 기본이 곧 쉽다는 의미는 아니기 때문입니다.
기본은 너무나 중요해서 기본이라 부르는 것이지, 쉬워서 기본이라 부르는 것은 아니니까요.

스터디의 진행은 기본적으로 인프런을 통해 온라인으로 학습하고 미션을 통해 학습도를 점검하는 방식으로 진행됩니다.

1주차 미션 자체는 실질적으로 코드를 작성하여 검증하는 느낌은 아니었고, "나는 강의에서 이렇게 말했는데, 너는 어떻게 생각해?"라는 느낌이었습니다.
그 생각을 묻는 질문에 대한 답변이 곧 미션이고, 저의 경우에는 블로그에 글을 기재하는 방식으로 수행했습니다.

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

 

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

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

masiljangajji-coding.tistory.com

 


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

 

클린코드&테스트 - 객체지향과 SOLID

제가 저번 글에서 다음과 같이 말한적이 있습니다.https://masiljangajji-coding.tistory.com/66 클린코드&테스트 - 추상과 구체요즘은 코드를 잘 짠다 = 읽기가 좋다 = 가독성 있다 , 라는 말을 많이 하는 것

masiljangajji-coding.tistory.com

 

이 글에서 가장 하고 싶은 말은 "클린 코드", "TDD", "DDD" 등은 그저 도구라는 것입니다.
저 모든 것들은 단순히 "이렇게 하면 좋다더라~"를 한 단어로 묶어 개념화한 도구입니다.
따라서 도입하는 것이 생산성을 높이면 도입하면 되는 것이고, 떨어지면? 도입하지 않아도 됩니다.

주석을 달았더니 팀이 코드를 더 쉽게 이해하고 생산성이 증가하는데? -> 그럼 주석을 다는 게 클린 코드입니다.
주석을 다는 행위로 인해 코드의 가독성이 증가했기 때문입니다.

소프트웨어를 개발하는 사람들은 생각도 soft하게 할 필요가 있지 않나 생각합니다.
어떻게 커스터마이징하느냐에 따라서 우리 팀만의 클린코드 컨벤션이 존재할 수 있다고 생각합니다.

한 줄로 정리하면, XX를 하면 클린 코드? XX를 안 하면 클린 코드? 이런 게 아니라,
생산성을 증가시키면 그게 뭐가 됐든 클린 코드라 생각합니다.

강의 내용에 대해서 말하자면 평소에 생각하던 부분들과 일치하는 부분이 많아 그리 어렵지 않게 들을 수 있었습니다.
그런데 가끔 "아, 이렇게도 생각할 수 있네?", "이런 게 기준이 될 수 있겠네?" 하는 부분들이 있어 생각했던 것보다 이미 많은 것을 얻어간 느낌입니다.

사실 강의 자체의 퀄리티가 좋습니다. 

이번 주는 강의를 듣고 미션을 수행하는 것이 조금 버거웠습니다.
강의의 양 자체가 적지 않기도 했고, 더 우선적으로 수행해야 할 과제들이 있어 더욱 그러지 않았나 생각됩니다.

주말 동안 재충전의 시간을 갖고 돌아오는 월요일부터는 다시 달려보도록 하겠습니다.

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

클린코드&테스트 - 객체지향과 SOLID  (2) 2024.10.03
클린코드&테스트 - 추상과 구체  (1) 2024.10.01

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

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


요즘은 코드를 잘 짠다 = 읽기가 좋다 = 가독성 있다 , 라는 말을 많이 하는 것 같습니다.

저 또한 매우 동의하는 말인데요 하지만 개인적으로 "가독성 있다"라는 말 또한 추상적인 개념이라 생각합니다.
가독성 있는게 좋아? , 그럼 어떤 코드가 가독성 있는건데? 라는 질문을 받게 되는 것이죠

그래서 우리는 "가독성 있는 것"에 대한 기준이 필요한데 저에게는 "직관적이냐" 입니다.

다음은 제가 좋아하는 3-Tier Architecture인데요 이것을 처음 봤을 때 느낀것은 정말 직관적이다 였습니다.

계층과 책임이 너무 명확히 나눠져있어서 누가 봐도 이해가 가능한, 이런게 "직관적이다" 라고 생각하고
이런 직관적임을 코드레벨까지 성공적으로 가져 왔을 때 곧 읽기좋은 코드가 된다 생각합니다.

 

 

 


저는 추상화를 이렇게 정의합니다,  무언가를 간추린다.
무언가를 간추린다면 전부 추상화에 해당하며 적용되는 범위또한 굉장히 넓다 생각합니다. 

예를 들어 우리가 "탈 것"이라는 인터페이스를 만든다면 이는 추상화 정도가 굉장히 높은 타입을 설계하는 것과 동일 하거든요
"탈 것"이라는 타입은 추상화 정도가 너무 높아 , 구체화가 없고 단순히 명시만 돼있는 Specification의 역할만 하는 것이죠.

단순히 말하면 야 너는 어떻게 하는지는 너 마음인데 , 가속이랑 감속은 가능해야 한다? 같은 것이죠 
따라서 구체화된 코드 없이 메서드의 Spec만 명시합니다.

"탈 것"을 구현한 "자동차"라는 타입을 만들고 이에 대한 api를 제공한다면 이 또한 추상화입니다.
api라는 통로를 이용해 외부에서 이 자동차라는 타입이 어떻게 설계되고 내부적으로 어떻게 동작하는지를 몰라도 
사용할 수 있게 됐기 때문입니다.

우리가 docker run 명령어를 사용했을때 내부적으로 어떻게 동작하는지 정확한 이해와 함께 쓴다고 생각하진 않습니다.
아 이 명령어는 = 이 api는 이런 동작을 하는 구나~ 정도의 이해만 가지고 있는 것이죠 

이렇게 api는 외부에서 접근 가능한 일종의 추상계층으로서 역할을 하게 됩니다.

저는 정보를 숨기는 것 , 절차를 간추리는 것 , 데이터를 간추리는 것.... 이 모든 것이 추상화의 영역이라 생각합니다.

마침 1년 전쯤에 추상화에 대한 글을 작성한 적이 있는데요 많이 부족한 글이지만 첨부해봅니다.

 

Abstraction(추상화) 기본 개념 - 1편

Abstraction(추상화)란? 자바에서는 추상클래스 , 추상메서드 , 추상화 등 "추상"이라는 말이 자주 쓰입니다. 그렇다면 추상화란 무엇일까요? 추상화는 프로그래밍에서 매우 중요한 개념 중 하나이

masiljangajji-coding.tistory.com

 

 

 

제가 이번 세션에서 가장 인상깊게 들었던 부분은 추상화 레벨인데요 


같은 세계에서는 추상화의 정도가 같아야 한다. 라는 의미입니다.
와 이건 정말 생각하지 못했던 부분이라 놀랐는데요 

이런 피드백을 한번쯤은 들어보셨을 겁니다.

"메서드 여러개로 분리해라" 

이유는 간단한데요 거대한 기능을 하나의 메서드로만 관리하면 너무 많은 책임을 갖게 됩니다.
기능이 고도화 될 수록 메서드는 더욱 거대해지고 이는 직관적으로 표현하기가 어려짐을 의미합니다.

따라서... 책임의 분리 -> 직관적인 표현 + 유지보수의 편의성을 위해 메서드를 분리 하는 것인데요

하지만 "그럼 정말 모든 메서드를 기능별로 하나하나씩 쪼개서 최대한 작은 단위로 만들어야 하나?" 라는 질문을 한다면  
저는 아니요 라는 답변을 할 것입니다.

여러 행위를 한다 해도 그것이 하나의 주제와 문맥으로 설명 가능하다면 하나로 만들어도 된다 생각합니다.

왜냐하면 메서드가 많아지는 것도 직관적인 것과는 거리가 멀어지기 때문입니다.
메서드를 분리한다는 것은 결국 코드의 양이 늘어난다는 것이며 누군가가 내 코드를 다룰 때 찾아봐야 하는 부분 또한 늘어납니다.
따라서 마냥 쪼개는 것이 좋다고는 생각 안합니다.
 


그리고 이런 고민은 커밋을 할때도 똑같이 발생 하는데요
커밋도 한번에 많은 양을 올리지 마라 , 쪼개서 하나의 단위씩 올려야 좋다. 라고 말하곤 합니다.
그럼 정말 하나의 코드 변경 = 1커밋 이여야 할까? , 만약 우리가 리펙토링을 통해서 이름을 변경했다고 했을 때
A이름변경 , B이름변경 , C이름변경 ......... 이렇게 이름변경에 관한 커밋을 수십개씩 쪼개놓았다면 어떨까요?

코드를 리뷰하려고 봤는데 커밋이 수십개씩 쌓여있으면 리뷰하기가 겁나는 경험이 있으실 겁니다.
설령 그것이 큰 내용이 없는 코드라 할지라도 너무 잘게 쪼개져있으면 집중력있게 리뷰하는 것은 어려워 집니다.
중요한 변경사항이 아니라면 차라리 묶어서 표현하는게 좋지 않을까요? User 관련 객체 이름변경.. 등으로

 

이렇게 되면 머리가 아파집니다. 무작정 쪼갤수도 , 무작정 합칠수도 없다면 무엇을 기준으로 쪼개고 합쳐야 하나?? 라는 것이죠
이 추상화 레벨에 대한 것은 판단에 대한 하나의 기준이 될 수 있다 생각합니다.

-> 지금 얘가 가지고 있는 추상화 레벨의 정도가 주변의 것들과는 조금 다른거 같은데??
-> 위에는 전부 강하게 추상화하여 구체화를 숨기고 명시적으로 어떤 기능을 할 것임만을 말했는데 갑자기 구체화가 등장하네??
-> 아 이거는 메서드 분리 해야겠다

와 같은 흐름을 통하는 것이죠 , 이 부분에 대해서는 기존에 생각했던 것이 아니라 참 인상깊은 부분이였습니다.

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

그래서 클린코드가 뭐냐  (0) 2024.10.06
클린코드&테스트 - 객체지향과 SOLID  (2) 2024.10.03

+ Recent posts