This month I Learned - 2021년 7월

(내용 정리에 생각을 덧붙일 경우 🤔 이모지를 첨부합니다.)

General

  • How To Create An On-Off Button For Your Mind - 마음 속 켜기/끄기 스위치 만들기 (미디엄 유료)

    • 부정적이든 긍정적이든 생각을 너무 많이 할 수록 정신력 소모가 크다. 그래서 때때로 뇌를 쉬게 해 주어야 한다. 이 글은 뇌를 쉬게 해주는 방법 몇 가지를 소개한다.
    • 매일 저널을 작성한다.

      • 우리 모두 머릿속에 계속 재잘대는 무언가가 있는데, 종이에 그 생각을 적어내려가면 조금 다루기 수월해진다.
      • 특별한 시스템을 구축하거나 대단한 강좌를 들을 것 없이 생각을 그냥 써 보라.
    • 매일 명상하라.

      • 명상을 통해 머릿속에 떠오르는 온갖 소음 같은 생각을 무시하는 법을 익히게 된다.
      • 명상을 직접 마음의 스위치로 이용할 수도 있다. 한 주제에 집중하다 짧은 명상 후 다른 주제로 넘어가는 식이다.
    • 일정 시간에 전자기기를 멀리 해보라.

      • 어떤 연구 결과에서 잠들기 전에 아이패드를 사용한 모집단과 종이책을 본 모집단을 비교해보니 아이패드를 사용한 그룹의 수면의 질이 더 낮다고 한다.
      • 때때로 늦게까지 일을 하게 되는 경향이 있다. 이러한 습관은 정신 건강과 생산성을 해치며, 밤에 불필요하게 뇌를 활성화시킨다.
      • 적어도 스크린 있는 기기를 잠들기 한시간 전에 멀리하는 습관을 들여보라.

