728x90

 Spring Boot를 이용해서 개발을 진행하면 @Transactional이란 어노테이션을 이용하여 데이터의 영속성을 지키고자 한다. 제대로 알고 사용하는 것일까? 프록시조차 모르는 나는 Spring Boot의 의도에 대로 비즈니스 로직에만 신경을 쓰는 것이었을까? 오늘은 그저 사용 중이었던 @Transactional에 대한 내 생각을 서술한 글을 쓴다.

@Transactional을 제대로 이해하려면 유튜브 뉴렉처 강의가 직관적이었다. Spring 자체를 이용해본 적이 없기 때문에 Bean을 xml에 직접적으로, 프록시를 직접적으로 설정해서 AOP를 구현하는 것을 본 것이 이해가 잘 가는 부분이었다.

https://www.youtube.com/watch?v=y2JkXjOocZ4&t=2s

문제는 그 다음이다. Spring Boot는 AspectJ 라이브러리를 이용한 것이 아니라, 컴파일 단계에 AOP를 구현한 것이라 Spring Aop 라이브러리는 프록시를 이용하였기에 Self-invocation이란 문제가 발생한다.

해당 문제는 자기 자신의 메소드를 호출할 때 AOP가 제대로 적용되지 않는 문제가 발생한다.

A 메소드에서 @Tranascational이 붙은 B를 호출하면 @Transcational이 적용이 안 된다. 하지만 A가 @Transcaional이 붙어 있으면 제대로 적용된다.

 해당 문제를 보면서 서비스를 하나 더 생성하여 해당 문제를 해결하고자 하였는데, 생각해보니 @Transcaional이 붙은 B는 insert 만 실행하는 코드가 하나만 있다. 그렇기에 해당 메소드에는 어노테이션이 필요없겠다는 생각이 들었다. Rollback할 요소가 없기 때문이다. Insert가 실패하면 어차피 데이터가 삽입되지 않기 때문이다.

문제는 반대로 A에서 @Transcational을 붙여서 해결할 수도 있는데 이때 DB Pool을 잡아 먹는 것인지는 더 알아볼만한 요소라 찾고 이후 글을 하나 더 작성하려고 한다.

반응형
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/92343

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

  DFS로 문제를 푸는 것은, "2 ≤ info의 길이 ≤ 17"을 보고 알 수 있습니다. 또한 그림도 친절하게 트리임을 알 수 있습니다.
하지만 이 문제는 기존 DFS문제를 조금 달랐고, 이렇게 생각해볼 수 있다는 것을 알 수 있었습니다. 해당 글에서는 그런 점에 대해 적어보겠습니다.

 기존 DFS라고 부르고 싶은 로직에서 탐색할 수 있는 노드는 자신에게 연결되어 있는 노드뿐이었습니다. 해당 문제에서 자신에게 연결된 노드가 아니라, 이전 노드, 현재 노드에서 갈 수 있는 곳이었습니다. 또한 이 뿐만 아니라 탐색을 할 수 있는 조건이 탐색하고 있는 중에 상태값이 변경됨에 따라 변경된다는 점도 새로웠습니다. 정리하면 다음과 같습니다.

1. 탐색할 수 있는 곳은 이전 노드와 연결된 노드 + 현재 노드와 연결된 곳
2. 탐색 중에 하위 노드를 탐색할 수 있는 조건이 변경됨 -> 탐색 중에 상태 값이 변경됨

 탐색할 수 있는 노드의 값을 원래 함수에서 들고 다니지 않았지만, 이전 노드와 연결된 곳을 가지고 다녀야 하기때문에 list로 들고 다녀도 되고, set으로 들고 다녀도 된다고 생각이 들었습니다. set으로 들고 다니려고 했는데 이터레이터를 조작하는 것이 너무 복잡해서, 우선 순위 큐로 변경해서 사용하였습니다. 기존 dfs 코드의 틀과 별 다른 점은 없습니다.

 처음 들었던 생각인 다시 올라가서, 다시 어떻게 내려오지엔 막연한 생각이 있었는데 이동할 곳은 저장해서 가지고 다니면 된다는 생각이 새로웠네요.

