면접후기

얼어붙은 취업시장에서 저에게 기회를 주시고, 리소스를 사용해주신 '제로원리퍼블릭'팀에게 감사인사를 전합니다.

기회를 주셨음에도 제대로 붙잡지 못했고, 부끄러운 점이 많은 면접이어서 죄송했습니다.

 

함께하고 싶은 개발자, 함께하고 싶은 팀원임을 보여드리고자 면접을 준비하였지만 준비했던 것에 비해 많이 부족했습니다.

여쭙고 싶은 내용이 많았는데 이미 한시간 삼십분 가량을 면접에 사용해주셔서 일부만 질문드릴 수 있었습니다.

 

2점인 이유는 마음에 드는 대답을 딱 두 개 하였기때문에, 실제로 더 높은 점수를 주셨음에도 2점으로 스스로 평가하였습니다.

 

글을 쓰게 된 이유

사실 이 글은 3시간에 걸쳐 완성되었습니다.

다만 아주 사소한 실수로 이미 완성되었던 글이 날아갔기때문에 미흡한 부분이 많을 수 있습니다.(소리를 지르고 싶습니다)

아무튼, 글을 쓰게 된 이유는 제 부끄러운 모습을 기록하고 싶기때문입니다.

이를 통해 더 나은 개발자가 될 수 있을 것이라고 믿습니다.

 

코드 리뷰 중 컴포넌트의 리턴 값을 False로 처리한 부분에 대하여

면접 중 대답: (당황)어.. 평소라면 null을 리턴하거나, Fragment(<></>)로 처리하였을텐데.. 잠시만요 등 제대로 대답하지 못하고, 인근의 컴포넌트를 확인했음에도 이유에 대해 명확히 전달드리지 못했습니다.

 

면접 이후 복귀 중 생각: 오버레이 함수의 리턴 값 이었고, False로 처리한다는 것은 명시적으로 렌더링을 생략하겠음을 의미한다고 이야기할 수 있었을 것 같습니다. 전체적인 흐름 상 컴포넌트이기때문에 Fragment로 JSX를 동일하게 리턴해주어야함이 좋을 수 있겠으나, 해당 오버레이의 역할 상 open상태가 아니면 그릴 필요가 없고, 그려질 가능 성도 없기때문에 리턴 문에서 open상태에서만 그려지는 것이 더 명시적일 것이라고 생각했을 것 같았거든요.


다만 명시적으로는 False가 아닌 Shortcut을 통한 null이 더 좋은 방식이었을 것 같습니다.

useEffect와 useLayoutEffect 등의 사이드이펙트 트리거가 있다는 점에서 조금 문제의 여지가 있을 수 있지만 후자에 좀 더 의미를 뒀을 것 같습니다.

 

여기서 추가로 이어지는 질문인 JS의 False, null, void, undefined 등에 대한 차이점을 물어보셨을 수 있었겠습니다.

이 부분에 대해서는 아마 당시의 저라면 뭉뚱그려서 이야기 했을 것 같습니다.

일단 TF라는 표현식(Boolean)과 null이 object라서 다르다거나, 타언어(C)의 경우 0x0번 주소를 가리키고 있는지를 확인할 것(실제로 주소가 없음)을 표현하기 위한 용도 등을 이야기하였을 것 같습니다.

undefined는 말그대로 메모리 주소는 점유되어있으나 내용물이 없는 것이고, void는 타입에 대한 부분이라고 말씀드렸을 것 같습니다.

(공부를 더 제대로 했다면 조금 더 딥하게 포인터와 실제 메모리에 대하여 말을 했을 것 같은데 아쉽습니다.)

 

REST API에 대해 얼마나 알고 있으세요?

면접 중 대답:이전에 사용하던 프로토콜(SOAP)의 불편함을 해결하기 위해 만들어졌으며, DATA 전송을 위한 프로토콜, 아니 인터페이스이고 특징으로는 serverless(아찔), 아니 stateless가 있습니다. 이전의 방식과는 다르게 데이터 중심적입니다. 요즘은 JSON 형태의 전달을 위해 많이 사용됩니다.

얼마나 잘못대답했는지 보이시나요? 제가 전하고 싶었던 내용은 이렇습니다.

 

1. Resource 전달이라는 목표
2. HTTP Method와 같은 GET, POST 등을 매칭해서 사용함

3. Stateless
4. 발전에 따라 XML -> JSON으로 형식이 변경됨에 따라 근래에는 JSON 형태로 전달 

 

이 과정에서 중언, 부언을 많이했고, 가장 중심이 되는 포인트일지도 모를 URI 등을 생략한 것이 아쉽습니다.

 

이전에 기록했던 내용을 발췌해왔습니다.

 

SOAP(Simple Object Access Protocol)

정의 → 컴퓨터 네트워크 상에서 HTTP or HTTPS 등을 통해 XML기반 메시지를 교환하는 프로토콜

목적 → SOA를 구현하는 규약

*SOA? Service Oriented Architecture, 소프트웨어의 기능을 비즈니스적으로 의미가 있는 단위로 묶은 것

 

구성 → 모든 Data가 XML로 표현되며, 데이터와 데이터를 다루는 오퍼레이션이 WSDL로 정의되면 UDDI에 등록되어 누구라도 서비스를 검색하고, 접근할 수 있도록 공개한다.

  1.  WSDL(Web Services Description Language) → 웹서비스를 정의한 언어, 서비스 인터페이스(형태: 타입-데이터타입, 메세지-메서드, 포트타입-인터페이스 정의) | 서비스 임플리멘테이션(명령: 바인딩-네트워크 프로토콜과 포트타입 매핑, 서비스-서비스정의)
  2. UDDI(Universal Description Discovery and Integration) → WSDL이 저장되는 저장소

한계 →

복잡한 구조: HTTP에서 전달되기 무겁다.

개발 환경 지원 필요: 웹서비스 개발 난이도가 높다. UDDI를 거쳐야한다.

 

REST(Representational State Transfer)

정의 → 자원의 표현에 의한 상태 전달

Resource(사진, 음악, 글자 등 모든 자료)의 이름

 

상태 전달 → Data를 JSON 혹은 XML로 전달

 

목적 → ROA를 구현하기 위한 규약

*ROA ? Resource Oriented Arhitecture, UDDI같은 중간 매개체 없이 자원을 직접 주고받는 구조

 

구성 →

자원: 모든 자원은 고유한 ID(URI)가 존재하고, Server에 저장됨. | 자원이 어디에있는지를 식별

행동(Verb): http Method(get, post, put, delete)

표현: Client의 Data 요청에 서버가 응답

 

특징 →

Client - Server 구조: 명확히 구분되어 있어 개발이 쉽다 ? 요즘은.. 점점 영역 구분이 명확해지면서도 불명확한 것 같은데…

Stateless: 상태가 없다.

 

한계 →

  1. 표준이 없다.
  2. 사용할 수 있는 메서드가 제한되어있다.
  3. 때로는 RESTful하게 만드려고 method를 사용하여 속도가 느려질 수 있다.(GET이 POST보다 빠름에도 REST를 지키기 위해 POST를 사용..근데 Method의 사용처가 우선되어야 하지 않나? Read인데 POST를 사용하고, Write인데 GET을?)

#부족한 포인트 

통합리소스식별자(URI), 서버 레벨에서 프록시나 로드밸런서 등 계층 구조에 대한 내용, 캐시에 대한 내용이 추가되면 좋을 것 같습니다.

 

#아쉬운 포인트

이후에 제대로 XMLHTTPRequest와 AJAX, fetch, promise 등과 잘 이어서 이야기를 했다면 더 재밌는 이야기를 할 수 있었을 것 같다고 생각이들었습니다.

 

GraphQL을 알고 있는지? REST API와의 차이?

면접 중 대답: 사용해본 적이 없어 자세히는 알고 있지 못합니다. 응답에 대하여 사전에 정의한 형태로 받아올 수 있습니다.

예를 들어 user라는 객체안에 name이 필요한 상황 등에서 name만을 받을 수 있도록 정의되어있다면 응답 비용을 감소시킬 수 있습니다.

BF(BFF를 언급하려고 했으나, 얕게 알고 있기로는 둘이 비슷하게 응답과 요청비용을 줄이기 위함 정도로만 알고 있어 이야기하지 않음)..아니 또, server에서의 연산 자체를 줄일 수 있어 비용 감소가 될 것 같습니다(잘못된 내용)

 

 

면접관님: 그러면, URL 쿼리 파라미터 등으로 key값을 받아서 서버에서 직접 파싱하는게 비용적으로 더 덜 들지 않을까요?
차이가 없을 것 같은데 GraphQL을 쓰는 의미가 없지 않을까요?

 

대답: 음..(당황)제가 서버는 잘몰라서(최악의 답변) 예를들어 로그인이라는 어떤 작업에 대해서 user객체 말고 다양한 객체에 대하여 응답이 조합되어야할 때는 비용 감소가 될 것 같습니다. 그리고 서버의 비용을 클라이언트와 같이 나눠서 부담하는 형태기 때문에 비용이 덜 할 것 같습니다(잘못된 내용)

 

면접관님: 여전히, 질문에 중심은 REST API와의 차이에 대한 것입니다.

 

