inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

워밍업 클럽 2기 BE 클린코드&테스트코드 1주차 발자국

hhw0850
0

Readable Code: 읽기 좋은 코드를 작성하는 사고법 수강 후 작성한 1주차 발자국입니다.

 

1주차 강의 정리

섹션1

인텔리제이, jdk 설치 및 readable code fork 를 진행하였다.

기존에 jdk8버전을 사용했기에 추가로 jdk17버전을 설치하여 실행했다.

섹션2

추상화

추상화 레벨

매직넘버, 매직 스트링

섹션3

뇌 메모리 적게 쓰기

Early return

사고의 depth 줄이기

공백 라인을 대하는 자세

→ 복잡한 로직의 의미 단위를 나누어 보여줌으로써 읽는 사람에게 추가적인 정보를 전달할 수 있다.

부정어를 대하는 자세

해피케이스와 예외처리

섹션4

객체 설계하기

객체가 제공하는 것

새로운 객체를 만들 때 주의할 점

SOLID

SRP: Single Responsibility Principle

단일 책임 원칙, 하나의 클래스가 하나의 책임만 갖도록 설계하는 원칙

OCP: Open-Closed Principle

개방-폐쇄 원칙

LSP: Liskov Substitution Principle

리스코프 치환 원칙

ISP : Interface Segregation Principle

인터페이스 분리 원칙

DIP: Dependency Inversion Principle

의존성 역전 원칙

섹션5

상속과 조합

Value Object

일급 컬렉션

Enum

다형성 활용하기

추상화와 다형성을 활용하여 반복되는 if문 제거, OCP 지키기

숨겨져 있는 도메인 개념 도출하기


1주차 강의 수강 회고

이번 1주차에서는 지금까지의 잘못된 코드 습관들을 하나씩 알게 되었고 강의에서 배운대로 고쳐 나가야겠다고 다짐하게되었다. 이번에 알게된 나의 잘못된 코드 습관은 다음과 같다.

  1. 복수형 이름 짓기 시 ~(e)s 사용하지 않고 ~List 사용했던 것

  2. 줄임말 사용했던 것 ex) cnt, idx 등

  3. 추상화 레벨을 생각하지 않았던 것

  4. 부정연산자 많이 사용했던 것

  5. getter, setter 남발했던 것

     

강의를 보면서 뜨끔했던 순간이 정말 많았다. 그중 특히나 추상화 레벨 강의가 기억에 남는다. 강사님께서 안 좋은 예시를 처음에 보여주셨는데 나였으면 그냥 작성하고 넘어갔을 것 같은 코드였어서 정말 뜨끔했다. 이번 강의를 통해 추상과 구체에 대해 알게 됐고 이제 코드에 배운대로 적용해봐야겠다고 생각했다. 아직 많이 부족하지만 코드를 작성할때 나름 가독성있게 코드를 작성하려고 노력했었는데 클린코드를 어떻게 작성하는지 제대로 강의를 들어보니 지금껏 내가 작성했던 코드는 뭐였을까... 다시 한 번 되돌아 볼 수 있었던 1주였던 것 같다.


1주차 미션

DAY2 미션

추상과 구체 예시 작성