#include <string>
#include <vector>
#include <algorithm>
#include <set>
#include <iostream>
#include <queue>
using namespace std;

int answer = 0;

void dfs(const vector<int> &info, const vector<set<int>> &graph, int node, queue<int> nextNode, int sheep, int wolf) {
    // 현재 노드가 양인지 아닌지 어캐 앎, 양의 최고 개수를 갱신해야함.
if (info[node] == 0) {
        sheep += 1;
    }
    else {
        wolf += 1;
    }
    answer = max(answer, sheep);

    if (sheep <= wolf) {
        return;
    }

    
    // 갈 수 있는 노드를 구해야함.
    for (auto it = graph[node].begin(); it != graph[node].end(); it++) {
        nextNode.push(*it);
    }

    // 순회 돌면서 dfs 내려가기
    // for (auto it = nextNode.begin(); it != nextNode.end();) {
    for(int i=0; i<nextNode.size(); i++) { 
        // 현재 노드는 제거
        int next = nextNode.front();
        nextNode.pop();
        dfs(info, graph, next, nextNode, sheep, wolf);
        nextNode.push(next);
    }
}

int solution(vector<int> info, vector<vector<int>> edges) {
    vector<set<int>> graph(info.size());
    // 그래프 생성
    for (vector<int> edge : edges) {
        graph[edge[0]].insert(edge[1]);
    }

    dfs(info, graph, 0, { }, 0, 0);

    return answer;
}
반응형
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/42627#

 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

문제 설명보다는 기존 다른 코드와 다른 점을 설명하고자 한다.
https://school.programmers.co.kr/learn/courses/14743/lessons/118891

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 
위 링크에서 문제에서 조금 더 고려해야 하는 부분을 알아두는 것이 좋다.
내 소감은 이렇게 풀면 안 되지만 Heap을 사용하는 문제라, Heap을 어떤 용도로 사용할까? 란 것에 초점을 맞췄다.
하지만 Heap 무조건 넣으면 안 되고, 각 요소마다 값이 2개씩이 있고 어느 쪽으로 정렬해서 넣어야 하는지 애매한 부분이 많았다.

#include <vector>
#include <iostream>
#include <queue>
#include <algorithm>
 
using namespace std;
 
struct cmp {
    bool operator()(vector<int> a, vector<int> b) {
        return a.at(1) > b.at(1);
    }
};
 
int solution(vector<vector<int>> jobs) {
    int answer = 0, j = 0, time = 0;
    sort(jobs.begin(), jobs.end());
    priority_queue<vector<int>, vector<vector<int>>, cmp> pq;
    while (j < jobs.size() || !pq.empty()) {
        if (jobs.size() > j && time >= jobs[j][0]) {
            pq.push(jobs[j++]);
            continue;
        }
        if (!pq.empty()) {
            time += pq.top()[1];
            answer += time - pq.top()[0];
            pq.pop();
        }
        else
            time = jobs[j][0];
    }
    return answer / jobs.size();
}

다른 분들의 코드를 보면 jobs을 한번 정렬해서 시간 순으로 만든다. 그리고 난 뒤 job을 훑어가면서 내 시간에 따라 heap에 넣고, heap에서 시간 계산을 해나가며 내 상태 값을 변경해 나간다.
그리고 heap에 값이 없는 경우에는 시간을 변경한다.
나는 Job을 정렬하는 것이 마음에 들지 않았다.

결국 Job을 정렬한다는 의미는 Heap을 사용해도 된다는 의미로 생각이 들었다.
그리고 무엇보다도 while문이 두 가지 행위를 한번에 하고 있었다. 
1. job을 돌기
2.Heap 체킹
어쩔 수 없이 heap에서 값을 빼내가며 상태값을 바꿔야 한다. 그래도 분리하고 싶어서 최대한 분리해봤다.