얼마나 잘못 대답했는지 보이시나요,

 

제가 전달하고자 했던 바는

1. 사전 정의를 통해 Client에서 원하는 응답을 API를 통해 바로 받아올 수 있다(설사 여러개의 엔드포인트에 대한 것이라도)

2. 이를 통해 사전 비용이 조금 소비될 수는 있지만, 응답 사이즈가 줄어들 수 있기때문에 비용이 절감될 수 있을 것이다.

 

정도 일 것 같습니다.

 

이후에 면접관님이 피드백을 주신 내용을 덧붙이자면 아래와 같습니다.

 

GraphQL을 사용했을때의 장점은 API 호출 수를 줄일 수 있다는 것입니다. 호출 한번에 여러개의 데이터를 받을 수 있다는 장점 덕분에요.

다르게 말하면 REST는 같은 데이터를 받기 위해 여러번의 API 호출이 있을 수 있습니다.

 

반면에, FE에서 여러 번 호출을 하면 되는 일을 BE에서는 중복 코드 선언, 불필요한 연산 등 엔지니어링 작업이 더 필요해질 수 있다는 단점이 있습니다.

 

#동료 프론트엔드 개발자를 통해 얻은 인사이트

면접 이후 통화 중에 면접관님이 듣고 싶었던 내용에 대하여 바로 정리하여 들어볼 수 있었습니다.

그리고 GraphQL에 가장 큰 장점은 A/B테스트 등에서 클라이언트 주도적으로 테스트를 진행해볼 수 있다는 것이라는 실무적인 인사이트를 다양한 예시와 근거를 통해 얻을 수 있었습니다. 요약하자면, API에 대한 엔드포인트 분리나 응답구조 구분을 서버에서 할 필요 없이, 클라이언트에서 쿼리만 다르게 조절하여 사용할 수 있기 때문입니다.

 

트리 쉐이킹에 대해 들어본 적이 있는지?

면접 중 대답: 음.. 들어본 적이 있는데, 정확히 기억이 나지 않습니다. 브라우저와 CSS와 관련이 있는 내용입니까?(당황)

 

면접관님: 아닙니다.

 

대답: 음 노드를 렌더링 할때, CSS 변경 사항 등의 이유로 렌더링 할 필요가 없어진 노드를 제거하는 것으로 알고 있었습니다.(잘못된 내용,

비슷한 목적인 '불필요한 것 제거'라는 점이 있지만 다름)

 

면접관님: 제가 말씀드린 트리 쉐이킹은 자바스크립트에 대한 것입니다.

 

대답: 죄송합니다. 잘 모르겠습니다.

 

이후 면접관님은 ES Module 등에서 Import, Export 사용에 대한 예시, Named Export와 Default의 차이, lodash의 사례 등을 설명해주셨습니다.

 

대단히 부끄러웠습니다. 분명 들어본 적이 있는 내용이고, 이후의 면접관님의 설명을 듣고 나서는 제가 겪었고 마주했던 문제들이라는 것을 깨달았기 때문입니다.

 

Firebase 버전에 의해 고통을 받았던 기억과 react-native-cameraRoll 등 일부 라이브러리에서 경험했던 문제입니다.

또, 이전에 import와 export에 대하여 조심하여 사용하기로, 불필요한 코드 호출에 대해서 공부했던 것도 떠올랐습니다.

 

script태그에 대해서 헤드와 바디 최상단 최하단에 선언하는 의미, Defer와 Async에 대해서 알고있는지?

면접 중 대답: 작년에 이 부분에 대해서 경험했던 내용을 토대로 기억나는대로 전달드리자면, 위치에 따라 렌더링에 영향을 미치는 것으로 알고 있습니다. 목적에 따라 로드가 먼저 되어야 하는 경우 바디 상단에 위치하거나, 렌더링이 우선인 경우 하단에 미뤄두거나 하는 것으로 알고 있습니다. 헤드도 마찬가지로 목적이 있었던 것 같은데 명확하게 기억이 나지 않습니다.

 

아마 실제 대답은 저것보다 훨씬 뭉뚱그려 대답했을 것입니다.

 

NextJS에서 제공하는 Script를 사용하면 목적에 따라서 stratgey를 다르게 사용할 수 있습니다.

기본값이 AfterIntractive인 이유는 SPA이기때문에 BODY의 내용(UI요소)와 연관된 스크립트 사용이 많기때문이고, 이러한 내용을 알지 못하는 유저이더라도 편리하게 사용할 수 있게 하기 위함이라고 정리했던 기억이 납니다.

 

면접관님이 알려주신 내용과 제가 정리한 내용으로 다시 한 번 정리해보자면,

 

1. 헤드 최상단 -> 제일 전달이 빠르다. 렌더링이 중단되고, 문서 파싱전에 로드되거나 전달되기때문에 Polyfill 등 설정이 우선되어야하는 경우 사용

 

2. 헤드 최하단 -> Metadata 등에 대한 Analystic Data를 외부 서드파티 솔루션(GA, Amplitude 등)으로 보낼 때 사용,

정적 동적스크립트에 따라 브라우저가 처음부터 인식하는지, 아니면 HTML 파싱과 별개로 진행되는지 등에 차이에 따라 파서차단 여부가 달라지기 때문에 동작이 달라질 수 있음.

 

3. 바디 최상단 -> 아래 UI 요소 들에 대한 의존 Script이거나, 빠른 JS실행이 필요한 경우

 

4. 바디 최하단 -> JS실행보다 렌더링이 우선되어야 하는 경우, 무거운 것을 제일 아래로 두는 느낌

 

Defer와 Async

면접 중 답변: 너무 오래전에 봤던거라 기억이 잘 나지 않습니다. 사용에따라 스크립트를 비동기적으로 로드할지, 아니면 나머지 작업을 멈추고 로드를 진행할지? 이런 목적 들에따라 다르게 사용하는 것으로 알고 있고, 각 브라우저 별 기본 값이 다른 것으로 알고 있습니다.

 

분명 더 명확히 알고 있던 내용이고, 정리했던 내용이 있음에도 기록을 찾지 못하여 아래의 이미지로 대체합니다!

#아쉬운포인트

이 문제들로 해결했던 문제들을 예시로 드는 것이 더 좋았을 것 같다는 생각을 합니다.

예를 들어 NaverMap API를 사용하며 겪었던 사례라던지 KAKAO Login 도입 과정 중에 사용했던 사례 또는 SSR 상황에서의 script 태그 사용 과 같은 내용이요!

 

CORS에 대하여 알고 있는지? 만약 CORS를 받으면 가장 먼저 확인할 것은?

면접 중 대답: CDN이나 Cloud가 연결되어 있는 API에 대한 CORS인지, 서버와의 API 통신 과정에서 일어난 문제인지를 확인할 것 같습니다. ..음 후자의 경우라면 화이트리스트를 제일 먼저 확인할 것 같습니다.

 

면접관님: 화이트리스트는 등록되어 있다고합니다. 그렇다면?

 

면접 중 대답:(머릿속으로 음..그럼 FE에서 API주소를 잘못, host에 오타를 냈던지 뭔가 사람의 오타로 인한 실수)음 제가 경험한 바에 의하면 두 가지 사례가 가장 많은 것 같습니다. 첫 번째는 휴먼에러에 의한 것이고(너무 축약함) 두 번째는..(CDN이나 외부리소스에 대한 접근 상황인데 이 경우는 앞서 이야기 했다고 착각) 음 네, 휴먼 에러가 제일 많은 것 같습니다.

 

이보다 더 대답을 못할 수가 없습니다.

 

면접관님이 클라이언트는 보통 열린 상태, 서버는 보통 닫힌 상태를 기본값으로 하고 있고 이에따라 외부 리소스에 대한 접근 그리고 URL에 대한 변경 등을 말씀해주셨을때서야 비로소 '아, URL 변경 되거나 하면.. 그렇겠네요!'라고 늦은 답변을 드렸습니다.

 

#아쉬운 포인트

너무 혼자서 생각을 하고 정리하지 않은 채 섣부르게 대답했습니다.

더 잘 대답할 수 있었을 내용입니다.

 

URL을 주소창에 입력하면 벌어지는 일? TCP/IP에 대하여 알고 있는지?

2연타로 굉장히 아프게 맞았습니다.

분명히 정리했던 내용이고, 알고 있던 내용이었거든요.

대답 전에 소켓을 열고, 커널을 통해 TCP/IP를 타고 내려가서 랜카드 드라이버를 거쳐 NIC을 통해 집에 연결된 ISP를 블라블라~

를 이야기 해야하나 잠깐 고민하다가 아래와 같이 대답했습니다.

 

면접 중 대답: 음.. 엔터를 치고, DNS를 찾습니다. 이를 통해 가장 인접한 서버로 갑니다. 이 서버를 통해 DB 또는 데이터에 접근 후 응답을 생성하고, 클라이언트로 발송합니다.

 

면접관님: 어.. 좀 더 자세하게 표현해주실 수 있을까요?

 

면접 중 대답: 음... 우선 웹서버에 도착을 하면, Redirect URI 또는 Block, Location 등의 규칙에 따라 데이터가 있는 곳으로 갑니다.
이 과정에서 WAS등의 미들웨어를 거치게 되고 이후에 DB 또는 데이터를 요청에 반환합니다.
이때 로드밸런서, 부하분산 등이 어떻게 처리되어있는가에 따라서 차이가 날 수 있습니다.

 

