코드 훔쳐보는 변태 코더
춤 좋아하는 백엔드 개발자(였으면 좋겠다)
전체 글 (95)
프로세스랑 스레드는 무슨관계?? ;ㅅ;

프로세스

프로그램 → 하드디스크 등의 저장매체에 저장되는 실행 파일의 형태를 갖고있는 것

프로세스 → 프로그램이 메모리에 적재되어있는 상태를 뜻한다.

  • 프로세스는 프로그램 실행에 필요한 모든 자원을 할당받는다.
  • 코드공간 / 힙공간 / 스택공간 / 데이터공간 을 갖고있다.

프로세스는 기본적으로 코드공간 힙공간 스택공간 데이터공간 을 가집니다. (코데힙스)

프로세스의 특징

  • 운영체제는 프로그램을 메모리에 적재하고 프로세스로 다루게 된다.
  • 메모리 할당 → 코드 , 데이터 적재
  • 프로세스들은 서로 독립적인 메모리 공간을 가지고 프로세스마다 고유한 번호 **(PID)**를 할당한다.
  • 프로세스의 모든 정보는 커널에 의해 관리된다.
  • 프로세스는 기본적으로 실행 - 대기 - 잠자기 - 대기 - 실행 - 종료 등의 생명주기를 가지고 커널에 의해 수행된다.

운용되는 동안에는 대기하고있는 프로세스 (기본적으로는 단순히 대기도 아닌 하드디스크에 저장되어있다.)

  • 그럼 프로세서는 ? → 하드웨어적인 부분으로 하드웨어 처리기라는 의미를 갖고있다.

프로세스는 프로그램의 단위라고 할 수 있으며 프로그램의 정보를 저장한 인스턴스라고도 볼 수 있습니다.

프로세스 관리

  • 커널 (운영체제의 핵심 부분을 의미합니다. 이 부분은 하드웨어와 소프트웨어 간의 인터페이스 역할을 하며, 프로세스 관리, 메모리 관리, 입출력 관리 등 시스템의 핵심적인 기능을 제공합니다. )
    • 커널에 의해 커널 영역에 프로세스 테이블을 만들고 프로세스 목록을 관리한다.
  • 그렇다면 한 프로그램을 여러번 실행할땐 ?
    • 프로그램 실행시마다 독립된 프로세스가 생성되고 다중 인스턴스라고 부른다.
      • 각 프로세스에 독립된 메모리 공간이 할당되며 별개의 프로세스로 취급한다.

프로세스 구성

  • 코드 영역
    • 프로그램의 코드가 적재되는 영역이다.
  • 데이터 영역
    • 프로그램의 변수 공간이라 볼 수 있다.
    • 프로세스 적재시 할당되며 종료시 소멸된다.
  • 힙 영역
    • 동적으로 사용할 수 있도록 할당된 공간이다.
  • 스택 영역
    • 함수가 실행될 때 사용될 데이터를 위해 할당된 공간이다.

프로세스는 기본적으로 자바 혹은 자바스크립트와 같은 언어들의 컨텍스트와 비슷한 영역을 갖고있습니다.

스레드

  • 스레드의 출현 목적
    • 멀티태스킹의 문제점
      • 커널에 많은 시간과 공간이 부담되며 시스템의 전체 속도를 저하시킨다.
    • 효율적인 새로운 실행 단위가 필요했기 때문에 스레드가 출현했다.
  • 스레드는 기본적으로 프로세스보다 크기가 작다.
  • 프로세스보다 생성 및 소멸이 빠르고 이로인해 컨텍스트 스위칭이 빠르다.

스레드의 개념

  • 스레드는 실행 단위이며 스케줄링 단위이다.
    • 하나의 작업의 개념을 갖고있다.
  • 운영체제에게는 실행단위이고 스케줄링 단위이다.
    • 스케줄링이란 ? → 시스템 내에서 실행될 작업들을 어떤 순서에 따라 할당하고 관리하는 것을 의미합니다.
  • 스레드 또한 코드 데이터 힙 스택을 가진 실체이며 프로세스와 마찬가지로 스레드마다 TCB (스레드 컨트롤 블럭) 이 존재한다.
    • 프로세스는 스레드들의 컨테이너라고 볼 수 있다.
      • 쉽게 생각하자면 리액트의 useState 가 클로저 개념을 사용해서 캐싱하고 변화를 눈치채는것처럼 프로세스는 스레드를 캐싱해두고 작업에 대한 정보를 얻어서 관리한다고 생각하시면 돼요!
    • 마찬가지로 프로세스는 회사 / 스레드는 직원에 비유할 수 있다.
      • 직원 → 회사의 목적을 위해 일을 하는 단위
      • 스레드 → 프로세스의 목적을 위해 동시에 실행될 작업 단위
    • 프로세스는 스레드들의 공유 공간을 제공한다.
      • 모든 스레드는 프로세스의 코드 데이터 힙을 공유하며 프로세스의 스택 공간을 나누어 사용한다. 따라서 스레드 사이의 통신이 용이하다. (멀티 프로세스와의 가장 큰 차이점)
    • 프로세스의 속한 모든 스레드가 종료되면 프로세스가 종료된다.
  Comments,     Trackbacks
Virtual DOM 가장 쉽게 이해하기

선언형 프로그래밍

버추얼돔에 대해서 공부하기 이전에 우리는 선언형 프로그래밍에과 명령형 프로그래밍에 대해 알고 갈 필요가 있다.

선언형 프로그래밍이란 ?

한 정의에 따르면, 프로그램이 어떤 방법으로 해야 하는지를 나타내기보다 무엇과 같은지를 설명하는 경우에 "선언형"이라고 한다.

선언형 프로그래밍은 무엇(What)을 원하는지를 명시하고, 시스템이 어떻게(How) 처리해야 하는지는 추상화된 레벨에서 결정하는 방식이다.

쉽게 말해 리액트에서 UI를 선언적으로 작성하면 개발자는 UI의 구조와 렌더링 결과물을 설명하고, 리액트가 이를 실제 DOM 조작으로 변환하고 최적화한다.

그럼 명령형 프로그래밍이란 ?

프로그래밍의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임의 일종이다.

어떻게(How) 작업을 수행해야 하는지 단계별로 명시하고, 변경 가능한 상태를 직접 조작하여 프로그램의 흐름을 제어하는 방식이다.

개발자의 의도대로 하나부터 열까지 동작해야 하는 상황에서는 좋을 수 있지만, 그게 아니라면 오히려 복잡한 작업순서와 예상치못한 결과를 초래할 수 있다.

DOM (Document Object Model)

DOM은 Document Object Model의 약자로 HTML 문서 의 모든 노드를 구조적으로 표현한 것입니다.

쉽게 말해 DOM은 응용 프로그램의 UI를 나타낸다고 할 수 있다.

만약 당신이 1부터 5까지의 값이 담긴 배열에서 중간의 3을 4로 바꾸고 싶다면 어떻게 할것인가?

const arr = [1,2,3,4,5] // 변경 전
const arr = [1,2,4,4,5] // 변경 후
 

작은 규모라면, 단순히 하나의 값을 변경한 새 배열을 선언해 대체할 수 있지만, 만약 규모가 작지 않다면 해당 방법은 비효율적일 수 있다.

이렇게 실제 DOM은 하나의 변경점이 발생해도 모든 노드를 업데이트 하게 된다. 변경점이 발생하지 않았던 노드도 리렌더링을 하게 된다면 얼마나 큰 비용이 소모되는지 알 수 있다.

Virtual DOM

버추얼 돔은 실제 돔의 추상화된 모델이라 볼 수 있다. 그러기 떄문에 추상화의 추상화이다. 경량 복사본이라는 점을 제외하면 실제 DOM 개체와 동일하다.

버추얼돔은 돔의 동작방식을 보완하여 속성이 변경되면 전체 트리가 아닌 해당 노드만 업데이트된다.

리액트에서 버추얼돔은 어떻게 업데이트에 관여할까?

  1. ReactDOM.render() 는 실제 돔과 버추얼 돔트리를 생성하여 첫 번째 로드시 화면의 요소를 렌더링한다.
  2. 요소에 대한 변경 사항 혹은 상태 변경에 대한 알림을 버추얼 노드로 전송한다. 마찬가지로 노드의 속성이 변경되면 자체적으로 업데이트 된다.
  3. 리액트는 업데이트된 버추얼돔을 실제 돔과 비교한다. 그리고 실제 돔을 업데이트 하며 이 과정을 재조정이라고 한다. 재조정은 Diffing Algorithm 으로 알려진 휴리스틱 알고리즘을 사용하여 수행된다.

그렇게 버추얼돔은 변경값이 존재하는 속성만 업데이트하여 렌더링함과 동시에

모든 변경점을 하나하나 리렌더링을 트리거하며 업데이트하지 않고, 필요한 부분만 업데이트하여 한번만 리렌더링을 트리거한다.

div.innerHTML='change text'
div.style.cssText='width:100px;height:100px'
 

예를 들어 이러한 변경점이 발생할 때 , 실제 돔은 변경점 하나하나마다 리렌더링을 한다고 하면, 버추얼 돔은 이 변경점을 모두 반영하여 한번의 리렌더링을 발생시킨다.

→ 실제 돔은 이렇게 개발자가 원하는 방향을 하나하나 일일히 명령하여 렌더링과 성능최적화를 직접 신경써야 한다면

→ 버추얼 돔은 개발자가 원하는 방향과 처리방법을 선언하면 리액트가 디핑 알고리즘을 통해 변경점을 캐치 후 렌더링한다.

이것이 선언형 / 명령형 프로그래밍 방식의 차이라고도 볼 수 있다.

그렇다면 버추얼돔과 실제돔 사이 비교는 어떻게 이뤄질까?

한 트리를 다른 트리로 변환하기 위하여 최소한의 연산을 제공하는 알고리즘은 O(n3)의 시간 복잡도를 가진다고 한다. → 1000개의 요소를 표시하는데 10억번의 비교가 필요하다는 소리이다.

리액트는 이러한 재조정 과정에서 Diffing 알고리즘 휴리스틱 알고리즘을 사용하여 성능을 최적화하며 시간 복잡도를 낮췄다.

휴리스틱 알고리즘

→ 휴리스틱이란 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나 체계적이면서 합리적 판단이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있도록 구성된 간편 추론 방법이다.

단순히 중요한 것들만 고려해서 최선의 값을 찾아내는 방법이라고 할 수 있다.

  • 다른 타입의 두 요소는 서로 다른 트리를 생성한다.
  • 개발자는 Key prop을 이용해 다른 렌더링 사이에서의 자식 요소에 대한 힌트를 얻을 수 있다.

리액트는 위 두가지 가정에 따른 휴리스틱 알고리즘을 채택하여 시간복잡도를 O(n)으로 낮췄다.

따라서 휴리스틱 알고리즘이 기반하고 있는 가정에 부합하지 않는 경우에 성능이 나빠질 수 있다.

재조정 과정

  • 기본적으로 두개의 트리를 비교할 때 ,리액트는 비교를 위한 새 버추얼돔을 생성 후 루트 엘리먼트부터 비교한다.
    • 돔 요소 타입이 다른경우 리액트는 이전 트리를 버리고 새로운 트리를 구축하며 새로운 돔 노드들이 돔에 삽입되며 연관된 모든 state 는 사라지며 새로 다시 마운트된다.
    • 같은 경우에는 변경된 속성들만 갱신한다.
  • 돔노드의 처리가 끝나면, 리액트는 해당 노드의 자식들을 재귀적으로 처리한다.
    • 더 이상 처리할 자식이 없을때까지 처리한다는 뜻
  • 이러한 상황에서의 Key 프로퍼티가 성능적인 측면에서 중요한 역할을 하게된다.
    • 자식들이 key를 갖고있다면, 리액트는 key를 통해 기존트리와 이후 트리의 자식들이 일치한지 확인하여 자식 요소들을 순회하지 않고 key 속성으로 쉽게 판단할 수 있게 된다.

→ key 값으로 인덱스를 사용하면 어떻게 될까?

  • 어떠한 상황에서 단순히 key 값으로 인덱스를 사용하는 경우가 존재한다.
  • 재배열되는 경우에는 항목의 순서가 바뀔 때 key 또한 바뀌기 때문에 식별자로서의 역할을 하지 못한다.

