코드

import './App.css';
import {useCallback, useState} from "react";
import Child from "./Child";

function App() {
  const map = [
      'https://www.daum.net/',
      'https://www.naver.com/',
      'https://www.nate.com/',
  ]
  const [urls, setUrls] = useState([...map]);
  const [count, setCount] = useState(0);
  const [_, forceRender] = useState(0);

  const longTimeWork = async () => {
    console.log('[call longTimeWork]');
    for (let j = 0; j < urls.length; j++) {
        console.log(urls);
        await fetch(urls[j], {mode: 'no-cors'});
    }
    setCount(prev => prev + 1);
  }

  const useLongTimeWork = useCallback(longTimeWork, [urls]);

  return (
    <div className="App">
      <button onClick={() => forceRender(prev => prev + 1)}>부모 그리기</button>
      <button onClick={() => setUrls([...urls, map[count % 3]])}>URL 추가</button>
      <Child count={count} longTimeWork={longTimeWork} />
    </div>
  );
}

export default App;
import {useEffect} from "react";

function Child({count, longTimeWork}) {
  useEffect(() => {
      longTimeWork();
  }, [longTimeWork])

  return (
    <div>
      <button>
          {'child'}
          {' '}
          {count}
      </button>
    </div>
  );
}

export default Child;

 

React에서 보통 상위 컴포넌트가 하위 컴포넌트에게 함수를 전해줄때가 많다.
위의 예에서는 longTimeWork가 이에 해당 한다. longTimeWork함수는 네이버, 다음, 네이트 사이트를 차례대로 하나씩 방문하는 네트워크 레이턴시가 아주 많은(시간이 아주 많이 걸리는) 함수다.
하위 컴포넌트는 useEffect를 사용해서 함수를 받자마자 실행 시킨다고 가정하자. 하위 컴포넌트가 받는 함수가 빠르고, 가벼운 함수라면 부모가 render할 때마다 호출해도 크게 상관없다.
하지만 함수 이름에서 보다 싶이 longTimeWork!
이때는 부모가 render할 때마다 함수를 실행시키기 부담스럽다. 어떻게 해야할까? 이때 바로 useCallback을 쓰면된다.

 

문제 이해하기

문제점을 파악해보자.
상위 컴포넌트에서 <Child longTimeWork={longTimeWork} />를 선언했다.
이때 부모 그리기(forceRender) 함수를 호출해서 부모를 그려보자. 그러면 어때 longTimeWork 함수도 다시 만들어진다. 이말은 즉 longTimeWork 함수는 새로운 함수가 된다는 말이다.
하위 컴포넌트(Child) 입장에서는 longTimeWork 함수가 새로 만들어졌으니 useEffect에서 다시 호출한다.
longTimeWork는 내부적으로 setCount(prev + 1)을 실행하니 무한대로 longTimeWork 함수를 호출한다.
나는 그냥 하위 컴포넌트에서 longTimeWork가 새로 생길 때만 호출하고 싶었는데......

 

문제 해결

const useLongTimeWork = useCallback(longTimeWork, [urls]);

<Child count={count} longTimeWork={useLongTimeWork} />

함수를 선언하고, Child에 전달하자.


useCallback 두번째 파라미터는 의존성 배열이다. 그러니깐 urls가 변경 될때만 해당 함수를 다시 만들겠다는 말이다.
다시 한번 부모 그리기(forceRender) 함수를 호출해서 부모를 그려보자. urls가 변경되지 않았기 때문에 Child에서 함수를 호출하지 않는다.
그리고 URL 추가 버튼을 누르면 urls가 변경 되기 때문에 longTimeWork가 재 정의되고 Child 컴포넌트에서 해당 함수를 실행하게 된다.
내가 원하는 결론이다. 아주 뿌듯하다.

 

결론

useCallback 함수는 의존성 배열에 정의된 변수가 변경 될때만 함수를 다시 만든다. 그러므로 성능 향상에 도움이 된다.
하지만 일단 useCallback없이 코딩하고 느려지면 그때 생각하자.

'React' 카테고리의 다른 글

React(SPA) Auth 로그인 흐름  (0) 2020.01.09
React Redux 기본  (0) 2019.12.19
React Form input  (0) 2019.12.16