Developer

  • 초보 웹 개발자를 위한 학습 안내서

    • 웹 개발 기준으로 프론트엔드, 백엔드, 인프라 개발자 정도를 크게 분류하여 볼 수 있다. 점점 요구사항이 고도화되고 그 요구사항을 구현하기 위한 기술도 다양하고 난이도가 올라가면서 여러 분야에 걸친 개발을 하기 보다 한쪽 분야에 특화된 개발을 하게 되는 경우가 많아졌다. 하지만 무언가를 만들기 위해 다른 분야를 이해하고 기본적인 내용을 익혀두는 것이 좋다.
    • Kamran Ahmed가 만든 웹 개발자 로드맵을 통해 각 분야별 필수적인 내용과 최신 트렌드를 참고할 수 있다.
    • 코딩테스트는 알고리즘 문제 풀이와 과제 구현 형식이 있다. 알고리즘 문제 풀이의 난이도는 회사마다 다르지만 적어도 정답을 보고 공부하면 연관 알고리즘을 이해하고 나중에라도 더 쉽게 구현할 수 있는 수준을 갖추는 것이 권장된다.
    • CS 커리큘럼, 주로 다루게 되는 프로그래밍 언어 기본, 프레임워크의 동작 원리 등의 기본기를 익히는 것은 지름길이 없다. 평소에 틈틈이 호기심을 가지고 기능한 한 깊이 학습하는 방법이 최선이다.
    • 공부 방법

      • 클론 코딩: 단순 결과물을 내는 것 뿐 아니라 제작 과정에서 배운 것과 어려웠던 점, 해결했던 내용을 정리해야 한다.
      • 토이/사이드 프로젝트: 작은 규모로 혼자 해보는 것을 추천한다. 팀 단위는 운영이 어렵다.
      • 스터디 모임: 혼자 하면 학습하기 어려운 것을 빠르게 습득하며, 여러 사람과 교류를 통해 학습 능률을 올리거나 자극받을 수도 있다.
      • 모각코(모여서 각자 코딩)
      • 온라인 강의
      • 컨퍼런스: 지나간 컨퍼런스는 잘 안보게 되어 가급적이면 라이브로 보는 것을 권장
      • 책: 본문의 책 추천 참고
      • 유튜브
      • 팟캐스트
    • 디테일 높이기

      • 차별화: 이력서 등을 작성할 때 해본 것, 시도한 것 등이 더 잘 드러날 수록 좋다.
      • 기록하기: 블로깅 및 노트 정리
    • 추가 팁

      • 질문을 잘 하라
      • 목표를 정하라
      • 환경을 바꾸어보라
    • 회사 고르기: 절대적으로 좋은 회사는 존재하기 어렵지만 구성원이 개선하려는 의지와 시도를 하는 공간에 있는 것이 중요하다.
  • How to Improve Software Architecture Skills Daily | by Ella sheer | Jun, 2021 | Level Up Coding - 날마다 소프트웨어 설계 스킬을 향상시키는 방법

    • 마주치는 문제마다 두 개 이상의 해결책을 찾아보자.

      • 문제 해결 능력 & 창의력을 향상시킨다.
      • 현재 하는 일이 디버깅이거나, 새로운 코드를 작성하거나, 리팩토링이거나, 개발자의 임무는 문제를 해결하는 것이다. 그 문제의 범위는 이미 있는 코드를 재사용하거나 데이터베이스를 선택하는 것 까지 다양하다.
      • 이미 답안을 골랐다 해도 실험삼아 시간을 들여 다른 해결책도 찾아보자.
    • 상충하는 부분의 목록을 만들고 그 중에서 해결책을 골라라.

      • 우선순위를 정하는 능력과 깊은 사고를 향상시킨다.
      • 더 많은 기준을 발견하고 왜 그렇게 해야하는지, 혹은 하지 말아야 하는지 다른 고려 사항에 대해 알아보자.
      • 해결책을 되짚어보며 어떻게 각각의 해결책이 각각의 기준에 맞는지 순위를 매겨보자. 판단을 명확하게 하면 “좋지 않은” 해결책이라도 어떤 기준에는 좋은 순위에 해당될 수 있다.
      • 마지막으로 그 기준의 우선순위를 정한다. 내가 속한 조직에서 어떤 부분이 가장 중요한지 이해하려고 하고, 나의 경우에 가장 효과적인 해결책을 선택한다. 이것이 테크 리드(Tech Lead)가 하는 일이다.
    • 다양한 사람과 기술적인 논의를 해 보라.

      • 커뮤니케이션 능력과 기술적 이해를 향상시킨다.
      • 각각의 사람은 나의 일을 이해하기 위해 각기 다른 능력을 가지고 있다. 그들과 이야기하며 디테일에서 한 발 물러나 상위 개념으로 일반화하여 설명할 수도 있고 디테일을 집중적으로 설명할 수도 있다. 누군가에게 문제를 설명하거나 논의를 하면서 내 자신이 특정 이슈를 얼마나 이해하고 있는가 확인할 수도 있다.
  • 한글과 유니코드 | Pusnow

    • 유니코드에서 한글이 어떻게 다루어지는지 정리된 글. 한글 + 유니코드 관련 이슈는 이 문서부터 살펴보면 되겠다 싶을 정도로 잘 정리되어 있다.
    • 유니코드에서 한글을 표현하는 방식은 아주 많다. 그리고 같은 글을 표현하더라도 표현하는 방식도 여러가지가 있다. 따라서 텍스트를 저장할 때 한 가지 규칙을 이용하여 정규화하여 저장하는 것을 권장한다.
    • 정규화 규칙

      • NFD

        • 모든 음절을 정준 분해하여 한글 자모 코드를 이용하여 저장한다.
        • -> ㄱ + ㅏ + ㄱ
        • 현대 한글과 옛 한글을 동일한 방식으로 저장하지만 NFC 방식과 비교하여 텍스트의 크기가 커진다.
        • macOS에서 주로 사용한다.
      • NFC

        • 모든 음절을 정준 분해 후 정준 결합(Canonical Composition)한다.
        • ->
        • NFD 방식보다 텍스트의 사이즈는 작아진다.
        • 현대 한글과 옛 한글이 다른 방식으로 저장되므로 텍스트를 처리할 때 유의해야 한다.
        • GNU/Linux 시스템, Windows에서 주로 사용한다.
      • NFKC, NFKD

        • 한글자모 한글음절 영역 외에 한글 유니코드 영역을 처리할 때 유용하게 사용된다. 자세한 내용은 본문의 표 참고.
        • 단순히 자모를 분리/결합할 뿐 아니라 각종 한글 특수 기호 또한 정규화를 진행한다.
        • 모든 기호를 오직 한글 음절/한글 자모로 변환하므로 한글 특수기호를 다룰 일이 있다면 유용하게 쓰일 수 있을 것이다.
    • 단위

      • 한글 문자열은 한글 음절의 연속이다. 또한 한글 음절은 한글 자모의 연속이다. 따라서 음절 단위로 처리할 것인지 자모 단위로 처리할 것인지 정해야 한다.
      • 자모 단위

        • 일반적으로 한글을 처리할 때 자모 단위를 사용하는 경우는 많지 않으나, NFD 방식으로 정규화한 뒤 처리하면 큰 문제가 없을 것이다.
      • 문자 단위

        • NFC로 정규화된 유니코드 문자열의 문자 하나를 하나의 음절로 간주한다.
        • 옛한글의 경우 음절이 하나의 문자로 저장될 수 없으므로 문제가 생긴다. 따라서 음절 단위를 처리하기 위한 다른 방식이 필요하다.
    • 정렬

      • 현대 한글의 경우 문자열이 NFC로 정규화되어 있다면 단순히 유니코드 코드 포인트로 정렬해도 괜찮은 편이다.
      • 옛한글은 표준화된 자소 규칙이 존재하지 않으므로 별도의 논의가 필요하다.
      • KS X 1026-1에 한글 정렬 알고리즘이 소개되어있으니 참고할만 하다.
  • 소프트웨어 (SW) 개발 경험이 먼저다 :: Channy’s Blog

    • 주니어 시절의 개발 경험은 정말 중요하다. 좋은 시니어 개발자와 코드 작성, 리뷰 및 배포 프로세스를 갖춘 회사에서 우선 성장하여 기본기를 갖추어야 IT분야의 신기술이 출현할 때 그 기술을 판단하고 소화하는 능력을 갖출 수 있다.
    • 다양한 딥러닝/ML(머신 러닝) 분야가 각광받으며 해당 분야의 진입 장벽이 낮아진 것은 사실이다. 소프트웨어 개발자들도 데이터 사이언티스트 등으로 전향하는 경우가 있지만 이 또한 SW 개발 경험이 어느정도 뒷받침 되는 것이 좋다.
    • 대부분 IT 분야 신기술은 전형적인 SW 개발 방식과 유사하게 진화해왔기 때문이다. 초창기에는 인공지능 분야에 박사급 연구 인력이 필요하다는 인식이 있었으나, 현업에 활용하기 위해서 SW 개발을 하는 것과 유사한 도구와 프로세스, 인력이 필요하다.
    • ML 서비스는 기존의 SW 개발 프로세스와 유사하며 모든 과정에서 개발 경험과 노하우가 접목되어야 한다.
    • 클라우드 컴퓨팅 기술 변화도 SW 개발자의 영역을 확대하였다.
    • 기존에 물리적 장비로 취급하던 것들(서버, 스토리지, 라우터 등)을 코드로 다룰 수 있는 환경이 되면서 시스템 엔지니어들이 하던 일을 SW 개발자들이 직접 할 수 있게 되었다. 그 예가 DevOps다.
    • 빅데이터 분야도 유사하다.
    • 필자가 Daum에 재직하는 동안 내부 빅데이터 사례들은 주로 엔지니어에 의해 구현되었다.
    • 프론트엔드의 경우도 과거 HTML + CSS + 약간의 자바스크립트 활용에 그쳤던 초창기를 거쳐 Ajax 기반의 역동적인 웹 애플리케이션으로 넘어가며 단순 UI 개발자를 넘어 많은 SW 개발자들이 프론트엔드 개발 영역을 개척했다. 지금은 MVP, MVVM 같은 패턴이 흔히 사용되기도 한다.
    • 오랜 시간동안 IT 업계는 위에서 언급한 다양한 기술과 분야를 맞이했고, SW 개발자들은 그 영역에서 주로 활동하고 있다. 앞으로 우리가 알지 못하는 기술이 출현하겠지만, 그 토대는 결국 소프트웨어 개발이 될 것이므로 기본기를 챙기는 것이 중요하다. 너무 조급해하지 말고 긴 호흡으로 멀리 볼 것을 권한다.
    • 🤔 그래서 본문에서 언급한 기본기 가 뭐지? 좋은 코드를 작성하는 방법? 다른 사람의 코드를 잘 보는 방법? 더 나은 개발 프로세스와 그 프로세스가 있는 환경에 적응하는 방법?
  • 2021 개발자 필독서 - 교보문고

    • 개발자의 학습, 성장을 이야기할 때 항상 언급이 되는 스터디 셀러 55권.
    • IT분야 8년차 MD가 커뮤니티와 블로그를 돌아다니며 수집한 리뷰를 꼼꼼히 읽어보고 선정하였다.
    • 읽어보지 못한 책이 무척 많지만, ‘이정도는 꼭 읽어봐야지’ 라고 생각했던 책들은 모두 들어있어서 약간의 안도감을 느낀다. 언젠가 충분히 읽어내리라 다짐하게 되니까.
    • 그런데 내가 읽은 책 중에 만족했던 ‘소프트웨어 장인’ 은 소개가 되어있지 않네.
    • 다른 것은 몰라도 주니어에게는 ‘프로그래머의 길, 멘토에게 묻다’ 를 우선적으로 읽어보라 추천해주고 싶다.
  • 잘 정리된 이력서보다 중요한 것 – by minieetea

    • 지원동기가 명확한가?

      • 단순히 경력사항이나 한 일이 깔끔하게 정리된 이력서만으로는 메리트가 떨어질 수 있다.
      • 지원동기를 통해 지원하는 회사에 얼마나 관심 있는지 판단할 수 있다.

        • 무엇을 왜 하고 싶은가?
      • 지원동기를 보고 퇴사를 언제 할 수 있을지 판단할 수 있다.

        • 지원동기가 퇴사 동기가 될 수 있기 때문이다.
        • 회사가 잘못되는 경우는 예외
      • 지원동기를 통해 ‘동기’ 를 어떻게 만들어낼지 판단할 수 있다.

        • 스스로 동기부여를 어떻게 하는가?
    • 어떻게 일하는가?

      • 일하는 방식에 따라 온보딩의 난이도를 결정한다. 지원자가 해 왔던 일하는 방식이 지원하는 회사의 일하는 방식과 어울리다면 면접까지 점수를 후하게 먹을 수도 있다.
      • 얼마나 많은 사람과 다양하게 일해봤는지 경험을 적어두면 커뮤니케이션 숙련도를 미리 알 수 있다.
    • 퍼포먼스를 내는가?

      • 나랑 같은 수준의 사람이 저 회사에 있는데 나는 왜 탈락이지? -> 정확한 탈락 사유다. 이미 똑같은 사람이 너무 많기 때문이다. 같은 일을 할 때 더 큰 성과를 낼 수 있다는 것을 증명할 필요가 있다.
      • 과정만큼의 결과는 중요하다. 개발자의 경력기술서에서 성과나 결과를 적어낸 경우는 아주 적더라.
      • 🤔 이력서 갱신할 때마다 어려워하는 부분이긴 한데, 실제로 내 작업물이 조직이나 회사의 이익에 어떻게 기여했는지 명료하게 표현하는 데이터를 그러모아 결론을 내는 것이 쉽지 않아서 그럴까?
      • 퍼포먼스를 내고 싶은 것과 내왔던 것은 다르다. 새로운 기술을 잘 사용해본 것이 도움이 되는 경우도 있고, 현재 많이 알려진 기술 스택으로 최고의 퍼포먼스를 내는 사람을 원하는 경우도 있다.
    • 어려움을 겪었는가?

      • 살면서 어려웠던 경험, 불가능을 가능하게 했던 경험 등 부정적인 경험은 대부분 성장의 뿌리가 된다.
      • 어려움의 경험은 스스로 성찰하는 성격인지 알게 한다. 어려움을 겪었을 당시 그 원인은 무엇이었는지 깊게 들어가보고(5 Whys를 생활화해보자), 진짜 원인이 무엇이었으며 어떻게 해결하려고 노력했는지 이야기를 적어낼 수 있는가? 반면 어려움의 깊이가 너무 얕을 때는 지원자의 현재 수준을 가늠하게 만든다.
      • 어려움의 경험 이후 어떤 일을 해왔고, 그만큼 성장한 내가 다시 비슷한 상황에 맞닥뜨릴 때 충분히 대처할 수 있는지 보여줄 수 있으면 좋다. 면접에서도 충분히 이야기 할 수 있는 부분이라 어려움을 겪었던 상황만이라도 적어도 좋겠다.
    • 본인을 잘 이해하는가

      • 자신의 생각 뿐 아니라 타인이 자기를 어떻게 평가해왔는지도 중요하다. 그리고 지원하는 팀의 JD를 보고, 팀이 어떻게 일하고 무엇을 위해 일하는지 알 수 있다면 지원해보라.
      • 지원하려는 팀과 나의 지향점이 맞는지 판단해야 한다. 간단한 예로 기술지향적인 사람이 비지니스 지향적인 팀과 맞지 않을 가능성이 높다.