#include <vector>
#include <iostream>
#include <queue>
#include <algorithm>
#include <iostream>
using namespace std;
 
struct pairFirstASC {
    bool operator()(const pair<int, int> &a, const pair<int, int> &b) {
        return a.first > b.first;
    }
};

struct pairSecondASC {
    bool operator()(const pair<int, int> &a, const pair<int, int> &b) {
        return a.second > b.second;
    }
};

int solution(vector<vector<int>> jobs) {
    int answer = 0;

    priority_queue<pair<int, int>, vector<pair<int, int>>, pairFirstASC> pq;
    priority_queue<pair<int, int>, vector<pair<int, int>>, pairSecondASC> pq2;
    
    for(vector<int> job: jobs) {
        pq.push({job[0], job[1]});
    } 
   
    int curTime = 0;
    
    while(!pq.empty()) {
        while(!pq.empty() && pq.top().first <= curTime) {
            pq2.push(pq.top());
            pq.pop();
        }
        
        if(pq2.empty()) {
            curTime = pq.top().first;
        } else if(!pq2.empty()) {
            curTime += pq2.top().second;
            answer += curTime - pq2.top().first;
            pq2.pop();
        }
    }
    
    while(!pq2.empty()) {
        curTime += pq2.top().second;
        answer += curTime - pq2.top().first;
        pq2.pop();
    }
    
    return answer / jobs.size();
}

코드가 더 늘어나긴 했지만, 재밌는 도전이었던 것 같다.

 

반응형
728x90

새로 알게 된 것
set<string> num(gems.begin(), gems.end());

중요하다고 생각한 것

1. for문 한번만 돌아야 된다.
2. 도는 것을 굳이 나눠서 첫 번째 초기값을 따로 구하지 않아도 된다.

더 생각해볼 것은 하나의 케이스에 메몰되지 않고, 더 다양한 케이스에 대해서 고민해볼 것

#include <string>
#include <vector>
#include <unordered_map>
#include <set>
#include <iostream>
using namespace std;

vector<int> solution(vector<string> gems) {
    vector<int> answer(2);
    unordered_map<string, int> m;
    set<string> num(gems.begin(), gems.end());
    
    int start = 0;
    bool isFirst = true;
    for(int i=0; i<gems.size(); i++) {
        if(m.find( gems[i] ) != m.end()) {
            m[gems[i]]++;
        } else {
            m[gems[i]] = 1; 
        }
        // 앞 요소 제거
        while(m[ gems[start] ] > 1) {
            m[ gems[start] ]--;
            start++;
        }

        if(m.size() == num.size()) {
            if(isFirst || answer[1] - answer[0] > i - start) {
                answer[0] = start;
                answer[1] = i;  
                isFirst = false;
            }            
        }
    }
    
    return {
        answer[0] + 1,
        answer[1] + 1
    };
}

https://school.programmers.co.kr/learn/courses/30/lessons/67258

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

반응형
728x90

 개인 프로젝트를 할 때엔 Vuejs, React 라이브러리를 기본으로 또는 이를 쓰는 프레임워크를 이용해서 개발을 진행했었다. 하지만 취직한 곳에서는 NodeJs기반의 프론트 프로젝트를 진행하지 않았고 그런 이유도 어느정도 납득 가능한 부분이었다. 백엔드 개발과 프론트 엔드 개발을 나누는 것이 좋지만, 인력이 두 배 이상으로 들 수도 있고 꽤나 여러 사람이 피곤해질 수 도 있다고 생각한다. 사수분은 리액트를 사용해본 적이 없기 때문에 왜 좋냐고 물어보았을 때 구체적인 이유를 이야기할 수 없었다. 지금은 스코프에 대한 이해가 있어서 이를 설명하겠지만 그 당시에는 제이쿼리도 다른 프레임워크만큼 나쁘지 않게 보였기에 이야기 할 수 없었다.

 왜 제이쿼리가 좋아보였는가? 사실 그 이유보다는 타임리프에 대한 편리한 점이 크게 보였다. 리액트를 사용하면 백엔드 서버는 모든 것을 API 서버 역할을 수행해야 하기에 많은 API가 생길 수도 있다. 비슷한 행위를 하는 API가 여러개 생길 수도 있고 이름도 겹칠 수도 있고 여러모로 머리가 아플 수도 있다. 오히려 서비스 로직을 공유하기에 더 좋은 코드가 생길 것 같다는 생각도 들기도 한다. 이런 문제를 웹 컨트롤러에서 서비스를 호출하여 데이터를 넘겨주는 방법으로 개발한다면 필요없는 api를 만들지 않아도 된다. -> 이를 해결하려고 Web Controller에 구현 로직 들이 첨가되니 api로 빠져도 되는 부분들, Controller가 무거워졌다.