주의점

  • 매우 비슷한 결과물을 출력하는 두 컴포넌트를 교체하고 있다면, 그 둘을 같은 타입으로 만드는 것이 더 나을수도 있다.
  • 위와 마찬가지로 key 는 변하지 않고 예상이 가능하며 유일해야한다. 변하는 키를 사용하면 자식 컴포넌트의 state 가 유실되거나 성능이 나빠질 수 있다.

레퍼런스

ReactJS Reconciliation - GeeksforGeeks

Difference between Virtual DOM and Real DOM - GeeksforGeeks

What is the difference between virtual and real DOM (React)?

[ React ] 재조정(Reconciliation)과 Key 사용 이유

재조정 (Reconciliation) – React

  Comments,     Trackbacks
원티드 프리온보딩 인턴십 1주차 개인&팀과제 회고록

프로젝트 주제

원티드 프리온보딩 인턴십 교육 (11차) 사전과제 주제는 투두리스트 만들기였습니다.
간단한 투두리스트 라고 생각할 수 있지만, 뭐든지 간단한만큼 신경쓸게 더 많다는 사실.. 방심하지 않고 핵심 기능들을 최대한 깔끔하게 구현해보자 라는 생각으로 임해보았습니다.

여담이지만 .. 왜 저는 항상 팀장이 되는걸까요 ..? 흑..

사전 개인과제 요구사항

  • 기능구현에 직접적으로 연관된 라이브러리 사용은 허용되지 않습니다.(React-Query 등)
  • README.md 작성은 필수입니다. 아래의 사항은 반드시 포함되도록 해주세요
  • 페이지별로 요구되는 경로는 도메인 URL뒤에 바로 이어지도록 설정해주세요

기본적으로 진입 루트가 회원가입 -> 로그인 -> 투두리스트 였고, 기능 구현에 연관된 라이브러리는 사용하지 않는것이 기본 규칙이었습니다.

1주차 팀 과제 요구사항

팀빌딩이 이뤄지고 진행되는 1주차 팀 과제는 BestPractice 를 선정하여 사전과제를 리팩토링 하는것이었습니다.

Best Practice란 모범사례라는 말로서, 특정 문제를 효과적으로 해결하기 위한 가장 성공적인 해결책 또는 방법론을 의미합니다.

쉽게말해 사전과제의 핵심 기능을 파트별로 나눠, 가장 잘 작성된 코드를 선정해 선정된것들로만 이뤄진 프로젝트를 완성해 제출하는 것이 1주차 과제였습니다.

1주차 세션에서 진행했던 협업을 위한 툴들을 적용시키기 위해 ES Lint 와 Prettier, 그리고 husky를 사용하여 포멧팅을 자동화 하도록 진행했고,

너무 세분화하여 진행하지 않고 큰 부분들만 나눠서 불필요한 딜레이를 줄이는것을 목표로 진행되었기 때문에 고민사항은 크게 3가지로 분류하여 BestPractice 를 선정했습니다.
(진행은 팀별 디스코드 채널,노션을 생성해 회의와 회의내용을 정리하여 기록하며 진행했습니다.)

  • 파일트리
    • 전체적으로 팀원들이 프로젝트의 디테일한 폴더 구조에 대한 고민이 많았기 때문에 여러부분을 참고하여 진행했지만 실무를 겪어보지 못했기 때문에 프로젝트의 규모별로 괜찮은 파일트리를 선정하기 어려웠다는 의견들이 많았습니다.
  • 인증/인가 로직
    • 인증에 대한 로직을 어디서 구현해야 할지, 공통 로직을 분리할 수 있을지 , 그리고 그것을 어떻게 구현해야 할지에 대한 고민이 많았습니다.
  • 컴포넌트 분리
    • 컴포넌트를 어떻게 어떠한 기준으로 분리하여 유지보수에 최적화된 컴포넌트를 구현할 수 있을지에 대한 고민이 공통적으로 존재했습니다.

과제 진행중 고민되었던 부분

개인 : 파일트리

개인으로 진행했던 사전과제의 경우에는 파일트리를 프로젝트 전체 깊이가 깊어지더라도 세분화하여 폴더를 생성하여 각 폴더별로 메인이 될 수 있는 컴포넌트를 index.tsx 로 생성하여 관리하는 구조를 택했습니다.

해당 구조가 개인적으로 각각의 컴포넌트에 대한 작명만 잘한다면 도서관의 원하는 도서 찾기처럼 크게 불편함이 없는 상태로 세분화를 할 수 있겠다는 생각이 들었습니다.

이전에 진행했던 개인 프로젝트는 아무래도 하나하나를 세분화 하다보니 전체적으로 깊이가 너무 깊어져서 오히려 가독성을 더욱 해칠 수 있겠다는 생각이 들었기 때문에 이번 프로젝트는 진행하면서 자식 컴포넌트 (HeaderMenu 의 Menu 같은)는 따로 폴더를 생성하지 않고 해당 컴포넌트의 이름으로 네이밍을 하는 방식으로 진행하였습니다.

팀 : 파일트리

팀과제로 진행된 프로젝트의 경우에는 일단 컴포넌트 구현에 앞서서 정확한 파일트리와 인증로직 구현을 진행 후에 컴포넌트를 구현하는게 맞다고 생각했기 때문에 파일트리를 먼저 정하게 되었습니다.

전체적으로 저는 백엔드를 학습하다가 프론트엔드를 시작했었던지라, 백엔드 프로젝트를 진행할 땐 항상 확장에 대한 가능성을 열어둔 채로 프로젝트를 진행했기 때문에 최대한 쪼갤 수 있는 부분은 쪼개고 각각의 모듈에 대해서 관심사를 분리하는것을 메인으로 삼고 진행했으나,

공통적으로 프론트엔드 개발자분들은 대부분 현재 진행하는 프로젝트의 규모에 따라 방식을 정하는듯 하셨습니다.

저희 팀의 경우 현재 진행해야 하는 투두리스트 또한 규모 자체가 크지 않았기 때문에 과한 세분화는 지양하고 최대한 커뮤니케이션에 문제가 없게끔 파일트리를 정의하여 깔끔한 프로젝트를 진행하기로 결정되었습니다.

여기에 api로직에 대한 부분들은 저의 방식을 BestPractice 로 선정해 apis 폴더에 각 컴포넌트 별로 비즈니스로직을 분리하여 선언하도록 구현했습니다.

해당 부분에 있어서는 기본적으로 큰 부분 (컴포넌트들,api들, 페이지들 등) 으로 나누어 폴더를 생성 후 각 부분에 맞춰서 필요시에 폴더를 생성하고, 큰 이유가 없다면 파일만 생성하여 관리하는 구조를 택했고, 전체적으로 파일 트리는 아래와 같이 정의되었습니다.

팀 진행으로 인해 얻어간것들 (파일트리)

  • 어떻게 보면 제가 개인으로 정의했던 파일트리는 예를 들었을 때
    쓸데없이 즉섭밥을 데워먹어야하는 상황임에도 직접 지은 밥을 고수하는 듯한 느낌을 줄 수도 있겠구나 .. 라는 생각을 하게 되었습니다.
  • 다른분들의 의견을 통해 상황별로 해당 구조가 필요한 경우가 있겠지만 너무 과하게 멀리보고 진행하기보다는 확실한 규모를 정하고 프로젝트를 진행하는게 좋겠다는 생각을 다시 한번 하게 되었습니다. 개발은 아무래도 나 혼자 하는것이 아니기 때문에 ..

개인 : 인증 로직 구현

인증 로직의 경우에는 기본적으로 요청시에 헤더에 토큰을 포함시키고, 토큰이 만료되거나 비밀번호가 옳지 않을때는 공통적으로 401에러를 반환하기 때문에 axios interceptor 로 분리하여 로직을 구현할 수 있겠다는 생각이 들었습니다.

이에 대해서 들었던 생각은 두가지였습니다.

  • ContextAPI 를 사용하여 인증 상태값 저장하기
  • 단순히 LocalStorage 에서 토큰값을 가져와서 헤더에 포함시키기

이번 과제의 경우에는 메인 기능을 구현하는데에 있어서 관련된 라이브러리를 설치하여 사용하는것이 금지되었기 때문에 ContextAPI 를 사용하여 구현하는것을 생각했습니다.

하지만 Axios Interceptor 의 경우에는 해당 로직 내부에서 hook사용이 불가능했기 때문에 구현에 있어서 문제가 발생했습니다.

따라서 Axios Interceptor 에서는 매 요청시마다 로컬스토리지에 저장된 값을 확인이 가능했기 때문에 로컬스토리지에서 토큰을 찾아서 같이 포함시켰고, Context API는 인증 여부에 따른 라우팅을 담당하는데에 사용하였습니다.

단순히 라우팅시에도 localStorage 에서 값을 찾아오면, 해당 변수에 저장된 boolean 값이 계속 업데이트 되는것이 아니기때문에 제대로된 구현이 어려웠습니다.
+@ 로 useEffect를 사용하여 navigate시에는 블링크 현상이 발생했기 때문에 신경쓸 부분이 많았습니다. (예: 토큰이 없는 상황에서 Todo로 라우팅시에 SignIn 컴포넌트로 네비게이트)

하나의 기능에 대한 구현을 여러 방법으로 구현하는것 처럼 보일 수 있었지만, 이렇게 지속적으로 인증 상태가 변경되는 부분을 감시해야하는 상황에서는 Context API 를 사용했고, Interceptor 에서는 LocalStorage 의 토큰 유무를 매 요청마다 확인하여 반영할 수 있었기 때문에 단순히 getItem 으로 유무를 확인 후 포함하도록 구현했습니다.

export function setInterceptors(instance: AxiosInstance) {

    // 요청 인터셉터 추가
    instance.interceptors.request.use(
        (config) => {
            config.headers["Content-Type"] = "application/json";
            if (getTokenFromLocalStorage) {
                config.headers["Authorization"] = `Bearer ${getTokenFromLocalStorage()}`;
            }
            return config;
        },
        (error) => {
            interceptorErrorHandler(error);
            return Promise.reject(error);
        }
    );

    instance.interceptors.response.use(
        (response) => {
            return response;
        },
        (error) => {
            interceptorErrorHandler(error);
            return Promise.reject(error);
        }
    );

    return instance;
}
function App() {
    const tokenState = useTokenState();
    return (
            <StyledContainer>
                <Header/>
                <Routes>
                    <Route path={"/"} element={tokenState.accessToken ? <Navigate to={"/todo"}/> : <Navigate to={"/signin"}/>}/>
                    <Route path={"/signup"}
                           element={tokenState.accessToken ? <Navigate to={"/todo"}/> : <SignUp/>}/>
                    <Route path={"/signin"}
                           element={tokenState.accessToken ? <Navigate to={"/todo"}/> : <SignIn/>}/>
                    <Route path={"/todo"}
                           element={tokenState.accessToken ? <Todo/> : <Navigate to={"/signin"}/>}/>
                    <Route path={"/signout"}
                           element={tokenState.accessToken ? <SignOut/> : <Navigate to={"/signin"}/>}/>
                    <Route path={"*"} element={<NotFound/>}/>
                </Routes>
            </StyledContainer>
    );
}

팀 : 인증 로직 구현

저희 팀원들 같은 경우에는 리액트 라우터의 createBrowserRouter를 사용하여 구현하셨고, ContextAPI를 사용하시지 않고 단순히 localStorage 에서 토큰의 유무를 확인하고 요청에 포함시키도록 구현하셨었고,

감사하게도 제 구현 방법이 BestPractice 로 선정되어 진행되었습니다.

하지만 이 부분에 있어서 ContextAPI를 사용하지 않고 구현했기 때문에 기본적인 깜빡임 (접근하면 안되는 페이지가 접근이 되었다가 순식간에 구현했던 로직대로 다른 페이지로 이동되는 현상) 이 존재했기때문에 아쉬웠습니다.

시간상 ContextAPI를 다시 적용시킬 여유가 되지 않았기때문에, 디테일한 구현보다는 기본적인 요구사항에만 초점을 두고 진행되었습니다.

//인터셉터
const onRequest = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
  const token = localStorage.getItem('token');

  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
};

const onResponse = (response: AxiosResponse): AxiosResponse => {
  return response;
};

