반응형

창발적 설계로 깔끔한 코드 구현하기

 

목차

1. 창발적 설계란

2. 모든 테스트를 실행한다

3. 중복을 없앤다

4. 의도를 표현한다

5. 실용적 관점에서 타협한다.


1. 창발적 설계란

창발성(Emergence) :

  - 하위 계층에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출연하는 현상.

  - 각각의 개미는 집을 지을 능력이 없지만, 작은 개미들의 상호작용을 통해 집이라는 결과물이 나오는 것처럼 작은 요소들의 상호작용의 반복이 전체구조에 영향을 미친다.

 

창발적 설계 : 

  단순한 4가지를 반복하다보면 전체적으로 깨끗한 코드가 만들어진다.

 

'켄트 백' 이 주장한 창발적 설계 원칙

  1. 모든 테스트를 실행한다.
  2. 중복을 없앤다.
  3. 프로그래머 의도를 표현한다.
  4. 클래스와 메서드 수를 최소로 줄인다. 실용적 관점에서 타협한다.

원칙의 중요도는 위 순서와 같다.

 

1. 모든 테스트를 실행한다.

  * 테스트를 작성할수록 설계 품질이 좋아진다.

  - 모든 테스트 케이스를 향상 통과하는 시스템은 '테스트가 가능한 시스템'이다. 테스트가 불가능한 시스템은 검증도 불가능하고, 절대 출시하면 안된다.

  - 테스트가 가능한 시스템을 만들려고 애쓰면 설계 품질이 높아진다. 크기가 작고 목적 하나만 수행하는 클래스가 나온다.

  - 결합도가 높으면 테스트 케이스를 작성하기 어렵기 때문에 결합도를 낮추는 설계를 하게 된다.

  - '모든 테스트를 실행한다'는 규칙을 따르면 시스템은 낮은 결합도와 높은 응집력이라는 목표를 저절도 달성할 수 있다.

 

2. 중복을 없앤다

  * 기존의 코드를 최대한 재활용한다.

  - 중복을 없애는 패턴중에 Template Method 패턴 :

        알고리즘의 구조를 상위 클래스의 메서드에서 정의하고, 하위 클래스에서 자신의 맞게 세부 알고리즘을 정의한다.

 

3. 의도를 표현하라

  * 다른 사람을 위해서 읽기 쉽게 만들어야 한다. (본인이 다른 사람이 될 수도 있다.)

  1. 좋은 이름을 선택한다.
  2. 함수와 클래스 크기를 가능한 줄인다. 작은 클래스와 작은 함수는 이름 짓기도 쉽다!
  3. 표준 명칭을 사용한다. 다른 개발자가 보고 바로 이해할 수 있도록 디자인 패턴을 사용했다면 그 이름을 클래스에 넣어준다.
  4. 단위 테스트 케이스를 꼼꼼하게 작성한다.
  5. 다른 사람을 위해 조금이라도 더! 읽기 쉽게 만들려고 노력한다.

 

4. 클래스와 메서드 수를 최소로 줄인다. 실용적 관점에서 타협한다.

  * 과도한 설계를 하지 말아야 한다.

  - 여러가지 규칙을 극단적으로 심취해 클래스와 메서드를 무수하게 만들지 말라

  - 결국 좋은 코드를 만드는 이유는 생산성을 올리기 위한것이다.

  - 실용적인 관점에서 타협해야 한다.

  - 환경과 상황이 바뀌면 그때 설게를 하면 된다. 거의 발생하지 않을 일에 미리 투자하는 비용이 더 크다.

 

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 14  (0) 2022.04.02
[Clean Code] Chapter 13  (0) 2022.03.31
[Clean Code] Chapter 11  (0) 2022.03.27
[Clean Code] Chapter 10  (0) 2022.03.26
[Clean Code] Chapter 09  (0) 2022.03.21
반응형

관심사 분리 패턴들

 

목차

1. 관심사 분리

2. Dependency Injection (의존성 주입)

3. Cross Cutting Concerns (횡단 관심 분리)


1. 관심사 분리

constrction(생성)과 use(사용)은 아주 다르다.

  - 소프트웨어 시스템은(어플리케이션 객체를 제작하고 의존성을 서로 '연결'하는) 준비 과정과 (준비 과정 이후에 이어지는) 런타임 로직을 분리해야 한다.

  - 객체의 생성과 객체를 사용하는 부분을 분리한다.

 