JS / TS

  • JS Is Weird - JS의 독특한 부분을 퀴즈로 엮은 것

    1. true + false : 답은 1이다. true 는 number 타입일 때 1이고 false 는 0이기 때문이다.
    2. [,,,].length: 답은 3이다. 마지막 콤마는 트레일링 콤마이다.
    3. [1, 2, 3] + [4, 5, 6]: 답은 “1,2,34,5,6” 이다. 배열은 더하기로 연결할 때 해당되는 내용의 문자열로 변환된다.
    4. 0.2 + 0.1 === 0.3: 답은 false이다. 꽤 유명한 문제인데 floating point의 고질적인 문제라고 해야하나.
    5. 10,2: 답은 2이다. 콤마로 이어진 표현식은 마지막 값을 리턴한다.
    6. !!"": 답은 false이다. 빈 문자열은 Falsy값이고, 이를 불린값으로 변환하는 !! 를 썼다.
    7. +!![]: 답은 1이다. 빈 배열은 Truthy이라서 불린값인 true로 변환되었고 거기에 더하기 기호를 사용하여 숫자 타입으로 변환되었다.
    8. !!!true: 답은 false이다.
    9. true == "true": 답은 false이다. 두 값은 숫자 타입으로 변환되는데 "true"NaN 으로 변환된다.
    10. 010 - 03: 답은 5이다. 010 은 8진수로 다루어진다.
    11. "" - - "": 답은 0이다. 빈 문자열은 0으로 치환되었고, 0 - -0 라는 표현식이 된 것이다.
    12. null + 0: 답은 0이다. null 은 숫자 0으로 변환된다.
    13. 0/0: 답은 NaN이다. 다른 언어라면 ZeroDivisionError 같은게 나야겠지만 JS는 그런 류의 에러는 취급 안한다.
    14. 1/0 > Math.pow(10, 1000): 답은 false다. 두 값은 모두 Infinity 로 처리된다.
    15. true++: 답은 SyntaxError이다. 놀랍게도 true 값을 변수에 담아두고 그 변수에다 ++ 연산을 하면 정상적으로(?) 2가 된다.
    16. "" - 1: 답은 -1이다. 빼기 연산은 문자열처럼 취급되지 않고 숫자끼리의 연산에만 사용하는 것으로 간주한다. 빈 문자열은 0으로 간주된다.
    17. (null - 0) + "0": 답은 “00”이다. null 은 0으로 간주된다. 그리고 바로 위의 문제에서 언급한 것처럼 문자열과 다른 타입끼리 연산할 경우 문자열 연산으로 간주된다.
    18. true + ("true" - 0): 답은 NaN이다. "true" 를 숫자형으로 변환하면 NaN 이 나온다. 이 시점에서 연산의 결과는 결정된 것이다.
    19. !5 + !5: 답은 0이다. 0 이상의 숫자는 Truthy 값이다. 이를 불린 값으로 뒤집었으니 0이다.
    20. [] + []: 답은 ""이다. 3번 문제대로 배열을 문자열 변환하면 내용물 그대로 나온다. 하나 유의할 점은 [,] 도 문자열로 변환하면 빈 문자열이다. 마지막 콤마가 트레일링 콤마 처리되었기 때문이다.
    21. NaN === NaN: 답은 false이다. IEEE-754 위원회에서 처음 이렇게 결정되었다고 한다. 다만 Object.is 로 비교하면 true 값이 나온다.
    22. NaN++: 답은 NaN이다.
    23. undefined + false: 답은 NaN이다. undefined 는 숫자로 형변환되지 않고 NaN 이 나온다.
    24. +0 === -0: 답은 true이다. Object.is 로 비교하면 true 값이 나온다.
    25. - "" + + "1" * null - [,]: 답은 0이다. 표현식을 치환하자면 -0 + 1 * 0 - 0 이 된다.
  • export default thing is different to export { thing as default } - JakeArchibald.com - default export, named + default 의 차이

    • import { thing } from './module.js' 형식으로 파일을 가져올 때 thing 은 레퍼런스를 가져오는 것이기 때문에 원래 파일에서 재할당 등의 변경을 일으키면 가져오는 쪽에서도 변경이 일어난다.
    • let { thing } = await import('./module.js'); 형식의 import는 일반적인 객체 분해처럼 가져온 값을 새로운 식별자에 할당한다.
    • 하지만 export default 는 몇 가지 다르고 특이한 동작이 있다.

      • export default 는 값을 직접 전달한다. 이는 export default 가 선언문이 아니라 표현식으로 동작하기 때문이다. 그래서 export default thing 을 하면 thing 이 export 되기 전에 어딘가 숨겨진 변수에 할당되는 것처럼 동작한다.
      • export { thing as default } 는 레퍼런스를 전달한다.
      • export default function 도 해당 함수의 레퍼런스를 전달한다. 만약 export default 에 함수가 전달되는 것도 표현식처럼 동작하게 된다면 가져다 쓰는 쪽에서는 아직 할당되지 않은 함수라서 undefined 를 맛보게 될 것이다.
      // These give you a live reference to the exported thing(s):
      import { thing } from './module.js';
      import { thing as otherName } from './module.js';
      import * as module from './module.js';
      const module = await import('./module.js');
      // This assigns the current value of the export to a new identifier:
      let { thing } = await import('./module.js');
      
      // These export a live reference:
      export { thing };
      export { thing as otherName };
      export { thing as default };
      export default function thing() {}
      // These export the current value:
      export default thing;
      export default 'hello!';
    • 그래서 모듈의 순환 참조 문제를 겪게 된다면, 이번 글에서는 함수를 위주로 다루었는데, foo 라는 함수를 먼저 선언한 뒤에 export default foo 를 하고서 순환 참조를 하게 되면 아직 foo 의 할당이 일어나기 전이기 때문에 문제를 겪게 될 것이다. 하지만 export default function foo ... 는 위에서 언급한 특수 케이스에 포함되어 바로 레퍼런스가 연결되기 때문에 문제없이 참조할 수 있다.
    • 하지만 되도록이면 순환 참조를 피하도록 하자
  • JavaScript iterators and generators: A complete guide - JS 이터레이터와 제네레이터 가이드

    • 더글라스 크록포드의 ‘자바스크립트는 왜 그 모양일까?’ 를 읽을 때 저자는 현재 ECMAScript의 제네레이터 구현체에 불만이 많았다. 나야 적극적으로 활용하고 있진 않아서, 잊을 때쯤이면 이렇게 키워드를 접하고 복습을 하게 된다.
    • 어떤 객체도 이터레이터 프로토콜에 따른 함수를 구현한다면 이터러블하게 동작시킬 수 있다. 흔히 접하게 되는게 someObj[Symbol.iterator] = function () {...} 형식으로 구현하는 것이다.
    • 이터레이터 객체는 next 함수가 있는 객체이며 이 함수의 리턴 값은 done, value 속성으로 이루어진 객체다.
    • 이터레이터 함수가 next 함수를 가진 객체를 리턴하기 전에 클로저를 활용하여 루프에 따라 점점 증가하는 변수를 다룬다던가 하는 방식으로 구현하는 방법 등이 설명되어있다.
    • 이터레이터는 강력하지만 만들 때 굉장히 신중해야한다. 의도치 않은 버그를 낳고, 이터레이터 객체의 next 를 호출하며 다음 단계로 넘어갈 때의 내부 로직을 다루는 것이 어려울 수 있다.
    • 제네레이터는 이터레이터를 만드는데 사용한다. 굉장히 추상적이지만 그게 전부다. 특정한 경우에 제네레이터는 아주 유용하게 활용될 수 있는데, 원하는대로 정지했다가 다시 재개할 수 있는 제네레이터의 특성을 적극적으로 활용하는 것이다.

  • Functional-ish JavaScript. Functional programming is a great… | by Daniel Brain | Medium - 함수형’스러운’ 자바스크립트

    • 함수형 프로그래밍이 많이 언급되면서 ‘지금 코드베이스가 이미 전부 불순하고 사이드 이펙트가 넘치는데 이걸 뭣하러, 어느세월에 함수형으로 고치냐?’, ‘나는 객체지향적 코드를 작성하는걸 선호하니까 함수형 프로그래밍은 나와 안맞다’ 같이 취하거나 아예 버리는 것처럼 접근하는 사람들이 많이 보인다.
    • 하지만 함수형 프로그래밍은 오히려 실용적으로 필요한 부분부터 접근하는 것이 더 유용하다. 그래서 코드를 ‘함수형스럽게(functional-ish) 작성하는 법을 소개한다.
    • 순수한 함수를 작성하지 못하더라도 예측 가능하게 작성하라.

      • 어떤 함수가 사이드 이펙트를 일으킨다고 할 때 가능하면 같은 인자일 때는 같은 사이드 이펙트가 일어나게 만들어라.
      • 어떤 함수가 외부 스코프에 있는 상태를 참조한다고 하면 그 상태 변화에 탄력있게 대응할 수 있어야 한다.
      • 가능하면 전달된 패러매터를 변경하지 말자.
    • 상태를 보수적으로 다루자.

      • 이미 있는 다른 값이나 입력에서 충분히 파생될 수 있다면 “정말 이 상태가 어딘가에 담겨있어야 하는가?” 라고 고려해보자.
      • 한번만 사용되고 버려질 값이라면 어딘가에 담아둘 필요가 없다.
      • 나중에 다시 필요한 정보인가, 아니면 앱의 라이프사이클에 따라 계속 변화하는 것인가? 상태를 저장하고 다루는데는 그에 걸맞는 이유가 필요하다.
    • 상태를 다루기로 했다면, 스코프를 활용하라.

      • 현재 블록 안에서만 필요하다면 const, let 이 다루어지는 범위 안에서만 활용한다.
      • 함수 호출 내내 필요하다면 함수 최상단에 변수를 선언하여 활용하자.
      • 비동기 호출 등으로 더 오래 남겨놓아야 한다면 클로저를 활용한다.
      • 리액트를 사용한다면 상태는 가능하면 그 상태가 필요한 컴포넌트에 가까이 둔다.
      • 여러 함수에 걸쳐 활용되는 것이 필요하다면 부모 함수쪽 스코프에 두거나 별도의 모듈로 분리하라.
      • 여러 페이지에 걸쳐 영속되는 상태가 필요하다면 서버의 세션이나 로컬스토리지 등을 활용하라.
      • 아주 긴 시간동안 유지되어야 하는 데이터라면 데이터베이스나 파일 시스템으로 다루어야 한다.
    • 상태를 캡슐화하라.

      • 값비싼 연산이나 비동기 호출 등이 들어가는 함수의 경우 직접 상태 변수를 만들어 들고 있다가 해당 변수가 변해야 할 때 다시 함수를 호출하는 식으로 관리하던가, 재사용 가능한 memoize 함수를 만들어 추상화할 수 있다.
      • 어떤 코드라도 궁극적으로 어느 시점에 상태를 가질 수 있다. 중요한 것은 어떻게 사이드 이펙트와 상태 변화가 충분히 캡슐화할 수 있고 상태 관련 버그가 안일어나도록 만들 수 있는가이다. 과도한 추상화를 하지 않고 적정한 수준에서 상태가 담긴 로직을 추상화하는 것이 중요하다.
    • 예외를 다루는데 너무 걱정하지 말라.

      • 에러를 던지는 것이 아니라 에러 값을 리턴하는 것을 고려해보라. 예외를 함수를 호출하여 얻는 기대값의 일부로 다루는 것이다.
    • 콜백 대신 Promise, Async/Await을 사용하라.

      • 프로미스를 통해 비동기 데이터를 절차적 동작이 아니라 으로 다룰 수 있게 된다. 이 값은 다른 비동기 값들과 매핑되거나 조합될 수 있다.
      • 콜백은 마치 “작업이 끝나면 이 사이드 이펙트를 실행해” 와 같은 면모를 보인다. 전혀 함수형스럽지 않다. 반면 프로미스는 캐싱되고, 메모이즈도 되고, 다른 함수에 값으로도 넘길 수 있다.
      • 비록 프로미스도 resolved, rejected, pending 같은 상태가 있긴 하지만 적절한 수준 안에서 캡슐화되어있다고 볼 수 있다.
    • 목적에 맞다면 클래스를 사용하라.

      • 클래스, 그리고 객체지향 프로그래밍은 함수형 프로그래밍과 정반대의 무언가가 아니다.
      • ‘상태가 얼마나 잘 캡슐화되어있는가’, ‘얼마나 오래 상태가 살아남아 있는가’, ‘코드에 사이드 이펙트가 얼마나 있는가’ 같은 내용이 중요하지, 함수를 활용해도 똑같이 효율적이지 못한 코드를 짤 수도 있는 것이다.
    • 타입을 사용하라.

      • 패러매터와 리턴 타입에 대한 인터페이스가 명확하고 보는 것만으로도 문서를 읽는 듯한 함수를 갖는 것이 함수형스러운 마음가짐을 쌓도록 하고, 입력값과 출력값 사이에 명확한 관계를 갖는 함수를 작성하도록 장려한다.
      • 거기에 정적 타입 시스템을 끼얹으면 상태를 다룰 때 예기치 못하게 코드가 잘못 동작하는 것을 방지하는데 도움이 된다.
    • 가능하면 불변 객체를 만들자.

      • 의도치않게 같은 데이터를 활용하는 다른 코드나 함수가 영향을 받는 것을 방지할 수 있다. 특히 가변 객체를 인자로 받고 이를 변경한다던가.
      • 물론 가변 객체가 함수 여기저기에 퍼져있을 수도 있다. 이런 경우 가능한 예측 가능하고 명확한 타입의 객체를 사용하여 어떻게 객체가 변화하게 되는지 알 수 있도록 해야한다.
    • 함수형스러운 테스트를 작성하라.

      • 유닛 테스트, 통합 테스트, E2E 테스트까지 모든 것을 함수처럼 다루어라. 구현 디테일을 테스트하는게 아니라 정해진 입력값에 따른 출력값, 그리고 그에 따른 사이드 이펙트만 테스트하는 것이다.
      • 테스트하고자 하는 것이 개발자가 활용하기 위해 만들어진 것이라면 개발자가 사용하는 환경처럼 테스트를 작성하는 것이고, 일반 사용자가 활용하기 위해 만들어진 것이라면 최대한 사용자 시나리오에 맞게 테스트를 작성해야 한다.
      • 오로지 입력값, 출력값만 신경 쓰고 그 외에 것은 신경쓸 필요 없다.
    • 실용적으로 접근하라.

      • 순수 함수형 코드가 더 예측 가능하고, 에러가 없고, 테스트 쉽고 등등의 장점이 있을 것이다. 하지만 실제 세계의 코드를 작성하는 사람으로서, 어떤 사이드 이펙트를 일으키고 있는지, 어떤 상태를 만들고 있는지, 이 상태는 얼마나 오래 남아있을지 등을 신경쓰며 작성하면 꼭 순수한 형태의 코드가 아니더라도 충분히 퀄리티 있는 코드를 작성할 수 있을 것이다.

