2022년 10월 26일 수요일 15시, 우테코 프리코스 과정이 본격적으로 시작됐다.

이번 우테코는 코딩테스트 과정을 없애고 누구나 프리코스 과정을 밟아볼 수 있도록 바뀌었기 때문에, 지금까지의 기수보다는 경쟁률이 좀 더 올라갈 것이라 생각했다. 3 ~ 4기가 대략 1400명 정도가 백엔드 과정을 지원했는데, 이번엔 코테라는 1차 관문이 없어졌으니 아마도 2000명정도가 백엔드를 지원할 거라고 어림짐작을 했다..나같은 경우는 프리코스를 경험하고자 지원한게 아니라 우테코 본 과정에 합격해 성장하고 싶은 마음에 지원한 것이기 때문에, 더 높아지게 될 경쟁률이 신경쓰일 수밖에 없었다..

 

그러나 이번에 서류를 준비하는 과정에서 이전 기수 합격자분들이 포스팅한 글들을 보며 나름의 팁들을 모으던 중, 공통적으로 내가 보던 말은 "프리코스 과정을 통해서도 성장했다"라는 말이었다. 그래 어차피 경쟁률이 어떻든 상관없이 4주동안 영혼 갈아서 하면 붙을 거다. 라는 생각을 가지고, 프리코스 과정을 통해서도 뭔가 배우고 느끼는 것이 있을 것이니 거기에 초점을 맞추자. 분명히 그 시간들을 견디면 성장해있을 거니까.. 라는 생각을 하며 15시가 되길 기다렸다.


15시가 되고 문제가 공개됐는데, 심히 당황스러웠다. 이전 기수들의 1주차 미션은 야구게임만들기 같은 학교에서 나오는 과제 느낌의 미션이 나올 줄 알았는데 형식이 코딩테스트할 때 보는 알고리즘 문제들과 똑같았다. 5기부터는 미션 형식도 바뀌는건가? 싶은 찰나에 메일로 온 미션 안내문을 읽고 1주차 미션의 목적을 알 수 있었다.

 

개발 환경 세팅 및 Git, 사용할 언어, 앞으로의 미션은 이렇게 진행된다 등을 체험하는 일종의 튜토리얼!

 

음 그러면 이거는 원래 코테로 보던 거를 1주차로 옮기던 건가..라는 생각도 들었다. 그리고 각 문제별로 주어지는 요구사항들을 토대로 기능목록을 만든 다음 기능별로 커밋하라는 요청도 있었다. 프론트를 공부할 때 Udemy에서 우아한형제들에 계신 임동준 님의 강의를 들을 때 기능목록을 만든 뒤 기능들을 하나하나 구현하는 경험을 해본 적이 있어 어떻게 하란 건지 조금은 감을 잡을 수 있었다. Git자체도 원래 기능단위로 변할 때마다 commit을 한다고 공부했었다. 그러나 돌이켜보면 기능목록을 만든 후 목록에 있는 기능단위로 구현하고 커밋해본 적은 없었다. 그래서 이 미션을 통해 기능목록을 만들고 그 기능 단위로 커밋해보는 경험을 해볼 수 있고, 이를 통해 더 나은 개발자로 성장할 수 있겠다는 생각이 들었다.

 

이번 미션을 통해 나는 어떤 성장을 하고 싶을까란 생각을 했다. 일단 자바를 거의 처음 접하는 만큼 자바랑 친해져야겠지..라는 생각을 하고, 내가 우테코에 지원한 이유가 읽기 좋고 재사용성이 좋은 실전적인 코드를 짜는 실력을 키우고 싶어서인 만큼 그 부분을 계속해서 키워봐야겠다는 생각을 했다. 그래서 이번 1주차 미션에선 다음과 같은 목표들을 세웠다.

 

  1. 일단 무엇보다도 자바에 익숙해지기
  2. 미션에서 요구하는 사항들 일단 모두 구현하기
  3. 깃허브 컨벤션에 맞춰 커밋메시지 작성하기
  4. 변수/메소드 이름 최대한 그 의미 알아볼 수 있게 작성하기
  5. 2를 하고 리팩토링할 때 자바 컨벤션을 지키려고 노력하기

일단 1, 2가 가장 중요하다고 생각했다. 따라서 일단 문제에서 요구하는 것들이 다 돌아가도록 코드를 짠 다음, 리팩토링을 진행하는 방식으로 미션을 수행했다.


기능 목록 정의

 총 7개의 문제가 제시됐는데, 문제 자체는 가벼운 난이도가 맞았다. 이걸 어떻게 구현해야 하지? 라는 과정에서 엄청난 고민이 들지는 않았다. 시간복잡도의 문제는 나중 문제고 일단 구현 자체는 그리 어려운 난이도가 아닌 문제들이라고 판단했다. 일단 문제에서 요구하는 내용들(요구사항)을 나만의 언어로 다시 정리한 다음, 머릿속에 떠오른 풀이과정을 기능 단위로 풀어서 목록으로 만들었다. 예를 들어 "포비의 왼쪽 페이지에서 나온 점수와 오른쪽 페이지에서 나온 점수 중 가장 큰 점수 구하기"는 

 

  1. 왼쪽 페이지의 점수 구하기
  2. 오른쪽 페이지의 점수 구하기
  3. 그 중 가장 큰 값 구하기

 