하지만 제이쿼리와 타임리프를 사용하면서 답답하고 야근을 하게 만드는 요인이 무엇일까?

 첫 번째로는 타임리프의 에러가 너무 길다.
타임리프에서 파싱 에러가 발생한다면 거의 한페이지만큼의 에러를 나열해주는데 보통 맨 하단 또는 중단 쯤에 에러 관련하여 나와 하나 하나 고쳐 나가야 한다. 그러다 안 고쳐지면 직접 타임리프에서 호출하는 모델 값을 출력하여 어떤 값이 왜 안 나오는지에 대해 하나씩 확인해야 한다.

 두 번째로는 컴포넌트화가 나쁘다.
리액트의 꽃은 컴포넌트가 아닐까? 디자인도 컴포넌트 단위로 진행되고 개발도 컴포넌트 단위로 진행할 수 있다. 그리고 제일 중요한 것은 코드를 재활용할 수 있고 동일한 코드에서 문제가 발생하였을 때 하나씩 찾아가서 고쳐야 한다. 타임리프에서 프래그먼트를 이용해서도 비슷한 효과를 볼 수 있겠지만 이 또한도 복잡하고 답답하다는 사실을 잊지 말자. IDE에서 컨트롤러에서 내려주는 모델의 객체의 변수를 다 알고 있어서 바로 쓸 수 있지만 페이지에서 프래그먼트에 값을 넘기면 프래그먼트에는 무슨 변수가 있는지에 대한 알려주지 않는다.
다만 같은 페이지에서 사용한다면 나올까..? 안 해봐서 모르겠다. 그렇게 쓸거면 프래그먼트를 사용하는 이유가 있을까?

 세 번째로는 렌더링이 답답하다.
리액트는 화면에 뿌려지는 것에 데이터를 넣어두고, 데이터가 변경되면 다시 렌더링이 된다. 그래서 편하다. 제이쿼리는 내가 직접 렌더링을 해줘야 하는 것이 꽤나 좀 그렇다. 데이터만 가지고 화면을 조작한다는 관점과 내가 직접 데이터와 화면을 조작한다는 것은 많이 다르다. 또한 화면에 있는 값이 내가 가지고 있는 값이란 생각을 하게 된다.따로 내부적으로 변수에 데이터를 가지고 있지 않아도 되겠다는 생각이 들 것이다.

 제이쿼리를 이용한다는 것은 자바스크립트의 버전때문에 복잡한 돔 요소 조작을 편리하게 하는 것이다. 그러니까 순수 JS를 쓰는 것과 같다는 것이다. 리액트를 사용하면 렌더링과 컴포넌트 등 신경을 써야 하기에 복잡할 수 있는 문제도 있기에 뭐.. 그렇다.

2024.03.02

You Don't know JS Yet과 Deep Dive React를 읽어보니 왜 내가 답답한 것인지에 대해 알 수 있었고, 아직도 js를 제대로 알고 있지 않는다란 생각이 들었다.

한번 읽어보길 추천한다.

반응형
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/172927

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

