기억의 실마리
2023. 6. 27. 15:15

탐욕알고리즘 (Greedy Algorithm)

현재 상황에서 가장 좋아보이는 상황만을 선택하는 알고리즘이다.

흔히 그리디, 탐욕알고리즘이라고 부리기도 하며 최적의 해를 구하기위한

방법으로 사용될 때가 많다.

 

탐욕 알고리즘의 접근방법

1. 현재 상황에서 어떤 것을 선택할지 방법을 고안한다.

2. 자신이 고안한 알고리즘이 항상 최적의 해를 보장하는지 정당성을 확인해야한다.

 

탐욕 알고리즘 문제

전형적인 탐욕 알고리즘을 이용해 풀 수 있느 문제로 "거스름 돈" 문제가 있다.

거스름 돈 문제를 통해서 그리디알고리즘에 대해서 이해해보자.

문제는 아래와 같은 조건과 답을 내어야 한다.

  • 카운터에 500원 100원 50원 10원이 무수히 많다고 가정한다.
  • 손님에게 11,480원을 거슬러 주어야 할때, 동전 개수의 최소값은?

결론부터 말하자면 이 문제의 해결방법은 가장 큰 화폐단위부터 거슬러 주는 것이다.

즉 500원부터 10원까지 거슬러 줄 수 있는 만큼 거술러주면 되는 것이다.

화폐단위 500원 100원 50원 10원
현재 금액 11,480 480 80 30
남은 금액 480 80 30 0
동전 개수 22 22 + 4 22 + 4 + 1 22 + 4 + 1 + 3

동전 개수의 최소값은 30이다.

 

큰 화폐부터동전의 개수를 구하며 내려왔을때 최적의 해를 구할 수 있는 이유는

각 화폐단위가 배수 관계에 속하기 때문에 그렇다.

 

예를 들어 640원을 거슬러 주어야한다고 하고 220원, 200원, 10원 동전이 있다고 가정하면

220원 2개, 200원 1개로 총 3개가 필요할 것이다. 이 또한 배수관계에 속하기 때문에

가장 큰 수의 동전부터 나눌 수 있는 만큼 나누고 점차 작은 수의 동전으로 나누어주는

같은 방식의 탐욕알고리즘을 사용하여 문제를 해결할 수 있다.

 

마치며...

탐욕알고리즘에 대해서는 간단하게 포스팅을 해보았다. 실질적으로 탐욕알고리즘은 코딩테스트를 볼 때 중요했던 개념이었다. 나는 탐욕알고리즘을 파악할때 예시로 주어지는 입력값과 출력값(정답)을 보며 연관성을 최대한 찾아보고 답을 만들기 위한 코드설계와의 연관성과 규칙성이 있는지를 확인하면서 코드를 작성할때 어떠한 연관성이 있는지를 파악하면서 실마리를 찾아냈던 것 같다. 개인적으로 익숙해지기 어려운 접근방식이었던 기억이 있다...

2023. 6. 26. 20:48

1. 선택정렬(Selection Sort)

단계별로 정렬을 수행할때 가장 작은 원소를 선택해서 앞으로 보내는 정렬 방법이다.

앞으로 보내진 원소는 위치가 변경되지 않고 시간복잡도 O(N²)으로 비효율적인 알고리즘에 속한다.

 

동작방식

1. 단계별로 수행하면서 가장 작은 원소를 선택한다.

2. 처리되지 않은 원소중에 가장 앞의 원소와 교체한다.

 

  • 선택정렬 함수 예시
function selectionSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    let minIndex = i;
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[minIndex] > arr[j]) {
        minIndex = j;
      }
    }
    // 스왑(swap)
    let temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
  }
}

 

 

2. 버블정렬(Bubble Sort)

인접한 두 원소를 체크하여 정렬이 안되어 있다면 위치를 서로 변경하는 정렬방식이다.

서로 인접한 두 원소를 비교하는 것이 마치 거품과 같다고 하여 붙여진 이름이다.

시간복잡도 O(N²)으로 비효율적인 정렬 알고리즘에 속한다.

 

동작방식

1. 단계별로 수행하면서 가장 큰 원소가 맨 뒤로 이동한다.

2. 다음단계에선 맨 뒤로 이동한 원소는 정렬에서 제외한다.

 

  • 버블정렬 함수 예시
function bubbleSort(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    for (let j = 0; j < i; j++) {
      if (arr[j] < arr[j + 1]) {
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
}

 

 

3. 삽입정렬(Insertion Sort)

각 원소를 적절한 위치에 삽입하는 정렬이다.

 

동작방식

1. 단계별로 수행하면서 현재 원소가 삽입될 위치를 탐색한다.

2. 지정 위치에 도달할 때까지 반복적으로 왼쪽으로 이동한다.

 

  • 삽입정렬 함수 예시
function insertionSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j > 0; j--) {
      // 시작인덱스부터 1까지 1씩 감소하며 반복
      if (arr[j] < arr[j - 1]) {
        let temp = arr[j];
        arr[j] = arr[j - 1]; // 한 칸씩 왼쪽으로 이동
        arr[j - 1] = temp; // 스왑(swap)
      } else {
        break; // 자기보다 작은 데이터를 만나면 그 위치에서 멈춤
      }
    }
  }
}

 

 

4. 병합정렬(Merge Sort)

병합정렬은 분할정복(divide and conquer) 알고리즘이다.

 

분할정복이란?

  • 분할(divide): 큰 문제를 작은 부분 문제(쉬운 문제)로 분할한다.
  • 정복(conquer): 작은 부분 문제를 각각해결하도록 한다.
  • 조합(combine): 해결한 작은 부분의 답을 이용하여 큰 문제를 해결해나간다.

일반적으로 재귀함수를 사용하여 구현한다.

큰 문제를 작은 문제로 분할하는 방식이 같은 경우가 많기 때문이다.

그렇기 때문에 쪼개질 수 없는 크기가 될 때까지 계속해서 분할한다.

 

장점: 최악의 경우에도 O(N log N)을 보장할 수 있다는 점에서 효율적이다.

단점: 전반적으로 정복과정에서 임시배열이 필요한 경우가 지배적이다.

 

동작방식

1. 단계별로 수행하면서 현재 원소가 삽입될 위치를 탐색한다.

2. 지정 위치에 도달할 때까지 반복적으로 왼쪽으로 이동한다.

 

  • 병합정렬 함수 예시
function merge(left, right) {
  let sorted = [];
  while (left.length && right.length) {
    if (left[0] <= right[0]) sorted.push(left.shift());
    else sorted.push(right.shift());
  }
  return [...sorted, ...left, ...right];
}

