기본 콘텐츠로 건너뛰기

무색의 초록 개념들이 격렬하게 잔다.

  문학을 좋아하는 분들이라면,   Colorless green ideas sleep furiously           - Avram Noam Chomsky   위의 문장을 언젠간 한 번 보았을 것이다. 무슨 말일까?  무색의 초록 개념들이 격렬하게 잔다.  => 무색의 초록? 이미 초록인데 무색이라, 전문가들은 색을 바라보는게 좀 특이하다.       ("The Expert"라는 단편영화에서 보면, 빨간선을 투명잉크와 녹색잉크로 그려달라는 내용이 있었다.)  뭐 일단 잡설은 넘어가고, 위의 문장이 뜻하는 것은 문법에는 맞아도 의미상으로는 통하지 않는 말이 있다는 것을 보여주기 위해 예로 든 문장이다.  개발자들은 작업을 어떻게 하는가?  문제 -> 해결 방안 -> 코드 : 아마도 간단하게 설명하면 이런 순으로 진행을 하지 않을까 생각한다.  커먼한 프로그램을 만드는 경우, 개발자들이 마주할 문제, 해결 방안들은 어느정도 유사한 경우가 많다.  => 만약에 게시판을 만든다고 하면, 필연적으로 글쓰기, 댓글 개발, 추가적으로 카테고리화 파일 업로드 기능 등등.... 예상 가능한 기능들이 있지 않은가?  하지만 코드의 영역으로 가면 어떤가? 서버 사양은 어떻고, 사용자들이 사용할 클라이언트는 어떻고, 이는 크게 서버 버전이나 프로그래밍 언어를 제약사항까지 추가 될 수 있다.  하나의 기능을 만들더라도, 누가 언제 어디서 만드는 지에 따라 코드가 달라질 수 있다. 이런 특성 때문에, 대학교수님은 언젠가 프로그래머는 작가와 같이, 문장 하나하나에 의미를 담을 수 있기 때문에, 예술가 같기도 한다고 하였다.  자 일단 잘못 작성한 코드를 보자. function isNumber () { const number = 'number' ...

빈 배열로 호출하면 안되요. reduce

 예전에 some, every를 빈배열로 실행하면 어떻게 되는지 확인하였다. 추가적으로 reduce를 빈배열로 호출하면 어떻게 되는지 이야기해보고자 한다. [ 1 , 2 , 3 , 4 ]. reduce ((a, b) => a + b) reduce는 위와 같이, 반복적인 작업을 누적 시키고 싶을 때, 사용하곤 한다. const list = [] list . reduce ((a, b) => a + b) // Uncaught TypeError: Reduce of empty array with no initial value 하지만, 빈 배열로 실행을 하게 되면, 위처럼 에러가 발생 하게 된다. 물론 기본값을 설정하게 되면 에러가 발생하지 않는다. const list = [] list . reduce ((a, b) => a + b, 0 ) 왜 이렇게 구현 되어있을까? 23.1.3.24  Array.prototype.reduce (  callbackfn  [ ,  initialValue  ] )  4번 조건을 보게 되면 알 수 있다. 일단 4번 조건 까지만, 구현 해보자. Object . defineProperty ( Array . prototype , "reduceImpl" , { value : function (callback) { // 1. Let O be ? ToObject(this value). const o = Object ( this ) // 2. Let len be ? LengthOfArrayLike(O). const len = o . length || 0 // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. if ( typeof callback !== 'function' ) { throw new...

에러 다시보기 1

 혹시 함수를 만들면서 return null을 해본 사람이 있을까? 다시 정확히 질문을 바꾸어 보자, 어떤 함수를 실행 했을 때, null이 나오기를 바라면서 함수를 실행해본 사람이 있을까? 나는 타입언어를 배우는 동안 리턴타입의 지정을 Null이라는 클래스(?)로 해본적이 없다. Nullable은 있어도, Null이라는 클래스가 있는 언어가 있는지도 모르겠다.  언젠가, 널 포인터라는 개념을 만든 토니 호어는 "내 10억 달러 짜리 실수"라고 이야기 하였다. 그런 이야기가 나올 당시가, 함수형프로그래밍이 한참 인기가 있을 때, 나한테 까지도 이야기가 들어왔다. nullalbe객체가 있는데, 그걸 사용 하면 코드에서 null체크를 할 필요가 없어서 코드가 짧아지고, 우아한 코드를 만들 수 있어, null을 왜 만든거야? 뭐 상황에 따라 맞는 말이 될수도 있고, 상황에 따라 불편한 상황을 만들 수 있다.  오늘 내가 해볼 말은 조금 null을 사용해도 되지 않을까? return null을 해도 되는 상황이 있지 않을까? 라는 말을 하고 싶다.  만약, 당신이 앞으로 많은 사람들이 사용하게 될 언어의 readFile함수를 만든다고 생각을 해보자.  일단 첫째로, File 클래스를 리턴을 받아야 하고 path를 파라미터로 받게 되는 아래의 형태의 함수가 될 것이다. function readFile (path: String): File {}  문자열 타입으로 파일경로를 받고, File타입을 리턴을 하게 될 것이다. 더, 고민을 해볼꺼리가 남아있겠지만, 가장 간단하게, 따져볼만한 것이, 해당 경로에 파일이 없는 경우는 어떻게 처리 할 것인가?  여기서, 생각해볼만한 것이다. 세가지가 있을 것이다. 1. throw new PathError(path); 그 상황을 에러로 처분하는 방법. 2. return new File(); 그 상황을 빈 파일의 객체를 리턴하는 방법. 3. return null;  각각...

Void 다시 돌아보기.

  옛날 코드에서는 void라는 키워드를 자주 보았을 것이고, 리턴 타입을 정의해야하는 언어를 사용하는 개발자라면, 수도 없이 보았을 만한 키워드이다.  하지만, 자바스크립트 기준으로 생각을 해보자. void function test () { console . log ( 'called test' ) } test()  위와 같은 코드르 보자, 자바스크립트를 배우지 않았지만, c라이크 언어를 배운 사람이라면, 아 test함수에는 반환값이 없구나 하고, 넘어갈 코드이다. 하지만....  Uncaught ReferenceError: test is not defined  저 코드에는 test자체가 존재 하지 않는다. 옛날의 자바스크립트에서는 undefined가 읽기 전용 이 아니라, 쓰기가 가능한 변수였다. 그래서 undefined를 재정의 할 수 있었기에, undefined = void 0 ; function foo (undefined) { var a ; console . log ( a === undefined ) } foo ()  위 처럼 void가 평가가 항상 undefined인 것을 고려하여, 위와 같이 undefined를 안전하게 관리하거나, 함수의 가장 마지막 인자를 항상 undefined로 이름으로 정의하여, undefined를 안전하게 관리 하였다.  이젠, undefined를 재정의 할 수 없으니, 절~~대 사용하면 안되는 방식이다.  그렇다면, 이제 void 키워드는 더 이상 쓰지 않을만한 내용일까? https://sejiwork.blogspot.com/2021/10/javascript-pseudo-protocol.html  a 태그 사용 시 click 이벤트만 사용 할 때에 사용 할 수 있긴 하지만.... 이걸 써야 할 까? 이 생각은 머리에서 떠나지 않는다. button태그가 있는데 굳이?  우리는 자바스크립트 사용자라면, function ...

async / await 사용 해도 될까?

  제목이 어그로성이긴 하지만... 이번 포스팅의 내용은 async / await을 사용하는 경우 놓칠 만한 오류가 있으니, 주의하자라는 내용을 담고자 하는 내용이니, 참고하자.  Array.protoype.*   // server api const checkValidation = async (v) => v % 2 const filteredList = [ 1 , 2 , 3 , 4 ]. filter ( async (v) => checkValidation (v) ) console . log ( 'filteredList' , filteredList ) // [1, 2, 3, 4]  위의 결과를 생각을 한 번 해보도록 하자, checkValidation이라는 서버 api가 있을 때, 이 처럼 개발되어있다는 것은 filteredList에 [1, 3]과 같이 홀수만 남아 있기를 원하는 것일 것이다.  하지만, filter입장에서는 async 키워드가 달려있는 순간, Promise를 리턴 받았으니, trucy값으로 판단 하여, [1, 2, 3, 4] 값을 리턴 하게 된다. Promise . resolve ([ 1 , 2 , 3 , 4 ]) . then ( list => list. reduce ( async (acc, v) => { acc = await acc if ( await checkValidation (v) ) acc. push ( v ) return acc }, Promise . resolve ( [] ) ) ) . then ( console . log ) // [1, 3]  정상적으로 홀수만 반환 받기를 원한다면, 위 처럼 Promise객체 기반으로 작동 할 수 있도록 작성하는 것이 맞다.  filter함수는 promise를 리턴 받는 순간, trucy로 인식하기 때문에 사용을 하는 순간 재기능을 할 수가 없...

Object.groupBy (배열 그륩핑)

 배열을 다루다 보면, 그륩핑을 해야하는 경우가 있다.  뭐 그러다 보면 아래처럼 불편한 코드가 생성 될 것이다. const list = [ { class : 'a' , score : 1 }, { class : 'a' , score : 2 }, { class : 'a' , score : 3 }, { class : 'a' , score : 4 }, { class : 'b' , score : 1 }, { class : 'b' , score : 1 }, ] const group = {}; for ( const item of list ) { group [ item . class ] = group [ item . class ] || [] group [ item . class ]. push ( item ) } console . log ( 'group' , group )  뭐 reduce를 사용 하게 되면 아래 코드 처럼 되겟지... const list = [ { class : 'a' , score : 1 }, { class : 'a' , score : 2 }, { class : 'a' , score : 3 }, { class : 'a' , score : 4 }, { class : 'b' , score : 1 }, { class : 'b' , score : 1 }, ] const group = list . reduce ( (groupRes, item) => { groupRes[item. class ] = groupRes[item. class ] || [] groupRes[item. class ]. push (item) return groupRes }, {} ) co...

array.with

 요즘, 자바스크립트 array에 추가되는 함수들을 보면, 변경 메서드를 대체하는 함수들이 추가 되고 있다. 예를 들면, sort메서드와 toSorted를 확인해보면 아래와 같다.  위 처럼 기존의 데이터를 변경하지 않는 것은 개발자들이라면, 얼마나 큰 의미가 있는지 알고 있을 것이다. 아마 조만간 sort함수를 쓰지말라는 개발자들이 나오겠지... 뭐 물론, 이미 데이터를 다루는 라이브러리를 쓰고 있다면, sort함수가 기존의 데이터를 변경 하지 않는 다는 것을 알고 있을 것이다.  이러한 array.with는 가장 기본적인 배열 내 속성의 할당에 관한 함수이다. 우리는 여태까지 배열내의 값을 수정을 하기 위하여, arr[1] = 3 이와 같이 진행 했을 것이다. 그렇다면, arr의 1번째 값은 3으로 변경 될 것이다.  하지만, 여러 곳에서 사용하는 데이터의 경우 해당 데이터의 변경이 어떻게 영향을 줄지 모르기에, 데이터를 변경하기 전, 복사를 하고 사용 하는 경우가 많다.  이럴때 사용 할 수 있는 함수가 array.with함수인 것이다.  위와 같은 방식이다.  뭐 물론 대괄호 표기법의 대체수단으로써만 생각하면, 큰 위화감은 없으나... 아래와 같이 여러 값을 변경 하는 경우가 문제가 된다.  코드 스타일이 계속된 함수 실행으로 인하여, 비용이 올라가며, with함수를 실행 할 때 마다, 새로운 레퍼런스를 제공받기 때문에 이 비용도 꽤 클 것으로 생각 된다.  변경 -> 복사로  뭐 물론 해당 함수가 추가 된 것은 원본 객체의 오염을 막기 위한 것이라는 것을 알 수 있다. 같은 시기에 추가된 함수가 아래의 함수들처럼 기존 객체의 값을 막고, 복사 방식이라는 것을 보면 말이다. Array.prototype.toReversed() = Array.prototype.reverse() Array.prototype.toSorted() = Array.prototype.sort...

공허참 (feat.javascript)

 지금 숫자 몇 개만 생각해보자.  두개의 질문을 던저보자.   1. 모든 수가 양수인가? 2. 모든 수가 음수인가?  이 질문에, 답변 될 수 있는 케이스는 아래와 같다.     모든 수가 양수이다. 모든 수가 음수이다. 1 false false 2 false true 3 true false 4 true true  1번 케이스의 경우에는 양수와 음수가 섞여있을 것이고,  2, 3번 케이스는 상상하기 쉬울 것이다.  4번 케이스는 무엇일까? 대부분은 이 케이스겠지만......  자 잠깐만, 고등학생 때로 돌아가 보도록 하자. 수학의 정석을 1장을 열시히 읽던 그때로 말이다. 아마 내 생각에는 집합과 명제부분이였던걸로 기억한다.  x < 0인 음수만 있는 집합 N  x >= 0인 양수만 있는 집합 P  두 집합이 있다고 하였을 때, 방금 전에 떠올린 집합 Q가 있다고 하였을 때,  위의 케이스들은 아래처럼 표현 할 수 있는 것이다.  1. N ∩ Q = Q - P이고, P ∩ Q = Q - N이다.  2. N ∩ Q = ∅이고, P ∩ Q = Q이다.  3. N ∩ Q = Q이고, P ∩ Q = ∅이다.  4. N ∩ Q = Q이고, P ∩ Q = Q이다.  자 이렇게 표현해보니, 4번의 케이스의 경우 Q = ∅인 것을 눈치를 챘을 것이다. 즉 아무런 숫자도 생각을 안 했다면, 4번의 케이스가 된다는 것이다.  자 간단하게, 이제 수학 말고, 코드로 따져 보도록 하자. console . log ( []. every (v => v >= 0 ) ) // true console . log ( []. every (v => v < 0 ) ) // true  우리는 every를 사용 할 때, 빈 배열인 경우 true 값이 나온다는 ...

Set.has vs Array.includes

  개발을 하다보면, 특정데이터가 배열 안에 있는지 확인해야 하는 경우가 있다. 그것도 자주... 그런경우 당연하게도, arr.includes를 이용 하는 경우가 많은데, set.has가 더 빠르다 이런식의 인터넷글들을 확인 해보았을 것이다.  특히나, 뭐 면접에서 set.has의 시간복잡도가 얼마냐? 이런식의 질문도 받아본 사람도 있을 것이다.  set.has  set의 경우 해시 테이블 구조로, 동일한 값을 같지 못한다. 따라서, set.has는 해당 속성이 있는가 없는가 수준을 O(1) 수준의 시간복잡도를 갖는다. 뭐 물론, 데이터가 많은 경우나 충돌이 나는 경우 처리 등등 뭐 문제가 일어나는 경우 그것보단 더 높아질 수도 있겠지... 일단 O(1)로 생각 하면 된다.  Array.includes  Array.includes의 스펙은 위와 같다. 딱 봐도, 로직이 길고, 배열의 길이만큼 순회를 하는 것을 볼 수 있다. 그래서 일반적으로 O(N)이라고 생각하는 것이 편하겠다.  자 문제는 여기서 발생한다. 당연하게도, set.has가 월등히 빠르다고 느껴지고 빠르다. 하지만, 실제 업무를 하는 경우에는 어떠한가? 생각을 해보아야 할 것이다.  뭐 알고리즘을 푸는 경우라면, Set을 이용하여, 값의 포함여부를 판단하는 것이 대부분의 상황에서 이득이 될 것이다. 하지만.... 서비스를 제작하는 경우 백엔드와 프런트엔드의 연동이 필요할 것이다. 이런 상황에서 문제가 되는 것은 api의 응답으로 Set을 줄 수 없다.  일반적으로 JSON형태로 데이터를 전달을 할 것이다. 즉 초기값이 배열일 것이다 라는 말이다. 이때 특정값의 포함여부를 확인 하기위하여, Set으로 데이터를 한 번 변경 하는 것은 꽤나 큰 작업이다.  위의 경우 같은 상황에서 Set으로 한 번 데이터 형태를 변환하는 것이 얼마나 큰 리소스를 사용하는지 볼 수 있을 것이다. 즉 상황에 따라서 잘 사용해야지, 무조...

착시 이용하기 (로딩의 중요성.)

  개발을 하다보면, 서비스가 느리다는 클레임을 듣는 경우 혹은 프로그램이 멈췄어요 등... 클레임을 받는 경우가 있을 것이다. 오류도 아니고, 생각보다 위의 클레임을 들으면 꽤나 곤란하다... 대부분의 개발서버의 경우 데이터가 부족하기도 하고해서 느린 것을 잘 확인 하지 못하는 경우가 있기 때문이다.  특히나, 프런트엔드의 경우 기기별로 퍼포먼스가 다른 경우도 있으니... 서버의 경우 서버의 스펙을 올려서 공짜점심을 먹을 수라도 있지만 말이다. 이런 경우 내가 자주 쓰는 방법은 더 오래걸리게 하는 것이다.  지금도 느린데, 더 오래걸리게 한다니... 이게 무슨 헛소리 일까? 뭐 일다보면 이해가 갈 것이다.  자 간단하게 10초가 걸리는 작업이 있다고 생각해보자.  버튼을 누르고, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 마음속으로 새어보자. 생각보다 엄청나게 긴 시간이라는 것을 알 수 있다.  자 그러면 이런 경우는 어떤가? 20초가 걸리는 중간 내용을 보여주는 것이다. 뭐 몇초가 남았다. 이런식의 내용이 포함이 되면 말이다.  아마 실제로 해보면, 10초짜리보다 20초가 짧게 느껴졌을 수도 있다. 그것뿐만 아니다. 10초짜리의 경우, 사용자가 해당 페이지에 오류가 발생했다고 생각 하여, 페이지를 새로고침하게 되는 경우가 최악의 경우이다. 아주 비싼연산을 다시해야 하기 때문이다. 위처럼 착시를 이용하는 것도 해결방법이니 적극적으로 사용해보도록 하자.

꺼진 불도 다시 보자. (feat.꼰대 개발자)

 오늘은 조금 다른 이야기를 해보고자 한다. 21세기의 대한민국에 살고 있는 사람이라면, 유아를 제외하고는, 모두 엘리베이터를 타보았을 것이다. 엘리베이터를 타게 되면 가장 먼저 보게 되는 것이 무엇일까? 닫힘 버튼?  거울일 것이다.  왜? 엘리베이터에는 항상 거울이 있을까? 뭐 얼굴에 뭐가 묻어 있는지, 머리는 단정한 지 보라고 있을 것이다.  엘리베이터가 느리다고 투정 부린 적이 있을 것이다. 뭐 한 번 은 들어봤을 것이다. 엘리베이터가 너무 느려, 기다리는 시간 동안 사람들에게 시간을 조금 더 빠르게 달아 두었다고.....  자 여기서 묻고 싶다. 여러분은 핸드폰을 보는가? 아니면 거울을 보고 있나? 내 생각에는 여러분은 다른 사람들의 뒤통수 혹은 스마트폰을 보고 있을 것이다.  만약에, 19세기 때처럼, 스마트폰이 없다면, 거울을 봄으로써, 시간을 때울 수 있을 것이다. 하지만, 21세기에 아직도, 거울이 있을까?  엘리베이터가 좁다고 생각해본 적이 있는가? 아마도, 자주 있었을 것이다. 특히 사람이 가득 탄 엘리베이터를 탄 경우라면 말이다. 이런 경우, 엘리베이터의 거울의 효과를 그나마 보고 있다고 볼 수 있다. 거울을 담으로써 엘리베이터를 조금 더 크게 느끼게 할 수 있다. 폐소공포증 환자라면, 조금 덜 공포를 느끼게 할 수 있는 장치이다.  자 여기서 잠깐 내가 전달 하고 싶은 부분이 있다. 아마 여러분은 19세기와 21세기 내용이 나왔을 때, 그러면 거울은 필요 없는 것이라고, 생각 할 수 있었을 것이다. 아니면 말고,  기존 코드에서 불필요 해 보이는 부분이 있을 때, 과감히 지우게 되었을 때, 다른 부수 효과가 없는지 한 번 더 확인 해보라는 것이다.  절대로, 당신들이 만나게 될 개발자들이 바보가 아니라고 말 하고 싶다. 만약에 기존 코드를 수정하고자 한다면, 정확히 파악 하자. 뭐 꼰대 같은 말일 수 있다. 기존 코드를 존중해야 한다는 말을...

유사배열?

 유사배열은 무엇일까? 간단하게 말하자면, NodeList, 문자열 같은 것을 유사 배열이라고 볼 수 있다.  NodeList  위와 같이 형태는 배열이지만,  Array.prototype.find 함수를 사용 할 수 없다. 이럴때 사용 할 수 있는 방법이 Function.prototype.call인데,   위와 같이 사용 하면 된다.  유사배열?  자 그렇다, Array.prototype은 Array객체만을 위한 것이 아니다. 그러면 조건은 무엇일까? 자 일단 첫번째 생각 해볼 수 있는 것이 있다. iterable 객체이다. [Symbol.iterator]가 있으면 일단, iterable객체 이다. NodeList도 [Symbol.iterator]가 있으니 테스트 해보자.  위의 결과를 한 번 생각해보자, obj로는 find가 되지 않지만, [...obj]는 된다. 즉, [Symbol.iterator]조건은 아니다. 인덱스 기반 컬렉션  인덱스 기반의 컬렉션. NodeList도, Array도 인덱스 기반의 컬렉션이다. 둘 다 속성을 숫자로 사용 하고 있고, 길이가 있다.  우리는 이제 유사배열을 속성이 숫자로 사용 하고, 길이가 있어야 한다. 이런 내용을 알 수 있을 것이다.  자 그러면 두가지를 더 테스트 해보자, length기반인지 속성 기반인지 확인해보자.  자 간단하게, 결과를 보면, 둘 다 만족해야지만, 함수가 실행됨을 알 수 있다. 그렇다면, 희소배열은 존재할까?      간단하게 존재함을 알 수 있다.  Array의 함수는 위와 같이 length기반으로 반복을 하되, in 키워드를 이용 하여, 존재 하는 값만을 순회하게 되는 것이다.  간단하게, Array의 함수를 다른 유사배열에 사용 할 수 있으니, 새로운 객체를 생성하지 않아도 되니 참고하도록 하자.

git lfs

  git lfs는 사실 프런트엔드 개발자라면, 잘 들어보지 못하였을 것이다. 혹시 100MB이상의 파일을 깃에 저장을 해보았는가? 만약에 그랬다면, 소스파일은 아니고, 동영상, 이미지 등 멀티미디어 자료일 가능성이 높다.  lfs는 무엇일까? 위에서 말하였듯이 파일 하나가 100MB이상을 저장해야하는 경우 필요한 장치이다. Large File Storage의 약자이니 말이다.   만약에 사용해보고자 한다면... 각 레파지토리에서 아래의 명령어를 실행 해주어야 한다. git lfs install https://docs.github.com/ko/repositories/working-with-files/managing-large-files/installing-git-large-file-storage  그러면 각각의 레파지토리에 저장공간이 따로 생길 것이다. 이때, 플랜별로 저장용량이 차이가 나니, 너무 많은 데이터를 저장하지 않도록 주의해야한다.  아쉽게도, 특정용량 이상의 파일을 자동으로 lfs로 관리를 해주는 것은 아니다. git lfs track "*.psd" https://docs.github.com/en/repositories/working-with-files/managing-large-files/configuring-git-large-file-storage  위와 같이 해당 레파지토리에 track을 해주어야 하는데, 위 처럼하게 되면, psd 확장자의 파일은 lfs로 관리를 하게 된다.  그러면 .gitattributes위의 설정대로, 설정이 추가 되므로, psd파일들을 커밋 하기전에, .gitattributes를 push 해주도록 하자.  이제 clone할 때, lfs등록되어 있는 파일들을 위한 포인터들을 다운로드 받게 된다. => 실제 파일이 아니다... 이 점들은 되게 귀찮을 것 같다.  실제로 파일을 받아보기 위해서는... 아래의 명령어를 별도로 입력 해...