인프런 워밍업 클럽 2기 - BE 클린코드, 테스트코드 스터디 1주차 발자국

인프런 워밍업 클럽 2기 - BE 클린코드, 테스트코드 스터디 1주차 발자국

일주일 간 학습 내용 요약

추상화 레벨을 맞추자.
읽는사람이 한번 더 생각하지 않도록 항상 코드를 추상화 레벨에 맞춰서 작성을 하는것이 중요한것 같다.
추상화 레벨에 맞게 구체화된 코드를 메서드로 만들어 추상화시키고 그에 맞춰서 이름을 지어주자.
사실 이름짓기가 제일 어려운것 같다.

읽는 사람으로 하여금 뇌의 메모리를 적게 쓰게 하자.
평상시에 사용하는 if - else 문은 코드가 길어질수록 생각을 많이 해야한다.
else 문에서 그 위에있던 if문의 조건들을 다 기억하고 제외해야하기 때문이다.
이럴때는 Early return을 사용하여 뇌의 메모리를 비워주도록 해보자.

공백라인을 사용해서 코드의 가독성을 높히자.
단순히 공백라인을 사용하는것이 아니라 특정 단위로 끊어서 의미있게 사용을 해보자.

객체를 설계할때 getter/setter 사용을 자제하자
객체를 만들때 getter와 setter를 무조건 만들곤 했는데, 매우 폭력적이란 말씀에 어떤식으로 설계해야 되는지 확 와닿았다.
객체를 객체답게 대우를 해주어야 할것 같다.


SOLID:

Single Responsibility Principle (SRP) - 단일 책임 원칙

• 클래스는 단 하나의 책임만 가져야 한다. 즉, 하나의 클래스가 하나의 기능만 담당해야 한다.

Open/Closed Principle (OCP) - 개방/폐쇄 원칙

• 클래스는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.

Liskov Substitution Principle (LSP) - 리스코프 치환 원칙

• 서브타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.

Interface Segregation Principle (ISP) - 인터페이스 분리 원칙

• 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

Dependency Inversion Principle (DIP) - 의존성 역전 원칙

• 고수준 모듈은 저수준 모듈에 의존해서는 안 된다. 둘 다 추상화된 것에 의존해야 한다.


회고

지난 일주일 동안 몇가지의 읽기 좋은 코드를 작성하는 방법을 학습을 했는데 일단 내가 이 스터디를 신청한 이유는
아직 이 분야에 대해 배운지는 얼마 되지 않았지만 처음부터 보기 좋은 코드를 짜는 습관을 들여야 된다고 생각해서 신청하게 되었다.

추상과 구체를 배우게 됐는데 원래 개념적으로 알고는 있었지만 직접 코드를 작성하면서 배우니 이렇게까지
추상,구체화할수 있겠구나 라는 생각이 들었다. 코드가 작동되도록 작성하는것도 중요하겠지만 나중에 코드를 읽어볼때
잘 읽히는 코드가 좋은 코드라고 생각이 되었다. 내가 저번주에 작성한 코드도 지금보면 읽기 힘들때가 많다.
코드를 작성한 순간 레거시 코드가 된다는것에 크게 공감했다.

칭찬하고 싶은 점: 일단 미션과 발자국을 기한안에 제출했고, 정해진 일정에 맞춰 스터디를 진행한점은 만족한다.

아쉬웠던 점: 점점 갈수록 코드가 복잡해져서 완벽히 이해 안가는 부분을 넘어갔는데 하나둘씩 쌓이면서 처음과는 달리 여러번 돌려봐야 이해되는 부분이 생겼다.

보완하고 싶은 점: 시간을 조금더 투자해서 확실히 이해하고 내것으로 만들자.


다음 주 학습 목표

• 다음주에는 내용이 더 딥해지겠지만 내것으로 만들고 잘 따라가는 것이 목표다.


미션 해결 과정

 

[Day 2 미션]

헬스장에서 운동을 하는 과정을 구체화시켜보았다.
헬스장에서 운동을 한다를 한단계 구체화해서
운동준비, 웨이트 트레이닝, 유산소운동, 마무리 로 구체화시켰고
거기서 각각 한단계 더 구체화 시켜보았다.

추상 : 헬스장에서 운동을 한다.