면접관님: 그럼 TCP/IP는 알고 계세요?

 

면접 중 대답: ..3년전에 공부했던거라(최악의답변) 기억나는대로 말씀드리면, 핸드쉐이킹을 하고 이 때 패킷헤더를 열어 플래그를 확인하거나 채우거나 하는 행위를 반복합니다. 

 

저보다 더 잘못대답하신 분은 없을 겁니다.

 

위에 대한 내용은 아래의 글과 캡쳐로 대체하겠습니다!

https://helloian.tistory.com/37

 

브라우저에 URL을 입력하면 일어나는 일 by 널널한 개발자님 -1-

 

helloian.tistory.com

 

#아쉬운 포인트

좀 더 정리해서 전달드릴 수 있었을 것 같습니다. CDN에 대한 내용도 이야기드릴 수 있었을 것 같다는 생각이 듭니다.

 

마무리

저는 부끄러운 모습을 기록하는 것에 익숙합니다.

이를 통해 다음 번에 같은 실수를 반복하지 않을 수 있다고 생각하기 때문입니다.

모르는 것을 부끄러워할 줄 알고, 모르는 것을 인정하고 배울 수 있는 사람이고 싶어 기록합니다.

중간 중간 부끄럽다는 표현과, 자책의 표현 등도 이를 통해 더 강렬하게 기억하고 싶기 때문입니다.

 

다시 한 번 좋은 면접을 진행해주신 '제로원리퍼블릭' 팀에게 감사인사를 전합니다.

덕분에 많은 생각을 할 수 있었고, 되돌아볼 수 있었습니다.

이 글을 읽어주신 분들도 좋은 연휴가 하루가 되시기를 바래봅니다.

 

[출처]: defer & async timeline

들어가기 앞서

이직을 위해 회사들을 탐색하던 중, 인상적인 CS질문 리스트를 사전 질문으로 물어보시는 회사가 있었습니다.

이전에 공부하던 내용들 중 정리했던 내용과, 더 알게된 내용, 새롭게 알게된 내용 등과 사견을 포함하여 정리하다가, 기록을 남기기 위해 적습니다.

 

앱 개발자 공통질문

• PNG와 JPG의 차이점은?

특성에 따라 용도가 다르다고 알고 있습니다.

JPG의 경우 압축 시 사람 눈이 잘 못느끼는 부분의 정보를 버리는 형식으로 데이터를 저장하기때문에 저장-불러오기를 반복하면 이미지가 손실되는 특성이 있습니다.

 

반면 PNG는 반복되는 패턴을 만들고, 중복 문자열을 참조포인터로 대체하거나 반복도에따라 더 짧거나 길게 치환하기때문에 버려지는 데이터가 없어 저장시에 손실이 일어나지 않습니다.

 

또 투명도 지원여부의 차이도 있습니다.

 

따라서 JPG는 얼굴, 풍경 등 복잡한 이미지에

PNG는 비교적 일관적인 UI로고, 스크린샷 캡쳐등에 사용하는 것으로 알고 있습니다.

 

다만, 요즘의 웹 서비스의 경우 이미지 리소스 자체의 크기가 더 중요해지고 있고, 비용으로 이어지는바 SVG와 webp가 더 많이 활용되는 것으로 알고 있습니다.

 

• Dynamic Programming이란?

큰 문제를 작은 문제로 나누고, 반복해서 계산되는 문제의 결과값을 재사용하는 것으로 알고 있습니다.

 

• Virtual Memory란?

물리메모리 활용을 위해서 OS차원에서 프로세스의 독립적인 공간을 보장하기 위해서 사용되기 시작한 것으로 알고 있습니다.

운영체제가 지원하는 bit수에 따라 다른 크기의 자원 공간을 가지며, 32비트 컴퓨터의 경우 약 4gb정도를 활용할 수 있으나, 운영체제가 반정도를 사용하고 있어 실제로 사용할 수 있는 공간은 약 1.7gb입니다. 선형 구조로 이루어져 있는 것 처럼 활용됩니다.

실제로 예전에 물리메모리에 직접 접근하던 시절에는 사용할 수 있는 메모리가 점점 줄다가 컴퓨터가 멈추면, 재부팅을 해서 메모리를 다 초기화시켰어야하는 경우도 빈번했다고 알고있습니다.

 

메모리 관리자는 현대 운영체제의 핵심 요소중 하나입니다. 

메모리 관리자가 관리할 수 있는 메모리의 크기는 물리 메모리 공간과 SWAP 영역 공간을 합친 만큼이고, 프로세스가 사용하는 가상 주소 공간을 실제 물리 메모리의 주소로 변환하는 것을 ‘동적 주소 변환’이라고 합니다. 이 과정이 적힌 맵핑 테이블을 가지고 있으며, 가변 분할 방식인지 고정 분할 방식인지에 따라 page 또는 segment로 부르기도 합니다.

 

메모리가 쭉 이어진 어떤 가상 주소가 0xffffffff까지 이어져있을때, 1byte의 메모리 0번부터 4095번까지 약 4kb이 윈도우 기준으로 끊어진 것이 페이지의 크기이며,  이 페이지만큼 실제 RAM에서의 메모리 조각을 Frame이라고 합니다.

 

C에서 포인터등을 연산 또는 대입하다가 Invalid한 Frame을 찾아 들어가게되면 PageFault가 나고, Segment로 관리된다면 Segmentation Falut가 나게 됩니다.

만일 존해하지 않는 공간에 대해 연산을 하게된다면 Bus Error가 발생하는 것으로 알고있습니다.

 

마지막으로 할당받은 공간 중 코드가 들어가는 영역과 데이터가 들어가는 영역이 존재하며, 데이터가 들어가는 영역에 [heap, stack, static] 영역이 존재하는데,

이 중 Static 영역은 읽기 쓰기가 가능한 공간(전역 변수)과 그렇지 않은 공간으로 나뉘고 나머지 둘은 읽기 쓰기가 가능합니다.

코드 영역은 대체적으로 쓰기가 불가능하고, 읽고 실행만 가능합니다. static 영역의 일부가 이쪽에 포함됩니다. 문자열이나 상수가 그렇습니다.

다만, 운영체제 함수에 의해 제어 또는 변경이 가능한 것으로 알고 있습니다.

 

• Garbage Collection이란?

가비지 컬렉션은 더 이상 참조되지 않는 객체를 자동으로 감지해 메모리에서 해제해주는 기능입니다.

대표적인 방식에는 참조 카운팅, 마크 앤 스윕, 세대별 수집 방식이 있고, 언어마다 구현 방식이 조금씩 다릅니다.

이를 통해 개발자는 수동 메모리 관리를 하지 않아도 되고, 안정성과 생산성이 올라갑니다.

 

다만, 현대의 GC역시 완벽하지는 않아서 메모리 누수가 발생할 수 있고, 장기 실행 서비스의 경우 반복되는 과 누수와 heap compaction이 겹쳐 GC주기가 짧아지다가

멈추게 되고, 서버 장애로까지 이어지는 경우가 있는 것으로 알고 있습니다.

 

• Cache란?

CPU가 어떤 작업을 할때 RAM과 보조 기억 장치와 연산을 같이하게 됩니다.

CPU의 속도가 다른 장치들보다 월등히 빠르기때문에 속도의 차이를 완충하는 작업들을 미리 해둘 필요가 있습니다.

따라서 예측 데이터를 가지고 있는 캐시 메모리와 우선 소통 후에 이 곳이 없으면 램과 소통하는 형식으로 작업을 진행합니다.

구조는 제조사 마다 다를 수 있지만, L1 레이어는 코어와 붙어있고, L2레이어는 공용으로 하거나, L3까지 있는 경우 L3를 공용으로 하는 경우가 있는 것으로 알고 있습니다.

L3에는 램에서 올라온 예측 데이터, L2에는 데이터와 명령어가 혼재, L1에는 명령어와 데이터가 따로 구별되어 있다고 알고 있습니다.

 

• Database Index 추가의 장단점은?

잘 알지는 못하지만, index의 장점을 생각한다면 접근을 빠르게 할 수 있어 조회 속도를 향상 시킬 수 있고 특정 구조의 경우 정렬 상태를 유지할 수 있어 검색에 최적화된 접근 방법인 것 같습니다. 단점으로는 index가 많아지면 관리가 어렵습니다. 그리고 index도 어딘가에 저장되기 때문에 추가 저장공간이 필요하게 되고, 업데이트 시에 인덱스도 업데이트 되어야해서 쓰기 시 오버헤드가 발생할 것 같습니다.

 

• 비대칭 암호화란?

두 개의 열쇠를 사용하는 방식으로 보통 공개 키와 비공개 키로 나뉘며, 한쪽으로 암호화한 데이터를 다른 쪽 키로 복호화하는 방식입니다.

공개 키로 암호화 하고 비공개 키로 복호화 하거나, 비공개 키로 암호화하여 공개 키로 검증하는 형태로 사용합니다.

인증서, HTTPS, 전자서명 등을 예시로 들 수 있습니다.

• HDD, SSD, DRAM 각각의 성능은?