장점
1. 꾸준히 코딩을 할 수 있는 환경을 마련해준다.(실습, 과제)
2. 방대한 알고리즘 내용 중 중요한 부분이 무엇인지 가르쳐준다.
3. 어려운 주제를 쉽게 잘 설명 해준다.

단점
1. 시험 난이도를 높이기 위해 약간 억지스러운 부분을 추가했다.
2. 슬랙방에 실습,과제,시험을 위한 조교가 있으나 없어도 되는 수준으로 일한다.

총평
매주 좋은 퀄리티의 실습, 과제를 진행하면서 코딩 실력을 상당히 향상시켜주는 시스템을 갖췄다.
위 단점에서 기술 했듯 아쉬운 부분이 존재하지만 온라인에서 포큐와 같은 교육 기관을 찾기 쉽지 않을 것 같다.

Django 프로젝트에서 비지니스 로직에 영향을 주지 않고 최대한 깔끔하게 로깅 하는 방법을 찾다보니 데코레이터가 떠올랐다.(Spring 에서 많이 사용하는 AOP(Aspect Oriented Programming) 느낌으로 접근하면 이해가 편할 것 같다.)

 

로그를 남기는 데이터는 별것없다. 누가, 언제, 어떤 action 3개의 데이터만 해당 view 에 접근했을때 Logging DBinsert 하려한다.

view


DRF를 이용해서 view를 만들었다. @api_view 또한 데코레이터를 사용하기 때문에 순서가 중요하다.
api_view 먼저 실행 시키고 그다음에 나의 custom logging 데코레이터를 실행 하려한다.

@api_view(['GET'])
@Logging.log_decorator('고객을 가져오는 액션!')
def get_customer(request: Request) -> Response:



데코레이터


Logging Modelstatic 메소드로 데코레이터를 생성했다.
데코레이터가 무려 두개나 있는데 이는 action 이라는 매개변수를 전달 받기 위함이다.