구체 :
[운동 준비]

  • 헬스장에 도착하면 먼저 운동복으로 갈아입는다.

  • 스트레칭을 통해 근육을 풀어준다.

  • 운동 계획을 확인하면서 오늘 할 운동을 상기시킨다.

[웨이트 트레이닝]

  • 벤치 프레스를 할 경우, 먼저 벤치에 누워 발을 단단히 바닥에 고정한다.

  • 바벨을 양손으로 어깨 너비보다 조금 넓게 잡고, 안정된 자세를 잡는다.

  • 숨을 들이쉬면서 바벨을 가슴까지 천천히 내린다.

  • 가슴에 닿기 직전에 멈추고, 숨을 내쉬면서 힘을 주어 바벨을 다시 위로 밀어 올린다.

  • 팔을 완전히 펴지 않고, 긴장감을 유지한 채 반복한다.

  • 세트가 끝나면 잠시 휴식을 취하며 호흡을 정돈한다.

[유산소 운동]

  • 러닝머신을 이용할 경우, 먼저 속도를 천천히 올리며 워밍업을 한다.

  • 몸이 충분히 풀리면 적당한 속도로 달리기 시작한다.

  • 시간이 지나면서 속도를 조금씩 올리며, 목표로 한 시간 혹은 거리만큼 달린다.

  • 마지막 5분은 속도를 낮춰 워밍다운을 통해 심박수를 천천히 안정시킨다.

[마무리]

  • 모든 운동을 끝낸 후에는 다시 스트레칭을 통해 근육을 풀어준다.

  • 샤워를 하고, 단백질 쉐이크를 먹어준다.

[Day 4 미션]

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

AS-IS

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;
}

 

AS-IS의 코드를 보면 불필요한 else문이 많고, If문의 조건식에 해당하는 코드를 보면 생각을 한번 거쳐야 이해되는 코드이다. [섹션 3. 논리, 사고의 흐름] 에서 배웠던 내용을 적용해보면 Early return으로 else문을 적지않고 다음 조건으로 넘어가기, 조건문 메서드로 뽑아내어 추상화 시키기, 그리고 부정어 사용을 지양했더니 훨씬 읽기 좋고 뇌의 메모리가 적게쓰인다는 것을 느꼈다.

TO-BE

validateOrder 메서드

    public boolean validateOrder(Order order) {
        if (order.hasNoItems()) {
            log.info("주문 항목이 없습니다.");
            return false;
        }
        if (order.isTotalPriceInvalid()) {
            log.info("올바르지 않은 총 가격입니다.");
            return false;
        }
        if (order.hasNoCustomerInfo()) {
            log.info("사용자 정보가 없습니다.");
            return false;
        }
        return true;

    }

Order.java

package cleancode.mission.day4.tobe;

import java.util.Map;

public class Order {
    Map<Item,Integer> items;
    String userid;

    public int calculateTotalPrice() {
        return items.entrySet().stream()
                .mapToInt(entry -> (int) (entry.getKey().getPrice() * entry.getValue()))
                .sum();
    }

    public boolean hasNoItems() {
        return this.items.isEmpty();
    }

    public boolean isTotalPriceInvalid() {
        int totalPrice = calculateTotalPrice();
        return totalPrice < 0;
    }

    public boolean hasNoCustomerInfo() {
        return userid == null || userid.trim().isEmpty();
    }
}

Item.java

package cleancode.mission.day4.tobe;

public class Item {
    String itemName;
    int price;

    public int getPrice() {
        return this.price;
    }
}

AS-IS의 코드를 보면 불필요한 else문이 많고 If문의 조건식에 해당하는 코드를 보면 생각을 한번 거쳐야 이해되는 코드들인데 [섹션 3. 논리, 사고의 흐름] 에서 배웠던 내용을 적용해보면 Early return으로 else문을 적지않고 다음 조건으로 넘어가기, 조건문 메서드로 뽑아내어 추상화 시키기, 그리고 부정어 사용을 지양했더니 훨씬 읽기 좋고 뇌의 메모리가 적게쓰인다는 것을 느꼈다.


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

SOLID

SOLID는 코드의 유지보수성과 확장성을 높이는데 도움을 주기위해 만들어진 원칙이다.