이런 식으로 풀어서 목록화했다. 목록화한 내용은 별도의 문서로 만들까 고민했지만, 전체적인 관점에서 각 문제들이 엄청나게 많은 기능들을 요구하는 게 아니었기 때문에 그냥 각 문제파일들의 하단에 주석으로 달아주고 미션을 진행했다.


간만의 새로운 친구 자바와 친해지기

이제 기능 목록으로 정의한 내용들을 코드로 옮길 차례다. 문제는..

 

"내가 자바를 다뤄본 적이 별로 없다!"

 

 난 프론트를 공부하다가 우테코를 지원하기 전 백엔드로 진로를 바꿨기 때문에, 지금까지 리액트, 자바스크립트 등의 프론트 관련 라이브러리와 언어만 학습해왔다. 또한 간간히 하던 알고리즘 공부는 파이썬으로 했었다. 자바 자체는 이번 학기 학교 수업을 통해 가볍게 접한 게 다였다..뭐 새로운 언어 학습에 대한 거부감이 없기는 하지만 처음 레포지토리를 클론받고 java로 작성된 파일들을 볼 땐 살짝 정신이 아득해졌다. 가만히 생각해보니, 내가 자바나 c++같은 컴파일 언어를 다뤄본지가 꽤 오래 됐다.. 알고리즘 문제같은 경우는 파이썬에서 편하게 풀곤 했는데, 첫인상이 부드러워서 맘에 들던 파이썬과는 달리 굉장히 딱딱해보이는 자바로 내가 이 문제들을 제때 할 수 있을까? 라는 생각이 들었다.

 

 근데 뭐 지금까지 공부하면서 나름 느낀 건, 직접 부딪혀보며 배우는 게 학습에 훨씬 좋다는 것.자바를 잘 모르는 상태긴 해도 문제들을 풀면서 그때그때 모르는 부분을 찾으며 공부하자고 생각하고 미션을 수행했다. List는 뭐지..문자열 인덱싱은 어떻게 하지..자바에서 형변환은 어떻게 하지..대문자 판별은 어떻게 하지..얘도 메소드가 따로 있나..자바도 파이썬 directory비스무리한게 있나..HashMap이란 게 있구나...등등.. 하나하나 모르는게 생길 때마다 구글링을 하고, 자바의 정석 책을 뒤지며 공부해나갔다. 그리고 실시간으로 궁금한 게 생기면 직접 코드를 짜서 돌려보며 "이 땐 이렇게 되는군!"하고 공부해갔다. 


리팩토링을 하며..

 1번 ~ 7번까지의 문제들을 일단 다 풀긴 했다. 문제들을 푸는 과정에서는 내 생각을 자바 코드로 어떻게 옮길 수 있지?를 고민했다면, 이제는 좀 더 잘 읽히고 재사용성도 좋도록 코드를 다듬을 차례다.

 

 메소드의 장점이 코드의 재사용이기 때문에 반복적으로 사용되는 코드들을 메소드로 묶는 것부터 시작했다. 그리고 기능 목록에서 정의했던 각 기능들을 추출해 메서드로 만드는 것과 한 메서드에서는 하나의 들여쓰기만 하도록 하는 것, 그리고 메서드 작성시 파라미터를 3개까지만 받게 하는 것, 그리고 자바 컨벤션을 최대한 지키는 것을 중점으로 리팩토링을 해나갔다.

 

 꽤 쉬울 거라 생각했다. 왜냐하면 이미 내가 기능 목록 만들었으니까! 그거 보고 그 기능에 해당하는 코드만 쏙 뽑아서 메서드화하면 되니까! 

 

근데 예상보다 훨씬 더 많은 시간이 걸렸다..

 

제일 많은 고민을 한 것 = 한 메서드에서 하나의 들여쓰기만 쓰기

예를 들어 다음과 같은 코드가 있다고 하자..

for (암튼 반복문) {
    if (특정 조건문) 
        continue;
    
    이하 코드들..
}

 여기서 한 메서드에서 하나의 들여쓰기만 가능케하기 위해, for 문 안에서 if문으로 들어가는 부분을 메서드로 바꿔줘야 했다.

근데 문제는, if문부터를 메서드로 만들어야 하는데 그러면 continue를 그대로 쓸 수가 없다!

 처음엔 if문부터를 메서드로 뽑아낸다음 if조건문을 만족하면 return;을 하면 된다고 생각했지만, 문제는 그러면 for문에서 if문 아래에 있는 "이하 코드들"에 해당하는 부분들은 언제나 실행된다. 이를 방지하기 위해선

 

  1. "이하 코드들"에 해당하는 부분도 if문과 함께 메서드 안으로 집어넣기
  2. 플래그 변수 만들어서 if조건문이 만족되면 플래그 변수에 변화를 준 다음, 플래그 변수에 변화가 없을 때만 "이하 코드들"이 실행되게 하기 (물론 이 경우 이하 코드들에 해당하는 부분도 메서드화해야 함)

 