function mergeSort(arr) {
  if (arr.length === 1) return arr;
  let half = Math.ceil(arr.length / 2);
  let left = arr.slice(0, half);
  let right = arr.slice(half);
  return merge(mergeSort(left), mergeSort(right));
}

let arr = [7, 4, 3, 2, 1, 6, 5];
const sortedArray = mergeSort(arr);

console.log(arr); //[7, 4, 3, 2, 1, 6, 5]
console.log(sortedArray); //[1, 2, 3, 4, 5, 6, 7]

function merge: 이미정렬된 배열 left와 right를 하나의 함수로 합쳐주는 순수 함수

function mergeSort: 배열을 반으로 나누어 merge함수에게 left와 right로 전달한다.

 

 

마치며...

오늘 포스팅한 정렬알고리즘은 사실, 알고리즘을 따로 공부를하기전까진 단순하게 정렬하는거지! 라고 생각했지만 정말 다양하고 어떤방식으로 정렬을 하느냐에 따라 시간복잡도가 제곱이상의 차이가 나기도 한다는 것이 새롭게 다가왔던 것 같다. 앞으로도 코드의 퀄리티를 위해서 프로젝트에서 정렬할때 잘 생각해보고 적용시켜야겠다.

2023. 6. 26. 13:44

1. 배열(Array)

가장 기본적인 자료구조이며 여러개의 변수를 담는 공간으로 이해할 수 있다.

배열은 인덱스(Index)가 존재하고 인덱스는 0부터 시작한다.

특정한 인덱스에 직접적으로 접근이 가능하고 수행시간은 O(1)을 가진다.

 

장점: 캐시(cache) 히트 적중률이 높고 조회가 빠르다.

단점: 배열의 크기를 미리 지정해야 하는 것이 일반적이며 데이터의 추가 및 삭제에 한계가 있다.

 

캐시히트 적중률이란?

CPU(클라이언트)에서 요청한 데이터가 캐시에 존재하여 이를 이행할 수 있을때 캐시 히트(Hit)라고 한다. 

Memory address 0x0000 0x0004 0x0008
Index 0 1 2
Value 10 30 70

 

2. 연결리스트(Linked List)

연결 리스트는 메인 메모리상에서 주소가 연속적이지 않으며 배열과 다르게

크기가 정해져있지 않고 동적으로 변경이 가능한 것이 특징이다.

 

장점: 포인터가 다음 데이터의 위치를 가지고 있기 때문에 삽입과 삭제가 간편하다.

단점: 특정 인덱스의 원소를 검색할때 앞에서부터 원소를 찾기 때문에 검색속도가 느리다.

3. 스택(Stack)

먼저 들어온 데이터가 나중에 추출되는 자료구조이며 박스가 쌓인 형태를 스택이라고 볼 수 있다.

새로운 원소를 삽입할때는 마지막위치에 삽입하고 삭제할때 마찬가지로 마지막 위치에서 삭제된다.

 

 

4. 큐(Queue)

큐는 먼저 삽입된 데이터가 먼저 추출되는 자료구조이다.

예시로 게임 대기 큐의 경우 먼저 대기한 사람이 먼저 게임에 매칭되는 것과 동일하다.

Javascript Queue

Javascript에서는 큐 라이브러리를 기본적으로 제공하고있지 않기 때문에

간단하게 Queue 라이브러리를 직접 작성하여 사용할 수 있다.

 

필자 역시 Javascript를 주로 사용하기 때문에 Queue라이브러리를 만들어 두었다.

class Queue {
  constructor() {
    this.items = {};
    this.headId = null;
    this.tailId = null;
    this.length = 0;
  }
  size() { return this.length; }
  isEmpty() { return this.length === 0; }
  enq(item) {
    if (!this.headId) this.headId = 0;
    if (this.headId == 0) {
      this.items[this.length] = item;
      this.length++;
      this.tailId = this.length - 1;
    }
    else  {
      this.tailId++;
      this.items[this.tailId] = item;
      this.length++;
    }
  }
  deq() {
    if (this.isEmpty()) throw new Error('Queue is empty');
    if (this.length == 0) return
    this.length--;
    const item = this.items[this.headId];
    delete this.items[this.headId];
    this.headId++;
    if (this.length == 1) this.tailId = this.headId;
    if (this.length == 0) {
      this.headId = null;
      this.tailId = null;
    }
    return item;
  }
  head() {
    if (this.isEmpty()) throw new Error('Queue is empty');
    return this.items[this.headId];
  }
  tail() {
    if (this.isEmpty()) throw new Error('Queue is empty');
    return this.items[this.tailId - 1];
  }
}

혹시나 블로그를 방문하시는분들께서 javasciprt Queue가 필요하셨다면 사용하셔도 무방합니다.

 

5. 트리(Tree)

트리는 계층적인 구조를 표현할 때 사용할 수 있는 자료구조다.

나무를 뒤집은 것 같은 형태이다.

  • 루트 노드(root node): 부모가 없는 최상위 노드
  • 단말 노드(leaf node): 자식이 없는 노드

서로 같은 부모를 가지는 노드간의 관계를 형제관계라고 하며 루트노드에서의 길이를

깊이(depth)라고 한다. 이는 즉 출발노드에서 목적지 노드까지 거쳐야 하는 간선의 수를 의미한다.

트리의 높이(height)는 루트 노드에서 가장 깊은 노드까지의 길이를 의미한다.

 

트리의 종류

  • 이진 트리(Binary Tree): 최대 2개의 자식을 가질 수 있는 구조의 트리를 말한다.

 

  • 포화 이진 트리(Full Binary Tree): 리프노드를 제외한 모든 노드가 두 자식을 가지고 있는 트리다.

 

  • 완전 이진 트리(Complete Binary Tree): 모든 노드가 왼쪽 자식부터 차례로 채워진 트리다.

 

  • 높이 균형 트리(Height Balanced Tree): 왼쪽과 오른쪽 자식 트리의 높이가 1이상 차이가 나지 않는 트리다.

 

6. 힙(Heap)

힙은 원소중 최댓값 또는 최솟값을 빠르게 찾아내는 자료구조이다.

원소 삽입과 삭제할때 O(logN)의 수행시간을 요구하며 N개의 데이터를 힙에 넣었다

모두 꺼내는 작업은 정렬과 동일하여 수행시간 O(N log N)을 요구한다.

 

힙은 완전 이진트리 자료구조를 따르며 우선순위가 가장 높은 노드가 루트에 위치한다.

 

1. 최대 힙(max heap)

  • 부모 노드의 키 값이 자식 노드의 키 값보다 항상 크다.
  • 루트 노드가 가장 크며, 값이 큰 데이터가 우선순위를 가진다.