(stackoverflow 참조: https://stackoverflow.com/questions/5929107/decorators-with-parameters)


function(*args, **kwargs)은 기존의 view 로직을 실행하는 구문이다.
필자는 기존의 view 로직을 수행하기 전에 로깅을 하기 원한다.
Logging 데이터를 추가하기 전에 argsapi_view 데코레이터를 처리한 뒤 반환된 값이라 Django에서 사용하는 user 객체를 이용할 수 있다. 해서 user 데이터를 args[0].user.username을 사용해서 추가했다.

필자가 실행한 전처리보다는 후처리를 하고 싶은 경우엔 # 후처리 부분에 로직을 넣으면 된다.

class Logging(TimeStampedModel):
    user = models.CharField(max_length=255)
    action = models.CharField(max_length=255)
    created = models.DateTimeField(auto_now_add=True)

    @staticmethod
    def log_decorator(action: str):
        def decorator(function):
            def wrapper(*args, **kwargs):
                try:  # 전처리
                    Logging.objects.create(user=args[0].user.username, action=action).save()
                except Exception as e:
                    print("###Error####", e)
                result = function(*args, **kwargs)
                # print('GoodBye~')  # 후처리
                return result

            return wrapper

        return decorator





결론 : 비지니스 로직에 Logging 로직을 넣지 말고 데코레이터를 이용해 넣자. 깔끔해진다.

라면 순서대로 끓이기

위 그림처럼 라면 끓이는 Step들이 정의 되어 있다.

어떻게하면 순서대로 출력 할까?

위상 정렬을 사용하면 된다.

 

스텝 정의

public final class Step {
    private final String name;
    private final ArrayList<Step> nextSteps = new ArrayList<>();

    public Step(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public ArrayList<Step> getNextSteps() {
        return nextSteps;
    }

    public void addNext(final Step step) {
        this.nextSteps.add(step);
    }
}

스텝 클래스는 자신이 수행하는 이름과 다음 스텝들을 정의한다.

 

 

그래프 생성

    private static ArrayList<Step> createStepGraph() {
        final Step start = new Step("start: 시작");
        final Step water = new Step("water: 물");
        final Step boil = new Step("boil: 물끓이기");

        final Step noodle = new Step("noodle: 면");
        final Step powder = new Step("powder: 스프");
        final Step powderEtc = new Step("powderEtc: 건더기");

        final Step boilFinish = new Step("boilFinish: 물끓이기 완료");
        final Step eat = new Step("eat: 라면 잡수기");

        final Step chopsticks = new Step("chopsticks: 젓가락 세팅");
        final Step kimchi = new Step("kimchi: 김치 세팅");

        start.addNext(water);

        water.addNext(boil);

        boil.addNext(noodle);
        boil.addNext(powder);
        boil.addNext(powderEtc);

        noodle.addNext(boilFinish);
        powder.addNext(boilFinish);
        powderEtc.addNext(boilFinish);

        boilFinish.addNext(eat);
        chopsticks.addNext(eat);
        kimchi.addNext(eat);

        ArrayList<Step> steps = new ArrayList<>();

        steps.add(start);
        steps.add(water);
        steps.add(boil);
        steps.add(noodle);
        steps.add(powder);
        steps.add(powderEtc);
        steps.add(boilFinish);
        steps.add(eat);
        steps.add(chopsticks);
        steps.add(kimchi);

        return steps;
    }

그래프 생성 : 각각의 스텝들을 만들고 그 다음 스텝은 무엇인지 정의한다

 

 

위성 정렬 실행

    public static void main(String[] args) {
        ArrayList<Step> steps = createStepGraph();
        LinkedList<Step> sortedSteps = sortTopologically(steps);

        for (Step step : sortedSteps) {
            System.out.println(step.getName());
        }

    }

    private static LinkedList<Step> sortTopologically(ArrayList<Step> steps) {
        LinkedList<Step> linkedList = new LinkedList<Step>();
        HashSet<Step> discovered = new HashSet<Step>();
        for (Step step : steps) {
            topologicalSortRecursive(step, discovered, linkedList);
        }

        return linkedList;
    }

    private static void topologicalSortRecursive(Step step, HashSet<Step> discovered, LinkedList<Step> linkedList) {
        if (discovered.contains(step)) {
            return;
        }

        discovered.add(step);

        for (Step nextStep : step.getNextSteps()) {
            if (discovered.contains(nextStep)) {
                continue;
            }

            topologicalSortRecursive(nextStep, discovered, linkedList);
        }

        linkedList.addFirst(step);
    }

위 코드에서 핵심은 각각의 모든 Step들이 위상정렬을 재귀적으로 실행 한다는 것이다.

 ※ 이미 실행한 Step이 있으면 반환(discovered)

그리고 DFS 처럼 자기 자신을 add 하기 전에 다음 스텝을 먼저 add한다

 

'JAVA' 카테고리의 다른 글

java annotation  (0) 2021.03.12
JAVA final 키워드  (0) 2020.06.26

IE11 에서 es6와 Promise까지 지원하고 싶다.

 

webpack 쓰면 되는거 알겠는데 그냥 간단히 빨리 지원하고싶다.

<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.12.1/polyfill.js"></script>

<script type="text/babel">
  alert(`dd`);

  function resolveAfter2Seconds() {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve('resolved');
      }, 1000);
    });
  }

  async function asyncCall() {
    console.log('calling');
    const result = await resolveAfter2Seconds();
    alert(result);
  }

  asyncCall();
</script>
</html>

1. html 파일에 babel, polyfill cdn을 넣는다.

2. es6 문법과 Promise 지원 확인한다.

 

IE11 쓰면 벌금 메겼으면 좋겠다.

 

'JS' 카테고리의 다른 글

javascript 비동기 재귀 함수  (0) 2020.09.28
Promise 자바스크립트(js)  (0) 2019.10.18
loading button 쉽게 만들기(with js)  (0) 2019.10.15
js 화살표함수(arrow function)  (0) 2019.10.14
JS prototype(프로토타입)  (0) 2019.09.25

1. 퀵소트 설명

퀵소트에서 가장 중요한 것은 퀵소트를 이해하는것이다.

퀵소트는 이해하기 까다로운 알고리즘이다. 무서운 마음으로 한번 보자.

 

퀵소트는 분할 정복(divide and conquer) 개념을 사용한다.

그럼 무엇이 분할이고 무엇이 정복인지 알아보자.

 

 

우선 정복 부터 알아보자.

배열 = [5,2,4,1,3]

위와 같이 정렬되지 않은 배열이 있다.

위 배열중에서 일단 하나만 내자리를 찾자. 라고 생각하자

 

일단 하나만 내자리를 찾자의 단계는 아래와 같다.

1. 가장 우측의 인덱스로 피벗을 정한다(내자리를 찾을 놈)