라는 2개의 방법이 있다고 생각했다. "if문을 통해 특정 조건을 체크하는 것"과 "문제없으면 이하 코드들을 수행하는 것"은 다른 기능이라고 생각했기 때문에 2번 방법으로 하려고 했으나, for문에서 수행하고자 하는 것이 결국은 "이하 코드들"에 해당하는 부분이기 때문에 1번으로 해도 괜찮다는 생각이 들었다. 근데 그러면서도 "아 그래도 조건 체크랑 코드 수행은 다른 기능으로 봐야할 것 같은데.."라는 생각이 계속 들어 고민에 고민을 거듭했다. 결국 이런 코드들은 대부분 1번으로 하긴 했지만, 뭔가 더 좋은 방법이 있을 것 같다는 생각이 든다..

 

그리고 충격적이었던 것 = 자바는 call by reference가 없다

리팩토링 과정에서 가장 충격적이었던 건, 자바는 Call by reference가 안 된다는 것이었다!!

 

파이썬에서는 for-else라는 문법이 있어 for문이 정상적으로 수행된 경우(break안 걸리고)에 else문 안의 코드를 실행하게 함으로써 for문의 정상동작여부를 확인할 수 있다. 그러나 자바는 for-else에 해당하는 문법이 없어, 이를 구현하려면 플래그 변수를 둬서 for문에서 특정 조건을 판단하고 break를 걸 땐 그 플래그 변수의 값을 변화시켜야 한다. 그러면 for문 밖에서 플래그 변수의 값이 변했는지를 판단하면 되니까. 

 

굳이 for-else가 아니더라도, 특정 조건을 만족하면 파라미터로 받은 변수의 값을 변화해주는 메소드를 만들고 싶었다. 아니 근데..알고보니 자바 이 친구는 call by value만 되는 언어라고 한다. 즉 내가 생각했던 방식이 안 되는 거다. 객체를 인자로 주고 객체의 속성을 바꾸는 식의 코드는 원본 객체의 속성도 바뀌긴 하지만, 그렇다고 플래그 변수로 쓸 녀석을 객체로 래핑해서 인자로 주긴 너무 번거롭고 읽기 힘들어진다고 생각했다.

 

뭔가 다른 방법이 있을 거야..하다가, 우선은

플래그변수 = 어떤메소드(플래그변수);

이런 식으로 코드를 작성해 리턴값을 담는 식으로 코드에 변화를 주었다. 이 방법이 최선인지는 잘 모르겠다..

 

전체적인 1주차 후기

 앞서 말했듯, 내가 우테코에 지원한 이유는 읽기 좋고 재사용성이 좋은 실전적인 코드를 짜는 개발자가 되고 싶었기 때문이다. 평소에 원하는 기능을 구현하도록 코드를 짤 순 있어도, 읽기 좋은 코드 혹은 재사용성이 좋은 코드를 짠다고는 생각하지 않았다. 그리고 이번 1주차 미션을 통해, 내가 확실히 이 부분이 많이 부족하다는 걸 느꼈다. 내 코드가 읽기 좋고 재사용성이 좋은 코드였다면 리팩토링 과정에서 이렇게까지 많은 시간이 걸리진 않았을 거라고 생각하기 때문이다. 재사용성이 좋은 코드였다면 각 기능을 추출해서 메서드화하는게 쉽게쉽게 진행됐겠지만, 전혀 그러지 않았다. 오히려 와 이걸 어떻게 뽑아내야 하지..어떻게 바꿔야 하지..등등의 고민을 엄청 했던 것 같다. 

 그러면서 느끼게 된 것은, 기능 목록을 의미있는 기능 단위로 작성하는 것이 더 좋을 것 같다는 것이었다. 나름대로 기능 목록을 잘 만들었다고 생각했고 그에 맞춰 1차 구현을 완료했다고 생각했지만, 리팩토링 과정에서 기능 목록에 작성했던 각각의 기능별로 메서드화하거나 별도의 코드로 분리하는게 쉽지 않았다. 내가 기능 목록을 투머치하다 싶을 정도로 세밀하게 하려고 했던 것도 한 몫 한 것 같고, 내가 만든 기능 목록의 기능들이 서로 깔끔하게 분리된 기능들이 아니었던 것도 한 몫 한 것 같다.. 

 또한 자바 컨벤션과 깃허브 컨벤션도 나름 잘 지키려고 노력했지만 부족함이 있음을 느꼈다. 제출 과정에서 다른 친구들이 올린 걸 보는데 와우..이게 맞다 싶을 정도로 한눈에 딱 들어오는 커밋메시지를 작성하는 친구들이 좀 보였다. 확실히 이런 부분은 이렇게 작성하는 게 좋겠군! 이란 배움을 얻을 수도 있었다..

 

