ZEROEQUALTWO
ZEROEQUALTWO

안녕하세요! 이곳은 제가 학습한 것들과 일상의 순간들을 모아놓은 공간입니다. 이곳에서 유익하고 즐거운 시간을 보내시길 바랍니다.

Review
Study NotesLife Hack
© 2025 — Zeroequaltwo. All Rights Reserved.
리뷰
cover_image
On August 24, 2026(스포 있음) 제인 오스틴 소설 도장 깨기 시리즈, 세번째 작품은 '설득'이다. 이야기는 주변인들의 만류로 인해 사랑하는 남자 앤트워스 대령과의 결혼을 포기해야 했던 여자 주인공 앤이 우연찮게 다시 그와 만나게 되면서 시작한다. 둘은 과거의 기억으로 인해 처음엔 서먹했지만 결국에는 떨어져있는 동안에도 서로를 계속 사랑해왔다는 것을 깨닫고 다시 서로에게 빠져들며 결국 결혼하게 된다. 이 작품은 넷플릭스 오리지널 영화로 먼저 접했다. 영화를 꽤 재밌게 봐서 소설도 엄청 기대하면서 봤지만 소설 설득은 개인적으로 큰 감흥 없이 술술 넘겼다. 제인 오스틴 특유의 문체라든가, 스토리를 전개하는 방식을 좋아해서 흥미롭게 읽었지만 전반적으로 무난하기만 하다는 게 내 감상이다. 앞서서 읽었던 작품들에 비해 내가 공감할 부분이 별로 없어서 몰입이 잘 안 됐다. 나는 책이든 드라마든 꽤나 주인공의 입장에 몰입하면서 보는 편인데 그러지 못 해서 그런 듯 하다. 고렇다면 내가 왜 주인공의 입장에 몰입을 못 했는지를 얘기해보겠다. 아주 단순하게 내가 깨붙을 한 번도 해본 적이 없어서 그렇다. 과거에 사랑했던 이와 다시 재회하고 울렁거리는 기분 같은 걸 느껴본 적이 없다! 헤어질만 하니까 헤어졌고, 그 결정을 후회 혹은 번복해본 적도 없다. 굳이굳이 레퍼런스를 찾자면 깨붙하는 친구들과 술잔을 기울이며 들었던 귀동냥 정도..? 솔직히 깨붙 위로도 한 두번이지 매번 똑같은 이유로 싸우고 헤어졌다가 다시 사귀는 걸 보다보면 그냥 저게 쟤네가 사귀는 방식인갑다 하고 넘어가게 된다. 그니까 내 말은 앤 엘리엇에게 공감할 여지가 없었다는 거다. 또 앤트워스 대령이 눈썹만 살짝 까딱해도 그의 기분을 단박에 알아채버리는 앤 엘리엇의 독심술 능력이 너무 대단해서 잘 몰입이 안됐다. 설정상 둘은 엑스 사이니까 초반에는 상대방을 신경 안 쓰는 척한다. 하지만 사실은 서로를 엄청 주시하고 있고, 앤 엘리엇이 앤트워스의 아주 작은 비언어적 표현도 모두 캐치하며 그의 심경을 읊는 장면이 종종 나온다. 그런 장면 나오면 나는 우오옼ㅋㅋ 저런 걸 어케 앎?;;'같은 허접스러운 생각이 절로 들었다. 사랑하면 저런 것도 알게 되나? 그럼 나는 확실히 사랑을 해본 적이 없는 듯. 요런 생각도 하게 만들었다. 글고 앤 엘리엇이 너무 도덕적으로 결함이 없는 인간이어서 재미가 없었던 점도 한 몫 했다. 오만과 편견의 리지와 에마의 에마가 인간이 마냥 완벽할 순 없지만 이 정도면 괜찮지~ 같은 느낌을 준다면, 앤은 흠 잡을 데 없다. 우유부단, 부화뇌동이라고 볼 수도 있겠지만 딱히 인격적 결함으로 삼을 만큼 한심한 결정이었다고 하기에도 애매하다. 쨌든 위와 같은 이유들로 주인공 입장에 잘 몰입하지는 못 했지만 그래도 재미없는 책이라고는 말할 수 없다. 다음 내용이 궁금해서 계속 짬날 때마다 틈틈이 읽었더니 일주일도 안 되어서 다 읽어버린 책이다(나에게는 대단한 속도다). 그냥 내 인생에 여자 주인공의 상황에 대입해 볼만한 참고 자료가 부족했을 뿐이다. 지금 독후감 쓰면서 왜 책 제목이 '설득'인지 문득 궁금해졌다. 이 단어가 책 내용 전체를 관통하는가?하면 의문이 들기 때문이다. 굳이 끼워 맞춰보자면... 설득 자체는 듣는 이에게 있어 하나의 의견일 뿐이지만, 설득을 따르는 것은 결국 개인의 결정이다. 과거의 앤 엘리엇은 설득을 따르는 결정을 내렸지만 그것을 후회했고, 이후의 엘리엇은 사촌 엘리엇과 결혼하라는 설득을 듣지만 그것을 따르지 않고 결국 자신의 사랑을 찾아간다. 제인 오스틴의 소설이 여성 주인공의 성장 서사에 초점을 맞춘 것이 많다는 것을 감안해보면, 비슷한 설득에 대해 다르게 대처하는 모습을 보여줌으로써 그녀의 자기 주체성 확립을 보여줬기 때문에 책 제목이 '설득'이다..! 정도로 해석해보겠다.
리뷰
cover_image
On August 5, 2026헝거는 록산 게이라는 작가이자 페미니스트이자 흑인이자 초고도비만인인 한 인간이 자신의 결핍과 욕망에 대해 고백한 책이다. 처음에는 자신의 몸을 사랑하지 못 하는 한 인간의 얘기라는 소리를 듣고 지금의 내가 읽어보면 좋을 거 같아서 독서를 시작했다. 책에서 그녀는 "존재"하기 때문에 당한 부당함에 대해 얘기한다. 의지의 영역을 벗어난 것들로 비난 받는 것이 그녀의 인생을 어떻게 바꿔놓았는지 얘기한다. 사회가 바라는 기준대로 개선되지 않으면 노력하지 않은 것으로 치부되어 버리는 세상의 잔혹함에 대해서도 얘기한다. 나도 세상의 구성원으로서 어떤 포지션에서 그녀를(혹은 나를) 대했었나를 생각해보게 하는 책이다. 결국 그녀가 모든 것을 극복하고 happily ever after했다는 내용은 아니다. 오랜 시간을 들여도 이겨내기 힘든 고통도 있으며, 이겨내면 좋겠지만 이겨내지 못 한다고 해서 욕할 필요도 없다는 게 내가 자의적으로 해석한 이 책의 메시지이다. 어차피 내내 행복하기만 한 인생 같은 건 어디에도 없다. 트라우마를 끌어안고 있든 말든 인생은 조금 행복했다가 많이 불행했다가 출렁출렁거릴 것이다. 그러니 어디 가서 남의 인생에 대해서 왈가왈부하지 말고 그냥 신경끄자. 남들이 나서서 나를 부정하지 않는 것만으로도 어떤 상처는 자연히 아물 것이다. 이 모든 말을 전해주려고 자신의 인생을 낱낱이 고백한 한 인간의 진정성과 용기를 존경하게 되는 책이다.
공부
cover_image
On August 4, 2026머리말 회사에서 AI 학습 데이터 버전 관리에 대해 찾아보다가 DVC에 대해 공부하게 됐다. 안 까먹을라고 게시글로 정리해둔다. 공부한 내용 DVC란? DVC는 재현 가능한 머신러닝 프로젝트를 만들 수 있도록 도와주는 커맨드라인 툴이자 VS Code 확장 프로그램 DVC가 데이터를 관리하는 방식 원천 소스가 originalsource 폴더에 있다고 치자. 여기서 dvc add originalsource/를 치면 두 가지가 일어난다. original_source/ 안의 파일들이 캐시(.dvc/cache/)로 복사됨 original_source.dvc라는 포인터 파일이 생김 캐시 안에서는 파일이 MD5 해시값으로 저장된다. 파일시스템 dvc에서 원천 소스와 cache 사이의 관계를 어떻게 설정하는지 알려면 아래 개념을 알아야 한다. inode inode는 모든 파일시스템 객체(일반 파일, 디렉토리, 심볼릭 링크, 디바이스 파일 등)가 공통으로 갖는 "메타데이터 구조"이다. 여기서는 각 파일이 갖는 고유한 "주민등록증" 개념으로 이해해도 된다. 파일 이름을 변경하면 블록이 새로 생기나? 파일 이름은 inode 소속이 아니고, 디렉토리가 들고 있는 정보다. 그래서 mv로 이름을 바꿔도 데이터 블록은 그대로다. 블록 디스크를 4KB 정도로 잘라놓은 조각으로, 실제 데이터가 담기는 최소 단위이다. 워킹 디렉토리와 cache 사이의 관계 위에서 워킹 디렉토리(original_source/) 안의 파일들이 캐시(.dvc/cache/)로 복사된다고 언급했다. 이때 두 개를 연결하는 방식은 hardlink, symlink, reflink, copy 네 가지가 있다. Hardlink 같은 inode를 가리키는 경로를 하나 더 만든다. 한쪽 수정하면 양쪽 다 바뀜 → 같은 inode니까 당연함 한쪽 지워도 나머지 경로로 데이터 접근 가능 같은 파일시스템 안에서만 가능 → inode가 하나의 파일 시스템에서만 유일하기 때문임 디렉토리엔 못 씀 → 디렉토리 자체를 하드링크 걸어버리면 트리 구조에 순환 참조가 생길 수 있음(= 내 안의 나를 바라보는 내 안의 나를 바라보는 내 안의 나를 바라보는...) → 대부분의 유닉스 계열 파일시스템은 아예 OS 레벨에서 디렉토리 하드링크 생성을 막아놓음 Symlink "이 경로면 저기로 가라"는 리다이렉트 메모이다. 원본 지우면 symlink는 끊김 (dangling link) 다른 파일시스템, 다른 서버 경로도 가리킬 수 있음 → hardlink나 reflink처럼 inode 값을 저장하지 않고, 경로 문자열만 저장하기 때문에 찾아갈 수만 있으면 어디에 있든 상관없음 ls -la 치면 -> 화살표로 보임 Reflink 같은 데이터 블록을 두 inode가 독립적으로 참조한다. 한쪽 수정하면 그 순간 그 파일만 새 블록으로 갈라짐 → hardlink는 같은 inode를 공유하기 때문에 한 쪽을 고치면 다른 쪽도 수정되지만, reflink는 완전히 독립적임 두 inode는 대등한 관계로, 어느 쪽도 원본이 아님 APFS(macOS), btrfs, XFS에서만 지원하고, ext4는 지원 안함 Copy 말 그대로 데이터 용량 두 배란 뜻이다. 비교표 | | hardlink | symlink | reflink | |---|---|---|---| | 원본 삭제 시 | 데이터 유지 | 깨짐 | 데이터 유지 | | 수정 시 | 양쪽 반영 | 양쪽 반영 | 각자 독립 | | 다른 파일시스템 | 불가 | 가능 | 불가 | | 디렉토리 | 불가 | 가능 | 가능 | | 관계 | 대등 | 종속 | 대등 | DVC는 실제로 어떤 순서로 링크 방식을 고를까 cache.type의 기본값은 reflink,copy다. 즉 아무 설정도 안 하면 DVC는 reflink를 먼저 시도하고, 안 되면 바로 copy로 넘어간다. hardlink나 symlink는 기본 시도 목록에 아예 없다. 이 두 개를 쓰려면 dvc config cache.type hardlink 처럼 사용자가 직접 지정해야 한다. hardlink나 symlink는 캐시 파일과 워킹 디렉토리 파일이 사실상 같은 데이터를 공유하는 구조라서, 워킹 디렉토리에서 파일을 실수로 고치면 캐시까지 같이 오염될 위험이 있다. (그래서 DVC는 cache.type을 hardlink/symlink로 설정하면 해당 파일들을 자동으로 읽기 전용으로 걸어 보호하고, 수정하려면 dvc unprotect를 쓰라고 안내한다.) reflink는 이런 위험이 없으니까 기본값으로 삼은 거였다. 실제 적용 방식 확인하는 법 캐시 지워도 괜찮은가? 여기서 말하는 '지우다'는 폴더를 그냥 삭제하는 걸 의미하는 건 아니고, dvc가 제공하는 gc를 의미한다. 링크 방식에 따라 다르다. | 방식 | 캐시 삭제 시 original_source/ | |---|---| | reflink | 안전 (블록을 독립적으로 참조) | | hardlink | 안전 (inode가 독립적) | | symlink | 위험 (final이 캐시를 바라보는 구조라 끊김) | | copy | 안전 (완전히 독립적인 사본) | 기본값이 reflink,copy라는 걸 다시 떠올리면, 사실 아무 설정도 안 건드린 보통의 상황에서는 reflink 아니면 copy가 걸려 있을 확률이 높다는 뜻이고, 그러면 캐시를 지워도 대부분 안전하다는 얘기가 된다. 물론 원격 저장소에 저장해두면 캐시를 지워도 다시 pull해오면 되기 때문에 괜찮다. 오히려 로컬 캐시는 최대한 가볍게 유지하고 필요한 버전만 그때 그때 원격에서 가져오는 게 권장된다. 캐시 용량 정리 — dvc gc 되돌릴 수 없는 명령이므로, --dry 옵션으로 뭐가 지워질지 먼저 확인하고 실행하는 습관을 들이는 게 좋다. 리모트 스토리지 설정 기본 워크플로우 맺음말 link 방식들에 대해서 잘 모르는 상태로 무작정 했다가 원천 소스가 그대로 복사돼서 데이터 두 배 이벤트를 맛보았다. 공부는 역시 선행이 제 맛. 참조 DVC 정의 — DVC readme cache.type 기본값, 설정 방법 — Configuration | DVC 캐시 디렉토리 구조(md5 해시 저장 방식) — Internal Files | DVC 링크 방식별 특징, reflink의 copy-on-write 동작 — Large Dataset Optimization | DVC dvc checkout --relink 옵션 — checkout | DVC dvc gc 옵션 전체 — gc | DVC
공부
cover_image
On July 28, 2026머릿말 2026년 1차 정처기 필기/실기 한방에 통과한 사람 나야 나 ☆~(ゝ。∂) 공부하고 싶은데 자격증도 겸사겸사 따면 좋을 거 같아서 정처기 도전했다. 근데 하다보니 공부는 뒷전이고 효율의 한국인답게 기출풀개로 전락해버렸다. (출제 범위에 있는 개념 다 공부하면 올해 안에 시험 못 볼 거 같았음ㅠ) 시험 보고 나니 오히려 마음의 여유가 생겨서 이제는 정처기에 나오는 개념들에 대해 공부다운 공부를 해보려고 한다. 첫번째로 공부할 개념은 기출 최다 빈출 개념인 GoF 디자인 패턴이다. 시험볼 때는 패턴의 정의를 외우는 데에 급급했다면, 이번에는 이게 왜 필요한지, 언제 쓰는지, 이미 쓰고 있었는지에 집중해서 보고자 한다. 생성 패턴(Creational Patterns) 이란? 객체를 만드는 방법을 숨겨서, 객체가 어떻게 만들어지는지 신경 쓰지 않고 사용할 수 있게 하는 패턴들 Singleton 패턴 1) 정의 특정 클래스의 인스턴스가 오직 하나만 존재하도록 보장하고, 어디서든 그 인스턴스로 접근할 수 있는 통로를 제공하는 패턴 2) 언제 쓸까? 인스턴스가 여러 개 생기면 오히려 문제가 될 때 DB Connection Pool: 연결을 여러 개 만들면 리소스 낭비임 설정(config) 객체: 앱 전체가 같은 설정값을 봐야 하는데 인스턴스가 여러 개면 어떤 건 옛날 설정, 어떤 건 새 설정을 참조하는 불일치가 생김 로그 기록기(logger): 로그를 한 곳에 모아야 하는데 인스턴스가 흩어지면 로그도 흩어짐 3) 어떻게 쓸까? 위 코드에선 new Database()를 부를 때마다 매번 새 연결이 생긴다. 커넥션 비용이 비싸다고 가정하면 낭비이다. 예시가 DB여서 좀 애매한데 만약 각 클래스가 각자의 캐시 데이터를 갖고있으면 둘의 싱크가 안 맞아서 문제가 생길 수 있다. 싱글턴 패턴을 반영한 코드에서는 DB 인스턴스가 이미 만들어진 게 있으면 그걸 돌려주고, 없으면 새로 만든다. 그래서 코드 어디서 몇 번을 new Database() 해도 항상 같은 객체가 나온다. 4) 이미 어디서 써봤을까? 사실 JS 개발자는 이 패턴을 의식하지 않고도 매일 쓴다. a.js와 b.js가 각각 import해도 같은 instance를 받는다. 왜냐하면 JS에서 동일한 모듈은 처음 import될 때 딱 한 번 평가되고, 그 이후 어디서 import하든 이미 모듈 캐시에 저장된 export를 재사용한다. 브라우저의 window, document도 전역에 하나뿐인 Singleton이다. Factory Method 패턴 1) 정의 객체 생성의 구체적인 방법을 서브클래스에 위임하는 패턴 부모는 "객체가 필요하다"는 것만 알고, "정확히 어떤 타입의 객체인지"는 몰라도 된다. 제품 종류(객체)마다 전용 공장(서브 클래스)을 짓는다. → Factory Method 2) 언제 쓸까? 타입에 따라 분기해서 객체를 생성하는 코드가 있고, 그 타입이 계속 늘어날 가능성이 있을 때 cf. Factory Method vs Strategy 언뜻 보면 그때 그때 필요한 것을 외부에서 주입한다는 점에서 Strategy 패턴과 헷갈릴 수 있다. 하지만 Strategy가 "알고리즘 선택(= 행동)"의 문제라면, Factory Method는 "어떤 객체를 생성할지"의 문제이다. 3) 어떻게 쓸까? 위 코드에선 새 문서 타입이 생기면 Application 안의 openDocument를 다시 열어 고쳐야 한다. 이미 검증된 로직을 if 조건이 확실한지 확인하기 위해 다시 검증하기도 해야 한다. Factory Method 패턴이 적용된 코드에선 엑셀 지원을 추가하고 싶으면 Application은 한 글자도 안 건드리고 ExcelApplication extends Application { createDocument() { return new ExcelDocument() } }만 새로 추가하면 된다. 4) 이미 어디서 써봤을까? JS 스펙 자체에 Factory Method 패턴이 적용되어 있는 사례가 있다. Array.prototype.map 내부에서 new Array()를 호출하는 것이 아니다. 대신 원본 객체의 constructor[Symbol.species]를 참고하여 어떤 생성자를 사용할지 결정한 뒤 그 생성자로 새로운 결과 객체를 만든다. 즉, 부모 클래스(Array)가 제공하는 map 메서드는 결과 객체의 구체적인 타입 결정을 서브클래스에 위임하므로 Factory Method 패턴과 유사한 구조를 가진다. Array의 filter, slice 등과 Promise.prototype.then도 유사한 species 메커니즘을 사용한다. ⚠️ 최근에는 Symbol.species가 배열 생성 로직 중간에 임의의 코드를 끼워 넣어 메모리를 조작할 수 있는 틈을 제공할 수 있다는 보안 위험성이 제기돼 그냥 내장 객체로 반환하고자 하는 움직임이 있다. (참고 링크) Abstract Factory 패턴 1) 정의 관련 있는 여러 종류의 객체들을 "제품군" 단위로 생성할 수 있도록 객체 생성을 추상화하는 패턴 구체적인 제품 클래스와 생성 방법을 숨기면서 돌아가는 공장 cf. Factory Method vs Abstract Factory 둘 다 이름에 factory가 들어가서 함께 언급되긴 하는데 둘은 초점이 전혀 다른 곳에 맞춰져 있다. Factory Method : 객체 생성을 서브클래스에 위임하는 것에 방점 Abstract Factory : 선택한 계열에 따라 항상 같은 조합을 만들어주는 일관성에 방점 2) 언제 쓸까? 관련된 여러 객체가 반드시 같은 제품군으로 묶여야 할 때 환경/제품군이 바뀌어도 클라이언트 코드를 수정하지 않아야 할 때 3) 어떻게 쓸까? 위 코드에선 새 위젯이 늘어날 때마다 if (theme === 'dark') 분기가 새로 생긴다. 혹시라도 누군가 실수로 한 곳만 빼먹으면 DarkButton + LightCheckbox처럼 세트가 어긋난 상태가 조용히 만들어질 수도 있다. Abstract Factory 패턴이 적용된 위 코드에서 renderToolbar는 theme이 뭔지 모른다. 그냥 받은 factory가 dark든 light든 내부적으로 항상 짝이 맞는 세트를 돌려준다. 새 테마가 추가되어도 renderToolbar는 수정하지 않고 새 팩토리 클래스 하나만 추가하면 된다. 4) 이미 어디서 써봤을까? UI 프레임워크에서 테마별 컴포넌트 생성 시스템을 설계할 때 Abstract Factory 구조를 적용할 수 있다. 색상 테마라든가 OS 환경에 맞게 UI가 다르게 렌더링된다. (→ Concrete Factory가 여러 개다) 클라이언트는 안에 어떤 내용이 있는지 모른다. 항상 일관되게 같은 계열의 UI가 렌더링된다. Builder 패턴 1) 정의 복잡한 객체를 만드는 과정을 여러 단계로 쪼개서, 그 단계들을 조합해 최종 객체를 완성하는 패턴. 한 번의 생성자 호출로 다 끝내는 게 아니라 필요한 부분만 하나씩 붙여 나가다가 마지막에 완성하는 방식 2) 언제 쓸까? 생성자에 넘겨야 할 파라미터가 너무 많고, 그중 상당수가 선택적일 때 → 객체 생성 과정이 복잡할 때 완성되기 전까지 "중간 상태"를 만들면서 검증이 필요할 때 3) 어떻게 쓸까? 위 코드에선 호출부에서 인수의 순서를 외워 써야하고, 건너뛰는 인수는 undefined로 자리를 채워줘야 한다는 불편함이 있다. builder 패턴이 적용된 코드에선 필요한 내용만 호출하면 되고, 각 메서드 이름 자체가 "무슨 값인지"를 설명해줘서 호출부만 읽어도 뭘 하는지 바로 보인다. 또한 build() 시점에 해야 하는 필수값 검증도 한 곳에 모을 수 있다는 장점이 있다. 4) 이미 어디서 써봤을까? Knex.js / Mongoose 쿼리 빌더: knex('users').where('age', '>', 18).orderBy('name').limit(5) — 쿼리 조건을 단계별로 붙여나가다가 마지막에 실행한다. cf. 그럼 메소드 체이닝도 빌더 패턴인가? array.filter().map().reduce() 같은 배열 메소드 체이닝은 Builder가 아니다. 이건 "데이터를 단계별로 변형"하는 거지 "하나의 복잡한 객체를 조립"하는 게 아니기 때문이다. 체이닝의 형태를 띠고 this를 반환하는 것이 비슷해 보여도 목적이 다르다. 체이닝은 단순히 문법의 영역에 속하고, Builder 패턴은 여러 조각을 조립해 하나의 복잡한 객체를 완성하는 데에 의의가 있다. Prototype 패턴 1) 정의 새 객체가 필요할 때 인스턴스를 새로 만드는 대신, 이미 만들어져 있는 견본(prototype) 객체를 복제해서 만드는 패턴 cf. JS에서의 prototype JS 개발자라면 prototype, prototype chaining이라는 개념을 필수적으로 접하게 되는데 여기서 말하는 프로토타입은 GoF에서와의 개념과 조금 다르다. JS의 prototype: 사본이 원본에게 의존하는 관계 → 엄밀히 말해 복사가 아니라 위임 GoF 디자인 패턴의 prototype: 원본과 사본이 완벽하게 독립된 관계 → 복제 위 코드에선 dog가 animalProto의 현재 상태의 영향을 받는다. 아래 코드에선 dogClone이 animalProto와 독립된 객체기 때문에 animalProto의 내용이 변해도 dogClone이 영향 받지 않는다. 2) 언제 쓸까? 객체를 처음부터 만드는 비용이 큰데(ex. 무거운 초기화, 설정 파일 읽기, 계산 등), 비슷한 객체를 반복적으로 많이 만들어야 할 때 Factory 계열 패턴들처럼 "타입별로 서브클래스를 늘려가는 방식"이 부담스러울 때의 대안 만들어야 할 객체의 정확한 클래스/타입이 런타임 전엔 모를 때(JS한텐 별로 의미 없는 케이스) 3) 어떻게 쓸까? 위 코드에선 새 인스턴스가 생길 때마다 무거운 초기화 작업이 반복된다. prototype 패턴을 적용한 코드에선 고블린 프로토타입 인스턴스를 만들어두고 그걸 복사해서 각각의 고블린들을 커스터마이즈한다. 무거운 초기화 작업을 한 번만 하기 때문에 비용이 절약된다. 다만 stats를 { ...this.stats }로 복제하지 않고 그냥 this.stats로 참조를 공유했다면, goblin2.stats.hp = 50이 goblin1.stats.hp까지 같이 바꿔버렸을 거이다. Prototype 패턴에선 얕은 복사 vs 깊은 복사 문제를항상 조심해야 한다. 4) 이미 어디서 써봤을까? 게임 개발 : 몬스터/아이템 하나의 "마스터 템플릿"을 만들어두고, 실제 스폰할 때마다 그걸 복제해서 위치/체력만 바꾸는 방식 — Prototype 패턴의 가장 고전적인 예시 생성 패턴 요약 | 패턴 | 핵심 목적 | JS 실무 대표 활용 사례 | | :--- | :--- | :--- | | Singleton | 단 하나의 인스턴스 공유 | 전역 상태 관리, Logger, DB Connection Pool | | Factory Method | concrete class를 서브클래스로 위임하 여 객체 생성| document.createElement(), 커스텀 SDK | | Abstract Factory | 연관된 제품군 세트의 일관된 생성 | Cross-platform UI 컴포넌트, 테마 시스템 | | Builder | 복잡한 객체의 단계별 조립 | ORM 쿼리 빌더(Knex), HTTP Request Client | | Prototype | 무거운 인스턴스의 복제 생성 | 게임 객체 스폰, structuredClone 활용 |
공부
cover_image
On July 23, 2026서두 2026년 1차 정처기 필기/실기 한방에 통과한 사람 나야 나 ☆~(ゝ。∂) 뭔가 공부하고 싶은데 자격증도 겸사겸사 따면 좋을 거 같아서 정처기 도전했다. 근데 하다보니 공부는 뒷전이고 효율의 한국인답게 기출풀개로 전락해버렸다. (출제 범위에 있는 개념 다 공부하면 올해 안에 시험 못 봄 ㅠ) 우선은 응시해보는 거에 의의를 두고 기출 문제를 무작정 외우고 시험장 들어갔다. 솔직히 시험 치고나서 필기는 붙을 줄 예상했는데 실기는 진짜 천운으로 붙었다. 전혀 모르는 개념의 약어인데 그냥 한국어 설명을 영어로 번역해서 앞글자만 따서 맞춘 문제도 있었다 껄껄. 시험 보고 나니 오히려 마음의 여유가 생겨서 이제는 정처기에 나오는 개념들에 대해 공부다운 공부를 해보려고 한다. 첫번째로 공부할 개념은 기출 최다 빈출 개념인 GoF 디자인 패턴이다. 시험볼 때는 어떤 패턴인지 정의를 외우는 데에 급급했다면, 이번에는 이게 왜 필요한지, 언제 쓰는지, 이미 쓰고 있었는지에 집중해서 보고자 한다. 공부한 내용 Strategy 패턴 정의 알고리즘을 교체 가능한 단위로 캡슐화하는 패턴 즉, 하나의 목적을 위해서 여러 전략(Strategy) 을 바꿔 끼울 수 있게 만드는 패턴 언제/어떻게 쓸까? if/else나 switch를 써서 타입에 따라 다른 알고리즘을 처리하고 있고, 그 종류가 앞으로 계속 늘어날 가능성이 있을 때 써야 한다. 위 내용을 보면 새 할인 정책이 생길 때마다 calculate() 메서드 내부를 계속 고쳐야 한다. 기존 코드를 안 건드리고 새 알고리즘을 추가할 수 있어야 한다는 OCP(Open-Closed Principle) 원칙에 위배되기 때문에 Strategy 패턴을 써서 해결해보겠다. 아래는 Strategy 패턴 적용 후의 모습이다. Strategy 패턴이 적용된 코드에서는 PriceCalculator(Context)가 한 번도 수정된 적이 없다. 새 할인 정책은 discountStrategies 객체에 함수 하나 추가하는 것으로 끝난다. 이미 어디서 써봤을까? array.sort(compareFn) : 정렬 전략을 함수로 갈아끼우기 array.filter(predicate) / array.map(fn) : "어떻게 걸러낼지", "어떻게 변환할지"를 함수로 주입 즉, JavaScript에서 콜백 함수를 인자로 넘기는 습관 자체가 이미 Strategy 패턴이라고 볼 수 있다. Observer 패턴 정의 어떤 주체(Subject)의 상태가 바뀌었을 때, 그 변화에 관심 있는 여러 관찰자(Observer)들에게 자동으로 알려주는 패턴 하나의 변화가 여러 곳에 퍼지는 1:N 관계 언제/어떻게 쓸까? 한 곳의 상태 변화에 반응해야 할 대상이 여러 개이고, 그 개수·종류가 앞으로도 계속 늘어날 수 있을 때 사용하는 패턴이다. Subject 코드 안에 반응해야 할 대상들이 하드코딩되어 있고, 새 대상이 추가될 때마다 그 코드를 계속 고쳐야 한다면 Observer 패턴을 고려해야 한다. 주문이 완료되면 이메일 발송, SMS 발송, 로그 기록을 해야 한다고 가정해보자. OrderService가 EmailService, SmsService, LogService를 전부 직접 알고 있어야 하고, 강하게 결합되어 있다. '슬랙 채널 알림' 같이 새 알림 방식이 생기면 이 클래스를 또 열어서 고쳐야 한다. Strategy 때와 똑같이 OCP 위반 문제가 있다. 위에서 OrderService는 EmailService, SmsService가 뭔지 전혀 모르고, 목록에 있는 함수들을 순서대로 호출한다는 것만 안다. 새 알림 채널을 추가하고 싶으면 subscribe만 호출하면 끝이다. Strategy 때와 마찬가지로 기존 코드를 재수정할 필요가 없다. 이미 어디서 써봤을까? element.addEventListener('click', handler) : element(Subject)는 몇 개의 핸들러가 붙어있는지, 그 핸들러가 뭘 하는지 전혀 모른다. 그냥 클릭이 발생하면 등록된 모든 핸들러를 순서대로 호출한다. Node.js EventEmitter: Node.js에서 이미 만들어놓은 observer .on('event', listener) = subscribe .emit('event', ...args) = notify .off(eventName, listener) = unsubscribe Redux store.subscribe(listener): state가 바뀔 때마다 등록된 모든 리스너(주로 React 컴포넌트 리렌더 트리거)를 호출 Iterator 패턴 정의 컬렉션의 내부 구조가 어떻게 생겼는지 몰라도, 그 안의 요소들을 순서대로 하나씩 꺼내볼 수 있는 통일된 방법을 제공하는 패턴 핵심은 순회하는 방법과 컬렉션이 내부적으로 어떻게 저장돼 있는지를 분리하는 것 언제 쓸까? 컬렉션의 내부 구현을 몰라도 되게 만들고 싶을 때 서로 다른 자료구조 여러 개를 동일한 방식으로 순회하고 싶을 때 — 배열이든 Set이든 Map이든 for...of로 똑같이 돌 수 있는 것처럼 컬렉션 내부 구조가 나중에 바뀔 수 있는데 그때마다 그걸 순회하던 바깥 코드까지 다 같이 고치고 싶지 않을 때 어떻게 쓸까? 위 구조에서는 shelf.books라는 이름과 그게 배열이라는 사실까지 알아야 한다. 나중에 Bookshelf를 배열에서 Map이나 연결 리스트로 바꾸면 이걸 순회하던 외부 코드가 깨진다. 이제 외부 코드는 shelf.books라는 필드가 존재하는지조차 모르지만 for...of는 그냥 Symbol.iterator를 호출해서 { next() {...} } 형태의 객체(이터레이터)를 받고, next()를 반복 호출해서 { value, done }을 받는 약속(프로토콜) 만 지키면 된다. 이제 Bookshelf 내부를 배열 대신 Map이나 연결 리스트로 바꿔도, [Symbol.iterator] 메서드 내부만 고치면 되고 바깥의 for...of 코드는 바뀌지 않는다. 참고로 제너레이터 문법을 쓰면 훨씬 짧게 쓸 수 있다: 이미 어디서 써봤을까? for...of : 루프 자체가 Iterator 패턴 위에 세워진 문법 array.entries(), array.keys(), array.values() : 전부 이터레이터를 반환 제너레이터 함수(function*) : 이터레이터를 쉽게 만들게 해주는 JS 문법 const arr = [...shelf]; : 스프레드 연산자 const [first] = shelf; : 구조 분해 할당 Array.from(shelf); : 배열이 아닌 것을 진짜 배열로 변환해주는 함수 Command 패턴 정의 요청을 객체로 캡슐화해서 실행부와 분리하는 패턴 패턴의 이름이 command인 이유는 군대에서의 상명하복처럼 실행하는 측은 요청이 뭔지 몰라도 무조건 실행하기 때문이다. (그렇다고 요청과 실행 사이에 위계질서가 있다는 뜻은 아님) Cf. Strategy와 헷갈리는 지점 Strategy와 Command 패턴 둘 다 동작을 함수/객체로 캡슐화해서 들고 다닌다라는 개념이 공통돼서 헷갈릴 수 있다. 하지만 두 패턴이 방점을 찍은 지점이 다르다는 것을 알면 헷갈리지 않을 수 있다. - Strategy: "이 알고리즘들 중 어떤 걸로 할지 고르는 것"이 목적으로, Context는 전략 하나를 골라 즉시 실행한다. - Command: "이 요청 자체를 나중에 실행하거나, 여러 번 실행하거나, 되돌릴 수 있게" 포장하는 게 목적으로, 반환값보다 실행 시점을 미루는 것 / 실행 이력 / 되돌리기 능력이 핵심이다. 언제 쓸까? 누가 무슨 요청했는지와 그 요청을 어떻게 실행/취소하는지를 분리하고 싶을 때 - "실행"과 "실행 취소"를 함께 관리해야 할 때 - 요청을 즉시 실행하지 않고 큐에 쌓았다가 나중에 순서대로 실행해야 할 때 - 실행된 작업들의 이력을 남기고 재실행해야 할 때 - 여러 명령을 하나로 묶어서 한 번에 실행해야 할 때 어떻게 쓸까? 위 코드에서 방금 한 작업만 취소하고 싶다면 editor 안에 히스토리 스택을 직접 넣고, undo 로직을 editor 내부에 추가해야 한다. 수정된 코드에서 TextEditor는 "undo"라는 개념을 몰라도 된다. undo 기능은 각 AddTextCommand 객체가 들고 있고, CommandManager는 그걸 순서대로 쌓았다 꺼내는 일만 한다. 이미 어디서 써봤을까? command line : - 셸 소스코드 안에는 요청 로직이 없고(cli 프로그램 따로 설치해야 됨) - 실행 후에도 이력이 남아있고 - 다시 실행 가능하며 - 여러개를 묶어서 하나로 실행할 수 있는 전형적인 command 패턴 Redux의 action : - action(요청 객체) → dispatch → reducer(실행) - 요청과 실행이 분리됨 텍스트 에디터 : 사용자가 과거에 한 요청의 이력을 쌓고 연속적으로 실행취소할 수 있음 후기 존경하는 AI 선생님께 GoF 디자인 패턴을 사사했는데 내용이 너무 많다보니 handoff.md를 작성해놓고 계속 세션을 새로 생성하면서 대화를 이어 나갔다. 어차피 내가 한 대화 기록일 테니 따로 handoff.md 파일을 읽어보지 않았는데 어쩌다 열어보니 이런 내용이 있더라. 사용자 학습 스타일 메모 정의 암기보다 "왜 필요한가"에 집중하길 원함 Before/After 코드 비교로 이해하는 걸 선호 이미 알고 있는 JS 관용구와 연결지어 설명하면 이해가 빠름 개념이 조금이라도 애매하면 바로 "이게 무슨 말이야?" 하고 파고드는 스타일 — 세밀하고 구체적인 재설명을 원함, 두루뭉술한 답변은 피할 것 직전 답변에서 쓴 특정 문장/단어를 그대로 인용하며 되묻는 경우가 많음 — 답변 시 그 원문 표현을 다시 가져와서 정확히 대응시켜 설명할 것 새 패턴을 배울 때 "이거 사실 전에 배운 패턴이랑 같은 거 아니야?"처럼 스스로 유사점을 찾아 비교하려는 경향이 있음 — 틀렸을 때 무조건 아니라고 하지 말고, 어느 지점까지는 맞는 관찰인지 인정한 뒤 정확한 차이(의도/목적)를 짚어줄 것 선생님 제가 귀찮으셨던 건 아니죠. 멍청해서 죄송해요 ㅠㅠ 덕분에 아무 죄책감과 쪽팔림 없이 바보 질문을 남발했습니다. 탄생해주셔서 감사합니다. 정처기 볼 때는 정의랑 패턴 이름이 도대체 무슨 연관이 있는지 모르겠어서 외우기 힘들었는데 역시 개념을 조금이라도 이해해야 외우기도 좀 더 수월하다. 다음에는 구조 패턴 공부해야징~