시작에 대한 관심사 분리

 객체의 생성은 시작 단계에서, 비즈니스 로직은 객체를 사용하는데 집중한다.

  - 시작 단계는 모든 어플리케이션이 풀어야할 관심사이다.

  - main 함수에서 시스템에 필요한 객체를 생성한 후 어플리케이션에 넘긴다.

  - 어플리케이션은 그저 만들어진 객체를 사용한다.

  - 모든 객체가 잘 생성되었다고 가정하고, 객체를 이용한 개발에 집중할 수 있다.

 

요청에 대한 관심사 분리

Spring 프레임워크를 통해 요청에 대한 관심사를 분리해 요청 처리에 대한 비즈니스 로직에 집중할 수 있다. (Filter, Intercepter, AOP)

  - 서블릿 필터는 DispatcherSevlet 이전에 실행이 되는데 요청 내용을 변경하거나, 요청을 처리하기 전에 작업을 수행할 수 있다.

  - Filter와 Interceptor는 Servlet 단위에서 실행된다. 반면 AOP는 메소드 앞에서 Proxy Pattern으로 실행된다.

  - 인터셉터는 여러 개를 사용할 수 있고 로그인 처리, 권한체크, 프로그램 실행시간 계산작업, 로그확인 등의 업무처리에 활용된다.

  - AOP는 메서드 앞에서 Proxy Pattern으로 실행된다. 주로 '로깅', '트랜잭션', '에러처리' 등 비즈니스 단위 메서드에서 조금 더 세밀하게 조정하고 싶을 때 사용한다. AOP는 주소, 파라미터, 애노테이션 등 다양한 방법으로 대상을 지정할 수 있다.

 

 

2. Dependency Injection (의존성 주입)

객체 의존성을 DI 컨테이너에 맡긴다.  - Setter 메소드 or 생성자 인수를 통해 의존성 주입한다.  - DI 컨테이너는 요청이 들어올 때 필요한 객체의 인스턴스를 만든 후 의존성 설정한다.     ex) Spring IoC Container 

 

3. Cross Cutting Concerns (횡단 관심 분리)

  어플리케이션 전반에서 가지는 공통적인 관심사를 분리한다.

  - 비즈니스 로직 외에 Logging, Transaction 관리, Security 등 신경써야 할 관심사들이 많다.

  - 관심사들은 많은 어플리케이션 레이어에 퍼져있는데, 이 관심사들을 분리해 처리하는 것이 효율적이다.

 

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 13  (0) 2022.03.31
[Clean Code] Chapter 12  (0) 2022.03.28
[Clean Code] Chapter 10  (0) 2022.03.26
[Clean Code] Chapter 09  (0) 2022.03.21
[Clean Code] Chapter 08  (0) 2022.03.20
반응형

클래스 잘 설계하기

 

목차

1. 캡슐화되어야 한다

2. 단일 책임 원칙

3. 낮은 결합도, 높은 응집도

4. 변경하기 쉬워야 한다


1. 캡슐화되어야 한다

캡슐화 : 객체의 실제 구현을 외부로부터 감추는 방식

 

2. 단일 책임 원칙

클래스는 작아야 한다.

클래스가 맡은 책임이 한 개인가

  - 함수와 마찬가지로 클래스도 작아야 한다.

  - 함수는 라인 수로 크기를 측정했는데, 클래스는 맡은 책임의 수로 크기를 측정한다.

  - 클래스 설명은 단일(if), 그리고(and), 하며(or), 하지만(but)을 사용하지 않고 25단어 내외로 가능해야 한다.

     -> 책임이 한 가지여야 한다.

 

단일 책임 원칙(SRP) 중요성

  - 자잘한 단일 클래스가 많아지면 큰 그림을 이해하기 어렵다고 우려한다. 하지만 작은 클래스가 많은 시스템이든 큰 클래스가 몇 개뿐인 시스템이든 돌아가는 부품은 그 수가 비슷하다.

  - 큼직한 다목적 클래스 몇 개로 이뤄진 시스템은(변경을 가할 때) 당장 알 필요가 없는 사실까지 들이밀어 독자를 방해한다.

  - 작은 클래스는 각자 맡은 책임은 하나며, 변경할 이유가 하나며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행한다.

 