Frontend

  • Simple CSS Hack to Reduce Page Load Time | by Mayank Gupta | Jun, 2021 | JavaScript in Plain English - 페이지 로딩 시간을 줄이는 간단한 CSS 핵

    • CSS는 페이지 로딩 시간에 영향을 미치는 주요한 요소 중 하나다.

      • 애플리케이션이 CSS 파일을 맞닥뜨리면 CSS를 우선적으로 읽어들이고, CSS Object Model(CSSOM)을 생성한다. 그리고 DOM 트리와 결합하여 “렌더 트리”를 만든다. 렌더 트리는 DOM 트리가 CSSOM이 합쳐져 생성되며, 페이지의 모든 요소에 정확한 스타일링을 제공한다.
      • CSS 규모가 클 수록 CSSOM을 생성하여 DOM에 결합하고 렌더 트리를 생성하는데 시간이 오래 걸린다.
      • HTML이 CSS 파일을 만나면 CSSOM을 만들기 위해 CSS 파일을 다운로드하고 파서가 모든 작업을 완료할 때까지 기다리기 때문에 CSS는 렌더링을 막는 요소라고 할 수 있다.
    • 그렇다면 브라우저가 페이지가 최초 로딩되는 시점에 CSS가 다운로드되고 CSSOM이 생성되는 것을 기다리지 않도록 만들면 된다.
    • 중요하지 않은 CSS 리소스는 CSSOM 생성이 늦춰지도록 만들 수 있다.
    <link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">
    • 위의 스니펫은 사용자가 프린트를 시도할 때만 적용되도록 만든다. 그러나 onload 속성에서 보다시피 페이지가 로딩되자마자 미디어 속성을 다시 “all” 로 바꾼다. 하지만 이러한 방법은 최초 로딩에 영향을 미치지 않는 스타일에만 적용해야 한다.