문제의 조건을 확인

곡갱이 종류별로 5개, 미네랄 50개

 

생각할만한 방법

제일 완벽하고 만능인 방법이 뭘까, 모든 방법을 탐색하고, 최적의 값을 가져오면 된다.

 

주먹구구식으로 값 찾기

모든 방법을 탐색하는 데 탐색을 몇 번 해야하나? 곡갱이가 총 15개이기에, 곡갱이를 총 곡갱이를 모든 조합을 구한다면 15!이 되고, 이는 1e9를 넘어간다.

 

문제의 조건 확인하기

문제의 조건에서 미네랄이 총 50개이고, 곡갱이가 5번씩 사용 가니, 총 미네랄을 10개의 묶음을 만들 수 있다.

10개의 묶음 그러니까 곡갱이를 15개 중 최대 10개 밖에 못 쓴다.

이는 결국 15C10이다. 이를 계산하면 다음과 같다. 3,003이다.

 

충분히 완전 탐색으로 구할 수 있는 수이다.

그래서 다른 사람들의 코드를 확인한다면, dfs로 탐색하는 것이다.

 

다른 방법은?

조합을 생각하지 못하고, 다른 방법을 생각하더라도 해볼 수 있는 수는 하나 더 있다.

그리디다. 광물을 5개씩 묶음으로 만들고 이를 광물의 가치에 따라 내림차순으로 정렬하면 된다.

그리고 가치가 높은 다이아 곡갱이부터 광물을 캐면 된다.

 

이 방법이 되는 이유는 곡갱이의 순서가 고정이 되고, 광물의 순서를 변경해서 최소를 구할 수 있기 때문이다.

이를 증명하려면 그리디 정당성을 증명해야 하는데 솔직히 말해서 잘 모른다.

 

고려해야 부분이 하나 더 있는데, 광물의 가치를 어떻게 판단하느냐이다.

가치를 하나의 값으로 관리를 하거나, 직접적으로 광물의 수를 기록하는 방법이 있다.

이때 돌 5개가 철 1개보다 가치가 낮음을 알아야 한다.

그렇기 때문에 정렬 시 무조건 가치가 돌이 하나라도 더 많으면 앞으로 와야한다.

이를 광물의 수로 정렬 시 정렬 함수를 광물의 보유 수로 하나씩 비교해서 해야 한다.

하지만 광물의 수를 기록하지 말고 하나의 가치로 합산하는 방법으로도 가능하다.

돌이 1, 총 5개의 값은 5임으로 철은 6이면 된다.

이런 방식으로 돌 1, 철 6, 다이아 31로 가치를 합산해서 하나의 값으로 내림차순 정렬을 해도 된다.

 

나중에 그리디 정당성 증명을 잘 알게 된다면 추가적인 글을 작성하지 않을까 싶다.

반응형
728x90

중첩 함수는 나쁜 아이가 아니에요

대게 함수를 중첩 해서 사용은 가능은 하지만 잘 하지 않는다. 그런 이유는 코드가 복잡해지고, 코드가 난해하다는 느낌이 들기도 하기 때문이다.

하지만 중첩 함수가 괜찮은 친구처럼 보이는 경우는 언제일 수 있을까? 내가 생각했을 때엔 응집도를 고려했을 때가 아닐까 싶다.

보통 코드에 대한 응집도를 높이기 위해 관련 있는 코드끼리 모아두려고 한다. 그래서 클래스를 사용한다고 본다. 

그런데 클래스에 들어가는 함수에서, 함수를 호출하는 경우 코드가 복잡해지는 경향이 있다. 왜 그런가 싶으면 코드가 떨어져 있어 연결되어 있음을 볼 수가 없다.

그래서 코드를 파악하기 힘들다.

중첩 함수가 괜찮은 녀석인지 보기 위해 코드를 하나 봐보자.

function order(menu) {
    console.log(menu);
}
 
 
function brand(brandName) {
    switch(brandName) {
        case "ABC_Market":
            order("신발");
            break;
        case "Mart":
            order("음료수");
            break;
    }
}