3. 낮은 결합도, 높은 응집도

 * 결합도는 낮을수록 응집도는 높을수록 유지보수성이 좋다.

 

문제점 

결합도가 높은 클래스의 문제점 :

  - 연관된 클래스가 변경되면 수정이 필요하다.

  - 결헙도가 높으면 연관된 클래스들을 모두 이해해야 한다.

응집도가 낮은 클래스의 문제점 :

  - 여러 기능이 있으므로 이해하기 어렵다.

  - 재사용하기 어렵다.

 

 

4. 변경하기 쉬워야 한다

  - 추상화를 잘 활용해야 한다.

  - 기존의 클래스는 건들지 않고 수정되어야 한다. (OCP 원칙 위반되지 않게)

 

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 12  (0) 2022.03.28
[Clean Code] Chapter 11  (0) 2022.03.27
[Clean Code] Chapter 09  (0) 2022.03.21
[Clean Code] Chapter 08  (0) 2022.03.20
[Clean Code] Chapter 07  (0) 2022.03.17
반응형

커맨드 패턴(Command Pattern)

  - 커맨드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

 

장점

  - 커맨드 패턴은 실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계한다.

  - 실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 호출자 클래스와 실제 기능을 실행하는 수신자 클래스 사이의 의존성을 제거한다.

 

코드

 

  - Command Interface :

    실제 기능을 수행하는 클래스들을 캡슐화 하기위한 Interface

 

public interface Command {

    public String run();

}

 

  - 디지니 구현 Class :

    실제 구현 클래스

 

public class DisneyCommand implements Command {

    @Override
    public String run() {
        return "Disney";
    }
}

 

  - 넷플릭스 구현 Class

 

public class NexflixCommand implements Command{

    @Override
    public String run() {
        return "Netflix";
    }
}

 

  - 호출자 Class

 

public class OttService {

    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public String displayOtt() {
        return command.run();
    }

}

 

  - 커맨더 패턴 Test Class

 

@SpringBootTest
class OttServiceTest {

    @Test
    void CommandPatternTest() {

        Command disneyCommand = new DisneyCommand();
        Command netflixCommand = new NexflixCommand();

        OttService ottService = new OttService();
        ottService.setCommand(disneyCommand);
        assertEquals(ottService.displayOtt(), "Disney");

        ottService.setCommand(netflixCommand);
        assertEquals(ottService.displayOtt(), "Netflix");

    }

}

 

 

정리

어떠한 객체를 공용으로 사용하는데 동작만을 객체마다 분리해서 사용하고 싶을 경우에 적합한 패턴인 것 같다.

1. 공용(위 예제의 OttService.java)으로 사용하고 있는 객체에 command라는 인터페이스를 구현한다.

2. 객체의 동작(DisneyCommand.java, NetflixCommand.java)을 호출하는 클래스에 각각 기능에 맞추어 command를 구현한다.

 

Java의 다형성을 이용해서 Command Interface를 구현한 클래스는 호출자 수정 없이 기능 추가가 가능하다.

이는 OCP(Open Close Principle) 원칙에 위배되지 않게 기능을 추가할 수 있게 하는 패턴이다.

 

참조 

  - https://gmlwjd9405.github.io/2018/07/07/command-pattern.html

728x90
반응형
반응형

책임 사슬 패턴(Chain of Responsibility Pattern)

  - 템플릿 메서드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

  - 책임 사슬 패턴은 특정한 객체가 담당하는 일반적인 방법과 다르게 객체를 연결리스트와 같은 사슬 방식으로 연결한 후에 요청을 수행하지 못하는 객체라면 다음 객체로 책임을 넘기는 형태의 패턴이다.

 

장점

  - 여러 클래스간에 걸쳐 이루어지는 일이기 때문에 구조가 다른 클래스에 대해서 낮은 결합도로 동일한 이벤트 핸들링이 가능하다. 

  - 사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가/제거 할 수 있다. 상황에 따라 동적으로 핸들러를 추가하거나 제거할 수 있으며, 이러한 변화가 전체구조에 아무런 영향을 주지 않는다는 점에서 객체지향적인 목적을 달성한다고 볼 수 있다.

 

코드

  * 한라봉의 무게에 따라 다른 박스로 분리되는 예시

 

  - 한라봉 Chain Interface :

    책임 사슬 패턴의 핵심 Interface

    setNextChain 메소드를 통해서 다른 체인을 이어준다.