2주차 목표

이번 1주차 때 내가 확실히 코드를 더럽게 짜는 편이구나..를 느꼈다. 그러나, 기능 목록을 좀 더 의미있는 기능 단위, 깔끔하게 분리되는 기능 단위로 작성한다면 코드도 좀 더 깔끔하게 짤 수 있고 메서드 분리도 좀 더 깔끔하게 할 수 있을 거란 생각이 든다. 따라서 이번 2주차에서는 기능 목록을 의미있는 기능 단위로 작성하여 메서드별로 깔끔하게 분리되는 코드의 작성을 목표로 할 것이다.

 

의미있는 기능 단위?

구체적으로 예를 들자면, 포비의 왼쪽 페이지 점수를 구하고 오른쪽 페이지 점수를 구해 둘 중 더 큰 점수를 구한다. 라는 요구사항이 있다고 하면

 

  1. 왼쪽 페이지 점수를 구한다
  2. 오른쪽 페이지 점수를 구한다
  3. 둘 중 더 큰 점수를 구한다.

라는 기능목록을 만들었다. 왼쪽 구하는 과정과 오른쪽을 구하는 과정을 분리한 것이다. 내가 원하는 것은 목록에 작성한 기능별로 그에 대응하는 메서드 또는 코드를 뽑아내는 것이다. 페이지 점수를 구하는 메서드인 getPageScore라는 메서드가 있으면, 이 기능 목록에서는 1번과 2번 모두에 getPageScore가 쓰이므로 내가 원하던 것과 맞지 않는다. 내가 원하던 건 목록에 작성한 하나의 기능이 하나의 메서드에 대응하는 거니까!! 내가 원하는 식으로 할려면 getLeftPageScore, getRightPageScore로 분리해야 하지만 이는 비효율적이다. 따라서, 기능목록 자체를

 

  1. 각 페이지의 점수를 구한다
  2. 구한 점수 중 더 큰 점수를 구한다

이렇게 만든다면 getPageScore가 1번에만 쓰이므로 좀 더 잘 짜여진, 의미있는 기능 단위로 만들어진 기능 목록이 된다.

...

또한, 깃허브 컨벤션과 자바 컨벤션 역시 좀 더 철저히 지키는 것을 목표로 해야 하겠다.

 마지막으로..재사용성이 좋은 코드를 작성한다는 것은 객체지향적인 코드를 작성하는 것과 관련이 깊다고 생각한다. 하지만 난 객체지향적인 코드 작성에 매우 취약하다. 이 부분 역시 2주차때부터는 좀 더 객체지향적인 코드를 짜는 것을 목표로 할 것이다.

 

 

1주차 때 얻어간 것들이 많다. 더욱 더 빨아서 이번 기회를 통해 좀 더 성장해보자.

암호화폐의 주요 이슈

  • 내가 받은 암호화폐가 진퉁이 맞나?
  • 내가 받은 암호화폐가 이중지불된 건 아닌가?
  • 내가 받은 암호화폐가 내 꺼라는 걸 다른 사람들도 인정할 수 있나?

 

본디 이 문제에 대한 해결책은 중앙에서 검증하고 보증하는 것이었으나, 중앙화 시스템은 근본적으로 공격에 취약하다는 단점이 있다. 이를 사토시 나카모토라는 인물이 기존 암호화폐 시스템을 발전시켜 탈중앙화된 개념을 사용하며 위 문제를 다 해결했다.

 

Bitcoin BlockChain에서 쓰이는 아이디어들

  • 기존의 서버-클라이언트 구조에서 P2P 네트워크를 사용해 탈중앙화를 이룬다.
  • P2P 네트워크에서 자신이 해당 암호화폐의 소유자임을 증명하기 다른 사람들이 이를 검증(즉 다른 사람들도 인정할 수 있게)하기 위해 전자서명(Digital Signature)을 사용
  • P2P 네트워크의 각 노드들간의 데이터가 일관성이 있어야 함. 즉 모든 노드가 같은 상태의 데이터를 갖고 있어야 한다
  • '합의 프로토콜'을 통해 P2P 네트워크의 모든 노드들이 데이터들의 일관성을 가지게 한다.
  • '합의 프로토콜'에 의해 수학적 퍼즐이 제시되고, 이 문제를 푼 채굴자가 블록을 생성할 권한을 받는다(거래 승인 권한). 이 과정으로 신규 암호화폐가 발행되고 데이터의 일관성이 유지된다

 

블록체인 기술의 의의

블록체인의 의의는 안전하지 않은 인터넷 망에서 어떠한 중재자(중앙 시스템)도 없이 가치 있는 품목들을 안전하고 자유롭게 교환할 수 있게 하는 기술이라는 데 있다.

 

비트코인은 가치가 부여되는 품목 중 가장 단순한 형태인 '화폐'를 교환하는 시스템으로 1세대 블록체인이라 볼 수 있다. 이더리움은 화폐뿐만 아니라 계약서나 증명서까지 교환할 수 있도록 '스마트 컨트랙트' 기능을 추가한 2세대 블록체인 기술이라고 보면 된다.

 