const onErrorResponse = (error: AxiosError): Promise<AxiosError> => {
  if (axios.isAxiosError(error)) {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
    }
  }

  return Promise.reject(error);
};

export { onRequest, onResponse, onErrorResponse };
//라우팅
type TCustomRouteObjectParams = {
  path?: string;
  name?: string;
  element?: ReactElement;
};

type TCustomIndexRouteObject = IndexRouteObject & TCustomRouteObjectParams;
type TCustomNonIndexRouteObject = Omit<NonIndexRouteObject, 'children'> &
  TCustomRouteObjectParams & {
    children?: (TCustomIndexRouteObject | TCustomNonIndexRouteObject)[];
  };
type TCustomRouteConfig = TCustomIndexRouteObject | TCustomNonIndexRouteObject;

const routeConfig: TCustomRouteConfig[] = [
  {
    path: '/',
    element: <Home />,
    errorElement: <div>404 Not Found</div>,
    name: '홈',
  },
  {
    path: '/signup',
    element: <SignUp />,
    name: '회원가입',
  },
  {
    path: '/signin',
    element: <SignIn />,
    name: '로그인',
  },
  {
    path: '/todo',
    element: <Todo />,
    name: '투두',
  },
];

팀 진행으로 인해 얻어간 것들 (인증 로직 구현)

  • 해당 부분에 대해선 개인적으로 아쉬웠던게 많았습니다.
  • 아무래도 요구 사항에 무조건적으로 맞춰서 불필요한 부분은 제외해두고 개발한다고 해도 최적의 사용자 경험도 무시할 수 없다고 생각했기때문에, 블링크 현상이라던가, 상황별로 헤더에 나타나는 메뉴를 컨트롤 할 수 없었던 부분이 많이 아쉬웠습니다.
  • 2주차부터는 자바스크립트의 기본 동작 원리에 대해 더 공부해서 기초 설계 시에 디테일한 부분까지 신경써서 진행해야겠다는 생각이 들었습니다 :)

개인 :컴포넌트 분리

아무래도 프론트엔드는 독학으로 공부해왔기 때문에, 어느 프로젝트를 진행하더라도 항상 고민되었던 부분은 컴포넌트를 어떻게 분리해야할까? 에 대한게 가장 컸습니다.

  • 제가 선택한 방식은 아무래도 가장 보편화된 방식으로 기능별로 분리를 하는 방식을 선택했습니다.
    • 공통된 기능을 가질 수 있겠다 싶은 부분들을 선정해 작은 컴포넌트를 만들어 하나의 큰 모듈을 완성하는 것으로 목표로 작은것부터 차근차근 개발에 임했습니다.
  • 페이지는 크게봤을 때 회원가입 / 로그인 / 투두리스트 로 분리를 할 수 있었고, 각 페이지별로 공통된 부분을 살펴 쪼갠 후 가장 작은 부분부터 구현을 진행하였습니다.

회원가입 및 로그인

회원가입 , 로그인 부분은 사전과제 요구사항에 공통적으로 요구되는 것이 , 1차적인 검증을 진행하고 검증이 실패하면 요청을 보내는 버튼이 비활성화 되어야 했습니다.

해당 검증은 공통적으로 이메일에는 '@' 이 포함되어야 했고, 비밀번호는 8자 이상으로 입력이 되어야 했습니다.

해당 요구사항으로 인해 로그인, 회원가입에 기본적으로 사용되는 input 컴포넌트는 동일하게 구현해도 되겠다는 생각이 들었습니다.

따라서 ValidationInput 이라는 컴포넌트를 생성해 기본적으로 검증을 진행하는 시각적 효과를 가진 스타일링 컴포넌트를 구현했고, 이에 필요한 공통 로직은 useFormControl 훅을 생성해 로직을 분리했습니다.

이 외에 디테일한 에러핸들링 또한 반영하여 구현하였고 최대한 사용자 입장에서 모르는 에러가 은밀하게 스쳐 지나가지 않도록 구현하는것을 목표로 진행하였습니다.

// useFormControl
export function useFormControl(options: {
    regex: RegExp;
    initialValue?: string;
}): [React.ChangeEventHandler<HTMLInputElement>, string,React.Dispatch<React.SetStateAction<string>>, boolean, React.Dispatch<React.SetStateAction<boolean>>,] {
    const { regex } = options || {};
    const [value, setValue] = useState(options.initialValue || "");
    const [validation, setValidation] = useState(false);

    const validateValue = (value: string) => {
        const isValid = regex.test(value);
        setValidation(isValid);
    };

    const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
        const value = e.target.value;
        setValue(value);
        validateValue(value);
    };

    return [handleChange, value,setValue, validation, setValidation ,];
}

기본적으로 요청을 담당하는 로직들은 Page 를 구성하는 컴포넌트에 선언해 처리를 진행했습니다.

	// SignIn.tsx
 ...
    return (
        <StyledFormControl onSubmit={onSubmit}>
            로그인
            <StyledInputBox>
                <ValidationInput {...emailInputProps}/>
            </StyledInputBox>
            <StyledInputBox>
                <ValidationInput {...passwordInputProps}/>
            </StyledInputBox>
            <StyledSignInButton type={"submit"} disabled={!emailValidation || !passwordValidation}
                                variant={"contained"}
                                data-testid={"signin-button"}>
                로그인
            </StyledSignInButton>
        </StyledFormControl>
    );

투두리스트

투두리스트를 구현하면서 가장 고민되었던 컴포넌트 분리 부분은 아무래도 요구사항 중 투두를 수정할 때 기존의 text가 사라지면서 input 으로 변경 된 후 '수정', '삭제' 버튼 또한 '제출','취소' 버튼으로 변경이 되도록 구현하는 것이었는데,

해당 부분을 수정모드일때의 컴포넌트를 구현하여 반영을 할지, 혹은 하나의 컴포넌트에 반영을 할지 고민이 많이 되었고, 주변의 어느분께 헬프를 요청하여 아이디어를 얻게 되었습니다.

아무래도 현업에서 개발을 하고 계시는 프론트엔드 개발자 분들도 공통적으로 고민하는 부분이 컴포넌트 분리에 대한 고민이라고 하시면서, 본인은 각 컴포넌트를 역할별로 분리한다고 하셨고, 해당 투두에대한 수정과 삭제는 해당 투두(항목) 밖에선 일어날 일이 없으므로 굳이 분리할 필요가 없다고 하셨고 , 해당 의견을 참고하여 구현하였습니다.

기본적으로 큰 페이지는 투두 입력창 / .map() 을 활용한 투두 로 구성이 되도록 하였으며 투두컨텐츠라는 컴포넌트에 수정/삭제 로직을 포함시켜 구현하였습니다.

TodoInput 또한 마찬가지로 useFormControl 훅을 재사용해 핵심 로직을 분리시켜서 가독성에 신경썼습니다.

TodoContent 는 과제 명세에 포함된 내용을 참고하여 디테일한 부분을 살리려 노력했습니다. (수정중에 체크박스를 클릭해도 업데이트가 진행된다던지 하는 부분들)

        <StyledBox>
            <TodoInput getTodoList={getTodoList}/>
            <StyledTodoList>
                {isLoading ? <Typography variant={"h6"}>로딩중..</Typography> : (
                    data && data.length > 0 ?
                        data.map((todo)=>{
                            return <TodoContent key={todo.id} data={todo} getTodoList={getTodoList} />
                        }) : <Typography variant={"h6"}>할 일이 없습니다.</Typography>
                    )}
            </StyledTodoList>
        </StyledBox>

팀 : 컴포넌트 분리

저같은 경우에는 팀원 중 기본적으로 구현이 되어야하는 컴포넌트 (예 : input, button 등) 을 먼저 구현 후 각 큰 컴포넌트 별로 다시 스타일링 및 구현을 진행한 후 페이지를 완성한 팀원분의 코드를 보고 구조적으로 깔끔하다 생각되어 해당 팀원분을 선정하였습니다.

스타일링 또한 기본적으로 코딩 컨벤션을 지켜가면서 기능 / 스타일링을 철저히 분리해 가독성을 살리셨고, 다른 팀원분들 또한 마찬가지로 의견이 거의 동일하게 해당 팀원분을 선정하여 진행됐습니다.

여기서 제가 진행했던 부분은 TodoInput 을 구현하는 것이었는데, 구현하는김에 제가 개인적으로 구현했던 useFormControl 훅을 재사용해 useInput 이라는 커스텀 훅을 구현했고,

해당 훅을 구현시에 고민했던 부분 (불필요한 리턴값에 대한 핸들링) 을 반영하여 배열 타입으로 반환하지 않고 객체 타입으로 반환하도록 구현했습니다.

여기에 BestPractice 로 선정된 팀원분의 의견 (현업에선 UI/UX디자이너 분들께서 MUI , Chakra같은 스타일링 라이브러리를 사용하는것을 안좋아한다)을 참고하여 직접 common 컴포넌트를 구성해 구현하도록 했기때문에, Input 컴포넌트를 직접 구현하게 되었습니다.

interface IUseInputReturn<T> {
  onChange: React.ChangeEventHandler<HTMLInputElement>;
  value: T;
  setValue: React.Dispatch<React.SetStateAction<T>>;
  isValidated: boolean;
  setIsValidated: React.Dispatch<React.SetStateAction<boolean>>;
  setFocus: () => void;
  setBlur: () => void;
}

const useInput = <T>(options: {
  regex?: RegExp;
  ref?: RefObject<HTMLInputElement>;
  initialValue?: T;
}): IUseInputReturn<T> => {
  const { regex } = options || {};
  const [value, setValue] = useState<T>((options.initialValue as T) ?? ('' as unknown as T));
  const [isValidated, setIsValidated] = useState<boolean>(false);

  const validateValue = (value: T) => {
    if (typeof value === 'string' && regex) {
      const isValid = regex.test(value);
      setIsValidated(isValid);
    } else {
      setIsValidated(false);
    }
  };

  const setFocus = () => {
    if (options.ref) {
      options.ref.current?.focus();
    }
  };

  const setBlur = () => {
    if (options.ref) {
      options.ref.current?.blur();
    }
  };

  const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
    const newValue = e.target.value as unknown as T;
    if (newValue !== value) {
      setValue(newValue);
      validateValue(newValue);
    }
  };
  // 기본적으로 훅 사용할때 <type> 형식으로 타입 지정 해주시거나 (객체도 가능) 초기화값 지정해주시면 타입 자동으로 들어갈겁니다.
  /*  폼 제출시 유효성 검사 초기화, 값 초기화를 위해 setter 까지 반환하도록 했습니다.
            전체적으로 빈 값이 들어가는 경우는 없기때문에 정규표현식으로 관리하도록 구현했습니다.*/
  /*  const { data: data1, isLoading: isLoading1 } = useCustomHook(params1);
      const { data: data2, isLoading: isLoading2 } = useCustomHook(params2);*/
  /*  여러번 선언해야할 경우 위와 같이 사용하면 됩니다. (여러번 사용하지 않더라도 변수명 헷갈리지 않게 하기 위해 이렇게 사용하시는걸 추천드립니다.) */
  return { onChange, value, setValue, isValidated, setIsValidated, setFocus, setBlur };
};

export default useInput;

개인과제로 진행했던 hook은 아무 생각 없이 단순 string 만을 핸들링하도록 구현했고, 이를 반성하며 리팩토링을 통해 제네릭 타입으로 checkbox 등에도 사용할 수 있도록 변경하였습니다.
여기에 refObject 를 전달받아서 focus나 blur 같은 이벤트도 구현할 수 있도록 전체적으로 react-hook-form 의 기능을 모방하여 사용할 수 있도록 구현하였습니다.

export interface IInputProps extends Omit<HTMLProps<HTMLInputElement>, 'ref'> {
  helperText?: string;
  error?: boolean;
  errorText?: string;
  dataTestId?: string;
  width?: string;
  height?: string;
}

//TODO: 부모 컴포넌트에서 ref 받아오도록 구현해야함
const Input = forwardRef((props: IInputProps, ref: ForwardedRef<HTMLInputElement>) => {
  const { helperText, error, errorText, dataTestId, width, height, ...inputProps } = props;

  return (
    <S.InputWrap>
      <S.Input {...inputProps} ref={ref} data-testid={props.dataTestId} width={width} height={height} />
      <S.HelperText error={error} color={error ? 'red' : 'grey'}>
        {error ? errorText : helperText}
      </S.HelperText>
    </S.InputWrap>
  );
});
export default Input;