성능이라는 질문에 고민을 해보았습니다.


속도라는 성능 지표로 본다면 DRAM이 가장 빠르고, SSD가 그다음, HDD가 마지막인 것으로 알고 있습니다.

HDD가 가장 느린 이유는, 실제로 돌아가는 원형 공간에 트랙과 섹션을 나누고 이를 찾아 읽거나 쓰거나 해야하기때문에 물리적 이동이 필요합니다.

반면에 SSD는 page와 page가 모인 block을 컨트롤러가 전기적 신호로 직접 접근하기때문에 물리적 이동이 필요하지 않아 빠른 것으로 알고 있습니다.

저장공간이라는 지표로 본다면 DRAM이 가장 작고, 그다음이 SSD, HDD였습니다.

• GIT의 장점은?
Git의 가장 큰 장점은 버전 관리와 협업의 체계화에 있습니다.

서비스 규모가 커지고 협업 인원이 늘어날수록, 동일 소스에 대한 동시 작업, 변경 이력 관리, 충돌 해결, 롤백등이 중요해집니다.

Git은 브랜치 전략과 커밋 이력을 통해 이러한 흐름을 효과적으로 관리하며, 개발 생산성과 안정성을 동시에 확보할 수 있는 도구입니다.

 

데스크탑 앱 개발자 기술 질문

• UTF-8 이란?

웹의 표준 통합을 위해 발전한 형식입니다.

 

잠깐 발전에 대해 언급하면, 아스키(7bit -> 8bit)에서 개인 컴퓨터 보급과 유니코드(2byte 이상, 16bit 이상)으로, wchar(wide char, 컴파일러 설정에 따라서 16bit or 32bit)로

MBCS(MultiByte Character Set), 한글의 경우 조합형에서 완성형으로, euc-kr에서 cp949 그리고 utf-8(24bit, 3byte 유니코드)로 발전하였습니다.

이후 utf-8 bom(byte order mark), utf-16le(little endian)과 utf-16be(big endian)으로 발전하였습니다.

 
UTF-8은 유니코드를 기반으로 하면서도 ASCII와의 호환성을 유지하고, 가변 길이 인코딩(1~4byte)을 통해 전 세계 문자를 효율적으로 표현할 수 있습니다.

이로 인해 다국어 환경과 호환성이 중요한 웹 환경에서 사실상 표준 인코딩 방식으로 자리잡게 되었습니다

 

• React 란?

React는 사용자 인터페이스(UI)를 만들기 위한 JavaScript 라이브러리입니다.
핵심 개념은 컴포넌트 기반(Component-based) 설계로, 복잡한 UI를 작은 단위로 나눠 재사용성과 유지보수성을 높일 수 있습니다.

React는 상태(state)나 데이터(props)가 바뀌면 자동으로 화면을 업데이트하는 방식으로 작동하며, 이를 위해 내부적으로 Virtual DOM을 사용해 실제 DOM 조작을 최소화하고 성능을 최적화합니다.

초기 진입점으로는 보통 index.html 안의 root DOM 요소에 React 컴포넌트를 렌더링하며, 렌더링은 ReactDOM.createRoot()와 같은 메서드를 통해 수행됩니다.

 

• Web Browser 의 프로세싱 중 Reflow 란?

Reflow는 웹 브라우저에서 DOM(Document Object Model) 트리를 기반으로 페이지의 레이아웃을 재계산하는 과정을 의미합니다.

주로 스타일이나 레이아웃 관련 속성이 변경될 때 발생하며, 이 과정은 브라우저가 웹 페이지의 요소들을 다시 그리기 전에 위치와 크기를 계산하는 작업입니다.

 

• 함수형 프로그래밍 패러다임이란?

입력 값을 단순히 사용하고, 동일한 Input에 동일한 Output을 반환하는 ‘순수 함수’를 지향합니다.

이는 예측하지 못할 side effect를 지양하여 코드의 흐름 제어를 용이하게 합니다.

 

이 과정에서 데이터는 불변해야하며, 필요할 시 새로운 데이터를 만들어야 합니다.

함수를 인자로 전달하거나 반환되는 고차 함수를 사용합니다.

 

람다 대수와 LISP가 예로 있습니다.

 

웹 개발자의 질문중 일부이기때문에 리액트에서의 함수형 프로그래밍을 덧붙입니다.

리액트의 함수형 컴포넌트는 함수형 프로그래밍을 지향합니다.

함수형 컴포넌트의 경우 JSX를 리턴하는 일정한 형태의 작은 단위를 통해 만들고자 하는 형태를 설계합니다. 

이 과정에서 정해진 리액트의 규칙을 통해 부수효과를 제어하고, HOC를 비롯한 함수형 프로그래밍의 규칙을 지향합니다.

Custom Hook을 통해 추상화 및 캡슐화된 함수 조합을 사용합니다.

이를 통해 사용자는 알지 못하는 새에 형태와 흐름을 일관되게 사용할 수 있습니다.


하지만, 완전한 함수형 프로그래밍은 아닙니다.

User 상호작용이 제일 중요한 프론트엔드 진영의 특성상 다양한 변화에 맞추어 컴포넌트가 렌더링 되어야하고,

내부에서 상태가 변화하고, 특정 부분에는 명령형 프로그래밍적 요소가 필요하기 때문입니다.

마지막으로 클래스형 컴포넌트와의 차이를 적으며 마무리하겠습니다.

기존 클래스형 컴포넌트의 경우 클래스 개념에 익숙한 개발자의 경우 this, bind 그리고 시점과 상태에 대해 더 명확하게 사용할 수 있다는 장점이 있고, render method를 직접 호출해야한다는 점이 가장 큰 차이가 있습니다. 

 

함수형 컴포넌트를 통해 OOP에 대한 개념이 어려운 사람들이 일관성 있는 코드 구성을 가장 익숙한 형태로 프로그래밍을 할 수 있습니다.  

 

• Callback hell 이란?

비동기 처리에 의해 함수의 매개변수로 넘겨지는 콜백 함수가 반복되어 코드의 깊이가 깊어지는 것을 말합니다.

이로 인해 가독성이 떨어지고, 흐름을 제어하기 어려워집니다.

 

• async I/O 란?

시간, 제어권, 병행성이라는 키워드로 말씀드리고 싶습니다.


I/O는 컴퓨터에 있어 굉장히 느린 작업입니다.

CPU와 운영체제는 이 자원을 활용하기위해 async I/O를 사용합니다.


이 과정에서 호출자가 제어권을 넘겨준 채로 결과를 기다린다면 blocking, 호출자가 제어권을 유지한 채로 다른 작업을 진행한다면

Non-blocking이라고 할 수 있습니다.

 

시간의 흐름에 의해 실행 순서가 요청과 응답의 흐름과 일치하면 Synchronous,

응답과 상관없이 여러 행동이 병행(Concurrency)될 수 있다면 Asynchronous하다고 합니다.

 

I/O요청에 의해 OS가 인터럽트 요청을 보내고, 실행중이던 Task가 큐에서 제거되거나 대기 됩니다.

이후 결과가 준비되면 OS가 재차 인터럽트를 알리고, Task가 큐에 복귀되어 이어서 실행됩니다.

이 과정에서 앞서 말한 제어권 반환여부에 의해 blocking, Non-Blocking 여부가 나뉩니다.

 

OS 레벨에서는 운영체제에 따라 epoll, kqueue 등으로 구현되어 있습니다.

 

웹 개발자의 측면에서 보자면 JS와 관련된 요소로는 Promise, Async/Await, Event Loop가 대표적입니다.

 

 

• 프로세스 간 통신(IPC) 이란?

앞서 적었듯 기본적으로 프로세스는 독립적인 공간을 보장받습니다.

프로세스에 대한 외부 접근이 OS에 의해 차단되는 것을 보장해준다고도 할 수 있습니다.


첫 번째는 File의 형태를 사용하는 방식입니다.

대표적인 형태로는 pipe가 있습니다. 시작은 있지만 끝이 정해져있지 않은 Stream의 형태로 보통 한쪽에서 쓰면, 한쪽에서는 읽는 형태로 사용합니다.

네트워크 개념으로 확장되면 socket이 있습니다.


Shared Memory 기반의 방식으로는 가상 메모리 중 특정 공간을 접근했을때 실제 RAM Memory에 같은 곳을 이용하여 I/O를 진행하게 하는 경우입니다.

Process A 에서 write가 일어나면, Process B는 시그널이나 이벤트를 대기하고 있다가 약속한 RAM Memory에서 read하는 방식으로 사용됩니다.

 

이 외에도 RPC(Remote Procedure Call)이나 Windows의 Registry를 통해 통신하는 방식 등이 있는 것으로 알고있습니다.

 

• C++ 에서 다형성이란?

정확하지는 않지만, C++도 상속과 가상함수를 통해 구현되는 것으로 알고 있습니다.

가상 함수가 타입마다 다르게 동작하기 위해 가상 함수 테이블이 있습니다.
인스턴스는 참조하고 있는 테이블이 있으며, 실제로 구조체 사이즈가 포인터 하나만큼의 크기로 달라지는 것으로 알고 있습니다.

 

• 그래픽카드를 이용한 하드웨어 가속이란?