public interface HanrabongChain {

    void setNextChain(HanrabongChain nextChain);
    void weightFilter(int weight);

}

 

  - 한라봉 Class

 

public class Hanrabong {

    private int weight;

    public Hanrabong(int weight) {
        this.weight = weight;
    }

    public int getWeight() {
        return this.weight;
    }
}

 

  - 한라봉 10kg Filter Class

 

public class Hanrabong10k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 10) {
            System.out.println("IN 10kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 한라봉 5kg Filter Class

 

public class Hanrabong5k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 5) {
            System.out.println("IN 5kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 한라봉 3kg Filter Class

 

public class Hanrabong3k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 3) {
            System.out.println("IN 3kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 책임 사슬 패턴 Test Class

@SpringBootTest
class HanrabongChainTest {

    @Test
    void hanrabongFilterTest() {

        HanrabongChain hanrabongChain = getHanrabongChain();

        Hanrabong hanrabong5 = new Hanrabong(5);
        Hanrabong hanrabong12 = new Hanrabong(12);
        Hanrabong hanrabong7 = new Hanrabong(7);
        Hanrabong hanrabong4 = new Hanrabong(4);
        Hanrabong hanrabong1 = new Hanrabong(1);

        List<Hanrabong> rabongList = Arrays.asList(hanrabong5, hanrabong12, hanrabong7, hanrabong4, hanrabong1);

        for (Hanrabong hanrabong : rabongList) {
            hanrabongChain.weightFilter(hanrabong.getWeight());
        }


    }
    
    // 다음 객체 연결
    private HanrabongChain getHanrabongChain() {
        HanrabongChain hanrabong10k = new Hanrabong10k();
        HanrabongChain hanrabong5k = new Hanrabong5k();
        HanrabongChain hanrabong3k = new Hanrabong3k();

        hanrabong10k.setNextChain(hanrabong5k);
        hanrabong5k.setNextChain(hanrabong3k);
        return hanrabong10k;
    }

}

 

정리

* 책임 사슬 패턴의 주의점

위 테스트 예제에서 무게가 1kg인 한라봉이 들어온다면 에러가 발생되거나 필터에 걸리지 않는다.

책임 사슬 패턴은 요청이 반드시 수행된다는 보장이 없기 때문에, 체인이 적절하게 구성되어 프로세스가 처리 될 수 있도록 구성하여야 한다. 추가적으로 요청을 처리하는데 걸리는 시간을 예측하기 힘들다는 단점도 있어 시간예측이 중요한 경우 해당 패턴의 적용을 검토해봐야 한다.

 

참조 

  - https://www.nextree.co.kr/p2533/

728x90
반응형
반응형

템플릿 메서드 패턴(Template Method Pattern)

  - 템플릿 메서드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

 

목적

"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다.

템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다." [GOF]

 

쉽게 이야기하면 부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다.

이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.

결국 상속오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.

 

장점

  - 알고리즘의 구조를 메소드에 정의하고, 하위 클래스에서 알고리즘 구조의 변경없이 알고리즘을 재정의하는데 유용하다.

  - 알고리즘이 단계별로 나누어 지거나, 같은 역할을 하는 메소드이지만 여러곳에서 다른 형태로 사용이 필요한 경우 유용하다.

 

코드

  * FactoryTemplate이라는 공장 템플릿 추상화 클래스를 이용해서 자동차, 폰 공장에서는 상품생산 프로세스만 다르게 구성한다고 설정

 

  - FactoryTemplate.java (추상화 클래스)

 

public abstract class FactoryTemplate {

    public final void buildProduct() {
        baseMaterial();
        productProcess();
        finishProduct();
    }

    private void baseMaterial() {
        System.out.println("원/부자재 수입");
    }

    public abstract void productProcess();

    private void finishProduct() {
        System.out.println("제품 완성");
    }

}

 

  - 자동차 공장 Class

 

public class CarFactory extends FactoryTemplate{

    @Override
    public void productProcess() {
        System.out.println("자동차 공장 프로세스 가동!");
    }

}

 

  - 핸드폰 공장 Class

 

public class PhoneFactory extends FactoryTemplate {

    @Override
    public void productProcess() {
        System.out.println("핸드폰 공장 프로세스 가동!");
    }
}

 

  - 테스트 Class

 

@SpringBootTest
class FactoryTemplateTest {

    @Test
    void templatePatternTest() {

        FactoryTemplate factory;
        factory = new CarFactory();
        factory.buildProduct();

        System.out.println("=======================");

        factory= new PhoneFactory();
        factory.buildProduct();

    }


}

 

결과 :

원 부자재 수입
자동차 공장 프로세스 가동!
제품 완성
=======================
원 부자재 수입
핸드폰 공장 프로세스 가동!
제품 완성

 

 

정리

 - 추상화 클래스에서 메서드에 알고리즘 골격을 정의하고, 소스코드의 중복을 줄이기 위해서 하위 클래스에서 공통적으로 나타나는 부분에 대해서 추상클래스에 정의해서 사용한다.

 * 핵심 템플릿 클래스abstract 추상 클래스임을 인지하자

 

참조 

  - https://niceman.tistory.com/142

  - https://yaboong.github.io/design-pattern/2018/09/27/template-method-pattern/

728x90
반응형
반응형

프록시 패턴(Proxy Pattern)

  - Proxy는 대리자, 대변인 이라는 뜻이다. 

  - 프록시 패턴은 구조패턴(Structural Pattern) 중 하나이다.
  - 구조 패턴은 작은 클래스들을 상속과 합성을 이용하여 더 큰 클래스를 생성하는 방법을 제공하는 패턴이다.

  - 인터페이스나 구현을 복합하는 것이 아니라 객체를 합성하는 방법을 제공한다.

    이는 컴파일 단계에서가 아닌 런타임 단계에서 복합 방법이나 대상을 변경할 수 있다는 점에서 유연성을 갖는다.

 

프록시의 주요 기능

프록시를 통해서 할 수 있는 일은 크게 2가지로 나뉜다.

1. 접근 제어 (권한에 따른 접근 차단, 캐싱 등)

2. 부가 기능 추가 (다른 로직을 추가하는 등의 부가 기능을 수행, 예) 실행 시간을 측정해서 로그를 남긴다.)

 

* GOF 디자인 패턴에서는 의도에 따라서 프록시 패턴과, 데코레이터 패턴으로 구분한다.