※ 스마트 컨트랙트 : 블록체인을 기반으로 금융 거래, 부동산 계약 등 서면으로 이루어지던 다양한 형태의 계약을 디지털 명령어로 작성해 조건에 따라 계약 내용을 자동으로 실행하는 것. 특정 조건이 충족되지 않으면 해당 계약은 실행되지 않기 때문에 두 사람이 서로를 모르거나 신뢰하지 않더라도 계약을 체결할 수 있음. 그렇기 때문에 중개인(중앙 시스템) 없이도 계약을 이행할 수 있다는 것이 스마트 컨트랙트의 특징

 

 

비단 화페, 계약서 뿐만 아니라 인터넷 상에서 가치있는 모든 품목을 블록체인을 통해 안전하게 교환할 수 있으며 신뢰와 보안이 중요시되는 4차 산업혁명 시대에 기여할 주요 기술 중 하나로 평가받는 중이다.

 

블록체인 기술의 특징

  1. 탈중앙화 : 중앙 서버나 기관 없이 원하는 기능을 수행한다
  2. 투명성 : 모든 기록이 투명하게 공개된다
  3. 익명성 : 거래 당사자 간 익명성을 보장한다
  4. 불변성 : 모든 기록은 변경 불가하며 증거로써 활용할 수 있다

 

블록체인 기술이 활용되고 있는 곳들

  • 금융산업 : 블록체인 플랫폼을 활용하면 낮은 비용으로 거래의 효율성과 보안성 향상을 기대 가능. 중앙 시스템이 없으니 거래 절차가 간소화되고 모든 거래 내역이 공유되니 부정거래의 발생을 줄일 수 있다는 점 등에서 금융산업에서 블록체인에 가장 관심을 보임
  • 비금융 분야 및 공공 부문 : 제조업 분야에선 식품 원산지, 생산 방식 등 유통 정보의 투명성을 높일 수 있음. 공공서비스에서는 정부 예산 집행의 투명성과 효율성을 제고시킬 수 있음. 이 외에 의료기록문서, 보험청구서 등의 투명한 관리 역시 가능

 

P2P 네트워크

서버-클라이언트 구조와 달리 중앙시스템이 없고 각 노드가 대등한 위치에서 서로가 서비스하면서 자율적으로 운영되는 네트워크. 데이터도 각 노드에 분산돼있음. 통제나 업그레이드가 어렵긴 하지만 중앙시스템이 갖는 문제점(중앙만 때리면 다 막힌다 등..) 자체가 없다. 또한 일부 노드가 손상돼어 그 노드에 보관된 데이터가 유실돼도 정상 노드의 데이터로 복구가 가능. 

 

비트코인 외에도 수많은 P2P 네트워크가 있으나, 일반적인 P2P는 가치가 부여된 품목을 교환하는게 아니어서 신뢰성에 대한 고려가 없음.  비트코인의 P2P 네트워크는 채굴, 거래 내역 검증 등의 업무를 각 노드가 분담하며, 각 노드는 사전에 정한 합의 프로토콜에 따라 각자의 업무를 수행. 각 노드가 가진 블록체인 데이터에 불일치가 발생(즉 내 데이터와 쟤의 데이터가 달라지면) 다수결 원칙에 따라 다수의 노드가 가진 데이터를 기준으로 복구함. 따라서 전체 노드의 50% 이상이 정상 데이터를 가지고 있으면 데이터의 일관성은 유지된다.

 

비트코인 네트워크의 각 노드들

1. 풀 블록체인 노드(풀 노드)

블록체인 데이터 전체를 관리하는 노드. 2009년 1월 3일부터 현재까지 발생한 모든 거래 내역(트랜잭션)들이 보관돼있다. 채굴자 노드가 최근 트랜잭션이 담긴 새로운 블록을 생성하고 네트워크로 전파하면, 풀 노드가 이 블록을 받아 합의 프로토콜에 따라 유효성을 검증하고 문제가 없으면 기존 블록체인에 연결한다. 또한 다른 풀 노드들에게 새로운 블록이 연결됐음을 알리기 위해 신규 블록의 헤더 정보(블록 헤더 hash)를 전파한 후, 이 정보를 받은 노드가 블록의 세부 내용을 요청하면 세부 내용도 보내준다. 이를 블록 Relay라고 함. 이 방식으로 다른 풀 노드들과 블록체인 데이터를 동기화해 데이터의 신뢰성과 일관성율 유지한다.

 

풀 노드가 개별 트랜잭션을 받으면 합의 프로토콜에 따라 유효성을 검증하고 자신의 메모리 풀(Memory pool)에 저장하고 다른 노드들로 전파하는데, 방식은 블록 Relay와 같다. 참고로 메모리 풀이란 각 노드들이 갖고 있는 임시 저장소로 아직 승인(즉 채굴)되지 않은 트랜잭션들이 담긴다. 채굴자들도 자신의 메모리풀에 이 트랜잭션들을 보관한다.

 