GPU의 병렬 연산 능력을 이용해서 반복적인 작업 등을 그래픽카드에 위임하고 CPU는 더 고가치의 논리 처리, I/O등의 작업을 하는 것과 같이 작업 부담을 나눠가지는 것으로 알고 있습니다.

 

마무리

열심히 정리하고 공고를 봤는데, 공고가 사라졌다!

아마 나보다 더 뛰어난 분이 먼저 들어가신 것 같다.

조금 아쉽지만, 그래도 파편화되있던 지식들을 정리할 수 있고,

부족한 것들은 채울 수 있는 좋은 기회였다고 생각한다.

아직 채용 중인 분야가 있기때문에 따로 태그를 입력하거나, 사명을 언급하지는 못하고

이 글을 보실지는 모르지만, 소중한 기회를 주셔서 꼭 감사하다고 전하고 싶습니다.


감사합니다.
안온한 새벽되세요!

'T.I.L. > C.S.' 카테고리의 다른 글

물리메모리와 가상메모리  (0) 2022.07.10
가상화와 도커 이해하기  (0) 2022.07.09
CS-7 with cs50 in 부스트코스  (0) 2022.03.05
CS-6 with cs50 in 부스트코스  (0) 2022.03.05
CS-5 with cs50 in 부스트코스  (0) 2022.03.05

안녕하세요, 아티투에서 프론트엔드 개발을 담당했던 민요한입니다.

저는 비전공자입니다.

제목으로 어그로를 끌었는데, 사실 허수도 많았을 것이고 운이 좋았다고 생각합니다.


입사 당시 아티투의 팀메이트들은 저를 제외한 전원 서울대 출신 또는 재학중인 상태였습니다.

합격 후에 면접을 봤던 팀메이트들이 좋은 점수를 주었던 이유들을 포함하여 스타트업 생활을 하며 보고 겪은 것들에 대해 회고하고자 합니다.

회사가 망했어요.

첫 회사였던 쏘쏘마켓은 '이웃스토리'라는 제주 지역 기반의 커뮤니티를 서비스하는 스타트업이었습니다.
저는 새롭게 추가될 유료 구독 관련(지금 생각하면 SaaS형태의)서비스의 페이지 및 기능을 담당했습니다.

(리액트 네이티브는 덤)
여러 악재들이 겹쳐 결국 입사 4개월 만인 23년 10월 30일에 팀이 해체되었습니다.

 

왜 아티투?

그리고 많은 일이 있었습니다. 서류 착오로 면접 하루전에 면접 기회가 박탈되거나,

서류 합격 이후 연락이 없어 연락을 드렸더니 내부 사정으로 채용 취소가 되었다거나 하는 일들을 겪으면서 자존감이 조금 낮아졌었습니다.
지금에야, '더 좋은 곳에서 더 성장해서 더 멋진 개발을 해야지, 더 좋은 일이 있으려고 나쁜 일이 있나보다'라고 생각하지만

개인적으로 쉽지 않은 시간이었던 것 같습니다.

제가 회사를 선택하는 기준은 지금이나 그 때나 같습니다.


1. 실제로 이용자가 있는 서비스가 운영되고 있는가
2. 내가 얼만큼 성장할 수 있는가
3. 나는 얼만큼 기여할 수 있는가
4. 기여한 만큼 대우 받을 수 있는가

그러던 중에 발견한 아티투 공고에는 다음과 같이 적혀있었습니다.
'팀메이트 전원 서울대 출신', '실제 서비스 중', '토스 출신 개발자가 작성한 소스코드 열람 가능 및 멘토링'
우대 사항 일부 (Amplitude 사용 경험)를 제외하고 제가 기여할 수 있는 포인트들과 어떻게 성장할 것 인지를 어필했습니다.

우리나라 최고 대학 출신들은 어떻게 생각하는지, 어떻게 결정을 내리는지, 어떤 태도나 습관을 가지는지가 궁금하다고 솔직하게 적었습니다. 

 

첫인상 - 면접.. 망한 것 같은데?

서류 합격 이후에도 솔직히 나름 전략적으로 행동했습니다.

면접 가능 일의 첫 날을 골랐습니다. 임팩트가 없으면 잊혀질 수 있지만, 반대로 임팩트가 있다면 끝까지 기억날 것이라고 생각했습니다.

제가 첫 면접자라면, 저를 기준으로 다른 지원자들을 판단할 것이라고 생각했습니다.

식사 시간 바로 다음 시간을 골랐습니다.
너무 이른 아침은 저도 제대로 머리를 굴릴 수 없을 것 같고,
면접을 보는 사람도 개인 업무를 미뤄야할 것 같다고 생각했습니다.
또 다른 이유로는 밥을 먹고 나면, 사소한 실수에 너그러워 질 것 같았습니다.


아티투의 면접은, 약 2시간 조금 넘게 진행되었고 기술 면접 - 컬처 핏 면접 순으로 진행됐습니다.
정말로 모두가 데자와를 마시고 있는 모습에 신기했습니다.
인상깊었던 포인트가 좀 많은데 CEO인 덴지의 말이 기억에 남습니다.
"다 똑같다. 서울대생 별 거 없다는 것을 아는 것만으로도 달라지는 것이 있지 않을까요"
(나중에 알게 된 사실 - 졸업장에 3개의 학과가 적힌 사람이...)

저는 사실 면접이 망했다고 생각했었습니다.

당시에 너무 오랜만에 개발이야기를 그것도 고민하던 주제를 현업 개발자와 이야기할 수 있다는 사실에 신이 나서 너무 딥하게 이야기했다는 점
(A라는 것을 물었는데, A를 포함한 A- ~ AA까지 떠들었었어요..)
이 때문에 간단명료하게 핵심을 전달하지 못했다는 점,
면접관이었던 한 팀메이트는 질문이 별로 없었던 점 등 때문이었습니다.

자존감이 조금 낮았던 때라서 더 그렇게 생각했던 것 같기도 합니다.

약 일 주일 쯤 후 송신된 오퍼레터를 받고, 시간이 흘러 팀메이트들과 첫 회식에서 합격 소감이나, 경쟁률, 왜 저를 뽑았는지 등에 대해서 자세히 들어볼 수 있었습니다.

1. 기술을 선택하거나 사용할 때 단순 사용한 것이 아니라 이유가 있는 것이 좋았다. - BE 담당 팀메이트

2. 기술 면접 때 기술 발전의 히스토리나 역사를 이야기하는 것이 인상깊었다. 본인도 좋아한다. - BE 담당 팀메이트
3. 상기 이유 들에서 '덕후' 냄새가 났다. - COO
4. 지원서에서 아티투에 바라는 점, 본인이 부족한 점, 부족한 점을 어떻게 극복하고 기여할 것인지 잘 묻어나서 좋았다 - 모든 팀메이트


더 많은 이유들을 들었었지만 기억나는 것은 이정도입니다.

기술 면접 직후 약간의 쉬는 시간 때 COO인 겐도와 사담을 나눌 때 기술 면접 난이도에 대해서 이야기 했었는데,

장난처럼 '더 어렵고 딥해도 좋을 것 같다'는 의견을 남겼었던 것이 실제로 면접 담당이었던 개발자 팀메이트들에게도 공유됐던 것을 알게되어 당황했던 것도 기억에 남네요.

장난처럼 '아 그거, 사실 어려웠는데 뒤에 보시는 분들 더 어렵게 보시라고 그렇게 이야기 해봤어요 :)' 라면서 넘기긴 했지만요.

살아남기 시작, 마음가짐?

"날 선택해 줄 줄은 정말 몰랐어.." 상태에서 합격 이후 계약서 작성 때까지 개인적으로는 고민이 조금 많았습니다.
계약서 서명 이후 당시 비교 대상이었던 타 회사에 비해 연봉이 적은 점을 솔직히 오픈했습니다. (약 천만원 차이났거든요)
그리고, 서비스의 비전에 대해 물었습니다.

단순하게 담당 업무(개발)을 제외하더라도 만든 서비스가 더 사랑받았으면 좋겠고, 오래 유지됐으면 좋겠다고 생각했습니다.

어떻게 성장할 생각이고, 당장 어떤 것을 더 발전시킬 것이고, 현실적인 문제(투자 or 유료화 등등)에 묻고, 대답을 들으면서 선택에 확신이 생겼습니다.

 

지금이나 이 때나 연봉에 대한 제 생각은 같습니다.

받은 것보다 더 하면, 누군가는 가치를 알아줄 것이고 그러다보면 올라갈 것이라고요.

3개월 종료 시점에서 연봉이 올랐고, 거기서 2개월이 지난 시점에서 차이났던 연봉보다 더 큰 스톡 옵션을 받았습니다.
운이 좋았다고 생각합니다.

 

아티투의 문화, 팀원으로의 성장

아티투에는 매 주 회의에서 스프린트 진행 상황에 대해 다 같이 이야기하는 문화와 스프린트 종료 후 다 같이 회고하며 KPT를 공유하는 문화,

그리고 한 달에 한 번 대표인 덴지와 1on1으로 아티투와 팀원이 서로 바라는 것을 공유하는 문화가 있습니다.