input 의 경우에는 기본적으로 input 태그가 갖고있는 attribute를 다 받아올 수 있도록 인터페이스 선언 시에 HTMLProps를 상속받아 사용하도록 구현했고, ref 같은 경우에는 따로 전달하는 값이 있기때문에 Omit 을 사용하여 해당 인터페이스중 ref 항목을 제외하고 상속받도록 구현했습니다.

팀 진행으로 인해 얻어간 것들 (컴포넌트 분리)

  • Omit과 HTMLProps 라는 새로운 것을 알게되었습니다.
    • common component 를 구현할 때 쓸데없이 interface 에 property를 와바박 쓸 일이 없어졌습니다..
  • 아무래도 컴포넌트 구현을 저 혼자 하는게 아니었기 때문에 협업을 통해 리팩토링에 신경쓰게 되었고, useInput 을 구현할 때 제네릭 타입을 처음 사용하는 등의 경험이 소중하게 느껴졌습니다.
    • 마찬가지로 주석처리에 신경쓰게 되었고, 커뮤니케이션의 대부분이 컴포넌트 구현 파트에서 이뤄졌기 때문에 실무를 정말 간접적으로 경험할 수 있었습니다.
  • 오히려 좋았던점은 커스텀훅을 사용하는 법이 익숙치 않았던 팀원분께서 계셨고, 이로 인해서 주석처리나 가독성 좋은 코드를 작성하려 많이 노력했던 부분이 오히려 실력상승으로 이어지게 되었습니다.

개인 : 테스팅

이번 과제에는 존재하지 않았지만, 개인적으로 Jest를 이용한 테스트코드 작성에 대한 흐름이 매번 궁금했었고, 전체적으로 프로젝트 규모가 크지 않으니 이번 기회에 배워서 작성해볼 수 있겠다 ! 라는 생각을 갖고 테스트코드 작성을 시도해보았습니다.

테스트는 기본적으로 각 Page별로 진행했고, 상황별로 mocking 을 통해 api를 호출했을때의 상황, routing 유무 등을 테스트하였습니다.

    it("이메일과 비밀번호가 일치할 때 contex에 토큰 저장 후 TODO 로 이동", async () => {
        const postSignIn = jest.spyOn(apiMethods, "postSignin").mockResolvedValue({
            access_token : 'token'
        });
        const mockedSetToken = jest.spyOn(context, "setToken");
        const token = 'token';
        customRender(<SignIn/>);
        const signInButton = screen.getByTestId("signin-button");
        const emailInput = screen.getByTestId("email-input") as HTMLInputElement;
        const passwordInput = screen.getByTestId("password-input") as HTMLInputElement;
        fireEvent.change(emailInput, {target: {value: "mytestemail@email.email"}});
        fireEvent.change(passwordInput, {target: {value: "password"}});
        fireEvent.click(signInButton);
        await waitFor(() => {
            expect(postSignIn).toBeCalledTimes(1);
        });
        await waitFor(()=>{
            expect(postSignIn).toBeCalledWith({
                email: emailInput.value,
                password: passwordInput.value
            });
        })
        expect(mockedSetToken).toBeCalledTimes(1);
        expect(mockedSetToken).toBeCalledWith(mockedDispatch, token);
        expect(mockedUsedNavigate).toBeCalledWith('/todo');
    });
Test Suites: 3 passed, 3 total
Tests:       28 passed, 28 total
Snapshots:   3 passed, 3 total
Time:        4.639 s
Ran all test suites related to changed files.

이런식으로 총 28개의 단위테스트를 3개의 페이지별로 진행했고, 모두 통과하였습니다.
다행히 스프링부트로 개발을 하면서 단위테스트를 진행했던 경험이 있어서 테스팅 라이브러리를 익히는데에 큰 문제는 없었으나

어려웠던건 스프링부트때와 똑같이 mocking 에 대한 부분이었습니다.
스프링부트는 내가 직접 생성하고 작성한 클래스나 api들을 mocking 해서 단위 테스트를 진행했으나, jest를 이용할때에는 내가 직접 작성한 비즈니스 로직 외에도 npm 으로 설치한 라이브러리까지 mocking 하여 상황별로 given 을 지정해야했기 때문에 .. 적응하는데 시간이 꽤 걸렸습니다.

아무래도 해당 부분은 리액트의 동작원리를 파악한다면 더 쉽게 이해할 수 있지 않았을까 하는 생각이 들었습니다.

좋았던점과 아쉬웠던점

좋았던점

  • 프론트엔드로 팀 개발에 참여한게 처음이었지만 전체적으로 능동적으로 행동하시는 팀원분들과 함께해서 좋은 첫 스타트를 함께 할 수 있었습니다.
  • 개발 공부를 시작한 이후로, 어디를 가던 팀장 역할을 맡아가면서 매번 저의 자질에 대한 고민을 항상 해왔는데, 이번을 계기로 확실히 나는 묻어가는거도 잘하지만 이끄는거도 나쁘지 않게 하고 있구나 라는 생각을 하게 되었습니다.
    • 팀원의 장단점을 잘 파악해 역할 분담을 적절하게 잘한것같습니다.
    • 팀장이라고 무조건 저의 방식대로 강압적으로 리드하지 않고 중립을 지켜가면서 전체적으로 어느 한 분위기에 휩쓸려서 진행되지 않도록 잘 진행한것 같습니다.
    • 전체적으로 실력향상을 할 수 있게끔 핸들링을 한 것 같습니다.
      • 다른 팀원분들께서 어려움을 겪는 부분이 있으실 때 최대한 할 수 있는 부분까지 진행하도록 격려하고 나머지 부분은 제가 도와드리는 방식으로 진행했기 때문에, (라이브코딩) 여러모로 상대방이나 제 자신한테 도움되는 부분이 많았다고 생각했습니다.

아쉬웠던점

  • 최악의 상황에 대한 대비책을 준비해두지 못했던게 아쉬웠습니다.
    • 이번 프로젝트의 경우에는 사상 최초로 잠수를 타는 팀원이 존재했고, 그 팀원을 믿고 해당 파트에 대한 대비책을 준비해두지 않았던 부분이 굉장히 아쉬웠습니다.
    • 그 팀원으로 인해 전체적인 스타일링이 기본 컴포넌트를 구현해 스타일링까지 직접 진행하는걸로 결정되었었지만, 해당 팀원이 제출 당일 저녁시간부터 연락이 되지 않아 급하게 마무리하여 제출하게 되었습니다.
    • 해당 팀원을 탓할수도 있지만, 팀장으로써 미리 대비책을 준비해두지 않았던게 굉장히 아쉬웠습니다. 프로페셔널한 개발자라면 미리 이러한 상황에 대비하여 진행해야 했다는 생각이 들었습니다.
    • 하지만 이로 인해서 다음 과제부터는 불필요한 시간 투자를 줄이고 최대한 해당 과제의 규모에 맞춰서 라이브러리를 선정하여 진행해야겠다는 생각을 하게 되었습니다.

마치며

처음으로 합류해봤던 프론트엔드 교육이라 떨리는 마음으로 임하고 있지만, 1주차 과제를 어찌저찌 끝내고 드는 생각은 아무래도 '시간이 지나고 보면 다 별거 아닌 일들이다' 인 것 같습니다.

2주차 과제는 1주차 과제에서 미흡했던 점, 아쉬웠던 점을 보완하여 욕심 부리지 않고 깔끔하게 마무리 하는 방향으로 가려고 합니다.

오랜만에 작성하는 회고록인만큼 열심히 깔끔하게 작성하려 노력했는데 잘 모르겠네요.. 읽어주셔서 감사합니다!

앞으로도 화이팅~!

'React' 카테고리의 다른 글

Virtual DOM 가장 쉽게 이해하기  (0) 2023.08.24
재사용 가능한 컴포넌트 만들기  (0) 2023.03.11
  Comments,     Trackbacks
프로그래머스 - 신고 결과 받기 (lv1, 카카오, 자바)

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

 

프로그래머스

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

programmers.co.kr

 

문제 설명

신입사원 무지는 게시판 불량 이용자를 신고하고 처리 결과를 메일로 발송하는 시스템을 개발하려 합니다. 무지가 개발하려는 시스템은 다음과 같습니다.

  • 각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.
    • 신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.
    • 한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 1회로 처리됩니다.
  • k번 이상 신고된 유저는 게시판 이용이 정지되며, 해당 유저를 신고한 모든 유저에게 정지 사실을 메일로 발송합니다.
    • 유저가 신고한 모든 내용을 취합하여 마지막에 한꺼번에 게시판 이용 정지를 시키면서 정지 메일을 발송합니다.

다음은 전체 유저 목록이 ["muzi", "frodo", "apeach", "neo"]이고, k = 2(즉, 2번 이상 신고당하면 이용 정지)인 경우의 예시입니다.

유저 ID 유저가 신고한 ID 설명

"muzi" "frodo" "muzi"가 "frodo"를 신고했습니다.
"apeach" "frodo" "apeach"가 "frodo"를 신고했습니다.
"frodo" "neo" "frodo"가 "neo"를 신고했습니다.
"muzi" "neo" "muzi"가 "neo"를 신고했습니다.
"apeach" "muzi" "apeach"가 "muzi"를 신고했습니다.

각 유저별로 신고당한 횟수는 다음과 같습니다.

유저 ID 신고당한 횟수

"muzi" 1
"frodo" 2
"apeach" 0
"neo" 2

위 예시에서는 2번 이상 신고당한 "frodo"와 "neo"의 게시판 이용이 정지됩니다. 이때, 각 유저별로 신고한 아이디와 정지된 아이디를 정리하면 다음과 같습니다.

유저 ID 유저가 신고한 ID 정지된 ID

"muzi" ["frodo", "neo"] ["frodo", "neo"]
"frodo" ["neo"] ["neo"]
"apeach" ["muzi", "frodo"] ["frodo"]
"neo" 없음 없음

따라서 "muzi"는 처리 결과 메일을 2회, "frodo"와 "apeach"는 각각 처리 결과 메일을 1회 받게 됩니다.

이용자의 ID가 담긴 문자열 배열 id_list, 각 이용자가 신고한 이용자의 ID 정보가 담긴 문자열 배열 report, 정지 기준이 되는 신고 횟수 k가 매개변수로 주어질 때, 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return 하도록 solution 함수를 완성해주세요.


제한사항

  • 2 ≤ id_list의 길이 ≤ 1,000
    • 1 ≤ id_list의 원소 길이 ≤ 10
    • id_list의 원소는 이용자의 id를 나타내는 문자열이며 알파벳 소문자로만 이루어져 있습니다.
    • id_list에는 같은 아이디가 중복해서 들어있지 않습니다.
  • 1 ≤ report의 길이 ≤ 200,000
    • 3 ≤ report의 원소 길이 ≤ 21
    • report의 원소는 "이용자id 신고한id"형태의 문자열입니다.
    • 예를 들어 "muzi frodo"의 경우 "muzi"가 "frodo"를 신고했다는 의미입니다.
    • id는 알파벳 소문자로만 이루어져 있습니다.
    • 이용자id와 신고한id는 공백(스페이스)하나로 구분되어 있습니다.
    • 자기 자신을 신고하는 경우는 없습니다.
  • 1 ≤ k ≤ 200, k는 자연수입니다.
  • return 하는 배열은 id_list에 담긴 id 순서대로 각 유저가 받은 결과 메일 수를 담으면 됩니다.

입출력 예

id_list report k result

["muzi", "frodo", "apeach", "neo"] ["muzi frodo","apeach frodo","frodo neo","muzi neo","apeach muzi"] 2 [2,1,1,0]
["con", "ryan"] ["ryan con", "ryan con", "ryan con", "ryan con"] 3 [0,0]

 

 

풀이

 

해당 문제는 적절한 자료구조와 문자열 관련 메소드의 활용으로 풀이하면 될 것 같았다.

 