위 코드와 같이 order가 계속 호출 되니 함수로 따로 빼서 쓰는 것이 맞다.

 

현재는 코드가 짧기 때문에, 그리고 작성한 이는 의도를 알지만, 코드가 길어지거나 다른 이가 보았을 때 코드를 이해하기 쉬울까?

order가 brand에서만 사용되는지 알 수 있을까? 확실하게!? 모를 것이다.

 

그래서 코드의 응집도를 높일 수가 있다.

function brand(brandName) {
    function order(menu) {
        console.log(menu);
    }
    switch(brandName) {
        case "ABC_Market":
            order("신발");
            break;
        case "Mart":
            order("음료수");
            break;
    }
}

남들이 보았을 때 거부감이 들 수 있으나 order 함수가 명확하게 brand에서만 사용이 됨을 알 수 있습니다.


Currying이 뭐에요? 먹는건가요?

 함수형 프로그래밍에서 나오는 개념으로, 기존의 복잡한 파라미터를 넘겨 복잡한 처리를 하는 함수를 읽기 쉬운 나누어진 코드로 변경하는 것을 말한다.

코드를 한번 봐보자.

function Hello(text) {
    return function (text2) {
        console.log(text, text2);
    }
}

이게 무슨 코드인지 너무 난해하다고 느낄 수 있다. 하지만 사용 방법은 더 난해하다.

코드를 보고 다 고개를 절레절레 지울 수 있는 으뜸한 코드가 완성되었다.

사실 그렇게 나쁘지 않아요 ( ╯□╰ )

함수를 호출 시에 파라미터를 저렇게 넘겨서 사용하니 정말 별로에 별로이지만 다음 호출 방법을 보면 생각이 조금 달라진다.

let hello_Name_To_Text = Hello("아무개님");
hello_Name_To_Text("안녕하세요");

이렇게 작성하니 뭔가 편해지는 느낌과 더불어 머리에 이런 방법도 가능하네?

기존에 불편하게 작성했던 코드를 쉽게 작성할 수 있을 것 같은 생각이 날 수 있다.

  • Partial Application은 Currying 방법을 다르게 부르는 동의어라고 보시면 됩니다.

기존에 하나의 함수에서 복잡하게 작성하였던 코드들이 잘게 분리되면서 복잡도가 낮아지며 코드가 유연해지는 쪽으로 사용하면 좋을 것이다.

  • 참고, 화살표를 사용한 코드 단축화

 코드가 중복적으로 들어가는 쓸데없는 코드를 보일러레이트 코드(Boilerplate code)라 부른다. 위 에서는 function과 return으로 코드가 장풍을 맞은 것마냥 들어가버린다.

이를 없앨 수 있는 방법은 화살표다. const로 변수처럼 함수를 사용할 때엔 Func를 붙이기를 추천하고, 아래 코드가 짧아지긴 했으나 더 헷갈릴 것 같다는 생각이 든다.

const Hello = text => text2 => {
        console.log(text, text2);
}

왜 갑자기 확장 함수가 떠오를까?

위 코드들을 보다 보면 확장 함수가 생각난다. 코틀린에서 확장 함수를 지원해주는데 뭔가 위 코드와 비슷한 것 같은 느낌이 든다.

fun List<Int>.getHigherThan(num: Int): List<Int> {
    val result = arrayListOf<Int>()
    for (item in this) {
        if (item > num) {
            result.add(item)
        }
    }
    return result
}
 
fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
    val filtered = numbers.getHigherThan(3).toString()
    System.out.println(filtered)
}

확장 함수는 다음 코드와 같이 돌아간다.

눈 여겨 볼만한 것은 List<Int>라는 타입에 getHigarThan을 넣었다는 점이다.

