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

[클린코드 완독하기] Chapter 03 함수

by 6161990 2021. 9. 17.

Chapter 03. 함수 작성하기

01 SOLID 원칙 : 객체지향 설계의 5가지 원칙

 * SOLID원칙 더 이해하기 -> https://6161990src.tistory.com/67?category=845791

 

[JAVA] 객체지향 5대원칙 / POJO JAVA

📌 SRP Single Responsibility Principle 단일 책임 원칙 클래스는 하나의 일에만 책임진다. 즉, 1개의 클래스에 하나의 역할만 가져야한다. 설계중인 프로그래밍에 클래스가 여러가지 역할을 가지고 있다

6161990src.tistory.com

 

1. SRP  단일 책임 원칙

  • 클래스는 하나의 기능만 가지며, 어떤 변화에 의해 클래스를 변경해야하는 이유는 오직 하나뿐이어야한다.
  • SRP 책임이 분명해지기 때문에, 변경에 의한 연쇄작용에서 자유로워질 수 있다.
  • 가독성 향상과 유지보수가 용이해진다.
  • 실전에서는 쉽지 않지만 늘 상기해야한다.

 

2. OCP 개방 폐쇄 원칙

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야한다.
  • 변경을 위한 비용은 가능한 줄이고, 확장을 위한 비용은 극대화 해야한다.
  • 요구사항의 변경이나 추가사항이 발생하더라도 기존 구성요소에는 수정이 일어나지않고, 기존 구성 요소를 쉽게 확장해서 재사용한다.
  • 객체지향의 추상화와 다형성을 활용한다.

 

 

3. LSP 리스코프 치환 원칙

  • 서브타입은 언제나 기반 타입으로 교체할 수 있어야 한다.
  • 서브 타입은 기반 타입이 약속한 규약 (접근제한자, 예외 포함)을 지켜야한다.
  • 클래스 상속, 인터페이스 상속을 이용해 확장성을 획득한다.
  • 다형성과 확장성을 극대화하기 위해 인터페이스를 사용하는 것이 더 좋다. 
  • 합성(composition)을 이용할 수도 있다.

 

 

4. ISP 인터페이스 분리 원칙

  • 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
  • 가능한 최소한의 인터페이스만 구현한다.
  • 만약 어떤 클래스를 이용하는 클라이언트가 여러 개고, 이들이 클래스의 특정 부분만 이용한다면, 여러 인터페이스로 분류하여 클라이언트가 필요한 기능만 전달한다.
  • SRP가 클래스의 단일 책임이라면, ISP는 인터페이스의 단일 책임

 

 

5. DIP 의존성 역전 원칙

  • 상위 모델은 하위 모델에 의존하면 안된다. 둘 다 추상화에 의존해야 한다.
  • 추상화는 세부 사항에 의존해서는 안된다. 세부 사항은 추상화에 따라 달라진다.
  • 하위 모델의 변경이 상위 모듈의 변경을 요구하는 위계관계를 끊는다.
  • 실제 사용관계는 그대로이지만, 추상화를 매개로 메시지를 주고 받으면서 관계를 느슨하게 만든다.

📍 예제로 이해하기

class PaymentController {
       @RequestMapping(value = "/api/payment", method = RequestMethod.POST)
       public void pay(@RequestBody ShinhanCardDto.PaymentRequest req){
             shinhanCardPaymentService.pay(req);
       }
}

class ShinhanCardPaymentService{
       public void pay(ShinhanCardDto.PaymentRequest req){
             shinhanCardApi.pay(req);
       }
}
  • 위와 같은 환경에서 새로운 카드사가 추가된다면?

 

      @RequestMapping(value = "/api/payment", method = RequestMethod.POST)
      public void pay (@RequestBody ShinhanCardDto.PaymentRequest req){
          if(req.getType() == CardType.SHINHAN){
                shinhanCardPaymentService.pay(req);
          } else if(req.getType() == CardType.BC){
                bcCardPaymentService.pay(req);
          }
       }
}
  • 이런 식으로 진행한다면 .. 뭐랄까.. 쫌... 확장에 유연하지 않다. 
  • 뿐만 아니라, 해당 함수에서는 shinhanCardPaymentService에 대한 의존성과 bcCardPaymentService의 의존성 둘 다 주입받아야한다. 만약 삼성카드 , 농협카드가 추가되면....?
  • PaymentController 가 각각에 대해서 의존성을 가지고있는데, 이들 사이에 추상화된 인터페이스를 추가해보자.

 

class PaymentController {
       @RequestMapping(value = "/api/payment", method = RequestMethod.POST)
       public void pay(@RequestBody ShinhanCardDto.PaymentRequest req){
          final CardPaymentService cardPaymentService = cardPaymentService.getType(req.getType));                          cardPaymentService.pay(req);
       }
}

public interface CardPaymentService{
       void pay(CardPaymentDto.PaymentRequest req);
}

class ShinhanCardPaymentService implements CardPaymentService {
       @Override
       public void pay(CardPaymentDto.PaymentRequest req){
             shinhanCardApi.pay(req);
       }
}
  • 인터페이스를 하나 추가했더니 관계가 느슨해졌다.

 

02 간결한 함수 작성하기

📍 길이가 길고 여러 가지 기능을 하는 함수 -> 작게 쪼갠 뒤, 함수 내 추상화 수준을 동일하게 맞춘다.

 

📍 한 가지 기능만(SRP) 을 가지고 있으며, 변경에는 폐쇄적(OCP)으로 .

 

📍 함수 인수는 0~2개가 적당하다

// 객체를 인자로 넘기기
Circle makeCircle(double x, double y, double radius); // 👎
Circle makeCircle(Point center, double radius); //👍

//가변 인자를 넘기기 (특별한 경우가 아니면 잘 사용하지 않는다)
String.format(String format, Object ... args);

 

03 안전한 함수 작성하기 

📍 부수 효과(Side Effect) 없는 함수

* Side Effect ? 값을 반환하는 함수가 외부 상태를 변경하는 경우

ex) 비밀번호를 check하는 메소드에서 비밀번호가 맞으면 session을 초기화 한다. 
---> 메소드의 목적보다 더 큰 범위인 session 초기화 기능이 존재한다. 
---> 부수 효과가 발생해버린다. 

 

04 함수 리팩터링

📍 기능을 구현하는 서투른 함수를 작성한다.

    : 길고, 복잡하고 중복도 있을 것이다... 아마도 

📍 테스트 코드를 작성한다.

    : 함수 내부의 분기와 엣지값마다 빠짐없이 테스트하는 코드를 짠다.

📍 리팩터링 한다.  

    : 코드를 다듬고, 함수를 쪼개고, 이름을 바꾸고, 중복을 제거한다.