2. 최소 힙(min heap)

  • 부모 노드의 키 값이 자식 노드의 키 값보다 항상 작다.
  • 루트 노드가 가장 작으며, 값이 작은 데이터가 우선순위를 가진다.

 

7. 우선순위 큐(Priority Queue)

우선순위에 따라서 데이터를 추출할 수 있는 자료구조다.

우선순위 큐는 일반적으로 힙(heap)을 이용해 구현하며 배열 자료형으로도 구현가능하다.

우선선순위 큐 구현방식 삽입 시간 삭제 시간
배열 자료형 O(1) O(N)
힙(Heap) O(logN) O(logN)

일반적으로 우선순위 큐는 이진트리 구조를 사용한다.

 

javascript는 기본적으로 우선순위 큐 라이브러리를 제공하지 않기 때문에 필요한 경우

별도의 라이브러리를 사용해야 한다.

 

참고할 수 있는 깃허브: https://github.com/ndb796/priorityqueuejs

 

8. 그래프(Graph)

그래프는 특정 노드의 정점과 노드와 노드의 간선을 나타내기 위한 자료구조이다.

그래프는 두가지 방식으로 구현할 수 있다.

 

  • 인접행렬: 2차원 배열을 사용하는 방식

각 인덱스에 각 노드가 자리해 있으며 위 이미지의 표를 배열로 표현한다면 아래와 같다.

[0, 3, 10]

[3, 0, infinity]

[10, infinity, 0]

 

각 행은 각 노드의 인덱스번호부터 출발했을때 얻을 수 있는 비용이다.

즉 0, 1, 2 의 인덱스를 가지고 있기 때문에 행에 3의 길이여야 하고

각 인덱스에서 출발한다고 가정하면 1행에서 인덱스 0번자리는 자기 자신이기 때문에

비용이 발생하지 않아 0이다. 다음 인덱스 1번자리는  0번노드 -> 1번노드로 이동하는

비용이 3이다. 그리고 0번노드 -> 2번노드는 비용이 10이기 때문에 결과적으로

1번째 행은 [0. 3, 10]이 되는 것이다. 나머지 값들도 모두 동일하게 적용되며

간선이 없어 갈 수 없는 경우는 무한(infinity)으로 표기한다.

 

  • 인접리스트: 연결 리스트를 이용하는 방식

인접 리스트의 경우 노드의 각 인덱스를 각 열로 나열하고 연결가능한 노드와 간선비용을 저장한다.

0번노드의 경우 1번노드와 2번노드를 연결 할 수 있고 1번노드와 연결하면 비용은 3,

2번노드와 연결하면 비용은 10이기 때문에 최상단 행이(index 0번 노드) [1, 3], [2, 10]를 가지게 된다.

나머지도 마찬가지로 연결이 불가능한 경우는 모두 제외하고 연결이 되는 경우만

연결되는 노드번호와 간선의 비용만 저장한다.

 

그래프의 시간 복잡도

  • 인접행렬: 모든 정점들의 연결 여부를 저장해 O(V²)의 공간을 요구한다.
    공간효율성이 좋지 않지만 두 노드의 연결 여부를 O(1)의 시간만을 요구한다.

  • 인접리스트: 연결된 간선의 정보만을 저장하여 O(V + E)의 공간을 요구한다.
    공간효율성이 좋은편이지만 두 노드의 연결여부를 확인하는데 O(V)의 시간이 요구된다.

 

마치며...

지난 알고리즘 학습을 복습하면서 포스팅을 진행하고 있는데 초기에 학습했던 내용들이 분명 학습당시엔 명료했던 이해도가 시간이 지나니 흐리게 남아있는 경우가 많은 것 같다. 정말 기록하는 것은 중요하고 어떠한 키워드를 가지고 생각하며 기록해야하는지를 다시한번 하게 되었다.

2023. 6. 25. 16:46

알고리즘 이란?

어떠한 문제를 해결하기 위한 절차나 방법을 뜻한다.

공식화한 형태로 표현한 것을 의미하며 유한성을 가지고 언젠가는 끝나야 하는 속성을 가지고 있다.

 

알고리즘의 조건

알고리즘은 아래 5가지 조건에 만족해야 한다.

  • 입력: 외부에서 제공되는 자료가 0개 이상 존재해야 한다.
  • 출력: 적어도 2개 이상의 서로 다른 결과를 도출해내야 한다.
  • 명확성: 수행과정은 무엇을 하기위한 것인지 정의가 명확해야 한다.
  • 유한성: 알고리즘의 명령어대로 수행하여 처리가 되었을때 종료되어야 한다.
  • 효율성: 모든 과정이 명백히 실행가능해야 하며 시간적, 공간적 효율성을 가져야 한다.

 

1. 선형 자료구조

하나의 데이터 뒤에 다른 데이터가 하나 존재하는 자료구조이다.
데이터가 일렬로 연속적으로(순차적) 연결되어있다.
배열, 연결리스트, 스택, 큐, 덱 등이 있다.

 

2. 비선형 자료구조

하나의 데이터뒤에 다른 데이터가 여러개 올 수 있는 자료구조이다.
데이터가 일직선상으로 연결되어있지 않아도 되며 트리, 그래프 같은 것이 있다.

 

자료구조와 알고리즘의 관계

효율적인 자료구조 설계를 위해 알고리즘 지식이 요구된다.
효율적인 알고리즘을 작성하기 위해서는 문제상황에 맞는 적절한 자료구조가 사용되어야 하며
프로그램을 작성할 때 자료구조와 알고리즘 모두 고려해야 한다.

 

프로그램의 성능 측정 방법

  • 시간복잡도(time complexity): 알고리즘에 사용되는 연산 횟수를 측정한다.
  • 공간복잡도(space complexity): 알고리즘에 사용되는 메모리의 양을 측정한다.

주로 공간을 많이 사용하는 대신 시간을 단축하는 방법을 선택한다.

 

Big-O 표기법

복잡도를 표현할 때는 Big-O 표기법을 사용한다.

특정한 알고리즘이 얼마나 효율적인지 수치적으로 표현할 수 있으며 가장 빠르게 증가하는

항만을 고려하는 표기법이다.

 

  • 다음 알고리즘은 O(n)의 시간 복잡도를 가진다.
let n =10;
let sum = 0;
for (let i = 0; i < n; i++) {
  sum += i;
}
console.log(sum); // 45

 

  • 다음의 알고리즘은 O(n²)의 시간 복잡도를 가진다.
let n = 9;