  - 프록시 패턴 : 접근 제어가 목적

  - 데코레이터 패턴 : 새로운 기능 추가가 목적

 

프록시 패턴의 장점

  - 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용한다.

  - 객체에 대하여 접근할 때에 Wrapper Class를 두어 접근에 대한 통제를 위해 사용된다.

 

코드

  * 더하기 빼기 계산기 로직을 구현 예제 (프록시 패턴을 이용해서 계산 시간을 출력)

 

  - 계산기 interface

 

public interface Calculator {

    public int plus(int left, int right);
    public int subtract(int left, int right);

}

 

  - 계산기 interface 구현 service

 

public class CalculatorService implements Calculator{

    private int result;

    @Override
    public int plus(int left, int right) {
        result = left + right;
        return result;
    }

    @Override
    public int subtract(int left, int right) {
        result = left - right;
        return result;
    }
}

 

  - 프록시 패턴을 사용하지 않은 테스트

 

@SpringBootTest
class CalculatorServiceTest {

    @Test
    void calculatorService() {

        Calculator calculatorService = new CalculatorService();

        int left = 10000;
        int right = 5000;

        int plusResult = calculatorService.plus(left, right);
        int subtractResult = calculatorService.subtract(left, right);

        assertEquals(plusResult, left+right);
        assertEquals(subtractResult, left-right);

    }

}

 

  - 프록시 패턴 클래스

 

public class CalculatorServiceProxy implements Calculator{

    private int result;

    @Override
    public int plus(int left, int right) {
        long beforeTime = System.currentTimeMillis();

        result = left + right;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long afterTime = System.currentTimeMillis();
        long secDiffTime = (afterTime - beforeTime)/1000;
        System.out.println(" plus 계산 시간(m) : "+secDiffTime);

        return result;
    }

    @Override
    public int subtract(int left, int right) {
        long beforeTime = System.currentTimeMillis();

        result = left - right;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long afterTime = System.currentTimeMillis();
        long secDiffTime = (afterTime - beforeTime)/1000;
        System.out.println("subtract 계산 시간(m) : "+secDiffTime);

        return result;
    }
}

 

- 프록시 패턴을 사용한 테스트

 

@SpringBootTest
class CalculatorServiceTest {