스프린트 관련 회의 때는 서비스 성장 지표 등 팀메이트로서 알면 좋은 정보들이 공유되어 좋았고,
개발자로서는 개발 일정을 공유하고, 이 과정에서 리소스 분배와 팀에게 더 이로운 결정을 내리는 방법을 배울 수 있어 좋았습니다.

비즈니스 입장에서 놓치지 말아야할 것과 포기해야할 것,

개발자의 입장에서 놓치지 말아야할 것과 포기해야할 것 들을 고민하는 시간들을 통해 성장할 수 있었다고 생각합니다.

1on1 때는 저는 깨닫지 못하는 제 모습들을 개선할 수 있어서 좋았습니다.
예를 들어, 내 시간(업무 시간 제외)를 활용하면, 앞서 적은 포기해야할 것을 가져갈 수 있다면 시간을 더 사용하는 것이 옳다고 생각하는 저의 입장과

반 년 뒤, 일 년 뒤의 제가 혹시 지쳐쓰러지면 그 부담이 팀원들에게 전파될 수 있다.

그렇기에 수면 시간을 늘리기를 바란다는 덴지의 입장 같은 것들이 있었습니다.

당시 제 스케줄은 아래와 같습니다.

 

06:00~06:30 | 기상

07:00 ~ 08:30 또는 09:00 | 아침 운동
09:30 | 버스 탑승
11:00 | 근무 시작
20:00 | 근무 종료
20:50 | 버스 탑승
21:30 | 집 도착
22:00 ~ 02:00 또는 03:00 | 개인 활동(개인 공부 같은)


출퇴근 이동 간에는 보통 당시에 해결하고 있던 문제들의 방향성이나 관련 자료를 찾으며 보냈고,

고민으로 문제가 해결되지 않으면 집에와서 관련 자료들을 찾거나 코드를 수정하곤 했습니다.

이 후에는 비슷했지만, 하루에 보통 5시간~6시간은 잘 수 있도록 했던 것 같습니다.

덴지의 배려로 근무 시간을 일부 변경해서 8시간 자는 날도 있었고요!

 

개발자로서의 성장

아티투는 새로운 기능도 많지만, 있던 기능이 변경되는 경우도 많았습니다.
그리고 기존의 서비스가 가지고 있던 고질적인 문제들도 당연히 존재합니다.
또, 사용 유저분들의 불편 사항에 최대한 즉각 대응하는 편이기도 합니다.

선배님들이나, 또는 누군가가 보기에는 별 거 없을지도 모르지만, 많은 경험을 한 것 같습니다.

라이브러리 이슈인지 모르고 삽질하다가, 라이브러리의 깃 헙의 이슈 리포트를 통해 버전 이슈라는 것을 알고 버전을 확인했더니 문제가 발생하고 있는 버전이어서 간단히 해결되어 허탈함과 안도감을 느낀 적도 있고,

실제로는 제대로 동작되고 있지 않았으나, 여러 원인들 때문에 지금껏 잘 동작하는 것처럼 보였던 것을 발견하여 처음부터 뜯어 고쳐야했던 적도 있었습니다.

안드로이드에선 지원하지만, iOS에서는 지원하지 않는 경우도 그 반대의 경우도 있어 새로운 방법을 찾아야하는 경우도 있었습니다.

테스트 환경에서는 너무 잘 동작하다가 라이브 서버에서는 동작하지 않는 아찔한 경우도 있었습니다.

유저의 '안 된다.'라는 한 마디 힌트밖에 얻지 못하는 경우가 많아, 여러 가지 가설을 검증하고 부딪히며 많이 배운 것 같기도 합니다.

개발자로서 해본 것 보다 해보지 않은 것들이 많아 즐거웠고, 배울 것들이 아직 많이 남아있어 설레고 즐겁습니다.

 

좋은 서비스, 그렇지 못한 비즈니스

좋은 문화와 좋은 팀원들, 그리고 성장하고 있는 서비스이고 투자금도 많이 남았습니다.

그럼에도, 아티투의 팀 곧 해체합니다.

저도 이제 다시 새로운 팀을 찾아야하는 입장이 되었습니다.


영화나 미국 드라마에서만 볼 것 같은 스타트업이었고, 개인적으로 너무 만족하는 회사 생활이었습니다.
많이 배웠고, 함께 할 수 있어서 성장할 수 있었습니다.


정말 아쉽지만, 
'앞으로 얼마나 더 멋진 팀원을 만나, 더 멋진 개발을 하고 성장할 기회가 생기려고 이러지?'하고 생각하려고 합니다.
마주했던 문제들의 디테일한 내용은 차차 블로그에 포스팅할 예정입니다.

긴 글 읽어주셔서 감사합니다.
읽어주신 분들도, 행복한 하루가, 내일이 되셨으면 좋겠습니다.

도입하게된 계기

현재의 직장의 소셜로그인은 [구글, 카카오톡, 애플]을 사용하고 있었다.

웹 서비스와 웹뷰를 사용한 앱을 서비스하고 있는데 아래와 같은 문제가 있었다.

1. iOS의 문제
P: 카카오톡이 깔려있지만, 이메일과 비밀번호를 입력하는 입력 창이 앱에서 새창 Safari로 열림 -> 로그인 -> 앱으로 돌아오지 못함 -> 돌아 오더라도 앱에서 로그인처리가 되어있지 않은 경우가 있음

P2: 1차 로그인 이후 2차 로그인 때부터 새창에서 앱으로 돌아오지 못함

2. AOS의 문제

P: 크롬 창이 열림 -> 이메일과 비밀번호를 입력하는 창이 뜸 -> 로그인 -> 앱으로 돌아오면 로그인처리가 되어있지 않음


3. CS
P: 특정 기능 업데이트 이후 전체적인 유저들의 로그인이 풀리는 사태가 발생했다. 복합적인 원인이 있었지만, 이 이슈로 인해 앱을 사용하여 소셜로그인을 이용하는 유저들의 CS가 폭증했다. 앞서 서술했던 앱으로 돌아오지 못하는 문제와 로그인처리가 제대로 되지 않는 문제와 맞물려, 아이디 | 비밀번호를 입력하는 과정을 몇 번씩 반복하는 과정에서 신뢰도와 만족도가 떨어지는 것이 체감될 정도였다. (추후 알게된 사실인데, 이 과정에서 평소보다 로그인 시도가 평소보다 40배 정도 증가했었다.)

-> 바로 대응할 수 없는 부분이 있어, 소셜로그인이 아니라 서비스의 아이디와 비밀번호로 로그인하는 기능을 차선으로 안내했지만, 이 부분에서 큰 자극을 받았다. 왜 해결하지 못하지?

4. 팀원들의 이야기

팀원 분들의 '다른 앱처럼 카톡이 깔려있으면, 카카오톡이 열렸으면 좋겠어요',  '아이디와 비밀번호를 입력해야하는게 너무 번거로워요', '어디서 봤는데 이렇게 아이디 비밀번호 입력하는게 최악의 서비스래요. 우리 서비스 최악인가요?'등을 들으며 결심했다.
이거 꼭 해결해야겠다.

 

원인을 파악해보기

Q: 왜 Safari가 새로 열릴까? 로그인 과정이 어떻게 되는거지?
-> 카카오 로그인은 사이드 프로젝트에서도 도입해본 적이 없어서, 정보가 거의 없는 상태였다.

-> 원인을 파악하기 위해 앱 코드를 열었다. 회사 코드기에 구조를 상세하게 서술할 수는 없지만 처음보았을때는 문제가 없는 줄 알았다.
-> 카카오 디벨로퍼 사이트에서 iOS의 로그인 문제를 검색해서 글들을 읽기 시작했다.
-> '간편 로그인'이라는 키워드를 얻었고, 카카오 로그인에서 'REST API 방식' | 앱 SDK | 'JS SDK' 방식을 제공하고 있다는 것을 알았다.
-> 공식 문서를 확인하며, 우리가 원하는 것이 간편로그인이며
리액트 네이티브를 이용한 하이브리드 앱을 사용하고 있는 점, 웹뷰를 사용하는 점 등에 따라 원하는 동작을 구현하려면 간편 로그인을 사용해야 한다는 것을 알았다. 
-> 웹의 로그인 쪽 코드를 보고, 통해우리가 'REST API' 방식을 사용하고 있다는 것을 알았다.
-> 여러 글을 확인하고, JS SDK방식으로 변경해야함을 깨달았다.

-> user-agent를 파악해서, 모바일 환경인지 그렇지 않은지에 따라 js SDK | rest API를 사용할 수 있도록 코드를 구현했다.

-> 빠르게 테스트 코드를 작성하고, iOS에서 트라이했지만, 로그인 처리는 되는데 사파리가 새롭게 열렸다.
-> 웹뷰 코드를 다시 열었다. user-agent에 대한 처리, 스킴 처리나 url이 어떻게 되는지 파악하고 싶었다.
-> talk-apps로 시작하는 url에 대한 처리가 없어 사파리를 통해 여는 코드로 빠지고 있음을 알았다.
(해당 url은 이전에 웹에서 카카오의 PF(플러스 친구)에서 앱을 여는 과정의 트러블 슈팅을 통해 앱실행을 위한 유니버셜 링크임을 알고있었다.)
-> 소셜 로그인을 처리하는 쪽에 인 앱에서 처리할 수 있도록 처리하였다.