for(let i = 1; i <= n; i++) {
  for(let j = 1; j <= n; j++) {
    console.log(`${i} X ${j} = ${i * j}`);
  }
}
/*
실행결과:
1 X 1 = 1
1 X 2 = 2
...
9X 8 = 72
9 X 9 = 81
*/

 

연산횟수에 대한 소요시간

일반적으로 연산횟수가 10억을 넘어가면 1초이상의 시간이 소요된다.
[예시] n = 1000

  • O(log n) : 약 10번 연산
  • O(n) : 약 1,000번 연산
  • O(n log n) : 약 10,000번 연산
  • O(n²) : 약 1,000,000번 연산
  • O(n³) : 약 1,000,000,000번 연산

Big-O표기법으로 시간 복잡도를 표기할 때는 가장 큰 항만을 표시한다.
가장 큰 항에 붙어 있는 계수는 제거한다.
O(3n² + n) = O(n²)

 

 

Big-O 표기법을 사용한 시간복잡도의 예시

  • O(1) : 스택에서 Push, Pop
  • O(log n) : 이진트리
  • O(n) : for 문
  • O(n log n) : 퀵 정렬, 병합정렬, 힙 정렬
  • O(n²): 이중 for 문, 삽입정렬, 거품정렬, 선택정렬
  • O(2ⁿ) : 피보나치 수열

 

마치며...

마무리 글에 갑자기 알고리즘에 대해서 공부를 하게 된 이유에 대해 적을까 한다. 메모서비스 개발을 진행하고 있다가 마무리되어 갈 즈음에 문득 스스로 문제해결능력에 대한 불만와 방식에 대해서 의구심을 품게 되었다. "과연 지금까지의 방식이 효율적이고 가장 빠른 길일까?" 그러다 알고리즘에 대해서 문득 떠올리게 되었고 하나씩 알아보게 되었는데 지금까지 나에게 느꼈던 부족함을 해결해 줄 수 있을 것이라 생각이 들었다. 그렇게 개발에 1개월의 공백을 두게 되었지만 다양한 알고리즘을 학습하고 코드를 직접 만들어보기도 하면서 지금까지 나에게 부족했던 부분들이 많이 채워지는 것을 느꼈다. 문제를 해결하기 위해 사고하는 방식이 달라졌고 시간복잡도에 대한 개념이 생기니 효율적으로 문제해결에 다가설 수 있게 된 것 같았다. 완벽하다고 할 수 없겠지만 나에게 있어 개발자로서 필요한 역량을 향상시키기 위한 없어선 안될 1개월이었던 것 같다.

 

2023. 5. 24. 17:31

git filter-branch란?

브랜치를 재작성할 수 있는 기능이며 간략하게 소개하자면 필터를 제공해서

필터에 적용된 파일만 가지고 히스토리를 재구축하는 기능이다. 

이번에 다룰 내용은 특정 파일에 대한 기록을 제외하고 재구축 하는 것에

대한 내용이다.

 

어떤 경우에 쓰면 좋을까?

개발을 진행하면서 깃허브 레포지토리에 push할때 간혹 실수를 하게 될 때가 있다.

예를 들면 절대로 공유되어선 안될 민감한 정보들이 담겨있는 env파일이

푸쉬되어버린 경우에는 아무리 이후에 gitignore파일을 수정하고 깃허브에서 푸쉬된

env파일을 삭제해도 해당 레포지토리의 공개범위가 public인 경우라면 히스토리를

확인하면 env파일의 내용을 누구나 볼 수가있다. 그렇기 때문에 Git의 히스토리 자체를

필터링하여 특정 히스토리를 삭제하고 깃허브에 적용해주어야 할 경우에 많이 사용한다.

 

filter-branch를 사용하게 된 계기

과거 개발 입문당시에 env파일을 실수로 push했던 일이 있었고 레포지토리에서 삭제하면

나에게만 보여지고 다른 유저는 볼 수 없을 것이라고 생각했었던 것 같다. 그렇게 둘러보다

2가지 프로젝트에서  env파일을 삭제했던 기록이 남아있었고 삭제처리가 필요했다. 때문에

git에서 제공하는 filter-branch라는 기능을 알게되었고 이 기능을 사용하여 env에 대한

기록을 모두 제거하고 재구축했다.

 

사용예시

1. 기능사용 환경

내가 filter-branch를 사용한 환경은 백엔드와 프론트엔드를 동시 개발을 진행하고 있었기 때문에

project라는 디렉토리 안에 fontend, backend 두가지 디렉토리로 나누어 개발했다. project디렉토리

내부에 존재하는 frontend와 backend디렉토리를 스테이징 시켜 버전을 관리하고 있었기 때문에

깃허브 레포지토리에는 fontend, backend 두가지 디렉토리가 존재하고 있는 환경이였다.

 

2. 기능사용이 가능한 위치

반드시 해당 기능은 최상위 트리에서만 실행해야 한다. 나의 경우는 fontend디렉토리가 아닌

backend디렉토리에서 env파일에 대한 기록을 삭제해야하기 때문에 처음에는 backend디렉토리 에서

파워쉘을 열어 filter-branch 명령어를 사용했었다. 하지만 실행되지 않았다.

그 이유는 최상위 디렉토리는 backend디렉토리가 아닌 project디렉토리이기 때문이다.

반드시 최상위 트리인 project디렉토리에서 명령어를 실행시켜야 하며 명령어를 작성할때

경로만 backend/... 이렇게 지정해주면 되는 것이다.

 

 

3.  예시코드

# 코드의 두번째 줄에서 backend/.env 는 필터링해줄 경로+파일
git filter-branch -f --index-filter 'git rm --cached --ignore-unmatch 
backend/.env' --prune-empty -- --all

# 이 후에 강제 push 해준다.
git push origin main --force

위에 명령어는 지정된 경로에서 지정된 파일과 일치하지 않는 히스토리만을 남긴채로

재구축하는 명령어다. " Ref 'backend/.env' was rewritten " 명령어가 나오면 재구축이 완료된 것이다.

이후에 강제푸쉬를 해주고 깃허브 레포지토리를 확인해보면 원하는 히스토리가 사라져 있는 것을

볼 수 있다.

 

마치며...

나의 경우는 특별히 문제없이 기능이 잘 수행되었고 문제를 해결할 수 있었지만 타 기술블로그를 찾아 보니 불가피하게 문제해결을 의도하지 못한 방향으로 해결해야하는 상황도 종종있는 것 같았다. 아무리 문제를 해결할 수 있는 좋은 기능들이 있다고 하여도 너무 맹신하는 것 보다는 미리 문제를 방지할 수 있는 기반을 구축한 후에 개발을 진행하는 것이 가장 중요하다고 생각했다.