'라면끓이기'를 구체 레벨에서 표현한다면?

  1. 라면 1개를 준비한다.

  2. 냄비를 준비한다.

  3. 물 500ml을 준비한다.

  4. 가열장치를 준비한다.

  5. 가열장치에 냄비를 올린다.

  6. 냄비에 물 500ml을 넣는다.

  7. 가열장치를 켜서 냄비를 가열한다.

  8. 냄비의 물이 끓을때까지 기다린다.

  9. 면을 넣는다.

  10. 건더기 스프를 넣는다.

  11. 분말 스프를 넣는다.

  12. 3분동안 면을 저으면서 끓인다.

  13. 가열장치를 끈다.

  14. 라면을 그릇에 담아 완성한다.

     

     

     

     

    '라면 끓이기' 라는 말이 물넣고 스프넣고 면넣고,, 등등 모든 과정이 함축되어 있는 말 같아서 위와 같이 작성했다.

     

    DAY4 미션

    1. 아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.

    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;
    }
    • 사용자가 생성한 '주문'이 유효한지를 검증하는 메서드.

    • Order는 주문 객체이고, 필요하다면 Order에 추가적인 메서드를 만들어도 된다. (Order 내부의 구현을 구체적으로 할 필요는 없다.)

    • 필요하다면 메서드를 추출할 수 있다.

       

     

    풀이과정

    validateOrder 메서드에서 수행하는 기능을 간단하게 정리하면,

    1. 주문 항목이 없는지 체크

    2. 총 가격이 0보다 작은 수인지 체크

    3. 사용자 정보가 없는지 체크

    총 3단계로 정리하였다. 그리고 각 단계에서 Early Return을 적용하여 불필요한 else를 없애고 depth를 줄였다.

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

     

    위 코드에서 부정 연산자(!)를 긍정어로 수정하고 get~ 같은 직접적인 메소드명 대신 Order 객체에게 의도된 메세지를 전달하여 가독성 좋게 if문을 수정하였다.

    public boolean validateOrder(Order order) {
        // 1. 주문 항목이 없는지 체크
        if (order.hasNoneItem()) {
            log.info("주문 항목이 없습니다.");
            return false;
        }
    
        // 2. 총 가격이 0보다 작은 수인지 체크
        if (order.isTotalPriceLessThenZero()) {
            log.info("올바르지 않은 총 가격입니다.");
            return false;
        }
    
        // 3. 사용자 정보가 없는지 체크
        if (order.hasNoneCustomerInfo()) {
            log.info("사용자 정보가 없습니다.");
            return false;
        }
        return true;
    }

    2. SOLID에 대하여 자기만의 언어로 정리해 봅시다.

    SRP: Single Responsibility Principle

    SRP는 하나의 클래스, 하나의 객체는 하나의 책임만을 가져야 한다는 원칙이다. 우리는 코드를 작성할 때 응집도가 높게, 결합도가 낮게 동작하도록 구현하여야 하는데 SRP를 지키게 되면 높은 응집도, 낮은 결합도를 가지게 된다. 응집도란 클래스나 모듈 내에 있는 요소들이 연관되어 있는 정도를 말하며 SRP를 지키면 이 연관되어 있는 정도가 낮아지므로 응집도는 높아진다. 결합도는 두 개 이상의 객체가 협력한다고 했을 때 하나의 객체가 변경될 경우 다른 객체가 영향 받는 정도를 의미한다. SRP를 지키면 두 객체간 의존성을 최소화시킬 수 있으므로 결합도를 낮출 수 있다.

     

    OCP: Open-Closed Principle

    OCP는 확장에는 열려있고, 수정에는 닫혀있어야 한다는 원칙이다. 우리는 코드를 한 번 작성하게 되면 끝이 아니라 지속적으로 코드를 수정하게되는 상황이 발생한다. 만약 코드 작성 후 새로운 요구사항이 발생하면 기존 코드를 수정해야하는데 기존 코드의 변경 없이 새로운 요구사항을 적용시킬 수 있어야 한다. 그렇게 하기 위해서는 인터페이스와 같은 추상을 통해 개방-폐쇄 원칙을 지킬 수 있으며 항상 기능의 확장을 고려하여 코드를 작성해야 한다.

     

    LSP: Liskov Substitution Principle

    LSP는 상속 구조를 설계할 때 부모 클래스를 자식 클래스로 변경해도 동일 결과를 낼 수 있도록 설계해야한다는 원칙이다. 자식 클래스는 부모 클래스의 기능 + @로 구현되어야 하고 부모 클래스의 기능이 변경되면 안 된다. 만약 LSP를 위반한다면 상속 구조에서 오동작이 발생할 수 있으므로 LSP를 지켜 상속 구조를 설계해야 한다.

     

    ISP : Interface Segregation Principle

    ISP는 기능 단위로 인터페이스를 분리하라는 원칙이다. 하나의 인터페이스 안에 기능이 여러가지 들어있다면 인터페이스 사용 시 사용하지 않는 기능이 생길 것이다. 이렇게 되면 불필요한 의존성이 발생하므로 결합도가 높아지게 된다. 또한 인터페이스에 기능 변경이 생겼을 때 여러 클래스에 영향을 끼칠 수 있다. 이를 방지하게 위해서 인터페이스는 기능 단위로 잘게 쪼개서 사용해야한다.

     

    DIP: Dependency Inversion Principle

    DIP는 상위 수준의 모듈은 하위 수준의 모듈에 의존하면 안되며 모두 추상화에 의존해야한다는 원칙이다. 상위 수준의 모듈은 추상화 레벨이 높은 모듈을 의미하고, 하위 수준의 모듈은 추상화 레벨이 낮은 모듈을 의미한다. 만약 상위 수준의 모듈이 하위 수준의 모듈을 참조하게되면 문제가 발생할 수 있다. 하위 수준 모듈은 구체에 가깝기 때문에 변경될 가능성이 높다. 변경 가능성이 높다면 상위 수준 모듈에도 영향을 줄 수 밖에 없다. 그래서 인터페이스와 같은 추상을 통해 상위수준, 하위수준 모듈 모두 직접 서로 의존하는게 아니라, 추상화에 의존하도록 하는 의존성 역전이 필요하다.


    1주차 미션 회고

    DAY4 미션 코드 리팩토링에서 강의를 듣기 전이었다면 Early return만 적용해서 else, else if만 없애고 끝냈을 것이었다. 이번에 강의를 듣고 '부정어 사용 지양', '객체에게 메세지 전달' 을 배워 부정 연산자(!)를 긍정어로 수정하고 get~ 같은 직접적인 메소드명 대신 Order 객체에게 의도된 메세지를 전달하도록 리팩토링 하였다. 앞으로 코드를 작성할 때도 강의에서 배운 내용 대로 적용해봐야겠다고 생각했다.


답변 0