    @Test
    void calculatorServiceProxy() {

        Calculator calculatorServiceProxy = new CalculatorServiceProxy();

        int left = 10000;
        int right = 5000;

        int plusResult = calculatorServiceProxy.plus(left, right);
        int subtractResult = calculatorServiceProxy.subtract(left, right);

        assertEquals(plusResult, left+right);
        assertEquals(subtractResult, left-right);

    }


}

 

결과 : plus 계산 시간(m) : 1
        subtract 계산 시간(m) : 2

 

 

정리

 - 프록시 패턴의 의도 : 다른 객체에 접근을 제어하기 위해 대리자를 제공

 - 테스트 코드를 보면 생성 객체만 바꿈으로서 Calculator interface는 영향을 받지 않게 Proxy 클래스를 통해서 원하는 목적에 맞게 흐름을 조정할 수 있었다. 해당 패턴은 Spring 프레임워크에 AOP 동작원리에 사용되어지고 있다.

 * 중요한 점은 흐름제어만 할 뿐 결과값을 조작하거나 변경시키면 안된다.

 

참조 

 - https://readystory.tistory.com/132?category=822867

728x90
반응형
반응형

깨끗한 테스트 코드

 

목차

1. 테스트 코드의 중요성

2. 테스트의 종류

3. Unit Test 작성

4. FIRST 원칙

5. 오픈소스 속 Unit Test


1. 테스트 코드의 중요성

  - 테스트 코드는 실수를 바로잡아준다.

  - 테스트 코드는 반드시 존재해야하며, 실제 코드 못지 않게 중요하다.

  - 테스트 케이스는 변경이 쉽도록 한다. 코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이 바로 단위테스트다.

  - 테스트 케이스가 있으면 변경이 두렵지 않다. 테스트 케이스가 없다면 모든 변경이 잠정적인 버그다. 테스트 커버리지가 높을수록 버그        에 대한 공포가 줄어든다.

  - 지저분한 테스트 코드는 테스트를 안하니만 못하다.

 

<Effective Unit Test 책에서>

'테스트는 실사용에 적합한 설계를 끌어내준다.'

'테스트를 작성해서 얻게 되는 가장 큰 수확은 테스트 자체가 아니다. 작성 과정에서 얻는 깨달음이다.'

 

'테스트트 자동화되어야 한다.' - 매번 배포할 때마다 실행되어야 한다.

 

2. 테스트의 종류

Unit Test : 프로그램 내부의 개발 컴포넌트의 동작을 테스트한다. 배포하기 전에 자동으로 실행되도록 많이 사용한다.

Integration Test : 프로그램 내부의 개별 컴포넌트들을 합쳐서 동작을 테스트한다. Unit Test는 각 컴포넌트를 고립시켜 테스트하기 때문에 컴포넌트의 interaction을 확인하는 Integration Test가 필요하다.

E2E Test : End to End Test. 실제 유저의 시나이로대로 네트워크를 통해 서버의 Endpoint를 호출해 테스트한다.

 

3. Unit Test 작성

'테스트 라이브러리를 사용하자' (실무에서 JUnit5 + mockito를 많이 사용한다.)

 

Test Double

  * 테스트에서 원본 객체를 대신하는 객체

 

Stub :   - 원래의 구현을 최대한 단순한 것으로 대체한다.  - 테스트를 위해 프로그래밍된 항목에만 응답한다.

 

Spy :

  - Stub의 역할을 하면서 호출에 대한 정보를 기록한다.

  - 이메일 서비스에서 메시지가 몇 번 정송되는지 확인할 때

 

Mock :

  - 행위를 검증하기 위해 가짜 객체를 만들어 테스트하는 방법

  - 호출에 대한 동작을 프로그래밍할 수 있다.

  - Stub은 상태를 검증하고 Mock은 행위를 검증한다.

 

'given, when, then 패턴을 사용하자'

  - given : 테스트를 위한 pre-condition

  - when : 테스트하고 싶은 동작 호출

  - then : 테스트 결과 확인

4. FIRST 원칙

Fast : 빠르게

  - 테스트는 빨리 돌아야 한다. 자주 돌려야 하기 때문이다.

Independent : 독립적으로