2. 피벗기준으로 작은놈들은 왼쪽, 큰놈들은 오른쪽!

3. 2번 수행 시 자동적으로 자기 자리 fix 왜냐? 작은놈은 왼쪽에, 큰놈은 오른쪽에 있으니깐!

 

정복 예제

1. p == pivot

2. i == loop돌면서 p와 비교할 대상

3. j == left

[5, 2, 4, 1, 3]
i            p 
l

i(5)는 p(3)보다 작은가? → 아니요 아무것도 하지말고 i를 오른쪽으로 한칸 당겨라

[5, 2, 4, 1, 3]
 l  i        p 

i(2)는 p(3)보다 작은가? i를 l(left)와 바꾸고, i, l을 우측으로 한칸 이동해라

[2, 5, 4, 1, 3]
    l  i     p 

i(4)는 p(3)보다 작은가? → 아니요  아무것도 하지말고 i를 오른쪽으로 한칸 당겨라

[2, 5, 4, 1, 3]
    l     i  p 

i(1)는 p(3)보다 작은가? → 네  i를 l(left)와 바꾸고, i, l을 우측으로 한칸 이동해라

[2, 1, 4, 5, 3]
       l    i,p 

i(1)는 p(3)보다 작은가? → 네  i를 l(left)와 바꾸고, i, l을 우측으로 한칸 이동해라

 

 

i와 p가 같아졌다는 말은 비교 loop가 끝났다는 말이다.

이제 l(left)와 p을 바꾸자.

[2, 1, 3, 5, 4]
       p    i,l 

 

1차 정복이 완료된 모습이다.

피벗 3을 기준으로 왼쪽엔 작은것, 오른쪽엔 큰것이 놓여있다. 그리고 3은 자기자리를 잡았다.

 

여기서 생각을 멈춰라. 멈추고 분할을 생각해보자

도대체 무엇이 분할이란 말인가?

위의 정복 프로세스를 분할해서 다시 하겠다는 말이다.

어떻게? 피벗 기준으로 왼쪽으로 한번, 오른쪽으로 한번

 

 

분할

[2, 1, 3, 5, 4] // 3은 고정


[2, 1] // 왼쪽 배열
 i  p
 l
 
[5, 4] // 오른쪽 배열
 i  p
 l

왼쪽 배열을 위와같은 정복 프로세스를 돌리면 [1, 2] 배열이 완성되고

오른쪽 배열을 돌리면 [4, 5] 배열이 완성된다.

이렇게 하면 [1,2,3,4,5] 정렬된 배열이 완성된다.

 

분할의 핵심 개념은 정복 프로세스를 범위를 줄이며 반복한다. 즉 범위를 줄이면서 재귀적으로 정복 함수를 호출하자!

 

코드

class QuickSort {

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[b];
        arr[b] = arr[a];
        arr[a] = temp;
    }          

    public static void recursiveSearch(int[] arr, int left, int right) { // 분할
        if (left >= right) {
            return;
        }

        int pivotIndex = partition(arr, left, right);

        recursiveSearch(arr, left, pivotIndex - 1);
        recursiveSearch(arr, pivotIndex + 1, right);

    }


    public static int partition(int[] arr, int left, int right) { // 정복
        int i = left - 1;

        while (i < right) {
            ++i;
            if (arr[i] < arr[right]) {
                swap(arr, i, left);
                left++;
            }
        }

        swap(arr, left, right); // 좌랑 우랑 바꿔서 피벗은 자기자리 확정!

        return left;

    }

}

 

DB CRUD는 많이 그리고  하는데, DB 튜닝의 꽃인 인덱스 기본 구조와 탐색 원리를 모르는 사람이  많다.

(나도  모름)

 

인덱스 기본에 대해 간략히 기술하겠다.

DB 테이블에서 데이터를 찾는 방법은 두가지다.

  1. 디비 풀스캔
  2. 인덱스 사용

디비 풀스캔은 말그대로 멀티 블락 I/O(한번에 많은 데이터를 옮길  있는 자료) 활용해 한번에 ! 읽어서 처리하는 방식이다. 

디비 풀스캔이 절대적으로  좋다고 생각하는 사람들이 많은데 절대 그렇지 않다.

where 절에 속하는 범위가 많을때(1~2년 범위로 데이터 조회) 그냥 풀스캔으로 한방에 처리하는게 훨씬 빠르고 좋다.

 

 