2. Lightweight 노드(SPV 노드)

블록체인 전체가 아닌 블록의 헤더 정보만 가지고 있는 노드다. 새로운 블록이 생성되면 풀 노드로부터 블록 헤더를 받아 자신의 헤더체인에 연결하는데, 블록 body에 비해 블록 head의 용량이 훨씬 적어서 저장 공간을 널널하게 쓸 수 있다. 

 

비트코인 지갑 애플리케이션이 주로 SPV 노드이며, 자신의 송/수금과 관련된 트랜잭션들의 유효성을 검증한다. 자신의 트랜잭션이 승인(채굴)되어 정상적으로 블록체인에 포함됐는지 여부도 확인한다. 

 

※ 참고: SPV노드는 블록체인 데이터가 아니라 헤더만 갖고 있으니 자신의 트랜잭션들을 검증하려면 풀 노드의 도움을 받아야 한다. 이 때 풀 노드로부터 필터(블룸 필터)를 통해 자신과 관련된 트랜잭션들만 선택적으로 받는다

 

3. 채굴자 노드

각 노드가 보내는 트랜잭션들의 유효성을 검증하고 이들을 모아 새로운 블록을 생성한 후 풀 노드로 보내는 역할을 한다. 풀 노드는 이 블록의 유효성을 재차 검증한 후 기존 블록체인에 추가한다. 이 절차가 트랜잭션을 승인하는 기능이다. 모든 트랜잭션은 채굴자가 블록에 포함시키고 풀 노드가 이 블록을 블록체인에 추가해야 유효한 거래로 인정되는 거라고 보면 된다.

 

채굴자들은 경쟁적으로 수학적 퍼즐을 풀고 블록 헤더에 그 해답을 명시한다(Nonce필드에 기록). 먼저 해답을 제시한 채굴자의 블록이 풀 노드로 보내지며, 풀 노드는 이 해답이 올바른지 확인하는 식으로 유효성을 검증한다.

 

채굴자가 이렇게 새로운 블록을 생성하면 일정량의 비트코인이 새롭게 발행되고 채굴자가 이걸 받는다. 그렇게 때문에 너도나도 채굴을 하는 것이며, 서로가 경쟁적으로 채굴하는 과정에서 신뢰성이 생기는 것이다.

 

블록체인의 구조

일종의 링크드 리스트라고 볼 수 있는데, 각 노드가 다음 노드가 아니라 이전 노드를 가리키는 형태의 링크드 리스트라고 보면 된다.

각 블록은 body와 header로 구성된다. body에는 여러 트랜잭션들이 저장돼있고, header에는 body정보가 요약돼있다. 그리고 header는 이전 블록의 header를 포인터로 가리킨다. 이런 식으로 각 블록들이 서로 체인으로 연결돼있다고 볼 수 있고, 이런 구조를 블록체인이라고 부른다.

 

헤더 뜯어보기

header는 블록의 요약 정보가 담겨 있다. 다음과 같이 생겨먹었다.

  • Version: 비트코인의 소프트웨어 버전 정보
  • 이전 블록 헤더의 해시: 말 그대로. 이전 블록을 가리키는 hash pointer역할을 하고, 블록들을 체인으로 연결하는 역할이다. 블록 안에 있는 트랜잭션들이 위조되면 헤더의 hash값이 변하는데, 내 다음 블록의 header에 저장된 이전 블록 헤더 hash값과 불일치하게 되는 것이므로 그 블록과의 연결이 끊어진다. 즉 한 번 체인으로 연결된 블록들은 그 내용이 바뀔 수 없다(정확히는 바뀌는 게 쉽게 감지된다고 해야 하나..) 이게 블록체인 데이터가 안전하게 유지되는 원리이다.
  • 머클 루트: 머클 트리의 루트 노드 값이다. 머클 트리는 블록에 있는 모든 트랜잭션들의 hash값을 이진트리 형태로 구성한 것으로, 트랜잭션 데이터를 보호할 목적으로 사용된다. 특정 트랜잭션의 내용이 바뀌면 머클 루트 값이 바뀌고, 머클 루트 값이 바뀌면 블록 헤더의 hash값도 바뀌어서 hash값 불일치 현상이 발생하게 되고 체인이 끊어진다. 참고로 커틀 루트는 특정 트랜잭션이 이 블록에 들어있는지 판별할 때도 사용 가능하다.
  • 타임스탬프: 이 블록이 생성된 날짜와 시각
  • Bits: 채굴의 난이도. 채굴자들이 푸는 수학적 퍼즐의 해답이 이 bits가 표현하는 어떤 값보다 작아야 한다. 참고로 bits는 채굴 시간이 평균적으로 10분정도가 걸리게끔 조절된다. 
  • Nonce: 수학적 퍼즐의 답. 채굴자들이 유일하게 바꿀 수 있는 값으로, 이 Nonce값을 이리저리 바꿔가며 헤더의 hash를 구하다가 어느 날 우연히 특정 Nonce를 넣었더니 나온 hash값이 bits가 표현하는 값보다 작았다? 그 문제 푼 거다

 