Q:다른 대안은 없었나?
Q1. 앱 SDK는 왜 사용하지 않았나?

-> 서비스 특성 상 자세히 기술할 수는 없지만, 앱 SDK를 사용할 수 없는 이유가 있었다.
Q2. @react-native-seoul/kakao-login(https://github.com/crossplatformkorea/react-native-kakao-login) 립을 통해 해결하는 것은?
-> rn을 사용하면, 립을 사용할 수 있다는 것을 검색을 통해 알았지만, 마찬가지로 앱SDK를 사용해야하기 때문에 사용하지 않았다.

Q: AOS? 웹뷰?
-> 여차저차 iOS를 해결하고, AOS로 넘어와서 테스트를 진행했다. 에러가 발생했다.
-> 여기저기 검색해서 문서 | 글을 읽었다.
-> agent에 wv가 있어서 문제라는 의견도, 이를 처리하기 위해서 웹 - 웹앱 간에 메세징 방식을 택하거나, React-native-webview를 포크해서 특정 부분을 고치는 방법이나, 위에 서술한 라이브러리를 사용하는 방법, wv를 제거하는  등, 원인 파악보다는 해결 법에 대한 정보들이 많았다.
-> 'intent'에 대한 정보를 습득했다. intent에 대한 처리가 없음이 원인임을 알았다.
-> RN 폴더 안에 android에 속한 코드를 보기 시작했다. AndroidManifest의 Activity를 보기 시작했다. 이해할 수 없어, 더 안쪽에 MainActivity와 MainAplication 코드를 읽기 시작했다. 코틀린이라 명확하게 이해할 수 없어 다시 검색을 시작했다. 
-> intent처리를 위한 모듈을 작성하고, 패키지로 묶어 MainAplication에 추가해야한다는 것을 알았다.
-> g선생님(Chat GPT)을 통해, 모듈을 작성하고 패키지로 묶어 추가했다. 다른 에러가 발생했다. 스킴을 제대로 처리하지 못하는 문제였다.
-> 웹뷰 코드로 돌아와 whiteList를 통해 인텐트 스킴을 받을 수 있도록 처리해주었다.
-> 로그인 처리가 되었다. 감격스러웠다.

Q: 구글로그인은 갑자기 왜?
-> 문제를 해결하고 테스트를 진행하던 중에 AOS쪽에서 구글 로그인이 제대로 동작하지 않았다. 크롬이 열리고, 흰화면이 보이는 현상이었다.
-> intent를 처리하며 넣은 모듈이 문제인가 싶어 모듈이나 패키지를 다시 확인했지만, 이쪽 문제는 아닌 것 같았다.
-> 다시 검색을 시작하며, intent-filter가 androidManifest에 제대로 작성되어 있지 않음을 알았다.
-> Action과 category를 분류하여 intent-filter를 나누고, 스킴에 대한 부분도 다시 작성하였다.
-> 문제가 해결되었다. 

 

왜 글을 쓰게 되었나?

보통은 처음 나오는 이 부분을 마지막에 적은 이유는, 내 접근 방법이 나와 비슷한 환경에서 고통받는 누군가에게 조금의 도움이라도 되길 바라기때문이다.

검색 실력이 부족해서 오래 걸렸고, 다른 더 빠른 방법이 분명 존재하며, 내부코드라 코드도 첨부할 수 없지만, 서비스마다 다른 환경에서 라이브러리 조차 선택할 수 없는 상황에서 카카오 로그인을 도입하는 누군가에게 도움이 됐으면 좋겠다.

문제 목차

1. 드래그 종료시 새로운 맵 생성해서 Element쌓임
1-1. 드래그 종료마다 재 렌더링

2. 화면 이동 -> 지도로 돌아왔을 때 이전에 클릭했던 마커를 기억함

3. 이용자 위치 수정 시 제대로 렌더링 하지 않음.

4. 마커 중복 렌더링

5. 이용자 위치 권한 거절 시 렌더링 하지 않음

6. Update 이후에 Bounds를 업데이트 하지 않음

7. 6번으로 인한 Marker 손실과 재렌더링

8. 6, 7번으로 인한 위치 변경 이후에도 이전 마커 기억하는 문제

9. 모드 변경 이후에 마커를 새로 그리지 않고, 기존 위치로 이동 시 마커 새로 그리지 않는 문제

10. update변경으로 이용자가 설정 변경하다가 창 종료하면 그 지점의 지도를 기억하는 문제

 

원인 및 해결

1. Drag 종료 시 새롭게 맵 생성 -> Update하는 방향으로 변경

해결 및 대응: 그러나 Update한다는 전제가, 이전에 커스텀(지도 중앙) Control이나 마커를 기억한다는 것이기때문에 물리적으로 Element 생성 요소를 제거함

 

Destroy하지 않은 이유?

-> Element는 깔끔하게 관리되나, 의존성 문제로 재생성 마다 재렌더링되기 때문에 지도 내부 요소들이 전부 깜박거리는 것처럼 보임

 

현 시점의 의견?

->component를 좀 분리하여 렌더링 의존성을 줄일 것 같음. 트리거 방식을 활용하는 것도 더 좋아보임.

2. 게시글 이동 -> 지도로 돌아왔을때 이전에 클릭했던 마커 기억

원인: 마커 생성 시점에서 판단 기준이 되는 상태 요소 중 DOM Reference객체를 잃어버렸기때문에, 관련 요소를 삭제해야하는데 제대로 삭제되지 않음.

 

해결: Atom 업데이트 시점의 상태가 명확하지 않아서였기때문에 useEffect로 관련 상태 초기화하는 로직 추가함.

3&4 이용자 위치 수정 시 제대로 렌더링 하지 않는 문제와 마커 중복 렌더링

원인: 컨텐츠 마커의 경우 드래그 종료 시점마다 map의 영역 변경이 필요, 의존성이 있는 상태 변경마다 계속 업데이트하기때문

 

해결: 중복렌더링 가능성이 있는 요소들 제거 후 Trigger 통해서 최소한으로 로딩되도록 변경

5. 이용자 위치 권한 거절 시 렌더링하지 않는 문제

원인: 브라우저 또는 컴퓨터 설정 상 location 거절하는 경우나 유저가 직접 거절하는 경우 getCurrentPosition method가 동작하지 않기때문

 

해결: 이전 회의때 나왔던 의견처럼 initialPosition을 제주도청 좌표로 변경하고, 유저 지역 상태 변경 후 API요청하여 백엔드에서도 현 유저 상태로 업데이트할 수 있도록 함 -> alert으로 새로고침 이후로도 제대로 업데이트 되지 않는 문제 발생하여 Async/Await으로 로직 동기화, Trigger에 관련 로직 추가하여 Map객체 생성할 수 있도록 변경.

6. Update 이후에 Bounds(좌표 범위) 업데이트 하지 않는 문제

원인: 맵을 Create할때만 Bounds관련 상태 변경하도록 로직처리 되어있었음.

 

해결: Dragend시에도 상태 업데이트 하도록 로직 추가

7. 6번으로 인한 Marker 손실과 재렌더링

원인: 기존에 Marker 관련 변수가 컴포넌트 지역 변수로 선언되어 있어, 지도 Update시점의 Marker는 Undefined, 따라서 업데이트나 생성 시 참조하는 변수가 초기값 또는 Undefined였음.

 

해결:  useState로 지역상태로 기억하도록 변경하고, useEffect통해 Marker 생성 시마다 재렌더링(MarkerUpdate)하여 맵에 생성된 마커 추가할 수 있도록 변경함.

8. 6번, 7번으로 인한 위치 변경 이후에도 이전 마커를 기억하는 문제

원인: 이전에 생성했던 마커의 ID를 Key로 삼아 마커 재생성을 하기때문에 이용자의 위치가 변경되어도 마커를 새로 생성하거나 변경하지 않음.

 

해결: useEffect 의존성 변경과 아래의 로직 추가

1안 -> 모드 변경 시 기존의 마커를 clear해주는 방안

2안 -> map 관련 method(생성이나 업데이트) 사용 중, .every method 활용하여 결과값이 false라면, 안쪽에서 분기문 설정.

Position 값이 변경되면, Marker의 Position도 변경해주는 방안

 

선택: 1안

이유: Drag Event가 일어나 Position변경 -> 생성 -> Every 결과값 확인 하는 것이 느린 상황에서, 마커의 배열에서 찾고, 포지션을 다시 변경해주고 하는 것이 옳지 못하다고 느꼈음.
당시 한 번에 그리는 마커 갯수가 10000개 미만이었기때문에 갯수 총량을 10000개나 10만개라고 본다면,

1안 최대 10만(기존) * 10만(생성)

2안 최대 10만(기존) * 10만(생성) * 10만(변경)의 차이가 있을 것 같아 좀 더 명확하게 업데이트 할 수 있는 로직으로 Refactoring해야한다고 적혀있음.

9. 모드 변경 이후에 마커를 새로 그리지 않고, 기존 위치로 이동 시 마커 새로 그리지 않는 문제

원인: 모드 변경때 마커를 지도에서 제거한 것이지, 실제 객체는 남아있어 이용자 위치가 변경되면 마커 위치가 변경되어야하나, 위치 변경시마다 렌더링하거나 모드 변경시마다 재렌더링 할 수 없는 문제. 명확히는 상태 관리 문제

해결: 마찬가지로 useEffect의 의존성 변경과 업데이트 이후 .refresh method(Map에 있는)활용하여 모드 변경마다 지도 내용 새로고침해줄 수 있도록 변경.

10. Update 변경으로, 이용자가 설정 변경하다가 창 종료하면 그 지점의 지도로 위치를 기억하는 문제

원인: localStorage에 저장된 user의 초기 Position 기준으로 맵을 새로그리게 되는데, 브라우저 종료되더라도, position정보는 손실되지 않기때문에 일어난 일

 

해결:
1안-> 저장 장소를 sessionStorage로 변경

2안-> 초기 렌더링 시 position 초기화

 

선택: 2안

이유: 1번으로 선택할 경우 탭 닫았다가 다시 접근하는 경우 의도대로 동작하지만, 일반 새로고침으로 돌아오는 경우 마지막 지도의 위치를 기억하고 있음. 초기 렌더 초기화해주는 로직이 필요함.

 

만약 1안으로 한다면?
interface 생성해서 storage에서 가져오는 데이터 타입 맞춰주고, sessionStorage로 AtomStorage의 메서드와 타입 변경해줘야한다. 추가로, 메인에서 초기렌더링 시 동작하는 useEffect에서 한번 초기화 해주는 로직이 필요함.

서론

최근, 사이드 프로젝트(클론)을 진행하며 했던 고민을 정리해두어야할 것 같아서 적게되었다.
기억을 더듬어 사고의 흐름대로 한번 따라가보려고 한다.

고민의 대상

고민의 시작은 드롭다운의 컨텐츠 관련 컴포넌트였다.

내 고민의
주범이 되었던
코드 조각들

이전에 Refactor쪽에서 다뤘듯, 언뜻 보면 별로 문제가 없어보인다.

근데, 내 주관적인 시점에서는 더 좋아질 수 있을 것 같다고 생각했다.

1. 고민의 시작점: Route 정의가 반복되는 것 같은데, 문제가 있지 않나? 나중에 갯수가 많아진다면?

2. 그렇다면 import가 이 컴포넌트에서 될 필요가 있는가?(관리 책임이 여기에 있나?)
3. 이 컴포넌트에서 맵을 돌려서 렌더링을 해야하는 이유, 또는 정의를 해야하는 이유가 있나?

 

A.1 잘 하면, 공용으로 뺄 것들을 분리할 수 있을 것 같은데?

문제 1번을 바라보면서 고민했다.

"아, 원하는 url + / + id + / + label 꼴인데, url, id, label을 바깥에서 받아서 그냥 리턴하는 함수를 만들자"

-> 저형태로 반복되는 url을 만들어낼때 편하겠다 ->근데 label과 endpoint가 다르면 어떡하지? -> 일단 보류

 

"흠, label이랑 JSX이름이 같네, label을 집어 넣으면 JSX를 리턴하는 function을 만들자 -> label이랑 JSX이름이 다르면 어떡하지?

->

A.1-1번, 배열로 JSX들을 들고 있는다 -> 해당 function call할때마다 전체 배열(지금은 4개지만 추후에 몇개가 될지 모를)다시 만들거나, 전역으로 관리해야함 -> 기각

 

A.1-2번, 어떻게 이름을 넣으면 동적으로 importing해서 JSX를 꺼내오는 방법은 없나? -> lucide-react를 열어보자

이런 형태이고,
이렇게 꺼내지는데..

-> 일단 동적 import 안됨, 방법이 있을지 모르나 내 지식선에서 해결 불가 -> 꺼내지게 하려면, 저 선언들 중 사용할 것들을 다 꺼내놓고name + Icon형태로 받아서 찾아쓸 수 있게 해야함 -> 일이 커짐 -> 기각

 

A.1-3 ifelse? 좀.. 찝찝, "요구 사항을 정리하면 label을 집어넣으면, label에 맞는 JSX를 리턴했으면 좋겠다."니까 Switch를 써보자.

className 중복도 거슬리지만, 불확실한 컴포넌트를 props로 던져서 CSS를 다시 입히는 행위를 할 최선의 방법이 생각나지 않았다.

 

이제 이걸 반복시키면 되는데, 어떻게 할까

label을 string으로 받아서 split하거나, 그냥 string[]로 받는 방식이 생각났다.

후자로 선택한 이유는, 쪼개고 집어넣고, 쪼개고 집어넣고 할때 드는 비용보다 n개짜리 배열을 통으로 집어넣는게 수가 커질수록 더 나을 것 같다고 생각이 들었기때문이다. 맵을 사용하는게 좀 찝찝하긴 하지만, 결과적으로 {}[]를 리턴해야하는 건 맞으니까 괜찮다고 생각했다.

 

보류한 url로 돌아와서, 현 시점에서 특이한 사항은 board의 경우 아무것도 붙지 않는다는 것이고 예측되는 특이사항은 앞서 말했듯 label과 endpoint가 다를 경우이다. 그리고 고려사항은 label을 lowcase로 변환해야한다는 것이다.

 

따라서, suffix(endpoint)를 받았을때, 
Board면 ? endpoint없이 `${url}/${id}` : `${url}/${id}/${toLowCase()`
여기에 추후 다를 경우 추가되면 switch해서 변환한 것을 꺼내오거나 변환하는 과정을 추가하자고 결정했다.

현시점에서는 board일 확률 < board가 아닐 확률이니  

return suffix !== "Board" ? `${url}/${id}/${suffix.toLowerCase()}` : `${url}/${id}`

로 처리함.

A.2,3 관리 책임?

내가 생각했을때 해당 컴포넌트의 주 역할은 컨텐츠인 item을 렌더링하는 것이다.
routes같은 경우 여기서 그려야할 컨텐츠이기때문에 여기서 들고 아래로 내려주는 것이 맞다고 생각했다.
정의나 맵 함수 사용 등은 컨텐츠를 렌더링하는 veiwer역할을 하는 컴포넌트에서 해야한다고 생각했다.
깊이가 깊어지는 것에 대해 고민했지만, 정의에 대한 책임을 분리하는 것이 더 이득이라고 생각했다.

따라서, 이 컴포넌트에서는 위 내용을 정의하지 않는 것이 좋고, lucide-React의 icon들도, useRouter, usePathname, AccordionContent and Trigger등도 존재하지 않아도 된다고 생각했다.

 

결과물

1. isActive,Expanded, onExpand, organization 등은 드롭박스보다 더 위인 organization에 존재해야함으로 props로 받는 것이 타당.

2. Routes의 요소는 navitem의 컨텐츠이기때문이 해당 컴포넌트 안에 존재하고, props로 내려주는 것이 타당.

 

3. Trigger, Contents, Item에 필요한 요소들은 props로 전달하고 세부 정의는 아래 컴포넌트에서 하는 것이 타당. 

4. route를 브라우저 히스토리에 push하거나, pathname을 사용하는 것들은 Contents안에서만 하는 것이기때문에 Contents로 이동

5. Trigger에서 필요한 것들도 Trigger에서만 사용되도록 이동

 

끝!

 

Docs Update가 안되고있다..


Shadcn의 docs 컴포넌트에서 yarn cli를 긁으면, npx명령어로 CLI Command가 저장된다.

Yarn을 사용하면서 npx키워드를 사용하는게 좀 뭔가 찝찝해서 깃허브를 찾기 시작했다.

Shadcn Github 이슈나 PR쪽 확인 해보니, yarn의 1.x.x버전에서도 npx는 당연히 동작한다.

동치어가 없다. dlx를 지원하지 않는다. dlx는 2.x부터 지원한다. 등 여러 의견이 있는 것을 확인함.

 

yarn과 npx 관련 글들도 이전에는 동치어가 없다, 20년도즘엔 dlx가 있다.

yarn 공홈에서도 2.x에서 "npx쓰지마세요, yarn dlx 만들었어요!!" 라는데.. 흠 dlx가 안되는걸까?

 

-> 더 찾아보니 문제없이 사용하시는 분들도 계시고, 나또한 실제로 사용은 가능 하고, yarnpkg shadcn docs에서도 해당 커맨드를 적어놓은 것을 보면 음 아직 잘 모르는 부분이 있는 것 같다만, 계속 해서 공식 깃에 이슈가 올라오는 것을 보면 충분히 헷갈려하거나, 찝찝해하는

나같은 사람이 있는가보다. (npx말고 yarn껄로 해줘!라는 댓글도 있었음)

 

관련해서 이슈를 읽으면, shadcn pro쪽에서 승인을 받아야해서 승인을 넘기는데 블락당하거나 중복된 내용이라고

close되는 것을 보면 수정하려고 하는 것 같은데, 아직 적용되지 않은 것 같음.

->동일한 커맨드 사용한다고 내버려두기로 결정했다는 글을 확인... 흠흠..

 

아무튼 어디선가, 나처럼 관련 이슈나 커맨드를 찾아 헤매는 분들을 위해

berry 아니어도 dlx 쓸 수 있어요.

 

Components 추가

yarn dlx shadcn-ui@latest add [component]

 

ex)yarn dlx shadcn-ui@latest add sheet

 

 

+ Recent posts