이유는 뭘까??

멀티블락I/O, 싱글블락I/O

DB 속도는 I/O와 관계가 있다.  I/O가 작을수록 좋다. I/O를 간략히 설명하면 실행속도가 빠른 CPU 에서 상대적으로 느린 저장장치에 데이터를 요청할때 I/O가 발생하는데

 

CPU : "아... 데이터 저장장치 너무 느리네. 나는 빠르니깐 다른 프로세스에게 CPU 넘겨 주고 할거하고있을게  읽으면 인터럽트 걸어라.

 

I/O Device: "! 알겠습니다. (데이터를 열심히 찾고) CPU님!  준비 됐습니다!"

 

I/O를 경박하게 말하면 위의 대화와 같다.

 

다시 본론으로 돌아와 풀스캔이  좋은 이유를 설명하겠다.

이유는 간단하다. 디비 풀스캔은 멀티 블락 I/O를 사용하기 때문에 I/O가 작다. 인덱스는 싱글 블락 I/O를 사용하기 때문에 I/O가 많다.

그래서 범위가 좁을땐 싱글 블락 I/O를 사용하는 인덱스가 훨씬 빠르지만(작은거 찾자고 풀스캔으로  읽으면 너무 느림) 범위가 많을땐 풀스캔이 오히려 유리하다.

 

인덱스 구조 및 원리

인덱스는 B트리 구조로 되어있다.

 

B 트리는 검색속도가 상당히 빠른 자료구조다(log n). 인덱스가 빠른 이유도 여기에 있다.

인덱스는 정렬된 데이터다. 이게 가장 중요하다. 정렬된 데이터. 그렇기 때문에 빨리 읽을  있다.

예를 살펴보자.

 

 

학생명부에서 시력이 1.0 ~1.5인 홍길동을 찾아보자 학생명부를 이름, 시력순으로 정렬했다면 아래와 같은 표가 된다.

 ※ 시력이 1.0 ~1.5인 홍길동은 2명이라고 가정

  시력 학년
이승우 1.5 4
이시연 1.2 3
탁유리 1.2 4
... ... ...
홍길동 1.0 4
홍길동 1.5 4

 

이때는 이름순으로 정렬되어 있으니 정말 소량만 스캔하면 된다.

(이름 홍길동 찾아서 시력 읽기)

 

 

반면 시력과 이름순으로 정렬해 두었다면 똑같은 두명을 찾는데도 스캔량이 많이 늘어난다.

(시력이 1.0 가서 이름 쫙 스캔, 시력 1.5가서 이름 쫙 스캔)

시력 이름 학년
0.5 이승우 4
...   3
1.0 홍길동 4
... ... ...
1.5 홍길동 4
1.5 홍길홍 4

 

인덱스 선두컬럼을 무엇으로 설정 했느냐에 따라 같은 인덱스라도 차이가 많이난다.

그럼 인덱스가 이름순으로 정렬된거 하나, 시력순으로 정렬된거 하나, 각각 있다면 어떤걸 선택해야 할까?

당연히 이름순이다. 이름찾고 시력찾는게 더 빠르니깐.

여기서 중요한 개념이 등장한다. 1. 인덱스 스캔 효율화, 2. 랜덤 액세스 최소화 두가지다.

 

인덱스 스캔 효율화는 학생 명부를 얼마나 효과적으로 빨리 읽느냐다.

 ※ 이름순으로 읽는게 더 효율적

 

랜덤 액세스 최소화는 학생명부에 없는 나머지 정보를 얻기 위해 직접 테이블을 액세스해서 비교하는 작업이다.

이때 바로 I/O가 발생한다.

 

시력순으로 정렬된 인덱스를 사용할 때 시력은 인덱스에서 알고 있으나 이름이 홍길동인지 확인해야 한다. 이때 테이블을 액세스(I/O) 해야한다.

 

물론 시력, 이름 두가지 다 인덱스에 있을때는 테이블 액세스가 발생하지 않는다.

그럼 두가지 인덱스 다 넣어서 만들면 되잖아?! 할 수 있겠지만 인생은 모든지 트레이드 오프다.

인덱스가 많아지면 초당 트랜잭션 횟수가 감소하고, 용량도 많이 차지한다.