React

  • preact-portal/preact-portal.js at master · developit/preact-portal · GitHub - Preact용 Portal API 구현하기

    • Preact로 Portal을 이용하려면 preact/compat 을 사용해야 한다. 번들 사이즈를 줄이기 위해 가능하면 compat 을 쓰지 않으려다보니 비슷한 걸 구현해야 하는데, 이미 몇년 전에 만들어져있는 preact-portal 패키지를 참고하여 간단한 형태로 한번 만들어보았다.

      • preact portal component - CodeSandbox
      • 너무 간단히 만든거라 나중에 퍼포먼스 문제가 일어나지 않을까 살짝 걱정되긴 하지만 적당히 제 역할은 할 수 있는 것으로 보인다.
    • 결국은 생으로 render 함수를 호출해야 하기 때문에 원본 코드에서는 setTimeout 을 사용하여 업데이트 시에 블락이 일어나지 않도록 만들고 있다. 그런데 기억하기로는 이펙트의 실행이 requestAnimationFrame 을 활용하고 있으니까 상관 없어 보인다.
    function Portal({ children, into }) {
      const renderRef = useRef(null);
    
      useEffect(() => {
        const container =
          typeof into === "string" ? document.querySelector(into) : into;
        if (!renderRef.current && container && hasChildren(children)) {
          renderRef.current = render(children, container);
        }
    
        return () => {
          renderRef.current = null;
        };
      }, [into, children]);
    
      return null;
    }