1차적으로는 해당 회원의 인덱스를 갖고있는 배열이 필요해보였고, 신고 상황을 저장할 배열, 신고 누적 횟수를 저장할 배열, 그리고 이메일 송신 횟수를 갖는 배열 이렇게 4개의 배열을 선언할까 생각했었고,

 

2차적으로는 인덱스를 해시맵으로 구현, 신고상황은 한 회원이 여러번 같은 회원을 신고할 , 다른 회원을 신고한 기록도 저장해야 하니 해시맵이 아닌 리포트배열은 2차원으로 , 그리고 신고 누적 횟수는 해시맵, 그리고 이메일 배열은 마찬가지로 2차원 배열로 구현하였다 (?)

 

제출 이후인 3차적으로 생각하면 인덱스와 누적 신고 횟수를 합쳐서 정수 리스트를 같는 해시맵과 신고누적 횟수 해시맵 이렇게 2개만 선언해도 됐을 것 같다.

 

class Solution {
 public List<Integer> solution(String[] id_list, String[] report, int k) {
    List<Integer> answer = new ArrayList<>();
        HashMap<String,Integer> idxMap = new HashMap<>();
        HashMap<String,Integer> reportedMap = new HashMap<>();
        int[][] reportArr = new int[id_list.length][id_list.length];
        int[][] emailArr = new int[id_list.length][1];
        int idx = 0;
        for(String id : id_list){
            reportedMap.put(id,0);
            idxMap.put(id,idx);
            for(int j=0; j<id_list.length;j++){
                reportArr[idx][j]=0;
            }
            idx++;
        }
        for(String detail:report){
            int spaceIdx = detail.indexOf(" ");
            String reportFrom = detail.substring(0,spaceIdx);
            String reportTo = detail.substring(spaceIdx+1,detail.length());
            int fromIdx = idxMap.get(reportFrom);
            int toIdx = idxMap.get(reportTo);
            if(reportArr[toIdx][fromIdx]!=1){
                reportArr[toIdx][fromIdx]=1;
                reportedMap.put(reportTo,reportedMap.get(reportTo)+1);
            }
        }
        reportedMap.forEach((key,value)->{
            if(value>=k){
                int toIdx = idxMap.get(key);
                for(int i=0; i<id_list.length;i++){
                    if(reportArr[toIdx][i]==1){
                        emailArr[i][0]++;
                    }
                }
            }
        });
        for(int i=0; i<id_list.length;i++){
            answer.add(emailArr[i][0]);
        }
        return answer;
    }
}

 

  Comments,     Trackbacks
프로그래머스 - 바탕화면 정리 (lv1,자바)

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

 

프로그래머스

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

programmers.co.kr

 

 

문제 설명

코딩테스트를 준비하는 머쓱이는 프로그래머스에서 문제를 풀고 나중에 다시 코드를 보면서 공부하려고 작성한 코드를 컴퓨터 바탕화면에 아무 위치에나 저장해 둡니다. 저장한 코드가 많아지면서 머쓱이는 본인의 컴퓨터 바탕화면이 너무 지저분하다고 생각했습니다. 프로그래머스에서 작성했던 코드는 그 문제에 가서 다시 볼 수 있기 때문에 저장해 둔 파일들을 전부 삭제하기로 했습니다.

컴퓨터 바탕화면은 각 칸이 정사각형인 격자판입니다. 이때 컴퓨터 바탕화면의 상태를 나타낸 문자열 배열 wallpaper가 주어집니다. 파일들은 바탕화면의 격자칸에 위치하고 바탕화면의 격자점들은 바탕화면의 가장 왼쪽 위를 (0, 0)으로 시작해 (세로 좌표, 가로 좌표)로 표현합니다. 빈칸은 ".", 파일이 있는 칸은 "#"의 값을 가집니다. 드래그를 하면 파일들을 선택할 수 있고, 선택된 파일들을 삭제할 수 있습니다. 머쓱이는 최소한의 이동거리를 갖는 한 번의 드래그로 모든 파일을 선택해서 한 번에 지우려고 하며 드래그로 파일들을 선택하는 방법은 다음과 같습니다.

  • 드래그는 바탕화면의 격자점 S(lux, luy)를 마우스 왼쪽 버튼으로 클릭한 상태로 격자점 E(rdx, rdy)로 이동한 뒤 마우스 왼쪽 버튼을 떼는 행동입니다. 이때, "점 S에서 점 E로 드래그한다"고 표현하고 점 S와 점 E를 각각 드래그의 시작점, 끝점이라고 표현합니다.
  • 점 S(lux, luy)에서 점 E(rdx, rdy)로 드래그를 할 때, "드래그 한 거리"는 |rdx - lux| + |rdy - luy|로 정의합니다.
  • 점 S에서 점 E로 드래그를 하면 바탕화면에서 두 격자점을 각각 왼쪽 위, 오른쪽 아래로 하는 직사각형 내부에 있는 모든 파일이 선택됩니다.

예를 들어

wallpaper

= [".#...", "..#..", "...#."]인 바탕화면을 그림으로 나타내면 다음과 같습니다.

이러한 바탕화면에서 다음 그림과 같이 S(0, 1)에서 E(3, 4)로 드래그하면 세 개의 파일이 모두 선택되므로 드래그 한 거리 (3 - 0) + (4 - 1) = 6을 최솟값으로 모든 파일을 선택 가능합니다.

(0, 0)에서 (3, 5)로 드래그해도 모든 파일을 선택할 수 있지만 이때 드래그 한 거리는 (3 - 0) + (5 - 0) = 8이고 이전의 방법보다 거리가 늘어납니다.

머쓱이의 컴퓨터 바탕화면의 상태를 나타내는 문자열 배열 wallpaper가 매개변수로 주어질 때 바탕화면의 파일들을 한 번에 삭제하기 위해 최소한의 이동거리를 갖는 드래그의 시작점과 끝점을 담은 정수 배열을 return하는 solution 함수를 작성해 주세요. 드래그의 시작점이 (lux, luy), 끝점이 (rdx, rdy)라면 정수 배열 [lux, luy, rdx, rdy]를 return하면 됩니다.


제한사항

  • 1 ≤ wallpaper의 길이 ≤ 50
  • 1 ≤ wallpaper[i]의 길이 ≤ 50
    • wallpaper의 모든 원소의 길이는 동일합니다.
  • wallpaper[i][j]는 바탕화면에서 i + 1행 j + 1열에 해당하는 칸의 상태를 나타냅니다.
  • wallpaper[i][j]는 "#" 또는 "."의 값만 가집니다.
  • 바탕화면에는 적어도 하나의 파일이 있습니다.
  • 드래그 시작점 (lux, luy)와 끝점 (rdx, rdy)는 lux < rdx, luy < rdy를 만족해야 합니다.

입출력 예

wallpaper result

[".#...", "..#..", "...#."] [0, 1, 3, 4]
["..........", ".....#....", "......##..", "...##.....", "....#....."] [1, 3, 5, 8]
[".##...##.", "#..#.#..#", "#...#...#", ".#.....#.", "..#...#..", "...#.#...", "....#...."] [0, 0, 7, 9]
["..", "#."] [1, 0, 2, 1]

 

 

풀이

해당 문제는 1차적인 생각으로는 2차원 배열로 풀이하면 됐었고, 2차적인 생각으로는 가장 큰 x,y 좌표값을 저장하는 변수와 가장 작은 x,y 좌표값을 저장하는 변수를 선언 해 마지막으로 가장 큰 x,y 좌표값에 +1를 하면 되겠다는 생각을 하게 되었다.

 

정답률이 35퍼센트라 겁먹고 들어갔으나 생각보다 너무 빨리 오류도 없이 쉽게 풀어서 당황..

 

3차적인 생각으로는 반례를 살펴보게 되었는데 파일이 하나일경우가 반례가 될까? 했지만 출력이 정답과 다르지 않아서 그냥 제출했더니 맞았다.

 

class Solution {
    public int[] solution(String[] wallpaper) {
        int leastX = wallpaper[0].length();
        int leastY = wallpaper.length;
        int maxX = 0;
        int maxY = 0;
        for(int i=0; i<wallpaper.length;i++){
            for(int j=0; j<wallpaper[0].length();j++){
                int nowX = j;
                int nowY = i;
                if(wallpaper[i].charAt(j)=='#'){
                    if(leastX>nowX){
                        leastX=nowX;
                    }
                    if(leastY>nowY){
                        leastY=nowY;
                    }
                    if(maxX<nowX){
                        maxX=nowX;
                    }
                    if(maxY<nowY){
                        maxY=nowY;
                    }
                }
            }
        }

        return new int[]{leastY, leastX, maxY + 1, maxX + 1};
    }
}

읽기 쉬운 코드를 작성하려고 하니 딱히 코드가 엄청 짧지 않아도 이해하기 쉽게 구현이 가능한거 같다. 실력도 늘은거 같고 뿌듯 ><

  Comments,     Trackbacks
프로그래머스 - 개인정보 수집 유효기간 (카카오, lv1,자바)

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

 

프로그래머스

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

programmers.co.kr

 

 

문제 설명

고객의 약관 동의를 얻어서 수집된 1~n번으로 분류되는 개인정보 n개가 있습니다. 약관 종류는 여러 가지 있으며 각 약관마다 개인정보 보관 유효기간이 정해져 있습니다. 당신은 각 개인정보가 어떤 약관으로 수집됐는지 알고 있습니다. 수집된 개인정보는 유효기간 전까지만 보관 가능하며, 유효기간이 지났다면 반드시 파기해야 합니다.

예를 들어, A라는 약관의 유효기간이 12 달이고, 2021년 1월 5일에 수집된 개인정보가 A약관으로 수집되었다면 해당 개인정보는 2022년 1월 4일까지 보관 가능하며 2022년 1월 5일부터 파기해야 할 개인정보입니다.당신은 오늘 날짜로 파기해야 할 개인정보 번호들을 구하려 합니다.

모든 달은 28일까지 있다고 가정합니다.

다음은 오늘 날짜가 2022.05.19일 때의 예시입니다.

약관 종류 유효기간

A 6 달
B 12 달
C 3 달

번호 개인정보 수집 일자 약관 종류

1 2021.05.02 A
2 2021.07.01 B
3 2022.02.19 C
4 2022.02.20 C
  • 첫 번째 개인정보는 A약관에 의해 2021년 11월 1일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.
  • 두 번째 개인정보는 B약관에 의해 2022년 6월 28일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.
  • 세 번째 개인정보는 C약관에 의해 2022년 5월 18일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.
  • 네 번째 개인정보는 C약관에 의해 2022년 5월 19일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.

따라서 파기해야 할 개인정보 번호는 [1, 3]입니다.

오늘 날짜를 의미하는 문자열 today, 약관의 유효기간을 담은 1차원 문자열 배열 terms와 수집된 개인정보의 정보를 담은 1차원 문자열 배열 privacies가 매개변수로 주어집니다. 이때 파기해야 할 개인정보의 번호를 오름차순으로 1차원 정수 배열에 담아 return 하도록 solution 함수를 완성해 주세요.

제한사항

  • today는 "YYYY.MM.DD" 형태로 오늘 날짜를 나타냅니다.
  • 1 ≤ terms의 길이 ≤ 20
    • terms의 원소는 "약관 종류 유효기간" 형태의 약관 종류와 유효기간을 공백 하나로 구분한 문자열입니다.
    • 약관 종류는 A~Z중 알파벳 대문자 하나이며, terms 배열에서 약관 종류는 중복되지 않습니다.
    • 유효기간은 개인정보를 보관할 수 있는 달 수를 나타내는 정수이며, 1 이상 100 이하입니다.
  • 1 ≤ privacies의 길이 ≤ 100
    • privacies[i]는 i+1번 개인정보의 수집 일자와 약관 종류를 나타냅니다.
    • privacies의 원소는 "날짜 약관 종류" 형태의 날짜와 약관 종류를 공백 하나로 구분한 문자열입니다.
    • 날짜는 "YYYY.MM.DD" 형태의 개인정보가 수집된 날짜를 나타내며, today 이전의 날짜만 주어집니다.
    • privacies의 약관 종류는 항상 terms에 나타난 약관 종류만 주어집니다.
  • today와 privacies에 등장하는 날짜의 YYYY는 연도, MM은 월, DD는 일을 나타내며 점(.) 하나로 구분되어 있습니다.
    • 2000 ≤ YYYY ≤ 2022
    • 1 ≤ MM ≤ 12
    • MM이 한 자릿수인 경우 앞에 0이 붙습니다.
    • 1 ≤ DD ≤ 28
    • DD가 한 자릿수인 경우 앞에 0이 붙습니다.
  • 파기해야 할 개인정보가 하나 이상 존재하는 입력만 주어집니다.