  - 각 테스트를 독립적으로 작성한다. 서로에게 의존하면 실패한 원인을 찾기 어려워진다.(다른 테스트의 샐패로 인한건지, 코드 오류인지)

Repeatable : 반복가능하게

  - 테스트는 어떤 환경에서도 반복 가능해야 한다. 실제 환경, QA 환경, 모든 환경에서 돌아가야 한다.

Self-Validating : 자가검증하는

  - 테스트는 bool 값으로 결과를 내야 한다.

Timely : 적시에

  - 테스트하려는 실제 코드를 구현하기 직전에 구현한다.

 

4. 오픈소스 속 Unit Test

Trino(PrestoSQL) 프로젝트 코드

  - 책에서는 하나의 테스트에 하나의 assert를 사용하라고 했다.

  - 테스트하려는 인자가 많은 경우는 하나에 몰아서 하는 경우도 많다.

 

public class TestTypeCalculation
{
    @Test
    public void testBasicUsage()
    {
        assertEquals(Long.valueOf(42), calculateLiteralValue("42", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("NULL", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("null", ImmutableMap.of()));
        assertEquals(Long.valueOf(42), calculateLiteralValue("x", ImmutableMap.of("x", 42L)));
        assertEquals(Long.valueOf(42), calculateLiteralValue("(42)", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("(NULL)", ImmutableMap.of()));
        assertEquals(Long.valueOf(42), calculateLiteralValue("(x)", ImmutableMap.of("x", 42L)));

        assertEquals(Long.valueOf(42 + 55), calculateLiteralValue("42 + 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 - 55), calculateLiteralValue("42 - 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 * 55), calculateLiteralValue("42 * 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 / 6), calculateLiteralValue("42 / 6", ImmutableMap.of()));

        assertEquals(Long.valueOf(42 + 55 * 6), calculateLiteralValue("42 + 55 * 6", ImmutableMap.of()));
        assertEquals(Long.valueOf((42 + 55) * 6), calculateLiteralValue("(42 + 55) * 6", ImmutableMap.of()));

        assertEquals(Long.valueOf(2), calculateLiteralValue("min(10,2)", ImmutableMap.of()));
        assertEquals(Long.valueOf(10), calculateLiteralValue("min(10,2*10)", ImmutableMap.of()));
        assertEquals(Long.valueOf(20), calculateLiteralValue("max(10,2*10)", ImmutableMap.of()));
        assertEquals(Long.valueOf(10), calculateLiteralValue("max(10,2)", ImmutableMap.of()));

        assertEquals(Long.valueOf(42 + 55), calculateLiteralValue("x + y", ImmutableMap.of("x", 42L, "y", 55L)));
    }
}

 

code from - https://github.com/trinodb/trino/blob/master/core/trino-parser/src/test/java/io/trino/type/TestTypeCalculation.java

 

Junit5 Samples

  - @DisplayName은 테스트 클래스나 메서드에 보여질 이름을 입력하는 것이다. 테스트의 목적을 명확하게 작정할수 있다.

  - @ParameterizedTest는 하나의 테스트 메서드로 여러 가지 paramter를 테스트할 수 있다. @CsvSource의 값을 parameter로 넘       긴다.

  - JUnit5가 테스트에 관한 유용한 기능을 많이 가지고 있기 때문에 실무에서 많이 사용한다.

 

class CalculatorTests {

	@Test
	@DisplayName("1 + 1 = 2")
	void addsTwoNumbers() {
		Calculator calculator = new Calculator();
		assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@ParameterizedTest(name = "{0} + {1} = {2}")
	@CsvSource({
			"0,    1,   1",
			"1,    2,   3",
			"49,  51, 100",
			"1,  100, 101"
	})
	void add(int first, int second, int expectedResult) {
		Calculator calculator = new Calculator();
		assertEquals(expectedResult, calculator.add(first, second),
				() -> first + " + " + second + " should equal " + expectedResult);
	}
}

 

code from - https://github.com/junit-team/junit5-samples/blob/main/junit5-jupiter-starter-gradle/src/test/java/com/example/project/CalculatorTests.java

 

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 11  (0) 2022.03.27
[Clean Code] Chapter 10  (0) 2022.03.26
[Clean Code] Chapter 08  (0) 2022.03.20
[Clean Code] Chapter 07  (0) 2022.03.17
[Clean Code] Chapter 06  (0) 2022.03.13

+ Recent posts