OSS

  • GitHub - wavesoft/dot-dom: .dom is a tiny (512 byte) template engine that uses virtual DOM and some of react principles

    • 512바이트짜리 Virtual DOM 기반 템플린 엔진
    • 상태 관리, 라이프사이클 등 어지간한건 다 있다. 흥미로운 점은 512바이트를 최대한 유지하기 위해 더 기능이 들어가더라도 사이즈를 크게 만든다면 과감히 기능 추가를 포기한다는 내용이었다.
    • 이 라이브러리를 통해 IoT 장비 등 자원이 빡빡한 기기에서도 유용하게 GUI를 만들 수 있도록 하려고 한다.
    • 코드가 약 300줄정도인데, 대부분 주석을 친절하게 채워넣느라 길어진 것이기 때문에 소스코드를 파악하는 것이 아주 좋은 공부가 되리라 생각한다.
  • GitHub - nektos/act: Run your GitHub Actions locally 🚀

    • Github Action은 Github에서 돌아가는 유용한 CI/CD 도구이다. 이를 로컬에서 돌리는 도구이다.
    • 실제 워크플로우가 의도대로 동작하는지 테스트해보기 위해 커밋하여 원격 저장소로 푸시할 필요 없이 해당 액션을 로컬에서 돌려볼 수 있다.
    • make 대신 로컬 태스크 러너도 활용할 수 있게 된다. Makefile 대신 .github/workflows 를 활용하게 되는 것이다.
    • .github/workflows 폴더를 읽어들여 도커 API를 사용하여 필요한 이미지를 생성하고 액션에 맞는 컨테이너를 실행한다. 그래서 도커 의존성이 있다.

안도형(Dohyung Ahn)
삽질을 하고, 글을 남기면서 다른 사람들과 함께 자라고 싶어하는 프론트엔드 개발자입니다. 더 좋은 코드와 설계를 항상 고민하며 지식을 어떻게 효율적으로 습득하고, 어떻게 잘 나눌 수 있을지도 고민합니다.

GitHubTwitterFacebook