입출력 예

today terms privacies result

"2022.05.19" ["A 6", "B 12", "C 3"] ["2021.05.02 A", "2021.07.01 B", "2022.02.19 C", "2022.02.20 C"] [1, 3]
"2020.01.01" ["Z 3", "D 5"] ["2019.01.01 D", "2019.11.15 Z", "2019.08.02 D", "2019.07.01 D", "2018.12.28 Z"] [1, 4, 5]

 

 

 

풀이

 

해당 문제 같은 경우는 설명만 길고 단순히 생각의 흐름대로 풀이해보면 되는 문제같다.

전체적으로 문자열 관련 메소드를 잘 활용하면 되는 문제였다.

가장 좋았던건 한 달이 28일이라고 지정해둔 덕에 어렵지 않게 풀 수 있었다.

 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

class Solution {
    public List<Integer> solution(String today, String[] terms, String[] privacies) {
        int dayForM = 28;
        int todayY = Integer.parseInt(today.substring(0,4));
        int todayM = Integer.parseInt(today.substring(5,7));
        int todayD = Integer.parseInt(today.substring(8,10));

        int totalD = (todayY*dayForM*12) + (todayM*28) + todayD;
        List<Integer> answerArr = new ArrayList<>();
        HashMap<Character,Integer> termsMap = new HashMap<>();
        for(String term : terms){
            char termO = term.charAt(0);
            int termM = Integer.parseInt(term.substring(2));
            termsMap.put(termO,termM);
        }
        for(int i=0; i<privacies.length; i++){
            char privacyO = privacies[i].charAt(11);
            int privacyY = Integer.parseInt(privacies[i].substring(0,4));
            int privacyM = Integer.parseInt(privacies[i].substring(5,7));
            int privacyD = Integer.parseInt(privacies[i].substring(8,10));
            int totalPrivacyD = (privacyY*12*dayForM) + (privacyM*dayForM) + privacyD;
            int termM = termsMap.get(privacyO);
            int termDay = termM * dayForM;
            if(totalPrivacyD+termDay <=totalD){
                answerArr.add(i+1);
            }
        }

        return answerArr;
    }
}

 

해당 문제풀이들을 살펴보니 객체지향으로 풀이하신분도 계셨다.

 

  Comments,     Trackbacks
운영체제 이론 03-28 (정리) 프로세스와 커널

인터럽트

  • 입출력 장치들이 비동기적 사건을 CPU에게 알리는 행위
    • 비동기 → 동기와 다르게 요청을 보냈을 때 응답이 도착하지 않아도 요청을 보낼 수 있다.
    • 마우스 조작 , 키보드 입력
    • 네트워크로부터 데이터 도착 등등

인터럽트 발생 전 프로그램 실행 중 → 입력장치로부터 인터럽트 발생 → 인터럽트 메시지 CPU로 전송 → CPU코어로부터 전송 및 인터럽트 벡트 번호 반환 → 인터럽트 벡터 테이블에서 인터럽트의 서비스 루틴의 주소를 알아낸다. → 인터럽트 서비스 루틴 실행 → CPU는 인터럽트 발생 전에 하던 작업을 지속한다.

 

 

  • 인터럽트 서비스 루틴
    • ISR → 인터럽트 핸들러
    • 위치 : 디바이스 드라이버나 커널 코드, 임베디드 컴퓨터 ROM

인터럽트는 다중 프로그래밍 실현의 키이다.

  • 다중 프로그래밍 리뷰
    • 여러 프로세스를 동시에 실행한다. (멀티 프로그래밍)
    • 프로그래밍과 프로세서는 프로그램 하나가 프로세서 하나를 같이 운영 할 수 있다.
    • 한 프로세스가 입출력을 시행하면 다른 프로세스로 교체 실행된다.
    • 입출력이 완료될 때, 장치로부터 입출력 완료 통보를 받는 방법이 필요하고 그것이 바로 인터럽트이다.
      • 인터럽트가 없다면 ? → CPU는 폴링을 실행해야 하므로 비효율정이다.
        • 폴링 ⇒ 하나의 장치(또는 프로그램)가 충돌 회피 또는 동기화 처리 등을 목적으로 다른 장치(또는 프로그램)의 상태를 주기적으로 검사하여 일정한 조건을 만족할 때 송수신 등의 자료처리를 하는 방식
      • 다중 프로그래밍 운영체제의 구현은 사실상 거의 불가능하다.
  • 인터럽트의 효과
    • 컴퓨터 시스템이 효율적으로 작동한다.
      • CPU 활용률이 높아진다. (시스템 처리율 향상)
    • 입출력 장치와 CPU가 동시에 각자의 작업을 실행한다.
      • 입출력 장치 → 지시받은 입출력 진행 / CPU → 다른 프로그램 실행
더보기

인터럽트는 프로세스가 입출력 등의 외부 사건에 의해 중단 후 처리 후에 다시 프로세스를 계속 실행시키는 것을 의미한다.

인터럽트 발생 시 ISR (Interrupt Service Routine) 혹은 Interrupt Handler 가 실행된다.

인터럽트가 존재하지 않는다면 다중 프로그래밍 운영체제의 구현은 사실상 불가능하다.

프로세스

  • 프로그램
    • 하드디스크 등의 저장 매체에 저장된다. (실행 파일의 형태)
  • 프로세스
    • 프로그램이 메모리에 적재되어 실행 되는 상태
      • 필요한 모든 자원을 할당받는다.
      • 코드공간, 데이터공간, 스택공간, 힙공간
    • 프로세스 특징
      • 운영체제는 프로그램을 메모리에 적재하고 프로세스로 다룬다.
      • 메모리를 할당 후 코드와 데이터 등을 적재한다.
      • 프로세스들은 서로 독립적인 메모리 공간을 가진다.
      • 운영체제는 프로세스마다 고유한 번호를 할당한다. ⇒ PID
      • 운영체제는 각 프로세스의 메모리 위치와 크기 정보를 관리한다.
      • 프로세스의 모든 정보는 커널에 의해 관리된다.
      • 프로세스는 실행 - 대기 - 잠자기 - 대기 - 실행 - 종료 등의 생명 주기를 가진다. (모든 관리는 커널에 의해 수행된다.)

  • 프로세서
    • CPU , 그래픽 프로세스 (실제 칩으로 되어있는 것들)
    • 하드웨어 처리기라는 의미를 갖고있다. (하드웨어 적인 부분)
  • 프로세스
    • 프로그램에 대한 실행 (소프트웨어적인 부분)

 

더보기

프로세스는 프로그램이 메모리에 적재되어 실행되는 상태를 뜻한다. 프로세스의 모든 정보는 커널에 의해 관리되며 프로세스마다 고유한 번호를 가지게 된다.

프로세스 관리

  • 프로세스의 생성에서 종료까지 관리는 모두 커널에 의해 이루어진다.
    • 커널 영역에 프로세스 테이블을 만들고 프로세스 목록을 관리한다.
  • 관리 내용
    • 프로세스 생성, 실행, 일시중단 및 재개, 정보 관리, 프로세스 통신, 프로세스 동기화, 프로세스 중단, 프로세스 컨텍스트 스위칭
  • 한 프로그램을 여러 번 실행시킬 때
    • 프로그램 실행 시 마다 독립된 프로세스 생성 → 프로세스들을 프로그램의 다중 인스턴스라고 부른다.
      • 각 프로세스에 독립된 메모리 공간을 할당
      • 각 프로세스를 별개의 프로세스로 취급한다.

 

더보기

프로그램을 여러 번 실행 시킬땐, 다중 인스턴스화 된다.

각 프로세스 마다 독립된 메모리 공간을 할당받는다.

응용 프로그램의 자원 접근 문제

  • 오늘날 운영체제는 다중 프로그래밍 운영체제이다.
    • 응용 프로그램이 직접 컴퓨터 자원에 접근하면 충돌과 훼손이 발생한다.
      • 다른 응용프로그램이 적재된 메모리를 훼손 할 수 있다.
      • 다른 응용프로그램이 만든 파일 삭제 및 훼손이 가능하다.
      • 응용 프로그램이 커널이 적재된 영역 훼손 가능
    • 해결 방안
      • 자원에 대한 모든 접근은 커널에만 부여한다.
  • 구체적인 해결 방법
    • 사용자 공간 → 응용 프로그램만 있는 공간
    • 커널은 커널 공간에만 적재한다.
    • CPU의 실행모드를 사용자 모드와 커널 모드로 분리한다.
    • 응용 프로그램이 커널 기능을 이용할 때, 시스템 호출을 잉요해서만 커널 코드를 이용하도록 한다.

사용자 공간과 커널 공간

  • 사용자 공간
    • 응용 프로그램들이 적재되는 공간
  • 커널 공간
    • 커널 코드, 커널 데이터 등 커널에 의해 배타적으로 사용되는 공간
    • 디바이스 드라이버를 포함한다.
  • 분리하는 이유
    • 커널 코드와 데이터를 악의적인 응용프로그램이나 코딩 실수로부터 지키기 위함이다.

 

더보기

프로그램이 직접 컴퓨터 자원에 접근하여 발생하는 충돌을 막기 위해 자원에 대한 접근은 커널에만 부여한다.

사용자 공간 크기의 의미

  • 한 응용프로그램의 최대 크기 결정
    • 프로그램 코드 + 데이터 (전역 변수) + 힙(동적 할당) + 스택
    • 운영체제에서 사용자 공간이 2GB → 응용프로그램을 2GB 크기 이상 개발 X
  • 사용자 공간의 주소 범위
    • 운영체제가 설정한 사용자 공간의 주소 범위를 넘어설 수 없다.
      • 바로 종료됨 (심각한 오류 예 : 블루스크린)

주소공간은 가상 공간이다. (사용자나 응용프로그램 관점에서 보는 주소 범위) / 사용자가 전체 메모리를 사용하고 있다고 착각하지만 실제론 아니다.

 

 

  • 사용자 공간의 충돌 해결 → 가상 주소 공간을 물리 메모리에 매핑한다.
    • 매핑 테이블 (주소 공간을 갖고 있는) → 운영체제가 소유하고 관리한다.
    • 물리 메모리를 여러 프로그램의 사용자 공긴이 나누어 사용
      • 실제로는 각 프로그램이 사용자 공간의 일부만 사용한다.
    • 커널 공간 역시 물리 메모리에 매핑 될 수 있다.
  • 물리 메모리가 작은 경우에는 하드 디스크에 저장하여 물리 메모리의 빈 영역을 확보한다. (가상 메모리 기법)

 

더보기

사용자 공간은 운영체제가 정한 범위 이상을 가질 수 없다.

해결 방법은 가상 주소 공간을 물리 메모리에 매핑하는 가상 메모리 기법 등이 있다.

 

 

사용자 모드와 커널 모드

  • CPU는 사용자 모드와 커널 모드 중 한 모드로 실행한다.
    • CPU 내부에 모드 레지스터 존재 → 모드 상태를 나타냄
  • 사용자 모드
    • CPU의 모드 비트 → 1
    • CPU는 커널을 쓸 수 없다. → 응용 프로그램으로부터 커널 영역을 보호한다.
    • 특권 명령 실행을 불허한다.
      • 특권명령 ? 입출력 장치 등 하드웨어나 시스템 중단 등 시스템 관련 처리를 위해 설계된 특별한 명령
  • 커널 모드
    • CPU의 모드 비트 → 0
    • CPU가 커널 공간에서 실행하는 중, 혹은 사용자 코드를 실행하는 중
    • 특권 명령 사용 가능
  • 사용자 모드에서 커널 모드로 전환하는 경우
    • 시스템 호출과 인터럽트
  • 시스템 호출
    • 특별한 기계 명령에 의해 진행된다.
      • 각 장비들마다 기계 명령이 다르다.
    • 기계 명령이 CPU의 모든 비트를 커널 모드로 전환한다.
  • 인터럽트
    • CPU가 인터럽트를 수신하면 커널 모드로 자동으로 전환한다.
    • CPU는 인터럽트 서비스 루틴을 실행한다.
    • 인터럽트 서비스 루틴이 끝나면 사용자 모드로 자동 전환한다.