2023. 4. 7. 17:28

OpenAPI

openAPI는 애플리케이션의 REST-API 문서를 자동으로 구성해주는 라이브러리이다.

이 라이브러리를 사용하게된다면 개발을 진행할때 backend에서 API를 구성할때

지정한 타입이나 데이터 변수를 따로 문서화하여 frontend개발자에게 전달할 필요가

없어지기 때문에 능률을 올려주고 backend, frontend개발자 모두에게 편의를

제공할 수 있는 라이브러리이다.

 

(* React, NestJS로 구성된 프로젝트를 기준으로 포스팅하였다.)

 

Swagger? or OpenAPI?

Swagger는 2010년대 초 Tam Wordnik가 개발해온 라이브러리이다.
시작은 Wordnik기업에서 자체 API용 UI로 개발되었고 2015년초에SmartBear라는 회사에서 Swagger를 인수했다. 이 후에 2015년 말SmartBear는 Linux Foundation의 후원으로 OpenAPI Initiative에 Swagger를기부하면서 OpenAPI Specification으로 이름이 변경되었다.

지금의 Swagger는 OpenAPI를 Implement하기 위한 도구를 뜻하며

명세된API를 공유하는 swagger-ui이며 web에서 공유하는 ui-tool이다.

 

결과적으로 라이브러리 자체는 openAPI라고 명칭하며 swagger는 tool을 명칭한다.

 

 

swagger & openAPI Install

// express를 사용하는 경우
npm install --save nest-openapi-tools @nestjs/swagger swagger-ui-express

// fastify를 사용하는 경우
npm install --save nest-openapi-tools @nestjs/swagger swagger-ui-fastify

단순 명세기능을 사용할땐 swagger설치만 해도 명세가 가능하지만 필자는

codegen에 대해서도 다루기 때문에 nest-openapi-tools 를 함께 install 해주어

편의성을 더했다.

 

 

사용예시

 

1. DTO와 Controller 명세하기

// DTO or Entity
export class Dto {
  @ApiProperty({ type: Number }) // default타입(string)이 아니므로 타입지정
  id: number;

  @ApiProperty() // default타입 = string
  email: string;

  @ApiProperty({ required: false })
  password?: string;  // chaning = 필수요소x

  @ApiProperty()
  name: string;

  @ApiProperty()
  mobile: string;
}

// Controller
export class Controller {
  @ApiResponse({ type: CoreOutput }) // return 타입을 지정
  @Post('register')
  createAccount(@Body() input: CreateAccountDto): Promise<CoreOutput> {
    return this.userService.createAccount(input);
  }

  /** 유저데이터 수정 */
  @ApiResponse({ type: CoreOutput }) // return 타입을 지정
  @Patch('modify')
  @UseGuards(JwtAuthGuard)
  profileUpdate(
    @Req() req,
    @Body() updateData: UpdateAccountDto,
  ): Promise<CoreOutput> {
    return this.userService.profileUpdate(req.user, updateData);
  }
}

단순하게 데코레이터를 넣어줌으로서 명세가 가능하다.

 

 

2.  Front에 API명세파일 보내기

main.ts에 적용해도 되지만 main.ts에서 설정해야하는 것들이 많아지고

복잡해질 수 있기 때문에 openApi.ts를 만들어서 main.ts에 import시켜서 사용했다.

 

  • openApi.ts
import { OpenApiNestFactory } from 'nest-openapi-tools';
import { DocumentBuilder } from '@nestjs/swagger';

export const useOpenApi = (app) => {
  return OpenApiNestFactory.configure(
    app,
    new DocumentBuilder()
      .setTitle('My API')
      .setDescription('An API to do awesome things')
      .addBearerAuth(),
    {
      // swagger-ui 설정 (web-ui)
      webServerOptions: {
        enabled: process.env.NODE_ENV !== 'production',
        path: 'api-docs',
      },

      // 자동생성된 api문서 경로 및 설정
      fileGeneratorOptions: {
        enabled: process.env.NODE_ENV !== 'production',
        outputFilePath: './openapi.yaml', // or ./openapi.json
      },

      // 서버에 저장된 api문서를 프론트로 보내주는 설정
      clientGeneratorOptions: {
        enabled: process.env.NODE_ENV !== 'production',
        type: 'typescript-axios', //typescript-axios
        outputFolderPath: '../frontend/src/openapi',
        additionalProperties:
          'apiPackage=apis,modelPackage=models,withoutPrefixEnums=true,withSeparateModelsAndApi=true',
        openApiFilePath: './openapi.yaml', // or ./openapi.json
        skipValidation: true, // optional, false by default
      },
    },
    {
      // 명세함수의 기본값을 설정해준다.
      operationIdFactory: (c: string, method: string) => method,
    },
  );
};

 

  • main.ts