기존이었다면 getHigherThan 함수를 만들고 getHigherThan(numbers, 3).toString()으로 호출했을 것이다.

 numbers.getHigherThan(3).toString() 코드에 비해  getHigherThan(numbers, 3).toString()은 어디로부터 시작하는지 명시가 되어있지 않는다는 느낌이 든다.

정리

위 모든 코드들이 코드를 단순하게 만들기 위한 기법이라 생각한다. 생소하지만 어느 한 구석 잘 적용한다면 유지 보수하기 좋은 코드가 탄생할 것일까 말까 아무도 모른다.

 

 
반응형

'코딩 관련 > 자바스크립트' 카테고리의 다른 글

generator function  (0) 2023.10.19
getDay(), getDate()  (0) 2023.10.19
Map 섞어 쓰면 에러 발생  (0) 2023.10.19
Javascript Template literal  (1) 2023.10.19
배열 초기화 & Json 값만 배열로 변경하기  (0) 2023.10.19
728x90

Java의 Iterator와 비슷한 느낌을 갖는 function이다. 표기법으로는 function 또는 function 으로 자신이 쓰고 싶은 방법대로 작성하면 된다.

왜 Iterator와 비슷한가? 보통 Iterator는 Collection에 들어가며, 내가 넣은 요소를 탐색하는 요소로 사용된다.

function에도 동일하게 내가 요소(구간)을 만들어 다음 로직의 요소(구간)을 실행할 수 있도록 function을 실행 구간을 블록킹할 수 있게 된다.

function* goStop() {
	console.log("go");
	yield "Stop";
	console.log("go2");
	yield "Stop2";
}

function* generateName() {
  yield 'W';
  yield 'O';
  yield 'N';
  yield 'I';
  yield 'S';
  yield 'M';
}

// for..of
const genForForOf = generateName();
for (let i of genForForOf) {
  console.log(i);
}

이터러블하기 때문에 of에 넣어도 동작한다.

어느 상황에서 유용할까?

모질라 페이지에서는 아래와 같이 설명한다.

Generators in JavaScript — especially when combined with Promises — are a very powerful tool for asynchronous programming as they mitigate — if not entirely eliminate -- the problems with callbacks, such as Callback Hell and Inversion of Control. However, an even simpler solution to these problems can be achieved with async functions.

비동기 프로그래밍에서 유용하다고 하는데 나는 다음과 같이 생각한다.

  • 여러 번에 걸쳐서 실행되어야 하는 하나의 로직 단위

우리는 보통 아토믹하게 작은 단위로 함수를 만들고 이를 조합해서 사용하며, 이는 재사용성에 의해 코드의 복잡도가 낮아진다고 말한다. 하지만 제너레이터 함수에 의해 함수의 동작을 내가 로직 단위로 쪼개서 조작할 수 있다.

로직은 하나의 흐름이지만, 로직 중간 중간 어느 시점에 이어서 진행되어야 하는 시점이 있다면 어떻게 될까?

일상 생활에 예를 들어보자.

나는 컴퓨터를 켜서 C언어를 공부한다고 생각하자.

C언어의 하나의 챕터를 공부하는 function* studyForC() {} 수행 중 이다.

하지만 모든 시간 내가 온전히 C언어를 공부하는 것이 아니다. 중간 중간 화장실에 가야 하기도 하고, 물도 마셔야 한다.

함수 실행의 주체를 공부가 아닌 나로 바꿔서 로직을 실행한다고 해보자.

javscript function Human() {

// studyForC.next() 시작 0~9페이지

// 화장실

// studyForC.next() 10~19페이지

// 물마시기

// studyForC.next() 20~29페이지

}

만약에 이를 제너레이터 함수가 아닌 다른 방법으로 실행했어야 했다면, 화장실 갔다 온 뒤 Callback함수로 넘겨 매 행위를 정해줘야 했을 것이다.

다른 언어에서 이런 함수를 제공하는지는 모르겠지만, 자바스크립트는 역시 난해하고 이상한 친구인듯 하다.

 

반응형

+ Recent posts