더보기

사용자 모드에서 커널 모드로 전환하는 경우는 시스템 콜이 일어나거나 인터럽트가 발생했을때이다.

CPU는 인터럽트를 수신하면 커널 모드로 자동으로 전환 후 서비스 루틴이 끝나면 사용자 모드로 자동 전환한다.

특권 명령

  • 특별한 목적으로 설계된 CPU 명령
  • 커널 모드에서만 실행된다.
  • 종류
    • I/O 명령
    • Halt 명령
      • CPU 작동 중지 (유휴 상태로 만듬)
    • 인터럽트 플래그를 켜고 끄는 명령
      • CPU가 인터럽트를 허용하거나 무시하도록 지시한다.
    • 타이머 설정 명령
    • 컨텍스트 스위칭 명령
    • 메모리 지우기 명령
    • 장치 상태 테이블 수정 등의 명령
  • 사용자 모드와 커널 모드는 CPU에 의해 구현되는가, 운영체제에 의해 구현되는가
    • 운영체제는 CPU 모드 레지스터를 이용하여 커널 영역을 지킨다.
    • 모드는 CPU에 의해 구현, 운영체제가 활용할 수는 있다.
  • 운영체제가 사용자 모드와 커널 모드로 나누는 이유는?
    • 커널 공간에 대한 보안과 보호
    • 사용자 응용프로그램은 사용자 모드에서 심각한 오류가 발생해도 시스템을 중단시키지는 못한다.
  • 사용자 응용프로그램이 커널 코드를 호출하는 일이 있는가?
    • 직접 커널 코드 호출 불가
  • CPU가 커널 모드와 사용자 모드 중 어떤 모드로 많이 실행될까?
    • 시스템 전체 통계를 보면 커널 모드에서 많이 실행된다.
    • 장치 액세스의 경우가 많으면 커널 모드 시간 비율이 높아진다.
더보기

특권 명령은 커널 모드에서만 실행되는 CPU 명령이다.

커널

  • 커널은 컴파일된 바이너리 형태, 하드디스크 특정 영역에 저장된다. (부팅 시에 커널 공간의 메모리에 적재된다.)
  • 커널 코드는 함수들의 집합이다.
    • 커널 모드에서 실행되는 함수들과 데이터들의 집합이다.

 

커널 자체가 프로세스가 될 순 없다.

 

  • 커널은 스스로 실행되는 프로세스가 아니다.
    • 함수들의 단순 집합, 시스템 호출을 통해 호출되는 함수들이다.
      • 시스템 호출인터럽트 서비스 루틴에 의해 커널 내 스케줄러 함수가 호출되어 실행된다.
  • 커널은 실행 중이 아니다.
    • 커널은 프로세스도 스레드도 아니다.
      • 커널 코드를 실행하고 있다 / 인터럽트 서비스 루틴이 실행되고 있다.
  • 커널은 스택이나 힙을 갖지 않는다.
    • 함수들의 단순 집합이기 때문 / 스레드가 가진다.
    • 스레드마다 사용자 스택과 커널 스택을 소유한다.
      • 스레드가 생성될 때 프로세스의 사용자 공간에 사용자 스택 할당 / 커널 공간에 커널 스택 할당 (시스템 호출을 통해)
더보기

커널은 컴파일 된 바이너리 형태의 함수들의 집합이다.

커널은 프로세스도 스레드도 아닌 단순 함수의 집합이다.

 

 

라이브러리

  • 프로그램에서 활용하도록 미리 작성된 함수들이 컴파일 되어 바이너리 형태로 제공되는 파일
  • 개발자는 라이브러리 활용이 없이는 응용프로그램 작성이 불가능하다.
  • 활용되는 라이브러리는 2가지 유형을 갖는다.
    • 표준 라이브러리
      • 사용자가 작성하기 힘든 함수를 제공한다.
      • 운영체제나 컴퓨터 하드웨어에 상관없이 이름과 사용법이 동일하다.
    • 시스템 호출 라이브러리
      • 시스템 호출 함수를 포함한다.
      • 시스템 호출을 진행하여 커널 모드로 바꾸고 커널로 진입하여 커널에 만들어진 함수를 실행한다.
      • 시스템콜 함수를 커널 API라고 부른다.
      • 운영체제마다 시스템콜 함수의 이름이 서로 다르다.

링킹

  • 실행 파일이 만들어지는 과정이다.
  • 응용프로그램이 사용자 공간에 적재 → 실행 파일 내 사용자 코드와 라이브러리 코드의 메모리 적재 → 사용자 전역 변수와 라이브러리의 전역 변수 모두 메모리에 적재 → 응용 프로그램은 사용자 모드로 실행 시작

함수 호출과 시스템 호출

  • 함수 호출로 라이브러리를 활용한다.
    • 사용자 공간에 적재된 함수가 사용자 공간에 적재된 다른 함수를 호출한다.
      • 스택에 돌아올 주소, 매개변수를 전달 → 호출된 함수의 지역 변수를 생성한다.
      • 사용자 공간에 적재된 함수의 주소로 점프
      • 함수가 끝나면 함수를 호출한 곳으로 복귀시킨다.
  • 시스템 호출로 커널 코드를 실행한다.
    • 커널에 작성된 함수 실행
      • 시스템 호출 라이브러리에 포함된 시스템 호출 함수가 시스템 호출을 일으킨다.
    • 기계 명령어를 실행한다.
    • 커널 모드로 전환되고 커널 함수마다 고유 번호를 전달한다.
    • 커널의 시스템 호출 핸들러를 실행한다.
    • 커널 함수의 고유 번호 분석, 해당 커널 함수 호출
    • 리턴할 때 사용자 모드로 전환, 사용자 프로그램으로 복귀

시스템 호출

  • 사용자 공간의 코드에서 커널 서비스를 요청하는 과정이다.
    • 커널 콜 / 트랩 이라고도 불린다.
    • 응용프로그램에서 커널 기능을 활용하도록 만들어 놓은 기능이다.
  • 운영체제는 시스템 호출 라이브러리를 제공한다.
    • 시스템 호출 함수 혹은 커널 API를 포함한다.
    • 대략 200개 이상의 시스템 호출 함수가 존재한다.
  • 시스템 호출을 일으키는 기계 명령
    • CPU마다 시스템 호출을 실행하는 특별한 기계 명령을 제공한다.

 

fread()와 read() 의 비교

  • 시스템 호출은 함수 호출에 비해 많은 시간 비용이 든다.
    • 시스템 호출을 많이 할수록 프로그램 실행 속도가 저하된다.
    • fread() ⇒ 1번의 시스템 호출
    • read() ⇒ 10번의 시스템 호출 (느리고 비효율적이다.)

프로세스 구성

  • 코드 영역
    • 프로그램 코드가 적재되는 영역
  • 데이터 영역
    • 프로그램에서 고정적으로 만든 변수 공간
    • 프로세스 적재시 할당, 종료시 소멸
  • 힙 영역
    • 프로세스의 실행 도중 동적으로 사용할 수 있도록 할당된 공간
  • 스택 영역
    • 함수가 실행될 때 사용될 데이터를 위해 할당된 공간
    • 함수 호출 외에 프로세스에서 필요시 사용이 가능하다.

프로세스 주소 공간

  • 실행중에 접근할 수 있도록 허용된 주소의 최대 범위 (범위를 벗어나지 않음)
  • 논리공간이다. (가상공간)
    • 0번지에서 시작하여 연속적인 주소를 가진다. (실제는 0번지가 아니라고 볼 수 있다.)
  • 32비트 CPU의 경우 4GB 까지 크기를 가질 수 있다.
  • 프로세스 크기 : 적재된 코드 + 전역 변수 + 힙 영역에서 할당받은 동적 메모리 공간 + 스택 영역에 현재 저장된 데이터 크기 로 매번 달라질 수 있다.

커널 공간

  • 시스템 호출을 통해 이용하는 커널 공간
  • 커널 코드 , 커널 데이터, 커널 스택(커널 코드가 실행될 때만) 이 존재한다.

결론

  • 프로세스의 코드와 데이터는 실행 파일에 결정된 상태로 코드 영역과 데이터 영역에 적재된다. → 실행 중에 크기가 변하지 않는다. (메모리를 아껴써야 한다.)
  • 프로세스는 사용자 공간의 최대 범위까지 동적할당 받으면서 힙 영역과 스택 영역을 늘려갈 수 있다.

커널 공간의 의미

  • 각 프로세스는 독립된 사용자공간을 소유하고 커널 공간을 공유한다.
    • 프로세스가 사용자 코드에서 시스템 호출을 통해 커널 코드를 실행할 때 커널 공간을 사용한다.
  • 결론
    • 프로세스마다 각각 사용자 주소 공간이 존재한다.
    • 시스템 전체에는 하나의 커널 주소 공간이 있다.
    • 모든 프로세스는 커널 주소 공간을 공유한다.

프로세스의 주소 공간

  • 프로세스의 주소 공간은 가상 공간이다.
    • 프로세스에서의 0번지는 가상 주소 0번지를 의미한다.
    • 가상 주소는 0번지부터 시작된다.
    • 코드 주소 변수 주소 등등은 모두 가상 주소이다.
  • 프로세스의 주소 공간은 각 프로세스마다 주어지고, 가상 주소가 물리 주소로 매핑되기 때문에 물리 메모리에서는 충돌하지 않는다.
  Comments,     Trackbacks
정보통신 개론 03-27 (정리) 컴퓨터, 부울대수 , 데이터표현

통신 시스템의 하드웨어

  • 통신 제어장치
    • 컴퓨터 ← 단말장치 → 모뎀
    • 컴퓨터 중앙처리장치와 데이터 전송회선 사이에서 전기적으로 연결된다.
      • 전송문자의 조립 / 분해
      • 코드 변환
      • 회선의 제어
      • 에러 제어 등
    • 네트워크 제어 장치 (NCU) 라고도 한다. (Network Control Unit)

통신 제어장치

  • 장비 측 (디지털 신호) 와 선로 (아날로그 → 전기신호) 측의 상호 교류를 지원한다.
  • 선로 낭비를 줄이기 위해 다중화, 역 다중화를 사용한다.
  • 다중화 (전송문자)
    • 여러대의 장비 → 한 선로
    • 한 선로 → 여러대의 장비

동기 제어 ** → 송신 측과 수신 측을 일치 시키도록 제어 / 흐름 제어 → 버퍼 초과 방지 ( 버퍼 : 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역 ) / 응답 제어 → 응답용 확인 메시지 전송 (3Way handshake방식)

 

더보기

통신 제어장치는 중앙 처리장치와 모뎀 사이에서 전기적으로 연결된다.

선로 낭비를 줄이기 위해 다중화 / 역다중화를 사용한다.

정보전송 기능 → 동기제어, 흐름제어 (버퍼 초과 방지 : 데이터 용량 초과 방지) , 응답제어 등

통신 제어장치의 부가 장치

  • 전위 처리기 (FEP) → 앞에 있어서 전위
    • 컴퓨터의 바로 앞에 있는 것은 대부분 전위 처리기이다.
    • 통신기능을 보강하는 특수한 일만 하는 주 프로세서에 결합되어있다. (통신용 cpu)
  • 후위 처리기 (BEP) → 뒤에 있어서 후위
    • 컴퓨터의 뒤에 있는것이 대부분 후위 처리기이다.
    • 컴퓨터의 부하를 줄이는 역할을 담당한다.
  • 통신 제어 처리 장치 (CCP)
    • 프로그래밍을 사용하여 통신 제어장치를 개선한다.
    • 필요에 의해서 연결시켜준다.
    • 프로그램 가능한 또는 프로그램 제어 방식의 통신 제어장치
    • CPU의 부담을 감소 시킬 수 있다.
    • CCP 가 없으면 CPU가 모든 일을 담당해야 한다.
    • 확장성이 좋다.
  • 주로 T1 급을 많이 사용한다. (24채널,30 채널까지도 확장이 가능하다.)