처음 자바공부를 할때 배우곤 당장의 코드짜기에 급급해 되짚어보지 않았지만 코드가 점점 복잡해 질수록 원칙의 필요성이 느껴진다. 단순히 개념을 배우기보다는 직접 코드를 작성하면서 깊게 생각해보고 이해도를 높여야겠다.

SRP(Single Responsibility Principle) : 단일 책임 원칙

하나의 객체에는 하나의 책임을 가져야 한다. → 높은 응집도와 낮은 결합도를 갖기위해 사람마다 역할과 책임의 경계가 모호하기 때문에 최대한 작성자의 의도를 알수있고, 일관성 있게 작성하여야 한다.

만약 객체가 여러 역할을 담당하면 기능이 변경될 때마다 그 객체도 계속 수정되어야 할 것이다. ⇒ 객체의 수정 이유가 여러가지 이다.

또한 어떤 기능을 수정하려고 하는데 그 기능이 어떤 객체에 들어가 있는지, 어느 부분에 있는지 내가 원하는 부분을 찾기 힘들 것이다. 하나의 객체에 하나의 책임을 가지게 되면 수정하고자 하는 부분을 찾기 수월해지고 객체를 수정하는 이유와 의도를 명확히 할수 있다. 이로써 유지보수성을 높일 수 있게 된다.

OCP(Open-Closed Principle) : 개방-폐쇄 원칙

확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다. 새로운 요구사항이 발생하더라도 기존 코드를 변경하지않고 새 기능을 추가할수 있어야 한다.

만약 기존 코드를 변경해야 한다면, 그로 인해 새로운 버그가 발생할 가능성이 높아지고 다른 모듈이나 클래스에 의존성이 있는 경우 문제가 확산될 수 있다.

상속이나 인터페이스 등을 활용해 기능을 확장하면서도 기존 코드를 손대지 않는 구조를 만들면 코드의 안정성이 높아진다.

LSP(Liskov Substitution Principle) : 리스코프 치환 원칙

하위 클래스는 상위 클래스의 역할을 대신할 수 있어야 한다. 즉, 상위 클래스의 인스턴스가 필요한 자리에 하위 클래스의 인스턴스를 대입해도 프로그램이 정상적으로 작동해야 한다.

내가 이해한 바로는 부모클래스의 모든 기능을 자식클래스가 수행할수 있어야 한다는 것이다. 부모클래스의 기능에 추가적인 기능을 부여하면서 점점 구체화 시키는것이 자식클래스 인데 부모클래스의 기능을 수행할수 없다면 그것은 구체화 했다고 할 수 없고 정체성을 잃어버리기 때문이다. 이렇게되면 코드가 의도하지 않은 방식으로 동작할 수 있다.

ISP(Interface Segregation Principle) : 인터페이스 분리 원칙

클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다. 하나의 큰 인터페이스보다 각 클라이언트가 필요한 메서드가 가지는 작은 인터페이스로 분리하는 것이 좋다. 큰 인터페이스를 구현하게 되면, 그 인터페이스의 메서드를 모두 구현해야 하는 부담이 생기며, 사용하지 않는 기능에 대한 구현도 강요받게 된다.

DIP(Dependency Inversion Principle) : 의존성 역전 원칙

상위 모듈은 하위 모듈에 의존해서는 안 되며, 둘다 추상화 된 것에 의존해야 한다. ⇒ 구체적인 구현이 아닌 추상적인 인터페이스에 의존해야 한다.

만약 상위 모듈이 하위 모듈에 직접 의존하면, 하위 모듈의 변화가 상위 모듈에 큰 영향을 미치게 된다. 따라서 둘 다 추상화된 인터페이스나 추상 클래스에 의존하게 하여, 결합도를 낮추는 것이다.

처음에는 OCP 와 비슷한것 아닌가? 라는 생각이 들었다. 둘다 객체지향 설계의 중요한 원칙이지만, 그 초점과 목적이 다르다. OCP는 확장과 수정의 관리에 초점을 맞추고 DIP는 의존성 관리에 초점을 맞춘 원칙이다.


참고강의

박우빈 - Readable Code: 읽기 좋은 코드를 작성하는 사고법

댓글을 작성해보세요.

채널톡 아이콘