알아보기 전, Polymorphism에 대해 알고 가면 좋다.

Polymorphism ?

우리 말로는 '다형성'이라고 하며, 똑같은 이름을 가졌지만 실제 수행 시엔 다른 결과를 내는 것을 말한다. 부모로부터 상속받은 메소드를 자식 단에서 오버라이딩했을 때를 그 중 하나의 예시로 들 수 있고 strategy pattern에서 이를 사용한다.

다음과 같은 관계를 갖는 아이들이 있다고 하자.

Dog와 Cat은 부모 Pet으로 받은 메소드 cry()를 각각 bark(), meow()를 호출하는 함수로 오버라이딩했다고 하자. 그리고 다음과 같이 자바 언어로 간단한 프로그램을 작성했다 하자.

public class PetMain(){
    public static void main(String[] args){
        Pet myPet;
        myPet = new Dog();
        myPet.cry();
        myPet = new Cat();
        myPet.cry();
    }
}

myPet의 cry()는 두 번 호출됐지만, 각각 다른 결과를 보일 것이다! 똑같은 이름을 가졌지만 실제 수행 시엔 다른 결과를 내는 것이며, 그 이유는 코드에서 드러나있듯이 첫 번째 cry를 할 땐 Dog인스턴스, 두 번째 cry를 할 땐 Cat인스턴스에 관한 것이기 때문. 

 

 

strategy pattern

결론부터 말하면 메소드, 알고리즘 등(객체들이 할 수 있는 '행위'라고 생각하면 됨)을 캡슐화하여 행위를 유연하게 확장하는 패턴을 말한다. 

 

다음과 같은 관계를 갖는 아이들이 있다고 하자.

부모 Duck을 상속한 RedHatDuck, MallardDuck이 있는 상황. 이 오리들은 fly, swim, quack을 할 수 있는데, display를 하면 화면에 서로 다른 모습으로 그려진다고 하자. 그래서 fly, swim, quack은 부모에 그냥 뒀지만 display는 추상 메소드로 둬서 자식 단에서 구현하는 모습을 확인할 수 있다.

 

이 상황에서, RubberDuck을 만드는데 얘도 Duck을 상속받아 만들려고 한다고 하자. 문제는 이 RubberDuck은 fly를 할 수 없고, quack(꽥!!)역시 살아있는 오리들의 꽥이 아니라 모형오리를 누르면 나오는 squack이라고 하자. 

 

...어떻게 이 상황을 처리할 수 있을까?

 

뭐 우선 RubberDuck이 자신의 메소드로 squack을 가지게 하고..

fly가 문젠데, 이 녀석을 오버라이딩하는 방법이 있을 것이다!

뭐 그러면 다음과 같은 꼴이 된다

.그러나 애당초 상속을 하는 이유가 뭐였는가? 코드의 재사용 떄문이다. 러버덕부터 시작해 이런 식으로 오버라이딩을 통해 해결한다면 결국 어느 순간에 이르러서는 상속의 의미가 없어지게 된다..(암튼 뭐 이 방법은 객체지향적인 관점에서 썩 좋은 방법은 아니라는 얘기)

 

 

그렇다면 어떻게 할 수 있을까..

 

메소드, 행위 자체를 인터페이스 클래스로 바꾼다면 어떨까! 기존 부모에 있는 fly, quack을 따로 빼서 flyable, quackable이란 클래스로 만들고 나중에 fly가 가능한 애들만 flyable을 상속받고 quack이 가능한 애들만 quackable을 상속받게 하는 것이다.

후..draw.io에서 그리다가 화딱지나서 파워포인트로 그렸다..

 

 

RubberDuck입장에선 쓰지도 않는 fly와 quack을 상속받지 않으니 으흠 깔끔하게 만들었군!이란 생각이 든다.

 

그러나...이 방법은 Multiple Inheritance, 즉 다중 상속이 많아지는 방식이므로 구조가 매우 복잡해질 수 있는 단점이 있기 때문에 역시나 완전 좋은 방법은 아니다.

 

방금 방법에서 메소드와 행위 자체를 인터페이스 클래스로 분리시켰음에 주목하자. 부모클래스 Duck이 이 메소드클래스와  association을 갖게 하면 어떨까? 다음과 같이 작성할 수 있다!

객체가 할 있는 행위들 중 바뀌는 행위들을 따로 클래스로 만든 모습을 볼 수 있다. 아까 Pet 인터페이스를 자식 Dog, Cat이 구현하고 Pet변수에 Dog객체, Cat객체를 대입하여 같은 메소드지만 서로 다른 일이 일어나게 했었는데(다형성!), 똑같이 여기서도 Duck에 멤버변수로 FlyBehavior변수, QuackBehavior변수를 둔 다음 이 변수들에 서로 다른 구현객체(FlyWithWings..)를 대입하여 서로 다른 동작이 일어나게 할 수 있다. 이를 테면,

 