인터페이스와 접속 규격

  • 인터페이스 → 마주보다
    • 데이터 단말장치 ↔ 데이터 통신장치 간의 접속 관계를 표시한다. (어떠한 매체 간의 접속 관계를 뜻하기도 하는것같다.)
    • 연결기의 신호선 핀 배치에 따라 종류가 다르다.
    • RS-232
      • 가장 대표적이다.
      • DTE와 DCE 간의 물리적 연결과 신호 수준정의한다.
      • 25핀 , 9핀 연결기를 모두 지원한다.

  • 접속 규격
    • DTE - DCE 인터페이스 규격은 ITU - T (Telecom) 권고에 정의되어 있다. (ITU - R → 무선)
    • V시리즈 : DTE 와 아날로그 통신회선 간의 접속 규정을 정의한다.
    • X시리즈 : DTE 와 디지털 교환망 간의 접속 규정을 정의한다.
    • I시리즈 : DTE와 종합정보통신망(ISDN) 간의 접속 규정을 정의한다.
  • 기계적 → 연결기 를 정의한다. (연결기 크기 , 핀 개수 등)
  • 전기적 → 형식을 알려준다. (신호 전압 , 전압 변동, 잡음 정도 등)
  • 기능적 → 데이터 제어, 타이밍, 접지 등 수행 기능 규정
  • 절차적 → 데이터를 전송하기 위한 사건이 일어나는 순서를 규정한다.

 

정보전송 시스템의 소프트웨어

  • 소프트웨어
    • 하드웨어의 전체 동작을 지시하고 제어하는 모든 프로그램
    • 하드웨어를 지시하고 통제하여 결과를 얻도록 하는 명령의 집합이다.
    • 시스템 소프트웨어응용 소프트웨어로 분류된다.
  • 시스템 소프트웨어
    • 컴파일러 → 기계어로 번역해준다.
    • 인터프리터 → ****코드를 한 줄씩 읽어 내려가며 실행하는 프로그램 (베이직 / 파이썬)
    • 운영체제
    • 언어 번역 프로그램
  • 응용 소프트웨어
    • 고급 프로그래밍 언어를 주로 사용한다.
  • 통신 소프트웨어
    • 컴퓨터 상호 간에 접속하여 정보를 교환할 수 있게 하는 소프트웨어 프로그램의 총칭이다.
    • 월드 와이드 웹 브라우저 소프트웨어, 단말 대행 소프트웨어 등
  • 세부적인 구조를 몰라도 통신 프로그램에 동작 지시가 가능하다.
더보기

통신 제어장치는 통신 기능을 보강하는 전위처리기, 부하를 줄여주는 후위처리기, 장치를 개선해주는 통신 제어 처리장치 정도가 있다.

RS 232→ 대표적인 인터페이스

정보 전송 소프트웨어엔 시스템 소프트웨어, 응용 소프트웨어, 통신 소프트웨어가 있다.

컴퓨터 (정보처리) 시스템

  • 컴퓨터
    • 하드웨어와 소프트웨어로 구분된다.
    • 사용자는 응용 소프트웨어를 사용한다.
  • 컴퓨터의 세대별 구분
    • 1세대 컴퓨터
      • 진공관 , 릴레이
      • 논리 소자 역할을 했다. (0 과 1을 표현하는데에 활용되었다.)
      • 1946 ~ 1959
    • 2세대 컴퓨터
      • 트랜지스터
      • 신호를 증폭시키는 역할을 했다. (0의 신호를 증폭시켜서 1을 만드는 행위 등)
      • 1959 ~ 1964
    • 3세대 컴퓨터
      • 반도체를 만나며 집적회로가 되었다. (작아짐)
      • 1965 ~ 1974
    • 4세대 컴퓨터
      • LSI
      • 1975 ~ 1984
    • 5세대 컴퓨터
      • VLSI (초고밀도 집적회로)
      • 1985 ~ 현재
  • 컴퓨터의 분류
    • 디지털 컴퓨터
      • 모든 데이터를 값으로 수치화하여 사용하는 계수형 자료를 취급하는 컴퓨터를 의미한다.
      • 일반적인 컴퓨터 → 디지털 컴퓨터
    • 아날로그 컴퓨터
      • 물리량을 입력으로 받아들여서 처리
    • 하이브리드 컴퓨터
      • 디지털 컴퓨터 + 아날로그 컴퓨터
      • 모든 자료에 대해서 처리가 가능하다.
      • 결과도 아날로그와 디지털로 표현할 수 있다.
  • 사용 목적에 따른 분류
    • 범용
      • 일반적인 모든 업무에 사용되는 컴퓨터
    • 전용
      • 특수 목적에만 사용되는 컴퓨터
  • 처리 성능에 따른 분류
    • 컴퓨터의 모든 자원을 고려하여 분류
    • 마이크로 컴퓨터 / 미니 컴퓨터 / 메인 프레임 컴퓨터 / 초대형 컴퓨터 등
      • 현재는 기술이 발전하여 처리 성능의 구분이 모호해지고 있는 실정이다.
더보기

컴퓨터는 데이터를 수치화 하여 계수형 자료를 취급하는 디지털 컴퓨터와, 물리량을 입력으로 받아들여서 처리하는 아날로그 컴퓨터 로 분류된다

컴퓨터 하드웨어 구성과 기능

  • 중앙처리장치
    • 연산 장치 , 제어 장치 구성
      • 연산 장치
        • 프로그램에 따라 계산을 처리하는 산술 연산과 비교 / 판단을 처리하는 논리 연산을 실행한다.
        • 말그대로 연산을 한다.
      • 제어 장치
        • 컴퓨터의 각 장치에 작업을 처리하는 순서를 지시한다.
        • 주 기억장치 → 실행명령 해석 → 제어 신호 생성
        • 시스템 전체에서 정확하게 수행되도록 통제한다.
        • 말그대로 제어를 한다.
  • 하드웨어 구성과 기능
    • 주기억장치
      • 수행 중인 프로그램과 필요한 데이터를 저장한다.
      • 용량에 제한이 있다.
          • 읽기만 가능한 비휘발성기억 소자
          • 읽기와 쓰기가 모두 가능한 휘발성 기억 소자
    • 보조기억장치
      • 하드디스크
        • 알루미늄 또는 플라스틱 판의 표면에 자기 물질을 얇게 입힌 것
      • CD / DVD
        • 레이저 광선을 투사하여 반사되는 빛을 읽어 자료를 해독한 후 처리하는 방식 사용
        • 컴퓨터의 정보 저장매체로 발전했다.
      • USB
        • 컴퓨터 ↔ 주변기기 연결하는데 사용되는 입출력 장치
    • 입출력 장치
      • 입력 장치
        • 컴퓨터가 처리하는 명령이나 데이터를 컴퓨터 내부로 읽어들이는 역할
      • 출력 장치
        • 프로그램 순서에 따라 실행을 마친 후 처리 결과를 사용자에게 보여주는 장치
더보기

컴퓨터는 중앙처리장치와 부가적인 하드웨어로 구성된다.

중앙처리장치는 산술연산과 논리연산을 수행하는 연산장치와 제어를 담당하는 제어장치로 구성되어있다.

부울 대수의 개념과 연산 **

  • 부울 대수
    • 하나의 명제가 참 (1) 또는 거짓 (0) 인가를 판단하는데 사용되는 수학적인 방법
      • AND (*) OR (+) NOT (’)
      • 참과 거짓으로만 생각하면 된다.

  • 논리회로 개념과 연산
    • 논리회로
      • 디지털 정보 입력을 디지털 신호 0,1로 출력한다.
      • 기본 게이트 AND, OR , NOT 게이트를 조합하여 구성한다.
      • 범용 게이트 NAND, NOR 게이트는 트랜지스터로 제조한다.
    • 조합 논리회로
      • 8개의 논리 게이트의 조합으로 이루어진 회로이다.
        • 0과 1의 조합에 의해 출력이 결정된다. (과거의 값을 갖고있지 않다.)
    • 순차 논리회로
      • 저장 능력이 있는 플립플롭과 게이트를 서로 연결하여 구성된다. (과거의 값을 갖고있다.)
    • 플립플롭
      • 1비트를 저장한다. ( 두가지 상태로만 변하는 전자회로이다. )
    • 레지스터
      • 여러개의 플립플롭의 묶음 (비트의 묶음) ,

기본 논리회로와 논리 게이트

 

  • 일반적으로 2개의 입력이 기본으로 들어간다.
  • AND → 작은 수가 결정 논리곱
  • OR → 큰 수가 결정 논리 합
  • NOT → 반대 (출력이 2개가 나올 수 있음) 논리 부정

 

 

더보기

부울대수는 0과 1로 참 또는 거짓을 판단하는 이산수학의 한 분야이다.

AND → * , OR → + , NOT→ 반대

XOR → 같으면 0 다르면 1 , XNOR → 같으면 1 다르면 0

NAND , NOR → AND ,OR 과 반대

각각의 게이트는 하나로 보이지만 더 많을 수 있다.

개인용 컴퓨터와 CPU

  • 4비트 → 8비트 → 16비트 → 32비트
  • CPU의 분류
    • CISC
      • 명령 복합형 프로세서
        • 연산용 명령어를 수백 개 탑재하고 있는 마이크로 프로세서
        • 명령어 개수 증가에 따라서 구조가 복잡하고 빠른 작동의 프로세서 제작이 어렵다.
    • RISC
      • 소수의 주소 기법을 사용한다.
      • 길이가 일정하고 한개의 클록 사이클로 처리된다.
      • 명령 축약형 프로세서
    • 플린 분류
      • 컴퓨터 시스템을 구조적 특징에 따라 분류하는 방식 중에서 가장 널리 사용된다.

컴퓨터의 데이터 표현

  • 10진수의 데이터를 입력받는다.
  • 입력 받은 10진수를 2진수로 변환하여 계산 / 분석한다.
  • 처리된 2진수의 결과를 10진수의 정보로 출력한다.
  • 리회로로 표현 할 수 있는 2진수로 표현된다.
  • 10진수를 2진수(혹은 n진수) 로 변환하는 법
    • 10진수 → 정수부분 / 소수부분 으로 나눈다.
    • 정수 부분을 2로 인수분해 한다. ( n진수일 경우엔 n으로 ) 인수분해
    • 나머지를 순서대로 읽는다.

 

 

  • 자료의 표현
    • ASCII 코드
      • 통신목적으로 제작되었다.
      • 7비트로 구성되어 128개의 문자를 표현한다.
      • 현재는 1비트를 추가해 8비트로 확장된 ASCII 코드도 많이쓰인다.
    • EBCDIC 코드
      • 최대 256 가지의 정보를 나타낼 수 있다.
      • IBM 에서 제작되었으며 모든 IBM 장비에 사용된다.
    • 2진화 10진 코드
      • 6비트 BCD 코드라고 한다.
      • 숫자 / 영문자 / 특수문자 를 코드화 햇다.
    • 유니코드
      • 텍스트나 스크립트 문자에 대해 바이너리 코드를 확립하기 위한 것
      • 2바이트 코드의 형식을 갖고있다. 조합형 코드
      • 아래한글이 나오면서 조합형 문자를 쓰게 되었다. (한글)
    • 논리자료와 포인터 자료의 표현 방식
      • 논리자료 → 부울식 (참과 거짓)
      • 포인터자료 → 주소를 직접 액세스 할것이냐, 간접적으로 액세스 할것이냐
        • 일반적으로 링크라고 불린다.
    • 정수의 표현 방식
      • 비 부호 방식
        • 음수 표현이 불가능 하다.
      • 부호 절댓값 방식
        • 앞자리가 0이면 양, 1이면 음
        • 음수 표현이 가능하다.
      • 1의 보수 방식
        • 절댓값에 대한 방식과 같으나 음수 표시가 다르다.
        • 1의 보수이기 때문에 0이면 1로, 1이면 0으로 바꿔준다.
        • 부호 절댓값과 동일하게 -0 같은 형식이 나온다.
      • 2의 보수 방식
        • 0의 자리를 찾을 수 있다.
        • 다른 값에 비해서 한자릿수가 더 크다.
    • 실수의 표현 방식
      • 부동 소수점 방식
        • 임의의 수를 가수부분과 지수부분으로 나누어 컴퓨터의 기억공간에 저장한다.
  Comments,     Trackbacks