(왜냐하면 insert 할때 인덱스 DB에도 정렬된 순서에 맞게 insert 해줘야 하니깐!)

 

 

결론 : 인덱스는 I/O 싸움이다. I/O는 최대한 적게!

 

 

 

 

1. 시나리오

여러개의 클래스중 아래의 Person 클래스만 그리고 Person 중에서
'@시리얼할래'가 적용된 필드에 한해서 serialize 하고싶다.

@Person클래스만
public class Person {
    @시리얼할래
    private String firstName;
    @시리얼할래
    private String lastName;
    private String age;
    private String address;
}

2. custom annotation 작성

JsonSerializable(Person클래스만) annotation 만든다
아래의 옵션을 간략히 설명하자면 언제? 런타임시, 타겟은? 클래스나 인터페이스

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(TYPE)
public @interface JsonSerializable {

}

 

@JsonElement(시리얼할래) annotation 만든다
아래의 옵션을 간략히 설명하자면 언제? 런타임시, 타겟은? 필드에 적용

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target({ FIELD })
public @interface JsonElement {
    public String key() default "";
}

 

Person 클래스에 적용

@JsonSerializable
public class Person {
    @JsonElement
    private String firstName;
    @JsonElement
    private String lastName;
    @JsonElement
    private String age;

    private String address;

3. object to json 시리얼 클래스 작성

checkIfSerializable 메소드를 보면 object.getClass()를 이용해 해당 오브젝트를 가져오고
clazz.isAnnotationPresent(JsonSerializable.class)을 통해 @Person클래스만 annotation이 있는지 확인한다.
말그대로 Person 클래스인지 확인 후 아니면 런타임 Exception을 던진다.

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class ObjectToJsonConverter {
    private void checkIfSerializable(Object object) {
        if (Objects.isNull(object)) {
            throw new RuntimeException("Can't serialize a null object");
        }

        Class<?> clazz = object.getClass();
        if (!clazz.isAnnotationPresent(JsonSerializable.class)) {
            throw new RuntimeException("The class " + clazz.getSimpleName() + " is not annotated with JsonSerializable");
        }
    }
}

 

getJsonString 메소드는 object의 모든 필드를 찾은 뒤 @시리얼할래 annotation 있는지 확인하는 코드다.

(있으면 시리얼 실시)

private String getJsonString(Object object) throws IllegalArgumentException, IllegalAccessException {
    Class<?> clazz = object.getClass();
    Map<String, String> jsonElementsMap = new HashMap<>();
    for (Field field : clazz.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(JsonElement.class)) {
            jsonElementsMap.put(field.getName(), (String) field.get(object));
        }
    }

    String jsonString = jsonElementsMap.entrySet()
        .stream()
        .map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"")
        .collect(Collectors.joining(","));
    return "{" + jsonString + "}";
}

5. 테스트 코드 작성

첫번째는 일반 object가 시리얼이 되는지 체크하는 테스트다.(Person만 통과)
두번째는 Person object가 정상적으로 시리얼 됐는지 확인하는 테스트.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

public class JsonSerializerUnitTest {

    @Test
    public void givenObjectNotSerializedThenExceptionThrown() throws JsonSerializationException {
        Object object = new Object();
        ObjectToJsonConverter serializer = new ObjectToJsonConverter();
        assertThrows(RuntimeException.class, () -> {
            serializer.convertToJson(object);
        });
    }

    @Test
    public void givenObjectSerializedThenTrueReturned() throws JsonSerializationException {
        Person person = new Person("soufiane", "cheouati", "34");
        ObjectToJsonConverter serializer = new ObjectToJsonConverter();
        String jsonString = serializer.convertToJson(person);
        assertEquals("{\"firstName\":\"soufiane\",\"lastName\":\"cheouati\",\"age\":\"34\"}", jsonString);
    }
}

결론

여러 클래스가 있을때 시리얼 하고 싶은 클래스 필드에 "@시리얼할래" annotation만 박으면 알아서 시리얼을 해주는 공통된 약속(annotation)을 정해 놓고 코딩을 하면 프로젝트 관리할 때 편하다.

 

 

※ 출처 : www.geeksforgeeks.org/annotations-in-java/

'JAVA' 카테고리의 다른 글

그래프 DFS(위상정렬)  (0) 2021.07.20
JAVA final 키워드  (0) 2020.06.26

+ Recent posts