public class MallardDuck extends Duck(){
    MallardDuck(){
        flyableBehavior = new FlyWithWings();
        quackableBehavior = new AnimalQuack();
    }
    // .. 생략
}

 

이런 식이다. 만약 나중에 새로운 나는 방식(예를 들어 로켓으로 난다든지..)을 도입해야 한다면 단순히 FlyBehavior를 구현하는 클래스 FlyWithRocket을 만들면 끝이다. 기존 코드를 건드리지 않으면서 확장이 가능한 셈이다.

 

이렇듯, 전략패턴(strategy pattern)은 메소드, 알고리즘, 행위 등을 정의하고 캡슐화하여 독립적으로 변경할 수 있게끔 하는 패턴이다. 

firebase와 연계해서 프로젝트를 진행하던 중, 로그인이후 Main page로 이동시킬 때 로그인된 user의 uid를 같이 보내야했다. 이를 어떻게 할 수 있을까?


UseNavigate

react router에서 지원하는 Hook. 지정한 경로로 페이지를 이동시킬 수 있다. 근데 두 번째 인자로 이동시킬 페이지에 함께 보낼 데이터를 지정할 수 있다! 예시는 다음과 같다.

import { useNavigate } from 'react-router-dom'; 

// ...생략

const navigate = useNavigate();

// ...생략

const gotoMain = () => {
  navigate("/main", {
    state: {
      userId: user.uid
    }
  });
};

참고로 이렇게 데이터를 보낼 때는 두 번째 인자의 state라는 속성으로 보내야 한다고 함. 

 

UseLocation

react router에서 지원하는 Hook. useNavigate를 이용해 전송된 데이터를 받을 수 있다. 예시는 다음과 같다.

import { useLocation } from 'react-router-dom'; 

// ...생략

const location = useLocation();

// ...생략

const [userId, setUserId] = useState(
  location.state?.userId
);

location의 state프로퍼티에 내가 보낸 데이터들이 담겨있다고 보면 된다. 

 

 

 

 

참고로, Link태그를 통해서도 데이터를 보낼 수 있다고 한다. 예시는 다음과 같다.

<Link to={`/main`} state={{ test: "hello world" }} >
   test
</Link>

 

또한 참고로, useNavigate의 두 번째 인자로는 state가 아니라 replace라는 것도 보낼 수 있다.

import { useNavigate } from 'react-router-dom'; 

// ...생략

const navigate = useNavigate();

// ...생략

const gotoMain = () => {
  navigate("/main", {replace: true});
};

기본값은 false이고, true로 돼있으면 페이지가 이동된 뒤 뒤로가기를 하더라도 방금 페이지로 돌아올 수 없고, 루트로 돌아오게 된다고 한다. false는 뒤로가기가 가능.

 

강의를 들으면서 Ref를 통해 Input태그의 값을 받아오는 걸 보곤 했지만, 무슨 이유로 Ref를 쓰는지는 정확히 몰랐었다.

이번 기회에 정리해보자.


Ref

리액트에서 원하는 시점에 실제 DOM노드에 접근하고 싶을 때 사용하는 놈이다. 다음과 같이 Ref객체를 생성할 수 있다.

import { useRef } from 'react';

// ...생략

const inputRef = useRef();

그리고, 만들어진 Ref객체는 다음과 같이 원하는 DOM요소에 ref속성을 통해 연결시킬 수 있다.

<input type="text" name="title" ref=[inputRef} />

이 Ref라는 객체는 console.log로 찍어보면 알 수 있지만 current라는 프로퍼티를 가지며, current프로퍼티는 ref속성으로 연결된 DOM노드를 가리킨다. 즉 Ref객체의 current속성을 이용해 연결된 DOM노드에 접근하여 value등을 읽어올 수 있는 것!

 

다만 주의할 점은, DOM노드는 렌더링이 끝나야만 생기니까 Ref객체의 current값 역시 컴포넌트가 화면에 렌더링됐을 때만 존재한다는 것. 따라서 Ref를 통해 DOM노드에 접근할 때는 Ref.current를 확인한 다음 사용하도록 하자.

 

그래..이래서 Ref의 current로 인풋 태그의 값을 읽어오고 뭐 그랬던 거구나~으흠~

한마디로 Ref를 사용하면 DOM노드를 직접적으로 건드릴 수 있구나~(포커스를 준다던가..)

 

..? 근데 Ref 왜 쓰는 거지?

자바스크립트에선 querySelector로 그냥 잡아올 수 있는데 말이다.


Ref를 쓰는 이유

id, class를 통해서 바닐라JS로 DOM조작이 가능한 건 맞지만, 컴포넌트가 여러 개 생긴 경우 id나 class로 원하는 DOM요소를 가져오는 건 매우 힘들다. 이 때 Ref를 통해 원하는 DOM요소를 특정하도록 할 수 있다.

 

+ Recent posts