function bootstrap() {
  (async () => {
    try {
      const app = await NestFactory.create(AppModule);

      await useOpenApi(app);
      
      // ...
}
bootstrap();

 

사용팁

서버를 돌리고 있을때 위의 설정들이 제대로 갖춰질 경우 해당 경로에

openapi.yaml 의 API명세파일이 생길 것이고 서버가 수정될 때마다

openapi.yaml를 최신화 시킨다. 그렇기 때문에 계속해서 최신화때문에

서버로딩이 길어지게 되니 개발중엔 enabled 설정을 'production'일때로 바꿔주고

최신화시켜주고 싶을때 다시 설정을 'production'가 아닐때로 바꿔주면 된다.

 

 

3.  Front에서 서버의 API명세파일 가져오기

Front에서는 package.json에서 script설정을 통해 npm 명령어를 사용하여

명세파일을 가져오거나 최신화 시켜 줄 수 있다.

이를 위해서는 Front에 openapi-generator-cli 를 install해야한다.

 

  • openapi-generator-cli Install
// 패키지가 많은경우 충돌방지목적으로 -D 옵션을 사용하면 좋다.
npm install @openapitools/openapi-generator-cli -D

 

  • package.json
{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "codegen": "npx @openapitools/openapi-generator-cli generate -i ../backend/openapi.yaml -g typescript-axios --additional-properties apiPackage=apis,modelPackage=models,withoutPrefixEnums=true,withSeparateModelsAndApi=true -o ./src/openapi"
  },
}

 

codegen script의 설명

  • "npx @openapitools/openapi-generator-cli"는 OpenAPI Generator CLI를 실행하기 위한 명령어
  • generate는 생성 명령을 나타내는 옵션
  • -i ../backend/openapi.yaml는 가져오는 API명세파일의 위치와 이름
  • -g typescript-axios는 TypeScript Axios Front에서 통신할 함수를 생성하기 위한 Generator 모듈을 지정
  • --additional-properties apiPackage=apis,
    modelPackage=models,
    withoutPrefixEnums=true,
    withSeparateModelsAndApi=true
    코드 생성에 사용되는 추가 속성 설정이다. 이 예제에서는 생성된 코드의 패키지 이름을 지정하고, Enum의 접두사를 제거한 후, 모델 및 API 코드를 분리하는 옵션이다.
  • -o ./src/openapi"는 생성된 파일의 위치를 지정하고 이 예제에서는 src/openapi 폴더에 생성된 파일을 저장한다.

 

명령어로 가져오기

// 터미널에 입력
npm run codegen

 Thanks for using OpenAPI Generator. 

문구가 뜨면 코드젠이 완료된 상태이다.

 

마치며...

작성일자에 진행중인 개인프로젝트에서 openAPI-codegen을 처음 사용해보았는데 typeORM을 사용했을때 만큼의 신선한 충격이었다. codegen은 효율을 혁신적으로 올려주는 라이브러리임을 확실하게 깨달았다. 실무에 들어가게 됐을때 openAPI-codegen과 swagger를 사용하지 않는 배경이라면 API명세문서의 간편화를 주장하며 이를 채용하자고 적극적으로 행동해야겠다는 생각이 들었다.

2023. 3. 25. 18:24

Tailwind CSS

Utility First를 지향하는 CSS프레임워크다.

Utility First란 미리 세팅된 유틸리티 클래스를 활용하여 HTML코드 내에서 CSS를 적용시키는 것을 뜻 한다.

CSS의 각 속성들을 직관적인 className으로 표현하여 효율적으로 사용할 수 있게 된다.

일반적으로 필요한 대부분의 CSS속성을 className으로 제공한다.

 

Tailwind CSS의 장점

내가 생각한 가장 큰 장점은 개발을 진행함에 있어 스타일에 대한 CSS className을 고민하지 않아도 되고

Style Sheet를 들여다보지않고 적용되어있는 className으로 보면서 변경, 적용시키기가 손쉬운 점

뽑고 싶다. 이 외에도 여러 장점이 있다.

  • Break Point가 존재하여 반응형 디자인을 쉽게 적용할 수 있다.
  • 기본으로 제공되는 class외에도 직접적으로 커스터마이징을 하여 새로운 CSS속성을 만들어 적용할 수 있다.
  • 반복되는 스타일은 component, class를 추상화 하여 재사용이 가능하다.
  • TailwindCSS Document는 비교적 친절하게 기능을 서술하고 있으며 러닝커브가 낮다.
  • 다크모드를 지원하기 때문에 따로 CSS속성을 지정하는 수고를 덜 수 있다.

 

Tailwind CSS의 단점

내가 생각한 가장 큰 단점은 코드의 가시성이 떨어지는 것과 속성들의 우선수위문제이다.

직관성이 좋은 반면에 가시성이 떨어지는 것이 실수하기 쉬운 요소로 다가오기도 한다.

또한 속성의 우선순위가 출현빈도나 순서에 상관없이 정의한 순서에 따라 적용되기 때문에

유의하며 className을 적용시켜야한다.

  • 정의된 CSS파일의 용량이 매우 커진다. 하지만 반대로 component가 늘어나도 CSS파일 용량이 상대적으로 늘어나지 않는다는 장점이 존재한다. 규모가 큰 프로젝트일수록 용량에 대한 혜택을 볼 수 있다.
  • 특정 prefix는 모든 CSS속성을 지원하지 않는다. hover: 또는 focus: 등 특정 prefix는 일부속성만 사용가능하다.

 

install & init

npm install -D tailwindcss
npx tailwindcss init

Tailwind CSS를 설치 후 적용시킨다.

 

 tailwind.config.js

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

모든 템플릿파일에 대한 경로를 추가하여 config를 적용시킨다.

tailwind.config.js파일은 기본적인 설정 외에도 설정을 커스텀하여 적용시킬 수 있는 config파일이다.

 

아래의 예시와 같이 적용시킬 수 있다.

/** @type {import('tailwindcss').Config} */

// tailwindCSS에서 px정의가 따로 되어있지 않기 때문에 px로 적용하는 경우를 대비한 함수
const createPxEntries = (size) => {
    return {...Array.from(Array(size + 1)).reduce((accumulator, _, i) => {
            return {...accumulator, [`${i}px`]: `${i}px` }
        })
    };
}
// tailwindCSS에서 보다 직관적으로 rem단위를 사용할 수 있도록 font-size: 16px기준으로  px과 동일한 숫자로 통일한 함수
const createRemEntries = (size) => {
    return {...Array.from(Array(size + 1)).reduce((accumulator, _, i) => {
            return {...accumulator, [i]: `${i * 0.0625}rem` }
        })
    };
}

module.exports = {
    content: [
        './src/**/*.{js,jsx,ts,tsx}',
    ],
    theme: {
        extend: {
            screens: {
            
            	// 다양한 mobile-size에 대응하기 위해 추가적인 media-query 생성
                'mobile-md' : '450px',
            },
            fontSize: {...createPxEntries(50), ...createRemEntries(50)},
            
            // spacing은 길이에 대한 모든 className에 적용시킨다.
            spacing: {...createPxEntries(100), ...createRemEntries(100)},
            colors:{
                'primary-300': '#EB5500', // 자주쓰이는 컬러를 지정
                'primary-200': '#FF7A00',
                'primary-100': '#FFE0B0',
            },
        },
    },
    plugins: [
        require('@tailwindcss/line-clamp'), // plug-in 적용도 가능하다.
        require('@tailwindcss/aspect-ratio'),
    ],
}

파일 자체가 js파일이기 때문에 추가적으로 함수를 만들어 적용시킬 수도 있다.

함수를 만들어 적용시켜 줄 때 주의사항은 용량이 커지기 때문에 코드작성시 검색기에 부하가 걸려 느려질 수 있다.

위에서 spacing의 경우 특히나 많은 속성에 관여하기 때문에 필요한 만큼만, 가능하면 적은 수를 적용하는 것이 좋다.

 

globals.css

tailwindCSS 만으로 style속성을 주기 난해한 경우에 쓰일 CSS파일을 만든 후

tailwindCSS의 속성을 같이 사용할 수 있도록 만들 수 있다.

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

 

아래 예시처럼 애매하거나 속성지정이 난해한 경우 @apply를 사용하여 CSS에 tailwindCSS속성을 같이 지정 할 수 있다.

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

input[name=tradeOrder]:checked + label{
    @apply bg-crypto-pale-grey text-crypto-dark transition-all duration-300
}

 

사용예시

import {useNavigate} from "react-router-dom";
import rocket from "../assets/rocket.svg";
import React from "react";

export const HomeIntroContents = (props: {img:string, title:string, summary:string, opt?:string}) => {
    const navigate = useNavigate();
    return (
        <li
            className="cursor-pointer flex w-full p-12 rounded-[16px] bg-crypto-pale-grey text-start text-crypto-dark"
            onClick={() => {return props.img === rocket ? navigate('trades') : navigate('wallets')}}
        >
            <figure className="relative w-52 h-52 rocket-bg rounded-[12px] rocket-gradient mr-16">
                <img src={props.img} className={`${props.opt}`} alt=""/>
            </figure>
            <div className="relative flex flex-col justify-center">
                <h1 className="text-16 font-normal">
                    {props.title}
                </h1>
                <p className="font-normal text-14 text-crypto-cool-grey">
                    {props.summary}
                </p>
            </div>
        </li>
    )
}

className내에서 백틱과 중괄호를 사용하여 `${isShow ? 'visible' : 'invisible'}` 와 같은 조건문을 넣거나 함수를 포함시켜 사용할 수도 있으며 tailwindCSS에 지정된 className이 없는 경우에는 w-[16vh] 와 같이 대괄호를 사용하여 사용자지정 CSS를 적용시킬 수도 있다.

 

마치며...

나는 tailwindCSS의 존재를 알게 된 이후로 사실상 style적용은 모두 tailwindCSS로 하고 있다. 처음엔 낯설게 느껴졌지만 프로젝트를 진행하면서 지속적으로 사용하고 사용성에 대해 저울질을 해본 결과 tailwindCSS의 압승이었다. 앞으로도 채택할 수 있다면 tailwindCSS를 채용하여 프로젝트를 진행할 예정이다.

2023. 3. 19. 15:29

Git & GitHub

Git은 개발자 리누스 토르발스에 의해서 개발되었고

분산 버전관리 시스템(DVCS : Distributed Version Control Systems)이다.

이는 컴퓨터파일의 변경사항을 추적할 수 있으며 주로 여려사람들이 하나의

프로젝트를 개발할때 작업을 조율하는데 사용한다.

 

GitHub는 Git을 지원하는 웹 호스팅 서비스 시스템(Cloud)이다.

작업중인 컴퓨터에 Git History를 GitHub-Cloud에공유할 수 있다.

공유된 파일은 fetch하여 clone파일을 만들 수도 있고 clone파일을 수정하여

기존 클라우드에 공유된 파일에 덮어쓰거나 새로운 파일을 추가로 공유할 수도 있다.

이러한 서비스는  GitHub뿐만 아니라 GitLab, BitBucket 등 다수 존재한다.

웹 호스팅 서비스 시스템에선 GitHub가 가장 대중화 되어있고 많은 오픈소스를

공유하고 있는 것이 가장 큰 장점이다.

 

 

GitHub에 대한 견해

처음 깃허브를 접했을때는 단순 클라우드가 아닌가? 굳이 써야하나 싶었던 생각을 0.1초나마 했던 것 같다. 하지만 지금의 나로서 깃허브를 바라보는 시점은 "깃허브가 없었다면... 지금의 나도 없다..." 라고 생각할 정도로 많은 도움을 받고 있다. 깃허브는 개발자의 대다수가 경험해보았을 것이고 대게 나와 같이 긍정적인 의견을 가지고 있을 것 같다.

 

 

GitHub 사용준비

1. Install

깃허브 홈페이지로가서 install을 먼저 해주어야한다.

OS환경이 window인 경우 공식홈에서 install을 하면 되지만

Mac인경우 Homebrew를 설치하여 brew install git 명령어를 입력하여

인스톨 해야 한다.

 

2. 깃허브에 레포지토리(저장소) 생성

깃허브 홈페이지에 회원가입한 후 로그인 하고 Repository -> New 를 눌러서

새로운 레포지토리를 생성한다. 가능하면 처음에 README는 제외하고 만든 후

프로젝트가 진행되면서 이후에 작성하는 것이 바람직하다.

 

3. 사용자정보 입력

깃허브에 공유하고자 하는 프로젝트 경로에서 파워쉘을 열거나

프로젝트를 개발툴로 열어서 터미널에 사용자 정보를 입력해준다.

git config --global user.name "내이름"  // 사용자이름

git config --global user.email "내메일@example.com"  // 이메일

 

4. Git Init

깃이 프로젝트를 감지할 수 있도록 만들어주는 명령어다.

프로젝트를 개발툴을 사용하여 열고 터미널에 git init 을 입력하면

프로젝트내부에 .git 파일이 생기면서 컴퓨터에 Git History를 남기며

깃허브에 레포지토리를 지정하면 해당 레포지토리로 공유할 수 있게된다.

 

5. 깃허브 레포지토리와 연결

깃허브에 공유하기 위해서는 저장소와의 연결이 필수적이다.

이를 위해서 깃허브 레포지토리의 주소가 필요한데 깃허브에 로그인해서

공유하고자 하는 레포지토리에 접속하여 녹색버튼으로 되어있는

  <> Code  버튼을 눌러서 주소를 복사하여 터미널에 아래와 같이 입력한다.

git remote add origin 레포지토리주소
// remote를 origin이라는 변수명으로 추가해주고 레포지토리주소와 연결

주의해야할 점 :  url에서 복사하면 연결에 실패할 가능성이 있다. 이때 주소끝에 .git을 추가하면 된다.

 

 

Staging

깃허브에 파일을 공유하기 전에 반드시 선행되어야할 작업이 스테이징이다.

스테이징은 공유할 파일을 Git이 감지할 수 있도록 만드는 것이다.

// terminal
git add 내파일.js  // 반드시 확장자명을 명시해야 한다. space를 넣어서 복수 스테이징 가능

git add .  // 모든 파일을 listen

git commit -a  // 수정된 파일 모두 스테이징

 

Commit

깃허브의 가장 기본이며 핵심인 커밋은 버전을 생성하는 명령어다.

커밋 또는 버전생성이라 하며 프로젝트를 공유를하거나 깃허브에서

가져온 파일을 수정한상태로 덮어쓸 때 모두 반드시 커밋이 선행되어야한다.

// terminal
git commit -m '버전생성 메모'  // -m 수정내용을 메모한다.

git commit -am '메모'  // 수정된 모든 파일을 커밋(버전생성)한다.

 

Push

푸시는 스테이징 이후에 커밋된 파일을 깃허브에 공유하는 명령어다.

반드시 스테이징 ->  커밋 이후에만 가능하며 기존에 깃허브에 공유되던 파일에

덮어쓴다. 푸시하기전에 레포지토리와 브랜치가 필요하다. master가 default이며

브랜치명을 main으로 바꿔주는게 의례적이지만 요근례에는 다시 master 그대로

쓰는 경우도 적지않으니 협업을 하는 경우 팀원들과 브랜치명을 먼저 결정 후에

개발을 진행해야 한다.

git branch -m main  // 브랜치이름 변경. 필요시 적용

git push 리모트변수명  // 레포지토리에 적용시킨 remote의 변수명을 써야한다.

 

 

GitHub 공동작업

혼자 깃허브를 버전저장소로 사용할때는 스테이징, 커밋, 푸쉬만 사용하면 되지만

프로젝트를 누군가와 공유하며 공동작업을 하는 경우에는 이 외에도 Clone, Pull, Merge

등을 사용한다. 그리고 Pull이지만 깃허브에서 요청하여 팀원들과 변경사항을 공유하여

merge할 코드를 선택하는 PR(Pull Request)라는 기능도 사용을 하게 된다. merge방식 또한

정하여 개발을 진행해야 한다. merge방식은 크게 3-way-merge, fast-forward-merge

두가지로 나눌 수 있다.

 

Git Merge & Pull

1. Merge

병합이라는 뜻으로 서로 다른 코드를 하나로 합치는 명령어다.

변경점이 일치하지 않아 충돌하는 코드를 명시해주고 이를 수작업을 통해서

어떤 코드를 사용하여 합칠 것인지 정한 후 병합한다.

<<<<<<< HEAD
main에서 짠 코드
=======
feature에서 짠 코드
>>>>>>> feature

// 두 코드중 하나를 지운 후(====, >>>이런 것들 모두 제거) commit하면 브랜치가 합쳐진다.

 

2. Pull

지정한 브랜치의 코드를 가져온다. 단순히 가져오는 것이 아닌

fetch + merge하기 때문에 해당 브랜치의 코드를 fetch하여 가져온 후

pull을 요청한 코드와 충돌하는 코드를 현재 코드에 merge까지 

해주는 것이 pull이다. 때문에 공동작업해서 dev브랜치에 올린다고 가정하면

dev에 다른사람이 코드를 merge해서 최신화를 시켜두면 나는 pull을 사용해서

나의 브랜치 히스토리에 dev의 최신화된 코드를 포함시켜 [dev + 내브랜치코드]를

dev로 push해서 업데이트 시키는 것이다. 때문에 실질적으로 협업을 하게되면

merge명령어보다 많이 사용하게 된다.

git pull origin dev

// 작업중이던 브랜치에서 git pull 리모트변수 메인브랜치명

 

fast-forward

< 예시 브랜치 >

origin / feature = 작업용 브랜치

origin / main = 배포브랜치(메인브랜치)

 

fast-forward 관계

이 관계는 기존 배포용브랜치 main 작업용브랜치 feature의 관계가 기준이며

feature의 커밋 히스토리 메인브랜치 main의 커밋 히스토리를 포함하고

있으면 fast-forward관계가 형성된다.

 

fast-forward merge

fast-forward관계인 상태에서 메인브랜치인 main에서 git merge feature하면 된다.

 

3-way-merge

3-way-merge는 작업중인 각 브랜치에 커밋이 1회이상 있어 코드가 일치하지 않는

두 브랜치를 합쳐서 새로운 커밋을 만들어주는 것이다. 이는 가장 기본적인 동작방식이다.

 

Git Clone

이름 그대로 프로젝트를 그대로 복사하여 클론을 만드는 명령어다.

개인이 오픈소스를 코드분해하여 학습할때도 쓰이겠지만 협업에서는 공동작업자가 있는

프로젝트에 합류하여 처음개발을 진행할때 많이 쓰인다. 작업할 폴더경로에서

파워쉘 또는 깃베스를 사용하여 git clone 명령어를 사용하여 최신화된 코드를 가져와서

작업을 진행한다.

git clone 레포지토리주소

 

브랜치 전략 Git-flow & Trunk-based

Git-flow

브랜치를 기능, 혹은 따로 기준을 만들어 나눈 후 최종배포 main브랜치 이전에 dev와 같은

임시배포브랜치를 만들어 개발을 진행하는 것이다. 예를 들어 feature에 신기능을 개발하고

auth브랜치에서 인증기능을 개발한다고 가정하면 이를 바로 main브랜치에 합치는 것이 아니라

dev브랜치에 합쳐서 QA 즉, 여러 테스트를 거친 후 최종적으로 main브랜치에 merge해서

배포하는 방식이다. 안정적인 운영이 필요한 경우 Git-flow전략을 많이 사용한다.

 

Trunk-based

Git-flow전략과 다르게 브랜치를 많이 만들어두지 않고 개발해야하는 기능에 맞는 브랜치명

지어주고 해당기능을 개발완료 후 main브랜치에 합친다.이후엔 해당기능의 브랜치는 삭제한다.

Trunk-based전략은 바로바로 공유배포해도 상관없고 큰 업데이트를 하지않는 안정적인

프로그램인 경우 많이 사용하고 브랜치는 크게 main과 feature정도만 운영해도 좋다.

 

PR(Pull Request)

개발툴에서 pull을 하게 될 경우 코드 자체에 충돌하는 내역이 그대로 fetch되어 적혀버리고 공동작업자가 있는 경우 변경된 커밋을 공유할 수도 없기때문에 이를 보완하기 위해 깃허브에서 만든 기능이 PR이다. PR을 하기 위해서는 깃허브에서 해당 레포지토리에 접속하여 Pull Requests를 눌러서 PR을 진행하면 된다.

  1.   New pull request   를 누른 후 base와 compare를 선택한다.
  2.   Create pull request  를 누르고 코멘트를 남긴 후   Create pull request  를 누른다.
  3. 충돌하는 코드가 없다면  Marge pull request  를 누르면 완료되고 있다면 선택하여 채택해주어야한다.

 

마치며...

깃허브는 모두가 대중적으로 쓰는 만큼 나 역시 개발자로서 공부를 시작하고 코딩을 시작할때 프로젝트를 저장하기 위해서 GitHub를 사용하기 시작했다. 사실상 현재 개발의 트렌드와 개발시장의 확장 중심에는 GitHub가 있기 때문이라고 생각한다. 포스팅을 하면서 생각보다 정리하여 쓸 내용이 많아서 포스팅에 꽤 많은 시간이 들어갔던 것 같다. 그 만큼 포스팅하면서 생각을 다시 한번 정리할 수 있어 좋았다.