<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <author>
    <name>어썸블로그</name>
  </author>
  <id>국내의 좋은 블로그 글들을 매일 배달해줍니다.</id>
  <title>개발자 어썸블로그</title>
  <updated>2026-03-10T00:00:24+00:00</updated>
  <entry>
    <author>
      <name>강진우</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;&lt;p&gt;"요즘엔 재밌게 읽은 책 있어?"  모임에서 누군가 가볍게 던진 질문이었다. 별것 아닌 질문인데, 바로 대답이 안 나왔다. 나는 그래도 책을 꽤 읽는 편이라고 생각했다. 특히 소설은 챙겨 읽는 편인데, 막상 "뭘 읽었어?"라고 물으면 제목이 안 떠오른다.  어물쩡 대답을 흘리고, 나중에 교보문고 구매 기록을 찬찬히 살펴봤다. 아 맞아 이 책 읽었지, 와 이&lt;img src="https://img1.kakaocdn.net/thumb/R1280x0/?fname=http%3A%2F%2Ft1.kakaocdn.net%2Fbrunch%2Fservice%2Fuser%2F3Y0%2Fimage%2FeL-afOumwPSjlEx4nQqZjHVxNxo.png" width="500"&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://brunch.co.kr/@@3Y0/81</id>
    <link href="https://brunch.co.kr/@@3Y0/81"/>
    <summary type="html">&amp;quot;요즘엔 재밌게 읽은 책 있어?&amp;quot;  모임에서 누군가 가볍게 던진 질문이었다. 별것 아닌 질문인데, 바로 대답이 안 나왔다. 나는 그래도 책을 꽤 읽는 편이라고 생각했다. 특히 소설은 챙겨 읽는 편인데, 막상 &amp;quot;뭘 읽었어?&amp;quot;라고 물으면 제목이 안 떠오른다.  어물쩡 대답을 흘리고, 나중에 교보문고 구매 기록을 찬찬히 살펴봤다. 아 맞아 이 책 읽었지, 와 이&lt;img src= "https://img1.kakaocdn.net/thumb/R1280x0/?fname=http%3A%2F%2Ft1.kakaocdn.net%2Fbrunch%2Fservice%2Fuser%2F3Y0%2Fimage%2FeL-afOumwPSjlEx4nQqZjHVxNxo.png" width="500" /&gt;</summary>
    <title>작년에 읽은 책 중 하나만 말해보세요.</title>
    <updated>2026-03-07T12:11:03+00:00</updated>
    <dc:date>2026-03-07T12:11:03+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>김재호</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;지난 번 어떤 메이저 신문사에 인터뷰 답변을 길게 보냈는데… 연락이 없으셔서 제가 &lt;a href="https://jeho.page/essay/2025/11/11/self-interview.html"&gt;셀프로 발행&lt;/a&gt;한 적이 있습니다(웃음)&lt;br&gt;
그 셀프 인터뷰를 보신 중앙일보 &lt;a href="https://media.naver.com/journalist/025/25728"&gt;홍상지 기자님&lt;/a&gt;께서 연락을 주셔서 또 다른 인터뷰를 하고 &lt;a href="https://n.news.naver.com/mnews/article/025/0003506944"&gt;기사를 발행&lt;/a&gt;해주셨습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://jeho.page/assets/img/hongsam.png" alt="1인 창업 생존기 인터뷰"&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://n.news.naver.com/mnews/article/025/0003506944"&gt;1인 창업 생존기 인터뷰&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;인터뷰 중 AI 시대에 살아남기 위해 어떤 노력을 하고 있냐는 질문을 해주셨습니다.&lt;/p&gt;

&lt;p&gt;음… 내가 무슨 노력을 하고 있더라? 아, 하나 있다.&lt;/p&gt;

&lt;p&gt;엔지니어라는 고정 관념을 깨고 디자이너, 마케터, 기획자의 시야를 갖출 수 있도록.&lt;br&gt;
진짜 1인 &lt;code class="language-plaintext highlighter-rouge"&gt;product developer&lt;/code&gt; 가 되기 위해 애쓰고 있습니다.&lt;/p&gt;

&lt;p&gt;미술적 감각이나 마케팅 센스가 떨어지는 것은 저의 큰 단점.&lt;br&gt;
AI 에게 부탁하면 어느 정도 그럴듯 하게 작업해주지만, 그렇다고 AI만 믿고 공부를 안 할 수는 없지 않을까? 적어도 기본기는 갖춰야 AI도 더 잘 쓰지.&lt;br&gt;
색감각, UI/UX, 타이포그라피, 더 나아가서 심리학 책들도 읽어보고 있습니다. 제품 개발자로서 역량을 높이기 위해서.&lt;/p&gt;

&lt;p&gt;학생 때 제일 싫어했던 과목이 미술, 음악이었는데요.&lt;br&gt;
신기하게도 나이 들어 공부하니 이런 것들조차 재밌네요.&lt;br&gt;
뭔가를 배우고 스스로 조금씩 발전하는 모습을 보는 것은 인생의 가장 큰 재미인 것 같습니다.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;em&gt;함께 읽으면 좋은 글:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://jeho.page/essay/2025/11/11/self-interview.html"&gt;1인 개발자 셀프 인터뷰&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://jeho.page/essay/2025/02/24/full-stack-life.html"&gt;풀스택 인생&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://jeho.page/essay/2026/03/05/developer-new-efforts.html</id>
    <link href="https://jeho.page/essay/2026/03/05/developer-new-efforts.html"/>
    <summary type="html">지난 번 어떤 메이저 신문사에 인터뷰 답변을 길게 보냈는데… 연락이 없으셔서 제가 셀프로 발행한 적이 있습니다(웃음) 그 셀프 인터뷰를 보신 중앙일보 홍상지 기자님께서 연락을 주셔서 또 다른 인터뷰를 하고 기사를 발행해주셨습니다.</summary>
    <title>개발자로 잘 지내기 위한 새로운 노력</title>
    <updated>2026-03-05T05:40:00+00:00</updated>
    <dc:date>2026-03-05T05:40:00+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Terry Cho</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;&lt;p&gt;Claude Code 쉽게 따라하기 (기본편)- 3. Claude Code를 더 똑똑하게 만들자 Planning &amp;amp; Thinking Mode
Claude Code에는 복잡한 문제를 더 똑똑하게 풀어낼 수 있는 Planning과 Thinking 모드라는 것이 있다. 
Planning mode
코드에 대한 조금 더 디테일한 분석이 필요하거나, 복잡한 구현이 필요하거나 여러 파일을 참고해야 할때는 Claude Code가 더 자세하게 계획을 만..&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://bcho.tistory.com/1503</id>
    <link href="https://bcho.tistory.com/1503"/>
    <summary type="html">Claude Code 쉽게 따라하기 (기본편)- 3. Claude Code를 더 똑똑하게 만들자 Planning &amp;amp; Thinking Mode
Claude Code에는 복잡한 문제를 더 똑똑하게 풀어낼 수 있는 Planning과 Thinking 모드라는 것이 있다.&amp;nbsp;
Planning mode
코드에 대한 조금 더 디테일한 분석이 필요하거나, 복잡한 구현이 필요하거나 여러 파일을 참고해야 할때는 Claude Code가 더 자세하게 계획을 만..</summary>
    <title>Claude Code - #3 더 똑똑하게, Planning &amp;amp; Thinking Mode</title>
    <updated>2026-03-09T07:32:21+00:00</updated>
    <dc:date>2026-03-09T07:32:21+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Terry Cho</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;&lt;p&gt;Claude Code 쉽게 따라하기 (기본편)- 2. 요금제 이해하기
조대협 (http://bcho.tistory.com)
 
Claude Code 요금제는 크게, Pro, Max 5x, Max 20X 가 있다.




구분
Claude Pro
Claude Max 5x
Claude Max 20x


월 요금 (USD)
$20
$100
$200


5시간 세션 한도
약 4.5만 토큰(메시지 약 45개)
약 22.5만 토큰(메시지 약 225개)
약..&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://bcho.tistory.com/1502</id>
    <link href="https://bcho.tistory.com/1502"/>
    <summary type="html">Claude Code 쉽게 따라하기 (기본편)- 2. 요금제 이해하기
조대협 (http://bcho.tistory.com)
&amp;nbsp;
Claude Code 요금제는 크게, Pro, Max 5x, Max 20X 가 있다.




구분
Claude Pro
Claude Max 5x
Claude Max 20x


월 요금 (USD)
$20
$100
$200


5시간 세션 한도
약 4.5만 토큰(메시지 약 45개)
약 22.5만 토큰(메시지 약 225개)
약..</summary>
    <title>Claude Code - #2 요금제 이해하기</title>
    <updated>2026-03-09T07:08:33+00:00</updated>
    <dc:date>2026-03-09T07:08:33+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Terry Cho</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;&lt;p&gt;Claude Code 쉽게 따라하기 (기본편)- 1. Project 설정, CLAUD.md 이해하기
조대협 (http://bcho.tistory.com)
Project Setup
첫번째로 해야 하는 일은, 기존의 프로젝트가 있는 (또는 작업할) 디렉토리에서 claude code 의 /init 명령을 수행하는 것이다. 
Claude code를 실행한다음 다음과 같이 “/init”명령을 실행한다

Init 작업은 현재 디렉토리..&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://bcho.tistory.com/1501</id>
    <link href="https://bcho.tistory.com/1501"/>
    <summary type="html">Claude Code 쉽게 따라하기 (기본편)- 1. Project 설정, CLAUD.md 이해하기
조대협 (http://bcho.tistory.com)
Project Setup
첫번째로 해야 하는 일은, 기존의 프로젝트가 있는 (또는 작업할) 디렉토리에서 claude code 의 /init 명령을 수행하는 것이다.&amp;nbsp;
Claude code를 실행한다음 다음과 같이 &amp;ldquo;/init&amp;rdquo;명령을 실행한다

Init 작업은 현재 디렉토리..</summary>
    <title>Claude Code - #1 Project 설정, Claude.md</title>
    <updated>2026-03-09T07:06:08+00:00</updated>
    <dc:date>2026-03-09T07:06:08+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Jaeyeon Baek</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p data-ke-size="size16"&gt;AI를 잘 쓰기 위한 도구를 이것저것 조합하다 보니까 뇌에서 단축키 혼란을 겪고 있음... 그래서 사용하고 있는 것들의 단축키를 짧게 메모. 훨씬 더 많은 단축키가 있지만 자주 사용하는 것들만 기술함. &lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;&lt;span style="color: #f89009;"&gt;&lt;b&gt;# tmux&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;tmux나 screen 같은 걸 원래 잘 안 썼음. 근데 claude code 때문에 어쩔 수 없이 써야 하는 상황이 왔음. 단축키 몇 개면 불편하지 않게 쓸 수 있으니까 적응하도록 함...&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;우선 설치는 아래와 같이 진행함&lt;/p&gt;
&lt;pre id="code_1772693435830" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install tmux&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;아래는 단축키인데 모든 명령어는 &lt;b&gt;Ctrl + b 이후에 타이핑&lt;/b&gt;하면 됨&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;" border="1" data-ke-align="alignLeft"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;창 가로 분할&lt;/td&gt;
&lt;td style="width: 50%;"&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;창 세로 분할&lt;/td&gt;
&lt;td style="width: 50%;"&gt;%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;detach&lt;/td&gt;
&lt;td style="width: 50%;"&gt;d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;View all keybindings&lt;/td&gt;
&lt;td style="width: 50%;"&gt;? &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;세션 rename&lt;/td&gt;
&lt;td style="width: 50%;"&gt;$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;판넬 이동&lt;/td&gt;
&lt;td style="width: 50%;"&gt;방향키&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;세션 이름 지정해서 접속&lt;/td&gt;
&lt;td style="width: 50%;"&gt;tmux new -s &amp;lt;세션이름&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;attach&lt;/td&gt;
&lt;td style="width: 50%;"&gt;tmux attach -t &amp;lt;세션이름&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;tmux 창에서 직접 설정할 수도 있지만 모든 세션에 기본 적용하기 위해 &lt;b&gt;~/.tmux.conf&lt;/b&gt; 파일에 아래 내용 추가. 첫 번째 줄은 마우스를 사용할 수 있게 만드는 옵션. 이를 설정하면 탭 간 이동할 때 단축키 누르지 않아도 편함. 두 번째는 tmux에서 일어나는 모든 복사 작업에 대해 macOS 클립보드를 사용하겠다는 의미. 세 번째는 드래그가 끝나면 클립보드로 복사하고 복사모드를 종료하겠다는 의미.&lt;/p&gt;
&lt;pre id="code_1772693549947" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;set -g mouse on
set -s copy-command "pbcopy"
bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size="size26"&gt; &lt;/h2&gt;
&lt;h2 data-ke-size="size26"&gt;&lt;span style="color: #f89009;"&gt;&lt;b&gt;# nvim&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;vim을 오랫동안 사랑해 온 유저로써 neovim을 쓰기로 했음. 굳이 vim이 아닌 neovim을 선택한 이유는 탐색기 때문. neovim을 설치하고 plugin을 추가로 설치하면 ide처럼 익숙한 탐색기를 쓸 수 있게 됨&lt;/p&gt;
&lt;pre id="code_1772693368543" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install neovim&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;제일 많이 쓰는 단축키는 아래 정도... 나머지는 vim 단축키라서 굳이 적지 않음. &lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;" border="1" data-ke-align="alignLeft"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;g?&lt;/td&gt;
&lt;td style="width: 50%;"&gt;nvim tree 단축키 도움말&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;gd&lt;/td&gt;
&lt;td style="width: 50%;"&gt;go to define&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;Ctrl + t&lt;/td&gt;
&lt;td style="width: 50%;"&gt;태그 스택에서 이전 위치로 팝(Pop)하여 돌아가기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;neovim의 설정파일은 ~/.config/nvim/init.lua 이쪽에 저장됨. 내가 최근에 사용하고 있는 설정을 그대로 첨부함 &lt;/p&gt;
&lt;div data-ke-type="moreLess" data-text-more="더보기" data-text-less="닫기"&gt;
&lt;a class="btn-toggle-moreless"&gt;더보기&lt;/a&gt;
&lt;div class="moreless-content"&gt;
&lt;p data-ke-size="size16"&gt;-- Bootstrap lazy.nvim&lt;br&gt;local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"&lt;br&gt;if not (vim.uv or vim.loop).fs_stat(lazypath) then&lt;br&gt;  local lazyrepo = "&lt;a href="https://github.com/folke/lazy.nvim.git" target="_blank" rel="noopener noreferrer"&gt;https://github.com/folke/lazy.nvim.git&lt;/a&gt;"&lt;br&gt;  local out = vi&lt;a href="http://m.fn.system(" target="_blank" rel="noopener noreferrer"&gt;http://m.fn.system(&lt;/a&gt;{ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })&lt;br&gt;  if vim.v.shell_error ~= 0 then&lt;br&gt;    vim.api.nvim_echo({&lt;br&gt;      { "Failed to clone lazy.nvim:\n", "ErrorMsg" },&lt;br&gt;      { out, "WarningMsg" },&lt;br&gt;      { "\nPress any key to exit..." },&lt;br&gt;    }, true, {})&lt;br&gt;    vim.fn.getchar()&lt;br&gt;    os.exit(1)&lt;br&gt;  end&lt;br&gt;end&lt;br&gt;vi&lt;a href="http://m.opt.rtp:prepend(lazypath)" target="_blank" rel="noopener noreferrer"&gt;http://m.opt.rtp:prepend(lazypath)&lt;/a&gt;&lt;br&gt;&lt;br&gt;-- Make sure to setup `mapleader` and `maplocalleader` before&lt;br&gt;-- loading lazy.nvim so that mappings are correct.&lt;br&gt;-- This is also a good place to setup other settings (vim.opt)&lt;br&gt;vim.g.mapleader = " "&lt;br&gt;vim.g.maplocalleader = "\\"&lt;br&gt;&lt;br&gt;-- LSP keymaps&lt;br&gt;vim.api.nvim_create_autocmd("LspAttach", {&lt;br&gt;  callback = function(args)&lt;br&gt;    local opts = { buffer = args.buf }&lt;br&gt;    vi&lt;a href="http://m.keymap.set(" target="_blank" rel="noopener noreferrer"&gt;http://m.keymap.set(&lt;/a&gt;"n", "gd", vi&lt;a href="http://m.lsp.buf.definition," target="_blank" rel="noopener noreferrer"&gt;http://m.lsp.buf.definition,&lt;/a&gt; opts)&lt;br&gt;    vi&lt;a href="http://m.keymap.set(" target="_blank" rel="noopener noreferrer"&gt;http://m.keymap.set(&lt;/a&gt;"n", "K", vi&lt;a href="http://m.lsp.buf.hover," target="_blank" rel="noopener noreferrer"&gt;http://m.lsp.buf.hover,&lt;/a&gt; opts)&lt;br&gt;  end,&lt;br&gt;})&lt;br&gt;&lt;br&gt;-- auto-reload files changed outside of nvim&lt;br&gt;vim.opt.autoread = true&lt;br&gt;vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter", "CursorHold" }, {&lt;br&gt;  command = "silent! checktime",&lt;br&gt;})&lt;br&gt;&lt;br&gt;-- disable netrw (recommended by nvim-tree)&lt;br&gt;vim.g.loaded_netrw = 1&lt;br&gt;vim.g.loaded_netrwPlugin = 1&lt;br&gt;&lt;br&gt;-- Setup lazy.nvim&lt;br&gt;require("lazy").setup({&lt;br&gt;  spec = {&lt;br&gt;    -- colorscheme&lt;br&gt;    {&lt;br&gt;      "ellisonleao/gruvbox.nvim",&lt;br&gt;      priority = 1000,&lt;br&gt;      config = function()&lt;br&gt;        vim.o.background = "dark"&lt;br&gt;        vim.cmd.colorscheme("gruvbox")&lt;br&gt;      end,&lt;br&gt;    },&lt;br&gt;    -- syntax highlighting&lt;br&gt;    {&lt;br&gt;      "nvim-treesitter/nvim-treesitter",&lt;br&gt;      build = ":TSUpdate",&lt;br&gt;      main = "nvim-treesitter",&lt;br&gt;      opts = {&lt;br&gt;        ensure_installed = { "python", "hcl", "yaml" },&lt;br&gt;        highlight = { enable = true },&lt;br&gt;      },&lt;br&gt;    },&lt;br&gt;    -- LSP&lt;br&gt;    {&lt;br&gt;      "neovim/nvim-lspconfig",&lt;br&gt;      dependencies = { "hrsh7th/cmp-nvim-lsp" },&lt;br&gt;      config = function()&lt;br&gt;        vi&lt;a href="http://m.lsp.config(" target="_blank" rel="noopener noreferrer"&gt;http://m.lsp.config(&lt;/a&gt;"*", {&lt;br&gt;          capabilities = require("cmp_nvim_lsp").default_capabilities(),&lt;br&gt;        })&lt;br&gt;        vi&lt;a href="http://m.lsp.config(" target="_blank" rel="noopener noreferrer"&gt;http://m.lsp.config(&lt;/a&gt;"terraformls", {&lt;br&gt;          cmd = { "terraform-ls", "serve" },&lt;br&gt;          filetypes = { "terraform", "terraform-vars" },&lt;br&gt;          root_markers = { ".terraform", ".git" },&lt;br&gt;        })&lt;br&gt;        vi&lt;a href="http://m.lsp.enable(" target="_blank" rel="noopener noreferrer"&gt;http://m.lsp.enable(&lt;/a&gt;"terraformls")&lt;br&gt;        vi&lt;a href="http://m.lsp.config(" target="_blank" rel="noopener noreferrer"&gt;http://m.lsp.config(&lt;/a&gt;"pyright", {&lt;br&gt;          cmd = { "pyright-langserver", "--stdio" },&lt;br&gt;          filetypes = { "python" },&lt;br&gt;          root_markers = { "pyproject.toml", "setup.py", "setup.cfg", ".git" },&lt;br&gt;          before_init = function(params, config)&lt;br&gt;            config.settings = config.settings or {}&lt;br&gt;            config.settings.python = config.settings.python or {}&lt;br&gt;            local bufname = vim.api.nvim_buf_get_name(0)&lt;br&gt;            local dir = vim.fn.fnamemodify(bufname, ":h")&lt;br&gt;            while dir ~= "/" do&lt;br&gt;              local venv_python = dir .. "/.venv/bin/python"&lt;br&gt;              if vim.fn.executable(venv_python) == 1 then&lt;br&gt;                config.settings.python.pythonPath = venv_python&lt;br&gt;                return&lt;br&gt;              end&lt;br&gt;              dir = vim.fn.fnamemodify(dir, ":h")&lt;br&gt;            end&lt;br&gt;          end,&lt;br&gt;          settings = {&lt;br&gt;            python = {&lt;br&gt;              analysis = { autoSearchPaths = true, useLibraryCodeForTypes = true },&lt;br&gt;            },&lt;br&gt;          },&lt;br&gt;        })&lt;br&gt;        vi&lt;a href="http://m.lsp.enable(" target="_blank" rel="noopener noreferrer"&gt;http://m.lsp.enable(&lt;/a&gt;"pyright")&lt;br&gt;      end,&lt;br&gt;    },&lt;br&gt;    -- formatting&lt;br&gt;    {&lt;br&gt;      "stevearc/conform.nvim",&lt;br&gt;      opts = {&lt;br&gt;        format_on_save = { timeout_ms = 500 },&lt;br&gt;        formatters_by_ft = {&lt;br&gt;          terraform = { "terraform_fmt" },&lt;br&gt;          ["terraform-vars"] = { "terraform_fmt" },&lt;br&gt;        },&lt;br&gt;      },&lt;br&gt;    },&lt;br&gt;    -- autocompletion&lt;br&gt;    {&lt;br&gt;      "hrsh7th/nvim-cmp",&lt;br&gt;      dependencies = {&lt;br&gt;        "hrsh7th/cmp-nvim-lsp",&lt;br&gt;        "hrsh7th/cmp-buffer",&lt;br&gt;      },&lt;br&gt;      config = function()&lt;br&gt;        local cmp = require("cmp")&lt;br&gt;        cmp.setup({&lt;br&gt;          sorting = {&lt;br&gt;            comparators = {&lt;br&gt;              function(entry1, entry2)&lt;br&gt;                local detail1 = entry1:get_completion_item().detail or ""&lt;br&gt;                local detail2 = entry2:get_completion_item().detail or ""&lt;br&gt;                local req1 = detail1:find("required") ~= nil&lt;br&gt;                local req2 = detail2:find("required") ~= nil&lt;br&gt;                if req1 ~= req2 then&lt;br&gt;                  return req1&lt;br&gt;                end&lt;br&gt;              end,&lt;br&gt;              require("cmp.config.compare").offset,&lt;br&gt;              require("cmp.config.compare").exact,&lt;br&gt;              require("cmp.config.compare").score,&lt;br&gt;              require("cmp.config.compare").recently_used,&lt;br&gt;              require("cmp.config.compare").kind,&lt;br&gt;              require("cmp.config.compare").length,&lt;br&gt;              require("cmp.config.compare").order,&lt;br&gt;            },&lt;br&gt;          },&lt;br&gt;          formatting = {&lt;br&gt;            format = function(entry, vim_item)&lt;br&gt;              local detail = entry:get_completion_item().detail&lt;br&gt;              if detail then&lt;br&gt;                vim_item.menu = detail&lt;br&gt;              end&lt;br&gt;              return vim_item&lt;br&gt;            end,&lt;br&gt;          },&lt;br&gt;          mapping = cmp.mapping.preset.insert({&lt;br&gt;            ["&amp;lt;C-l&amp;gt;"] = cmp.mapping.complete(),&lt;br&gt;            ["&amp;lt;CR&amp;gt;"] = cmp.mapping.confirm({ select = true }),&lt;br&gt;            ["&amp;lt;Tab&amp;gt;"] = cmp.mapping.select_next_item(),&lt;br&gt;            ["&amp;lt;S-Tab&amp;gt;"] = cmp.mapping.select_prev_item(),&lt;br&gt;          }),&lt;br&gt;          sources = {&lt;br&gt;            { name = "nvim_lsp" },&lt;br&gt;            { name = "buffer" },&lt;br&gt;          },&lt;br&gt;        })&lt;br&gt;      end,&lt;br&gt;    },&lt;br&gt;    -- git signs&lt;br&gt;    {&lt;br&gt;      "lewis6991/gitsigns.nvim",&lt;br&gt;      opts = {},&lt;br&gt;    },&lt;br&gt;    {&lt;br&gt;      "nvim-tree/nvim-tree.lua",&lt;br&gt;      dependencies = { "nvim-tree/nvim-web-devicons" },&lt;br&gt;      keys = {&lt;br&gt;        { "&amp;lt;leader&amp;gt;e", "&amp;lt;cmd&amp;gt;NvimTreeToggle&amp;lt;cr&amp;gt;", desc = "Toggle file explorer" },&lt;br&gt;        { "&amp;lt;leader&amp;gt;E", "&amp;lt;cmd&amp;gt;NvimTreeFocus&amp;lt;cr&amp;gt;", desc = "Focus file explorer" },&lt;br&gt;      },&lt;br&gt;      config = function()&lt;br&gt;        require("nvim-tree").setup({&lt;br&gt;          filesystem_watchers = {&lt;br&gt;            enable = true,&lt;br&gt;          },&lt;br&gt;          filters = {&lt;br&gt;            dotfiles = false,&lt;br&gt;            git_ignored = false,&lt;br&gt;          },&lt;br&gt;        })&lt;br&gt;      end,&lt;br&gt;    },&lt;br&gt;  },&lt;br&gt;  -- Configure any other settings here. See the documentation for more details.&lt;br&gt;  -- colorscheme that will be used when installing plugins.&lt;br&gt;  install = { colorscheme = { "habamax" } },&lt;br&gt;  -- automatically check for plugin updates&lt;br&gt;  checker = { enabled = true },&lt;br&gt;})&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size="size16"&gt;그리고 아래 두 개 정도 설치해 주면 terraform도 해피코딩 할 수 있게 됨. 이럴 거면 그냥 vscode 쓰라는 말도 있지만, 그건 왠지 손이 안 가서... :) &lt;/p&gt;
&lt;pre id="code_1772695642855" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install hashicorp/tap/terraform-ls
brew install pyright&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;설정을 완료하면 대략 아래와 같은 화면으로 코딩할 수 있게 됨.&lt;/p&gt;
&lt;p&gt;&lt;figure class="imageblock alignCenter" data-ke-mobilestyle="widthOrigin" data-origin-width="1028" data-origin-height="548"&gt;&lt;span data-url="https://blog.kakaocdn.net/dn/vvYZR/dJMcajnLIAi/dXx3wAqpk2p8BthrErz8z0/img.png" data-phocus="https://blog.kakaocdn.net/dn/vvYZR/dJMcajnLIAi/dXx3wAqpk2p8BthrErz8z0/img.png" data-alt="neovim"&gt;&lt;img src="https://blog.kakaocdn.net/dn/vvYZR/dJMcajnLIAi/dXx3wAqpk2p8BthrErz8z0/img.png" srcset="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvvYZR%2FdJMcajnLIAi%2FdXx3wAqpk2p8BthrErz8z0%2Fimg.png" onerror="this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';" loading="lazy" width="1028" height="548" data-origin-width="1028" data-origin-height="548"&gt;&lt;/span&gt;&lt;figcaption&gt;neovim&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;&lt;span style="color: #f89009;"&gt;&lt;b&gt;# iTerm2&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;원래 iterm2를 쓰다가 ghostty로 넘어갔었음. 근데 claude code 에이전트 팀 기능이 나오고 tmux와 호환이 iterm2이 좋아서(자동으로 창을 열고 에이전트에게 할당) 다시 iterm2으로 돌아옴. 근데 테마나 정서적인 건 ghostty가 넘사. iterm2를 그나마 쓸만하게 만드는 팁 몇 개 기록.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;먼저 nvim에서 디렉터로 아이콘이 깨지는 문제 해결을 위해 폰트 설치.&lt;/p&gt;
&lt;pre id="code_1772693824348" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install --cask font-jetbrains-mono-nerd-font&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;&lt;span style="color: #333333; text-align: start;"&gt;설치하고 나서 iTerm2 상단 메뉴의 Settings에 Profile 탭으로 이동한 다음, Text -&amp;gt; Font 항목에서&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;JetBrainsMono Nerd Font&lt;/b&gt;를 선택하면 됨&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;구린 테마를 탈출하기 위해 Dracula  설치&lt;/p&gt;
&lt;pre id="code_1772693939214" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;git clone https://github.com/dracula/iterm.git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;설치하고 나서 Dracula.itermcolors 파일 클릭하면 iterm2에서 자동으로 감지됨. 폰트 사이즈는 13 정도가 눈이 편함.&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://jybaek.tistory.com/504754</id>
    <link href="https://jybaek.tistory.com/504754"/>
    <summary type="html">&lt;p data-ke-size="size16"&gt;AI를 잘 쓰기 위한 도구를 이것저것 조합하다 보니까 뇌에서 단축키 혼란을 겪고 있음... 그래서 사용하고 있는 것들의 단축키를 짧게 메모. 훨씬 더 많은 단축키가 있지만 자주 사용하는 것들만 기술함.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;&lt;span style="color: #f89009;"&gt;&lt;b&gt;# tmux&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;tmux나 screen 같은 걸 원래 잘 안 썼음. 근데 claude code 때문에 어쩔 수 없이 써야 하는 상황이 왔음. 단축키 몇 개면 불편하지 않게 쓸 수 있으니까 적응하도록 함...&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;우선 설치는 아래와 같이 진행함&lt;/p&gt;
&lt;pre id="code_1772693435830" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install tmux&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;아래는 단축키인데 모든 명령어는 &lt;b&gt;Ctrl + b 이후에 타이핑&lt;/b&gt;하면 됨&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;" border="1" data-ke-align="alignLeft"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;창 가로 분할&lt;/td&gt;
&lt;td style="width: 50%;"&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;창 세로 분할&lt;/td&gt;
&lt;td style="width: 50%;"&gt;%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;detach&lt;/td&gt;
&lt;td style="width: 50%;"&gt;d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;View all keybindings&lt;/td&gt;
&lt;td style="width: 50%;"&gt;?&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;세션 rename&lt;/td&gt;
&lt;td style="width: 50%;"&gt;$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;판넬 이동&lt;/td&gt;
&lt;td style="width: 50%;"&gt;방향키&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;세션 이름 지정해서 접속&lt;/td&gt;
&lt;td style="width: 50%;"&gt;tmux new -s &amp;lt;세션이름&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;attach&lt;/td&gt;
&lt;td style="width: 50%;"&gt;tmux attach -t &amp;lt;세션이름&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;tmux 창에서 직접 설정할 수도 있지만 모든 세션에 기본 적용하기 위해 &lt;b&gt;~/.tmux.conf&lt;/b&gt; 파일에 아래 내용 추가. 첫 번째 줄은 마우스를 사용할 수 있게 만드는 옵션. 이를 설정하면 탭 간 이동할 때 단축키 누르지 않아도 편함. 두 번째는 tmux에서 일어나는 모든 복사 작업에 대해 macOS 클립보드를 사용하겠다는 의미. 세 번째는 드래그가 끝나면 클립보드로 복사하고 복사모드를 종료하겠다는 의미.&lt;/p&gt;
&lt;pre id="code_1772693549947" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;set -g mouse on
set -s copy-command "pbcopy"
bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size="size26"&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size="size26"&gt;&lt;span style="color: #f89009;"&gt;&lt;b&gt;# nvim&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;vim을 오랫동안 사랑해 온 유저로써 neovim을 쓰기로 했음. 굳이 vim이 아닌 neovim을 선택한 이유는 탐색기 때문. neovim을 설치하고 plugin을 추가로 설치하면 ide처럼 익숙한 탐색기를 쓸 수 있게 됨&lt;/p&gt;
&lt;pre id="code_1772693368543" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install neovim&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;제일 많이 쓰는 단축키는 아래 정도... 나머지는 vim 단축키라서 굳이 적지 않음.&amp;nbsp;&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;" border="1" data-ke-align="alignLeft"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;g?&lt;/td&gt;
&lt;td style="width: 50%;"&gt;nvim tree 단축키 도움말&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;gd&lt;/td&gt;
&lt;td style="width: 50%;"&gt;go to define&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="width: 50%;"&gt;Ctrl + t&lt;/td&gt;
&lt;td style="width: 50%;"&gt;태그 스택에서 이전 위치로 팝(Pop)하여 돌아가기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;neovim의 설정파일은 ~/.config/nvim/init.lua 이쪽에 저장됨. 내가 최근에 사용하고 있는 설정을 그대로 첨부함&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type="moreLess" data-text-more="더보기" data-text-less="닫기"&gt;&lt;a class="btn-toggle-moreless"&gt;더보기&lt;/a&gt;
&lt;div class="moreless-content"&gt;
&lt;p data-ke-size="size16"&gt;--&amp;nbsp;Bootstrap&amp;nbsp;lazy.nvim&lt;br /&gt;local&amp;nbsp;lazypath&amp;nbsp;=&amp;nbsp;vim.fn.stdpath("data")&amp;nbsp;..&amp;nbsp;"/lazy/lazy.nvim"&lt;br /&gt;if&amp;nbsp;not&amp;nbsp;(vim.uv&amp;nbsp;or&amp;nbsp;vim.loop).fs_stat(lazypath)&amp;nbsp;then&lt;br /&gt;&amp;nbsp;&amp;nbsp;local&amp;nbsp;lazyrepo&amp;nbsp;=&amp;nbsp;"&lt;a href="https://github.com/folke/lazy.nvim.git" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;https://github.com/folke/lazy.nvim.git&lt;/a&gt;"&lt;br /&gt;&amp;nbsp;&amp;nbsp;local&amp;nbsp;out&amp;nbsp;=&amp;nbsp;vi&lt;a href="http://m.fn.system(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.fn.system(&lt;/a&gt;{&amp;nbsp;"git",&amp;nbsp;"clone",&amp;nbsp;"--filter=blob:none",&amp;nbsp;"--branch=stable",&amp;nbsp;lazyrepo,&amp;nbsp;lazypath&amp;nbsp;})&lt;br /&gt;&amp;nbsp;&amp;nbsp;if&amp;nbsp;vim.v.shell_error&amp;nbsp;~=&amp;nbsp;0&amp;nbsp;then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vim.api.nvim_echo({&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;"Failed&amp;nbsp;to&amp;nbsp;clone&amp;nbsp;lazy.nvim:\n",&amp;nbsp;"ErrorMsg"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;out,&amp;nbsp;"WarningMsg"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;"\nPress&amp;nbsp;any&amp;nbsp;key&amp;nbsp;to&amp;nbsp;exit..."&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;true,&amp;nbsp;{})&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vim.fn.getchar()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;os.exit(1)&lt;br /&gt;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;end&lt;br /&gt;vi&lt;a href="http://m.opt.rtp:prepend(lazypath)" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.opt.rtp:prepend(lazypath)&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;--&amp;nbsp;Make&amp;nbsp;sure&amp;nbsp;to&amp;nbsp;setup&amp;nbsp;`mapleader`&amp;nbsp;and&amp;nbsp;`maplocalleader`&amp;nbsp;before&lt;br /&gt;--&amp;nbsp;loading&amp;nbsp;lazy.nvim&amp;nbsp;so&amp;nbsp;that&amp;nbsp;mappings&amp;nbsp;are&amp;nbsp;correct.&lt;br /&gt;--&amp;nbsp;This&amp;nbsp;is&amp;nbsp;also&amp;nbsp;a&amp;nbsp;good&amp;nbsp;place&amp;nbsp;to&amp;nbsp;setup&amp;nbsp;other&amp;nbsp;settings&amp;nbsp;(vim.opt)&lt;br /&gt;vim.g.mapleader&amp;nbsp;=&amp;nbsp;"&amp;nbsp;"&lt;br /&gt;vim.g.maplocalleader&amp;nbsp;=&amp;nbsp;"\\"&lt;br /&gt;&lt;br /&gt;--&amp;nbsp;LSP&amp;nbsp;keymaps&lt;br /&gt;vim.api.nvim_create_autocmd("LspAttach",&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;callback&amp;nbsp;=&amp;nbsp;function(args)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;opts&amp;nbsp;=&amp;nbsp;{&amp;nbsp;buffer&amp;nbsp;=&amp;nbsp;args.buf&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vi&lt;a href="http://m.keymap.set(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.keymap.set(&lt;/a&gt;"n",&amp;nbsp;"gd",&amp;nbsp;vi&lt;a href="http://m.lsp.buf.definition," target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.lsp.buf.definition,&lt;/a&gt;&amp;nbsp;opts)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vi&lt;a href="http://m.keymap.set(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.keymap.set(&lt;/a&gt;"n",&amp;nbsp;"K",&amp;nbsp;vi&lt;a href="http://m.lsp.buf.hover," target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.lsp.buf.hover,&lt;/a&gt;&amp;nbsp;opts)&lt;br /&gt;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;--&amp;nbsp;auto-reload&amp;nbsp;files&amp;nbsp;changed&amp;nbsp;outside&amp;nbsp;of&amp;nbsp;nvim&lt;br /&gt;vim.opt.autoread&amp;nbsp;=&amp;nbsp;true&lt;br /&gt;vim.api.nvim_create_autocmd({&amp;nbsp;"FocusGained",&amp;nbsp;"BufEnter",&amp;nbsp;"CursorHold"&amp;nbsp;},&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;command&amp;nbsp;=&amp;nbsp;"silent!&amp;nbsp;checktime",&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;--&amp;nbsp;disable&amp;nbsp;netrw&amp;nbsp;(recommended&amp;nbsp;by&amp;nbsp;nvim-tree)&lt;br /&gt;vim.g.loaded_netrw&amp;nbsp;=&amp;nbsp;1&lt;br /&gt;vim.g.loaded_netrwPlugin&amp;nbsp;=&amp;nbsp;1&lt;br /&gt;&lt;br /&gt;--&amp;nbsp;Setup&amp;nbsp;lazy.nvim&lt;br /&gt;require("lazy").setup({&lt;br /&gt;&amp;nbsp;&amp;nbsp;spec&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;colorscheme&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"ellisonleao/gruvbox.nvim",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;priority&amp;nbsp;=&amp;nbsp;1000,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config&amp;nbsp;=&amp;nbsp;function()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vim.o.background&amp;nbsp;=&amp;nbsp;"dark"&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vim.cmd.colorscheme("gruvbox")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;syntax&amp;nbsp;highlighting&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"nvim-treesitter/nvim-treesitter",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;build&amp;nbsp;=&amp;nbsp;":TSUpdate",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;main&amp;nbsp;=&amp;nbsp;"nvim-treesitter",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;opts&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ensure_installed&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"python",&amp;nbsp;"hcl",&amp;nbsp;"yaml"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;highlight&amp;nbsp;=&amp;nbsp;{&amp;nbsp;enable&amp;nbsp;=&amp;nbsp;true&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;LSP&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"neovim/nvim-lspconfig",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencies&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"hrsh7th/cmp-nvim-lsp"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config&amp;nbsp;=&amp;nbsp;function()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vi&lt;a href="http://m.lsp.config(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.lsp.config(&lt;/a&gt;"*",&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;capabilities&amp;nbsp;=&amp;nbsp;require("cmp_nvim_lsp").default_capabilities(),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vi&lt;a href="http://m.lsp.config(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.lsp.config(&lt;/a&gt;"terraformls",&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmd&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"terraform-ls",&amp;nbsp;"serve"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;filetypes&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"terraform",&amp;nbsp;"terraform-vars"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root_markers&amp;nbsp;=&amp;nbsp;{&amp;nbsp;".terraform",&amp;nbsp;".git"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vi&lt;a href="http://m.lsp.enable(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.lsp.enable(&lt;/a&gt;"terraformls")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vi&lt;a href="http://m.lsp.config(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.lsp.config(&lt;/a&gt;"pyright",&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmd&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"pyright-langserver",&amp;nbsp;"--stdio"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;filetypes&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"python"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root_markers&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"pyproject.toml",&amp;nbsp;"setup.py",&amp;nbsp;"setup.cfg",&amp;nbsp;".git"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;before_init&amp;nbsp;=&amp;nbsp;function(params,&amp;nbsp;config)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.settings&amp;nbsp;=&amp;nbsp;config.settings&amp;nbsp;or&amp;nbsp;{}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.settings.python&amp;nbsp;=&amp;nbsp;config.settings.python&amp;nbsp;or&amp;nbsp;{}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;bufname&amp;nbsp;=&amp;nbsp;vim.api.nvim_buf_get_name(0)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;dir&amp;nbsp;=&amp;nbsp;vim.fn.fnamemodify(bufname,&amp;nbsp;":h")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;dir&amp;nbsp;~=&amp;nbsp;"/"&amp;nbsp;do&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;venv_python&amp;nbsp;=&amp;nbsp;dir&amp;nbsp;..&amp;nbsp;"/.venv/bin/python"&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;vim.fn.executable(venv_python)&amp;nbsp;==&amp;nbsp;1&amp;nbsp;then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.settings.python.pythonPath&amp;nbsp;=&amp;nbsp;venv_python&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dir&amp;nbsp;=&amp;nbsp;vim.fn.fnamemodify(dir,&amp;nbsp;":h")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;settings&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;python&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;analysis&amp;nbsp;=&amp;nbsp;{&amp;nbsp;autoSearchPaths&amp;nbsp;=&amp;nbsp;true,&amp;nbsp;useLibraryCodeForTypes&amp;nbsp;=&amp;nbsp;true&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vi&lt;a href="http://m.lsp.enable(" target="_blank" rel="noopener&amp;nbsp;noreferrer"&gt;http://m.lsp.enable(&lt;/a&gt;"pyright")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;formatting&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"stevearc/conform.nvim",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;opts&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;format_on_save&amp;nbsp;=&amp;nbsp;{&amp;nbsp;timeout_ms&amp;nbsp;=&amp;nbsp;500&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;formatters_by_ft&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;terraform&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"terraform_fmt"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;["terraform-vars"]&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"terraform_fmt"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;autocompletion&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"hrsh7th/nvim-cmp",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencies&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"hrsh7th/cmp-nvim-lsp",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"hrsh7th/cmp-buffer",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config&amp;nbsp;=&amp;nbsp;function()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cmp&amp;nbsp;=&amp;nbsp;require("cmp")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmp.setup({&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sorting&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;comparators&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;function(entry1,&amp;nbsp;entry2)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;detail1&amp;nbsp;=&amp;nbsp;entry1:get_completion_item().detail&amp;nbsp;or&amp;nbsp;""&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;detail2&amp;nbsp;=&amp;nbsp;entry2:get_completion_item().detail&amp;nbsp;or&amp;nbsp;""&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;req1&amp;nbsp;=&amp;nbsp;detail1:find("required")&amp;nbsp;~=&amp;nbsp;nil&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;req2&amp;nbsp;=&amp;nbsp;detail2:find("required")&amp;nbsp;~=&amp;nbsp;nil&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;req1&amp;nbsp;~=&amp;nbsp;req2&amp;nbsp;then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;req1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("cmp.config.compare").offset,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("cmp.config.compare").exact,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("cmp.config.compare").score,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("cmp.config.compare").recently_used,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("cmp.config.compare").kind,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("cmp.config.compare").length,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("cmp.config.compare").order,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;formatting&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;format&amp;nbsp;=&amp;nbsp;function(entry,&amp;nbsp;vim_item)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;detail&amp;nbsp;=&amp;nbsp;entry:get_completion_item().detail&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;detail&amp;nbsp;then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vim_item.menu&amp;nbsp;=&amp;nbsp;detail&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;vim_item&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mapping&amp;nbsp;=&amp;nbsp;cmp.mapping.preset.insert({&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;["&amp;lt;C-l&amp;gt;"]&amp;nbsp;=&amp;nbsp;cmp.mapping.complete(),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;["&amp;lt;CR&amp;gt;"]&amp;nbsp;=&amp;nbsp;cmp.mapping.confirm({&amp;nbsp;select&amp;nbsp;=&amp;nbsp;true&amp;nbsp;}),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;["&amp;lt;Tab&amp;gt;"]&amp;nbsp;=&amp;nbsp;cmp.mapping.select_next_item(),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;["&amp;lt;S-Tab&amp;gt;"]&amp;nbsp;=&amp;nbsp;cmp.mapping.select_prev_item(),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sources&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;name&amp;nbsp;=&amp;nbsp;"nvim_lsp"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;name&amp;nbsp;=&amp;nbsp;"buffer"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;git&amp;nbsp;signs&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"lewis6991/gitsigns.nvim",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;opts&amp;nbsp;=&amp;nbsp;{},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"nvim-tree/nvim-tree.lua",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencies&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"nvim-tree/nvim-web-devicons"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keys&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;"&amp;lt;leader&amp;gt;e",&amp;nbsp;"&amp;lt;cmd&amp;gt;NvimTreeToggle&amp;lt;cr&amp;gt;",&amp;nbsp;desc&amp;nbsp;=&amp;nbsp;"Toggle&amp;nbsp;file&amp;nbsp;explorer"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;"&amp;lt;leader&amp;gt;E",&amp;nbsp;"&amp;lt;cmd&amp;gt;NvimTreeFocus&amp;lt;cr&amp;gt;",&amp;nbsp;desc&amp;nbsp;=&amp;nbsp;"Focus&amp;nbsp;file&amp;nbsp;explorer"&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config&amp;nbsp;=&amp;nbsp;function()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require("nvim-tree").setup({&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;filesystem_watchers&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enable&amp;nbsp;=&amp;nbsp;true,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;filters&amp;nbsp;=&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dotfiles&amp;nbsp;=&amp;nbsp;false,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;git_ignored&amp;nbsp;=&amp;nbsp;false,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;--&amp;nbsp;Configure&amp;nbsp;any&amp;nbsp;other&amp;nbsp;settings&amp;nbsp;here.&amp;nbsp;See&amp;nbsp;the&amp;nbsp;documentation&amp;nbsp;for&amp;nbsp;more&amp;nbsp;details.&lt;br /&gt;&amp;nbsp;&amp;nbsp;--&amp;nbsp;colorscheme&amp;nbsp;that&amp;nbsp;will&amp;nbsp;be&amp;nbsp;used&amp;nbsp;when&amp;nbsp;installing&amp;nbsp;plugins.&lt;br /&gt;&amp;nbsp;&amp;nbsp;install&amp;nbsp;=&amp;nbsp;{&amp;nbsp;colorscheme&amp;nbsp;=&amp;nbsp;{&amp;nbsp;"habamax"&amp;nbsp;}&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;--&amp;nbsp;automatically&amp;nbsp;check&amp;nbsp;for&amp;nbsp;plugin&amp;nbsp;updates&lt;br /&gt;&amp;nbsp;&amp;nbsp;checker&amp;nbsp;=&amp;nbsp;{&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;true&amp;nbsp;},&lt;br /&gt;})&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size="size16"&gt;그리고 아래 두 개 정도 설치해 주면 terraform도 해피코딩 할 수 있게 됨. 이럴 거면 그냥 vscode 쓰라는 말도 있지만, 그건 왠지 손이 안 가서... :)&amp;nbsp;&lt;/p&gt;
&lt;pre id="code_1772695642855" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install hashicorp/tap/terraform-ls
brew install pyright&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;설정을 완료하면 대략 아래와 같은 화면으로 코딩할 수 있게 됨.&lt;/p&gt;
&lt;p&gt;&lt;figure class="imageblock alignCenter" data-ke-mobileStyle="widthOrigin" data-origin-width="1028" data-origin-height="548"&gt;&lt;span data-url="https://blog.kakaocdn.net/dn/vvYZR/dJMcajnLIAi/dXx3wAqpk2p8BthrErz8z0/img.png" data-phocus="https://blog.kakaocdn.net/dn/vvYZR/dJMcajnLIAi/dXx3wAqpk2p8BthrErz8z0/img.png" data-alt="neovim"&gt;&lt;img src="https://blog.kakaocdn.net/dn/vvYZR/dJMcajnLIAi/dXx3wAqpk2p8BthrErz8z0/img.png" srcset="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvvYZR%2FdJMcajnLIAi%2FdXx3wAqpk2p8BthrErz8z0%2Fimg.png" onerror="this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';" loading="lazy" width="1028" height="548" data-origin-width="1028" data-origin-height="548"/&gt;&lt;/span&gt;&lt;figcaption&gt;neovim&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;&lt;span style="color: #f89009;"&gt;&lt;b&gt;# iTerm2&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;원래 iterm2를 쓰다가 ghostty로 넘어갔었음. 근데 claude code 에이전트 팀 기능이 나오고 tmux와 호환이 iterm2이 좋아서(자동으로 창을 열고 에이전트에게 할당) 다시 iterm2으로 돌아옴. 근데 테마나 정서적인 건 ghostty가 넘사. iterm2를 그나마 쓸만하게 만드는 팁 몇 개 기록.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;먼저 nvim에서 디렉터로 아이콘이 깨지는 문제 해결을 위해 폰트 설치.&lt;/p&gt;
&lt;pre id="code_1772693824348" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;brew install --cask font-jetbrains-mono-nerd-font&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;&lt;span style="color: #333333; text-align: start;"&gt;설치하고 나서 iTerm2 상단 메뉴의 Settings에 Profile 탭으로 이동한 다음, Text -&amp;gt; Font 항목에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;JetBrainsMono Nerd Font&lt;/b&gt;를 선택하면 됨&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;구린 테마를 탈출하기 위해 Dracula&amp;nbsp; 설치&lt;/p&gt;
&lt;pre id="code_1772693939214" class="bash" data-ke-language="bash" data-ke-type="codeblock"&gt;&lt;code&gt;git clone https://github.com/dracula/iterm.git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size="size16"&gt;설치하고 나서 Dracula.itermcolors 파일 클릭하면 iterm2에서 자동으로 감지됨. 폰트 사이즈는 13 정도가 눈이 편함.&lt;/p&gt;</summary>
    <title>요즘 엔지니어링을 위한 도구 설정과 단축키</title>
    <updated>2026-03-05T11:31:53+00:00</updated>
    <dc:date>2026-03-05T11:31:53+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>박해선</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;&lt;p&gt;&amp;lt;파이토치로 배우는 LLM &amp;amp; AI&amp;gt; 도서에 대한 소개 영상을 만들었습니다. 이전 판에 해당하는 &amp;lt;개발자를 위한 머신러닝 &amp;amp; 딥러닝&amp;gt;과 어떤 차이가 있는지 알기 쉽게 요약했는데요. 이 영상을 SNS에 공유해 주시는 분 중 다섯 분을 뽑아 도서를 보내드리겠습니다! 도서는 한빛미디어에서 직접 발송할 예정입니다. 많은 참여 부탁드립니다. 감사합니다!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://tensorflow.blog/2026/03/05/%ed%8c%8c%ec%9d%b4%ed%86%a0%ec%b9%98%eb%a1%9c-%eb%b0%b0%ec%9a%b0%eb%8a%94-llm-ai-%ec%86%8c%ea%b0%9c-%ec%98%81%ec%83%81-%ea%b3%b5%ec%9c%a0-%ec%9d%b4%eb%b2%a4%ed%8a%b8-%ec%95%88%eb%82%b4/</id>
    <link href="https://tensorflow.blog/2026/03/05/%ed%8c%8c%ec%9d%b4%ed%86%a0%ec%b9%98%eb%a1%9c-%eb%b0%b0%ec%9a%b0%eb%8a%94-llm-ai-%ec%86%8c%ea%b0%9c-%ec%98%81%ec%83%81-%ea%b3%b5%ec%9c%a0-%ec%9d%b4%eb%b2%a4%ed%8a%b8-%ec%95%88%eb%82%b4/"/>
    <summary type="html">&amp;#60;파이토치로 배우는 LLM &amp;#38; AI&amp;#62; 도서에 대한 소개 영상을 만들었습니다. 이전 판에 해당하는 &amp;#60;개발자를 위한 머신러닝 &amp;#38; 딥러닝&amp;#62;과 어떤 차이가 있는지 알기 쉽게 요약했는데요. 이 영상을 SNS에 공유해 주시는 분 중 다섯 분을 뽑아 도서를 보내드리겠습니다! 도서는 한빛미디어에서 직접 발송할 예정입니다. 많은 참여 부탁드립니다. 감사합니다!</summary>
    <title>“파이토치로 배우는 LLM &amp; AI” 소개 영상 공유 이벤트 안내</title>
    <updated>2026-03-05T11:05:09+00:00</updated>
    <dc:date>2026-03-05T11:05:09+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>joosing</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;중요한 일에 시간을 분배하고 미리미리 준비했다면 이렇게 괴롭게 고생을 안할 수 있었을텐데, 해야만하는 일에 매달려 있다가 중요한 일은 정작 방치된 것 같다. 이사를 하며 내 삶의 다른 요소들도 바라보게 된다. &lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://velog.io/@joosing/%EB%B0%A9%EC%B9%98%EB%90%9C-%EC%A4%91%EC%9A%94%ED%95%9C-%EC%9D%BC</id>
    <link href="https://velog.io/@joosing/%EB%B0%A9%EC%B9%98%EB%90%9C-%EC%A4%91%EC%9A%94%ED%95%9C-%EC%9D%BC"/>
    <summary type="html">&lt;p&gt;중요한 일에 시간을 분배하고 미리미리 준비했다면 이렇게 괴롭게 고생을 안할 수 있었을텐데, 해야만하는 일에 매달려 있다가 중요한 일은 정작 방치된 것 같다. 이사를 하며 내 삶의 다른 요소들도 바라보게 된다. &lt;/p&gt;
</summary>
    <title>방치된 중요한 일</title>
    <updated>2026-03-06T00:57:28+00:00</updated>
    <dc:date>2026-03-06T00:57:28+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>joosing</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;나에게 싫은 일들이 있다. &lt;/p&gt;
&lt;p&gt;흔히 SI 프로젝트라고 하는 수주 사업은 싫다. 수주 사업에서는 고객이 무엇을 필요로 하는가, 현재 우리 상황에서 최선의 제품은 무엇인가, 이런 것들을 생각하는 대신 계약서를 잘 써서 계약서 대로 해줄 수 있는 방법을 찾아야 한다. 더 나은 제품, 더 나은 고객의 성공이 아니라 계약서 대로 해주고 돈을 버는 방법을 찾는다. 계약서에 없으니까 안하면 된다는 비지니스맨의 이야기는 맞는 말이지만 그렇게 엉뚱한 시스템을 엉뚱한 계약서 대로 만드는 일을 하고 싶지는 않다. &lt;/p&gt;
&lt;p&gt;우리가 외주를 줘야 하는 일도 싫다. 그들도 마찬가지로 일한다. 가치나 최선이 아니라 계약을 잘해서 계약대로 일하고 돈을 받는데 집중한다. &lt;/p&gt;
&lt;p&gt;수주 받는 일이나 외주를 주는 일 모두 계약을 할 때는 무엇을 만들지 명확하지 않아서 늘 계약은 완벽하지 못하고 늘 뒤에 계약으로 다툼이 일어난다. 이런 딱딱한 계약에 얽힌 일들은 변화되는 상황에 유연하게 대응할 수 없게 하며 상황이야 어떻든 제품이 좋든 말든 계약 대로 해야만 하는 일을 반드시 낳는다. &lt;/p&gt;
&lt;p&gt;그리고 처음 만난 상대와 신뢰 관계가 잘 형성되어 있지 않다. 서로가 제대로 일해주고 일을 주는 관계는 이 업계에서 거의 보지 못했다. &lt;/p&gt;
&lt;p&gt;이런 일들이 다시 내 앞에 있다. 아주 최근에 수주 사업을 하며 약간 특이한 경험을 한 것은 있다. 수주 사업임에도 불구하고 고객이 추구하는 바가 과학 연구라는 명확한 가치 지향점이 있었던 것인지 내 안에 힘이 조금 났다. 그러나 무기를 만든다는 고객의 목표는 잘 공감이 되지 않는다. 무기라는 건 내가 어려움을 뚫고 나갈 만큼 원동력이 되지 않는다. &lt;/p&gt;
&lt;p&gt;어떻게 이 위기를 극복해야 할까?&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://velog.io/@joosing/%EB%82%B4-%EC%95%9E%EC%97%90-%EC%9E%88%EB%8A%94-%EC%8B%AB%EC%9D%80-%EC%9D%BC%EB%93%A4</id>
    <link href="https://velog.io/@joosing/%EB%82%B4-%EC%95%9E%EC%97%90-%EC%9E%88%EB%8A%94-%EC%8B%AB%EC%9D%80-%EC%9D%BC%EB%93%A4"/>
    <summary type="html">&lt;p&gt;나에게 싫은 일들이 있다. &lt;/p&gt;
&lt;p&gt;흔히 SI 프로젝트라고 하는 수주 사업은 싫다. 수주 사업에서는 고객이 무엇을 필요로 하는가, 현재 우리 상황에서 최선의 제품은 무엇인가, 이런 것들을 생각하는 대신 계약서를 잘 써서 계약서 대로 해줄 수 있는 방법을 찾아야 한다. 더 나은 제품, 더 나은 고객의 성공이 아니라 계약서 대로 해주고 돈을 버는 방법을 찾는다. 계약서에 없으니까 안하면 된다는 비지니스맨의 이야기는 맞는 말이지만 그렇게 엉뚱한 시스템을 엉뚱한 계약서 대로 만드는 일을 하고 싶지는 않다. &lt;/p&gt;
&lt;p&gt;우리가 외주를 줘야 하는 일도 싫다. 그들도 마찬가지로 일한다. 가치나 최선이 아니라 계약을 잘해서 계약대로 일하고 돈을 받는데 집중한다. &lt;/p&gt;
&lt;p&gt;수주 받는 일이나 외주를 주는 일 모두 계약을 할 때는 무엇을 만들지 명확하지 않아서 늘 계약은 완벽하지 못하고 늘 뒤에 계약으로 다툼이 일어난다. 이런 딱딱한 계약에 얽힌 일들은 변화되는 상황에 유연하게 대응할 수 없게 하며 상황이야 어떻든 제품이 좋든 말든 계약 대로 해야만 하는 일을 반드시 낳는다. &lt;/p&gt;
&lt;p&gt;그리고 처음 만난 상대와 신뢰 관계가 잘 형성되어 있지 않다. 서로가 제대로 일해주고 일을 주는 관계는 이 업계에서 거의 보지 못했다. &lt;/p&gt;
&lt;p&gt;이런 일들이 다시 내 앞에 있다. 아주 최근에 수주 사업을 하며 약간 특이한 경험을 한 것은 있다. 수주 사업임에도 불구하고 고객이 추구하는 바가 과학 연구라는 명확한 가치 지향점이 있었던 것인지 내 안에 힘이 조금 났다. 그러나 무기를 만든다는 고객의 목표는 잘 공감이 되지 않는다. 무기라는 건 내가 어려움을 뚫고 나갈 만큼 원동력이 되지 않는다. &lt;/p&gt;
&lt;p&gt;어떻게 이 위기를 극복해야 할까?&lt;/p&gt;
</summary>
    <title>나에게 싫은 일들이 있다</title>
    <updated>2026-03-04T23:55:39+00:00</updated>
    <dc:date>2026-03-04T23:55:39+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>joosing</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;공식적으로 Head of ~ 라는 직책을 가지는 건 처음이다. 팀의 머리가 되면 어떤 일을 해야할까? 'Head' 라는 단어가 무척 많은 생각을 하게 한다. 동료들과 가볍게 커피를 한 잔 마시며 Head의 일을 시작했다. &lt;/p&gt;
&lt;p&gt;Head는 올바르고 지혜로운 결정을 내려야 한다. 그리고 주변을 설득하고 이끌 수 있어야 한다. 손과 발을 열심히 움직이는 것과는 다른 일들을 분명 해야한다.&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://velog.io/@joosing/Head-of</id>
    <link href="https://velog.io/@joosing/Head-of"/>
    <summary type="html">&lt;p&gt;공식적으로 Head of ~ 라는 직책을 가지는 건 처음이다. 팀의 머리가 되면 어떤 일을 해야할까? &amp;#39;Head&amp;#39; 라는 단어가 무척 많은 생각을 하게 한다. 동료들과 가볍게 커피를 한 잔 마시며 Head의 일을 시작했다. &lt;/p&gt;
&lt;p&gt;Head는 올바르고 지혜로운 결정을 내려야 한다. 그리고 주변을 설득하고 이끌 수 있어야 한다. 손과 발을 열심히 움직이는 것과는 다른 일들을 분명 해야한다.&lt;/p&gt;
</summary>
    <title>Head of ~ </title>
    <updated>2026-03-04T23:38:39+00:00</updated>
    <dc:date>2026-03-04T23:38:39+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>joosing</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;다시 절벽에 선 기분이다. 절벽에서 떨어지며 나에게 어떤 날개가 있는지 확인해야 할 것 같은 기분이다. 다만 아내도 나도 너무 고생하지 않기를 바란다.&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://velog.io/@joosing/%EB%8B%A4%EC%8B%9C-%EC%A0%88%EB%B2%BD</id>
    <link href="https://velog.io/@joosing/%EB%8B%A4%EC%8B%9C-%EC%A0%88%EB%B2%BD"/>
    <summary type="html">&lt;p&gt;다시 절벽에 선 기분이다. 절벽에서 떨어지며 나에게 어떤 날개가 있는지 확인해야 할 것 같은 기분이다. 다만 아내도 나도 너무 고생하지 않기를 바란다.&lt;/p&gt;
</summary>
    <title>다시 절벽</title>
    <updated>2026-03-04T23:36:36+00:00</updated>
    <dc:date>2026-03-04T23:36:36+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>향로 (기억보단 기록을)</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;h1&gt;도달력&lt;/h1&gt;
&lt;p data-ke-size="size16"&gt;&lt;a href="https://www.youtube.com/watch?v=czItVEAINqw"&gt;히그스필드의 Alex 대표의 인터뷰&lt;/a&gt;와 &lt;a href="https://www.youtube.com/watch?v=BR1-JrGbwxY"&gt;클루이의 Roy 대표의 인터뷰&lt;/a&gt; 2개를 보다가 둘 다 공통되게 이야기한 주제가 있었다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;도달력&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;둘의 인터뷰 내용을 정리했다.&lt;/p&gt;
&lt;hr data-ke-style="style1"&gt;
&lt;h2 data-ke-size="size26"&gt;1. "제품 만드는 사람"과 "세상에 닿는 사람"은 동등하다&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;두 인터뷰의 공통된 결론은 하나다.&lt;br&gt;&lt;b&gt;빌더(Builder, 24시간 안에 아이디어를 제품으로 만드는 기술 인력)와 GTM 담당자(Go-to-Market, 제품을 시장에 알리고 고객에게 닿게 하는 전략 전반을 담당하는 역할)는 동등한 가중치를 가진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Alex는 이것을 아예 창업팀의 최소 구성 공식으로 제시했다.&lt;br&gt;&lt;b&gt;"빌더 + GTM 담당자"&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;그런데 그가 강조한 건 이 GTM이 "이전 시대의 마케팅 직군과 전혀 다른 스킬셋"이라는 점이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;DB, 결제, 클라우드 인프라가 너무 쉬워져서 MVP를 만드는 문턱이 없어진 지금, 팀의 진짜 역량은 기술력보다 &lt;b&gt;고객과의 공감 + 도달 감각&lt;/b&gt;으로 이동했다는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;히그스필드 팀 구성이 이 철학을 직접 반영한다&lt;/p&gt;
&lt;ul style="list-style-type: disc;" data-ke-list-type="disc"&gt;
&lt;li&gt;엔지니어 40%, &lt;b&gt;크리에이터(영상 제작자) 40%&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size="size16"&gt;Roy는 훨씬 더 단호하게 표현했다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;"회사에는 두 가지 역할만 있다&lt;/b&gt;.&lt;br&gt;&lt;b&gt;엔지니어와 인플루언서.&lt;/b&gt;"&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;그리고 거기서 그치지 않고 기준을 수치로 제시했다.&lt;br&gt;&lt;b&gt;"마케팅 헤드가 팔로워 10만 명이 없다면 교체해야 한다."&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이건 마케터를 채용할 때 포트폴리오가 아니라 실제 도달력을 검증하겠다는 뜻이다.&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;2. 왜 도달력이 제품 자체만큼 중요해졌는가&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;Alex는 "매달 산업이 리셋된다"고 표현했다.&lt;br&gt;OpenAI, Anthropic, Google이 분기마다 대규모 모델 업데이트를 내놓고, 어떤 달은 메이저 업데이트가 두 번 동시에 나온다.&lt;br&gt;이 환경에서는 오래 공들인 제품 자체가 오히려 리스크다.&lt;br&gt;빠르게 출시하고,&lt;br&gt;빠르게 확산하고,&lt;br&gt;빠르게 반응을 받아야 한다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Roy의 투자자 Brian은 이것을 이론으로 정리했다.&lt;br&gt;모바일 시대의 해자(Moat, 경쟁사가 쉽게 따라올 수 없는 경쟁 우위의 방어벽)는 리텐션(Retention, 재방문율)과 네트워크 효과였는데, &lt;br&gt;AI 시대에는 기반 모델이 매주 바뀌니 정교하게 만든 기능이 OpenAI의 기본 모델에 포함되는 순간 존재 이유가 사라진다.&lt;br&gt;그래서 &lt;b&gt;진짜 해자는 빠르게 움직이는 능력&lt;/b&gt;이고, &lt;br&gt;그것을 가능하게 하는 것이 도달(Distribution, 제품을 고객에게 도달하는 채널과 전략 전반) 역량이라는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style="style2"&gt;
&lt;p data-ke-size="size16"&gt;"도달력이 높으면 사용자와 이야기할 필요조차 없다. 그들의 데이터를 보면 된다." — Roy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size="size26"&gt;3. 구체적인 실행 방식 — 경로와 조직&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;Alex는 소셜미디어 확산 경로를 직접 관찰한 결과를 공유했다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;X(트위터) 소규모 커뮤니티 → X AI 뉴스 페이지 → 인스타그램 AI 뉴스 페이지 → 크리에이터들 → 텔레그램&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;X에서는 "breaking", "just in" 같은 단어를 앞에 붙이고 자극적인 표현으로 탑 슬롯(Top Slot, 피드 최상단 노출 자리)을 노리라고 했다.&lt;br&gt;2025년부터는 LinkedIn이 부상 중이라고 덧붙였다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Roy는 여기서 한 발 더 나아가 &lt;b&gt;플랫폼별 시차&lt;/b&gt;를 활용한다.&lt;br&gt;&lt;b&gt;X/LinkedIn은 Instagram/TikTok보다 2년 뒤처져 있다&lt;/b&gt;는 것이 그의 핵심 주장이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Instagram과 TikTok의 알고리즘이 논란성(Controversialness)과 진정성(Authenticity)을 강하게 보상한다는 것을 10년 경험으로 체득했는데, X/LinkedIn 사람들은 아직도 "&lt;b&gt;가장 지적이고 사려 깊게 보이는 콘텐츠&lt;/b&gt;"를 만들려 한다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;공급이 없으니, 조금만 눌러도 타임라인을 장악할 수 있다는 논리다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;실제 조직으로는 &lt;b&gt;계약직 크리에이터 60명&lt;/b&gt;을 운영하며 &lt;b&gt;영상당 보수&lt;/b&gt;를 지급한다.&lt;br&gt;$20,000 (약 2,800만원)으로 슈퍼볼 광고와 동등한 노출을 달성하고 있고, 실제 전환(구매)이 발생하는 유일한 채널이 이 크리에이터 콘텐츠라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;4. Viral Fit 이 PMF보다 먼저 온다&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;Roy가 제시한 &lt;b&gt;Viral Fit (바이럴 적합성, PMF 이전에 콘텐츠로 먼저 아이디어의 시장 반응을 테스트하는 방식)&lt;/b&gt; 개념이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;제품 없이 영상만 만들어 올리면 알고리즘이 즉시 숫자&lt;/b&gt;로 반응(조회수, 좋아요, 공유)한다.&lt;br&gt;이 숫자가 PMF (Product-Market Fit) 테스트보다 훨씬 빠르고 정확한 신호다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;클루이가 영업 전화 기능을 추가한 것도 이 방식으로 발견됐다.&lt;br&gt;영상에 "세일즈 콜(Sales Call, 영업 전화)에서 써보세요"라고 넣었더니 바로 엔터프라이즈 고객이 몰렸고, 이것이 수익 $1M+ (약 14억원+)의 시작이 됐다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Alex의 히그스필드도 같은 방식이었다. 크리에이티브 디렉터 8명을 인터뷰해서 카메라 컨트롤 페인포인트(Pain Point, 고객이 불편함을 느끼는 핵심 문제)를 발견한 것도, 팀 안에 그들과 공감할 수 있는 도달력 있는 인력이 있었기에 가능했다.&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;5. 기술 역량 + 도달 역량을 동시에 이해하는 사람이 극히 드물다&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;흥미로운 역사적 맥락이 있다.&lt;br&gt;폴 그레이엄(Paul Graham, Y Combinator 공동창업자)이 YC를 시작할 때 발견한 것은, &lt;b&gt;기술 창업자가 저평가되어 있다&lt;/b&gt;는 사실이었다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;당시 시장은 "창업하려면 MBA가 있어야 한다"는 통념을 믿었지만, 폴 그레이엄의 판단은 달랐다.&lt;br&gt;&lt;b&gt;기술자에게 비즈니스를 가르치는 것이, 비즈니스맨에게 훌륭한 제품을 만들거나 코딩을 가르치는 것보다 훨씬 쉽다는 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이제 그 구도가 반전되고 있다.&lt;br&gt;기술의 문턱이 낮아진 AI 시대에는, 반대로 도달 역량을 가진 사람이 저평가되어 있다.&lt;br&gt;&lt;b&gt;기술자는 넘쳐나지만, 실제 도달력을 가진 사람은 여전히 희귀하다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;폴 그레이엄이 "기술자에게 비즈니스를 가르쳐라"고 했다면, 지금의 주장은 "기술자에게 도달을 가르쳐라"다.&lt;br&gt;그리고 그 교집합에 있는 사람이 이 시대의 가장 강력한 자산이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Brian(Roy의 투자자)이 지적하기를, 클루이의 진짜 차별점은 Roy가 &lt;b&gt;기술 역량(엔지니어)과 도달 역량(인플루언서)을 동시에 이해하는 극히 희귀한 창업자&lt;/b&gt;라는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;벤 다이어그램으로 보면, 투명 오버레이(Translucent Overlay, 현재 작업 화면 위에 반투명하게 AI가 띄워지는 UX) 아이디어를 떠올릴 수 있는 사람과, 그것을 바이럴로 도달할 수 있는 사람의 교집합이 극히 작다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Alex도 같은 말을 했다.&lt;br&gt;히그스필드가 PMF를 단 8명 인터뷰로 발견할 수 있었던 것도, 크리에이티브 디렉터들이 뭘 원하는지 공감하는 능력이 기술 구현 능력만큼 팀에 내재화돼 있었기 때문이다.&lt;/p&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;"타겟 고객 세계에 살고 있는 사람이 있어야 한다&lt;/b&gt;.&lt;br&gt;기술만 아는 팀이 만든 제품이 왜 안 팔리는지 그 이유가 바로 여기 있다." — Alex&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size="size26"&gt;핵심 인용&lt;/h2&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;"GTM은 이전 시대의 마케팅 직군과 전혀 다른 스킬셋이다.&lt;br&gt;팀의 핵심 역량은 기술력보다 고객과의 공감 + 도달 감각으로 이동했다." — Alex&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;"회사에는 두 가지 역할만 있다. 엔지니어와 인플루언서." — Roy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;"분배가 강력하면 시장 적합성을 걱정할 필요가 없다.&lt;br&gt;사용자들이 어디로 가야 하는지 알려준다." — Roy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://jojoldu.tistory.com/869</id>
    <link href="https://jojoldu.tistory.com/869"/>
    <summary type="html">&lt;h1&gt;도달력&lt;/h1&gt;
&lt;p data-ke-size="size16"&gt;&lt;a href="https://www.youtube.com/watch?v=czItVEAINqw"&gt;히그스필드의 Alex 대표의 인터뷰&lt;/a&gt;와 &lt;a href="https://www.youtube.com/watch?v=BR1-JrGbwxY"&gt;클루이의 Roy 대표의 인터뷰&lt;/a&gt; 2개를 보다가 둘 다 공통되게 이야기한 주제가 있었다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;도달력&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;둘의 인터뷰 내용을 정리했다.&lt;/p&gt;
&lt;hr data-ke-style="style1" /&gt;
&lt;h2 data-ke-size="size26"&gt;1. "제품 만드는 사람"과 "세상에 닿는 사람"은 동등하다&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;두 인터뷰의 공통된 결론은 하나다.&lt;br /&gt;&lt;b&gt;빌더(Builder, 24시간 안에 아이디어를 제품으로 만드는 기술 인력)와 GTM 담당자(Go-to-Market, 제품을 시장에 알리고 고객에게 닿게 하는 전략 전반을 담당하는 역할)는 동등한 가중치를 가진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Alex는 이것을 아예 창업팀의 최소 구성 공식으로 제시했다.&lt;br /&gt;&lt;b&gt;"빌더 + GTM 담당자"&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;그런데 그가 강조한 건 이 GTM이 "이전 시대의 마케팅 직군과 전혀 다른 스킬셋"이라는 점이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;DB, 결제, 클라우드 인프라가 너무 쉬워져서 MVP를 만드는 문턱이 없어진 지금, 팀의 진짜 역량은 기술력보다 &lt;b&gt;고객과의 공감 + 도달 감각&lt;/b&gt;으로 이동했다는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;히그스필드 팀 구성이 이 철학을 직접 반영한다&lt;/p&gt;
&lt;ul style="list-style-type: disc;" data-ke-list-type="disc"&gt;
&lt;li&gt;엔지니어 40%, &lt;b&gt;크리에이터(영상 제작자) 40%&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size="size16"&gt;Roy는 훨씬 더 단호하게 표현했다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;"회사에는 두 가지 역할만 있다&lt;/b&gt;.&lt;br /&gt;&lt;b&gt;엔지니어와 인플루언서.&lt;/b&gt;"&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;그리고 거기서 그치지 않고 기준을 수치로 제시했다.&lt;br /&gt;&lt;b&gt;"마케팅 헤드가 팔로워 10만 명이 없다면 교체해야 한다."&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이건 마케터를 채용할 때 포트폴리오가 아니라 실제 도달력을 검증하겠다는 뜻이다.&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;2. 왜 도달력이 제품 자체만큼 중요해졌는가&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;Alex는 "매달 산업이 리셋된다"고 표현했다.&lt;br /&gt;OpenAI, Anthropic, Google이 분기마다 대규모 모델 업데이트를 내놓고, 어떤 달은 메이저 업데이트가 두 번 동시에 나온다.&lt;br /&gt;이 환경에서는 오래 공들인 제품 자체가 오히려 리스크다.&lt;br /&gt;빠르게 출시하고,&lt;br /&gt;빠르게 확산하고,&lt;br /&gt;빠르게 반응을 받아야 한다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Roy의 투자자 Brian은 이것을 이론으로 정리했다.&lt;br /&gt;모바일 시대의 해자(Moat, 경쟁사가 쉽게 따라올 수 없는 경쟁 우위의 방어벽)는 리텐션(Retention, 재방문율)과 네트워크 효과였는데, &lt;br /&gt;AI 시대에는 기반 모델이 매주 바뀌니 정교하게 만든 기능이 OpenAI의 기본 모델에 포함되는 순간 존재 이유가 사라진다.&lt;br /&gt;그래서 &lt;b&gt;진짜 해자는 빠르게 움직이는 능력&lt;/b&gt;이고, &lt;br /&gt;그것을 가능하게 하는 것이 도달(Distribution, 제품을 고객에게 도달하는 채널과 전략 전반) 역량이라는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style="style2"&gt;
&lt;p data-ke-size="size16"&gt;"도달력이 높으면 사용자와 이야기할 필요조차 없다. 그들의 데이터를 보면 된다." &amp;mdash; Roy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size="size26"&gt;3. 구체적인 실행 방식 &amp;mdash; 경로와 조직&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;Alex는 소셜미디어 확산 경로를 직접 관찰한 결과를 공유했다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;X(트위터) 소규모 커뮤니티 &amp;rarr; X AI 뉴스 페이지 &amp;rarr; 인스타그램 AI 뉴스 페이지 &amp;rarr; 크리에이터들 &amp;rarr; 텔레그램&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;X에서는 "breaking", "just in" 같은 단어를 앞에 붙이고 자극적인 표현으로 탑 슬롯(Top Slot, 피드 최상단 노출 자리)을 노리라고 했다.&lt;br /&gt;2025년부터는 LinkedIn이 부상 중이라고 덧붙였다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Roy는 여기서 한 발 더 나아가 &lt;b&gt;플랫폼별 시차&lt;/b&gt;를 활용한다.&lt;br /&gt;&lt;b&gt;X/LinkedIn은 Instagram/TikTok보다 2년 뒤처져 있다&lt;/b&gt;는 것이 그의 핵심 주장이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Instagram과 TikTok의 알고리즘이 논란성(Controversialness)과 진정성(Authenticity)을 강하게 보상한다는 것을 10년 경험으로 체득했는데, X/LinkedIn 사람들은 아직도 "&lt;b&gt;가장 지적이고 사려 깊게 보이는 콘텐츠&lt;/b&gt;"를 만들려 한다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;공급이 없으니, 조금만 눌러도 타임라인을 장악할 수 있다는 논리다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;실제 조직으로는 &lt;b&gt;계약직 크리에이터 60명&lt;/b&gt;을 운영하며 &lt;b&gt;영상당 보수&lt;/b&gt;를 지급한다.&lt;br /&gt;$20,000 (약 2,800만원)으로 슈퍼볼 광고와 동등한 노출을 달성하고 있고, 실제 전환(구매)이 발생하는 유일한 채널이 이 크리에이터 콘텐츠라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;4. Viral Fit 이 PMF보다 먼저 온다&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;Roy가 제시한 &lt;b&gt;Viral Fit (바이럴 적합성, PMF 이전에 콘텐츠로 먼저 아이디어의 시장 반응을 테스트하는 방식)&lt;/b&gt; 개념이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;제품 없이 영상만 만들어 올리면 알고리즘이 즉시 숫자&lt;/b&gt;로 반응(조회수, 좋아요, 공유)한다.&lt;br /&gt;이 숫자가 PMF (Product-Market Fit) 테스트보다 훨씬 빠르고 정확한 신호다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;클루이가 영업 전화 기능을 추가한 것도 이 방식으로 발견됐다.&lt;br /&gt;영상에 "세일즈 콜(Sales Call, 영업 전화)에서 써보세요"라고 넣었더니 바로 엔터프라이즈 고객이 몰렸고, 이것이 수익 $1M+ (약 14억원+)의 시작이 됐다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Alex의 히그스필드도 같은 방식이었다. 크리에이티브 디렉터 8명을 인터뷰해서 카메라 컨트롤 페인포인트(Pain Point, 고객이 불편함을 느끼는 핵심 문제)를 발견한 것도, 팀 안에 그들과 공감할 수 있는 도달력 있는 인력이 있었기에 가능했다.&lt;/p&gt;
&lt;h2 data-ke-size="size26"&gt;5. 기술 역량 + 도달 역량을 동시에 이해하는 사람이 극히 드물다&lt;/h2&gt;
&lt;p data-ke-size="size16"&gt;흥미로운 역사적 맥락이 있다.&lt;br /&gt;폴 그레이엄(Paul Graham, Y Combinator 공동창업자)이 YC를 시작할 때 발견한 것은, &lt;b&gt;기술 창업자가 저평가되어 있다&lt;/b&gt;는 사실이었다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;당시 시장은 "창업하려면 MBA가 있어야 한다"는 통념을 믿었지만, 폴 그레이엄의 판단은 달랐다.&lt;br /&gt;&lt;b&gt;기술자에게 비즈니스를 가르치는 것이, 비즈니스맨에게 훌륭한 제품을 만들거나 코딩을 가르치는 것보다 훨씬 쉽다는 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이제 그 구도가 반전되고 있다.&lt;br /&gt;기술의 문턱이 낮아진 AI 시대에는, 반대로 도달 역량을 가진 사람이 저평가되어 있다.&lt;br /&gt;&lt;b&gt;기술자는 넘쳐나지만, 실제 도달력을 가진 사람은 여전히 희귀하다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;폴 그레이엄이 "기술자에게 비즈니스를 가르쳐라"고 했다면, 지금의 주장은 "기술자에게 도달을 가르쳐라"다.&lt;br /&gt;그리고 그 교집합에 있는 사람이 이 시대의 가장 강력한 자산이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Brian(Roy의 투자자)이 지적하기를, 클루이의 진짜 차별점은 Roy가 &lt;b&gt;기술 역량(엔지니어)과 도달 역량(인플루언서)을 동시에 이해하는 극히 희귀한 창업자&lt;/b&gt;라는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;벤 다이어그램으로 보면, 투명 오버레이(Translucent Overlay, 현재 작업 화면 위에 반투명하게 AI가 띄워지는 UX) 아이디어를 떠올릴 수 있는 사람과, 그것을 바이럴로 도달할 수 있는 사람의 교집합이 극히 작다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;Alex도 같은 말을 했다.&lt;br /&gt;히그스필드가 PMF를 단 8명 인터뷰로 발견할 수 있었던 것도, 크리에이티브 디렉터들이 뭘 원하는지 공감하는 능력이 기술 구현 능력만큼 팀에 내재화돼 있었기 때문이다.&lt;/p&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;"타겟 고객 세계에 살고 있는 사람이 있어야 한다&lt;/b&gt;.&lt;br /&gt;기술만 아는 팀이 만든 제품이 왜 안 팔리는지 그 이유가 바로 여기 있다." &amp;mdash; Alex&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size="size26"&gt;핵심 인용&lt;/h2&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;"GTM은 이전 시대의 마케팅 직군과 전혀 다른 스킬셋이다.&lt;br /&gt;팀의 핵심 역량은 기술력보다 고객과의 공감 + 도달 감각으로 이동했다." &amp;mdash; Alex&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;"회사에는 두 가지 역할만 있다. 엔지니어와 인플루언서." &amp;mdash; Roy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style="style1"&gt;
&lt;p data-ke-size="size16"&gt;"분배가 강력하면 시장 적합성을 걱정할 필요가 없다.&lt;br /&gt;사용자들이 어디로 가야 하는지 알려준다." &amp;mdash; Roy&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    <title>도달력</title>
    <updated>2026-03-06T03:46:13+00:00</updated>
    <dc:date>2026-03-06T03:46:13+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>노수진</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;[&lt;a href="https://boostcamp.connect.or.kr/insight"&gt;네이버 부스트캠프 인사이트 리포트 | AI 시대 개발자 교육&lt;/a&gt;]에 기고한 글입니다.&lt;/p&gt;
&lt;div&gt;&lt;hr&gt;&lt;/div&gt;
&lt;p&gt;올해 2월 샌프란시스코에서 열린 포럼(&lt;a href="https://www.pragmaticsummit.com/"&gt;링크&lt;/a&gt;)에 참석했습니다. 시니어 개발자, 엔지니어링 리더, 테크 창업자 등 500여 명이 모여 AI가 바꿔놓을 소프트웨어 엔지니어링의 미래를 논의했습니다. 다양한 의견들이 오고 갔지만 그중에서도 참석자들이 공통적으로 공감한 두 가지가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;첫째, 지금의 AI의 발전과 개발 현장 도입 속도는 역사적으로 유례가 없을 만큼 빠르다는 점이고, 둘째로 그 어느 때보다 프로그래밍이 재밌어졌다는 사실입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예전에는 개발자들이 지루한 디버깅이나 끝이 없는 레거시 코드 분석에 많은 시간을 쓸 수밖에 없었습니다. “시작이 반”이라는 말은 최초 개발 환경 세팅이 얼마나 힘든지 나타내는 역설적인 표현이기도 했습니다. 이제는 AI의 빠른 추론 능력과 낮아진 비용 덕분에 반복적이고 따분한 작업에 파묻히지 않고 온전히 몰입할 수 있습니다. 그 결과 현장에서는 코딩을 손에서 놓은 지 오래된 매니저들도 자신의 아이디어를 직접 구현하고 있고, 한 사람이 혼자서 완성할 수 있는 제품의 규모가 급격히 커졌습니다.&lt;/p&gt;
&lt;p&gt;그런데 이런 빠른 변화 속에서도 개발자의 핵심 역량은 크게 달라지지 않았습니다. 뛰어난 개발자는 단순히 코드를 잘 작성하는 사람이 아니라, 어떤 코드를 작성해야 하는지를 판단할 수 있는 사람입니다. 이는 문제를 정의하는 능력, 그리고 소프트웨어를 사용하는 사람들과 소통하는 능력과 직결됩니다. 코드 작성도 중요한 기술이지만 점차 그 중요도는 낮아지고 있으며, &lt;strong&gt;무엇을 만들어야 하는지 판단하는 능력이야말로 개발자의 가장 본질적인 역량&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;AI를 잘 활용하는 능력 역시 중요해졌습니다. 하지만 주니어 개발자가 AI를 사용할 때 가장 어려운 점은 AI의 결과물이 얼마나 좋은지 스스로 판단하기 어렵다는 것입니다. 그래서 지금까지도 그래왔듯 멘토의 역할이 더욱 중요합니다. 저 역시 프로그래밍을 처음 배웠던 넥스트에서부터, 이후 여러 직장에서 좋은 멘토들을 만난 것이 커리어 방향과 성장 속도에 큰 영향을 주었습니다. 초년생이라면 기술 스택이나 학습 방법보다도, ‘&lt;strong&gt;좋은 멘토를 찾는 일’을 우선순위에 두기&lt;/strong&gt;를 권합니다.&lt;/p&gt;
&lt;p&gt;마지막으로 강조하고 싶은 것은 불확실성을 받아들이는 자세입니다. 예전에는 모르는 것이 생기면 책이나 온라인 자료를 통해 비교적 명확한 해답을 찾을 수 있었습니다. 그러나 지금은 변화가 너무 빠르기 때문에 정답이 존재하지 않는 경우가 훨씬 많습니다. 불과 한 달 만에 정답이 바뀌기도 합니다. 소프트웨어 업계의 오랜 선구자인 켄트 벡조차 “지금은 그 누구도 답을 모르는 상태다.”라고 말할 정도입니다. 과도한 확신에 차서 얘기하는 사람(또는 AI)을 경계하고, 실험하고 배우며 스스로 방향을 수정해 나가는 유연함을 지녀야 합니다. &lt;strong&gt;불확실성을 두려워하기보다, 새로운 것을 만들어 내는 즐거움을 발견하시기를 바랍니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://www.nyprogrammer.com/p/ai</id>
    <link href="https://www.nyprogrammer.com/p/ai"/>
    <summary type="html">[&amp;#45348;&amp;#51060;&amp;#48260; &amp;#48512;&amp;#49828;&amp;#53944;&amp;#52896;&amp;#54532; &amp;#51064;&amp;#49324;&amp;#51060;&amp;#53944; &amp;#47532;&amp;#54252;&amp;#53944; | AI &amp;#49884;&amp;#45824; &amp;#44060;&amp;#48156;&amp;#51088; &amp;#44368;&amp;#50977;]&amp;#50640; &amp;#44592;&amp;#44256;&amp;#54620; &amp;#44544;&amp;#51077;&amp;#45768;&amp;#45796;.</summary>
    <title>AI 시대를 맞이하는 예비 개발자들에게</title>
    <updated>2026-03-08T23:35:32+00:00</updated>
    <dc:date>2026-03-08T23:35:32+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>eddy_song</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;Git이라는 버전 관리 시스템은 개발자라면 반드시 배워야 합니다.
하지만 한번에 다 소화하기엔 복잡하고 어렵죠.&lt;/p&gt;
&lt;p&gt;하지만 &lt;strong&gt;초등학생도 이해할 수 있게 단순화시킨 설명&lt;/strong&gt;을 들어보고,
대학생, 개발자로 &lt;strong&gt;차츰 더 수준을 높여간다면&lt;/strong&gt; 훨씬 더 이해가 쉽지 않을까요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wired의 &lt;a href="https://www.wired.com/video/series/5-levels"&gt;1 concept in 5 levels&lt;/a&gt;에서 영감을 받았습니다.&lt;/li&gt;
&lt;li&gt;내용은 주로 &lt;a href="https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control"&gt;Pro git&lt;/a&gt;을 참고했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="설명할-개념"&gt;설명할 개념&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;가지치기 (Branch)&lt;/li&gt;
&lt;li&gt;가지 합치기 (Merge)&lt;/li&gt;
&lt;li&gt;충돌 (Conflict)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="👩🦱-대학생에게-설명하기"&gt;👩‍🦱 대학생에게 설명하기&lt;/h2&gt;
&lt;h3 id="지난-시간-복습"&gt;지난 시간 복습&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 저번에 git이 뭔지 얘기했었지? 기억나?&lt;/p&gt;
&lt;p&gt;👩‍🦱 네. 개발자들이 쓰는 분산 버전 관리 시스템이요. 각자 자기 컴퓨터에 버전 기록이 있고, 중간중간 동기화하는 거.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 오, 잘 기억하네. 그때 마지막에 너가 좋은 질문을 했거든.&lt;/p&gt;
&lt;p&gt;👩‍🦱 제가요? 뭐라고 했죠?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 "각자 자기 컴퓨터에서 버전을 저장하다보면, 서로 저장한 버전 이력이 달라질 텐데 뭘 기준으로 하는 거냐"고 물어봤잖아.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아 맞다. 그건 어떻게 해요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그걸 이해하려면 먼저 '가지치기(Branch)'라는 개념을 알아야 해.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="왜-가지branch가-필요할까"&gt;왜 가지(Branch)가 필요할까?&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 팀플 상황을 다시 생각해보자. 너네 조 4명이서 PPT를 만들고 있어.&lt;/p&gt;
&lt;p&gt;근데 너가 보기에 지금 PPT 디자인이 좀 별로야. 전체적으로 색감을 싹 바꿔보고 싶은 거지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 맞아요 그런 거 많죠. 근데 함부로 바꿨다가 다른 조원들이 "뭐야 이게" 하면 곤란하잖아요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그렇지. 그렇다고 아예 안 해볼 수도 없고.&lt;/p&gt;
&lt;p&gt;이럴 때 어떻게 해?&lt;/p&gt;
&lt;p&gt;👩‍🦱 음... 사본을 하나 만들어서 거기서 먼저 해보겠죠. "디자인 시안 v2" 이런 식으로.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 바로 그거야. Git에서 Branch가 딱 그 역할이야.&lt;/p&gt;
&lt;p&gt;지금 다같이 작업하고 있는 버전이 있잖아? 이걸 &lt;strong&gt;'메인(main)'&lt;/strong&gt; 이라고 불러. 팀플로 치면 '발표용 최종본'이지.&lt;/p&gt;
&lt;p&gt;여기에 바로 이것저것 시도하면 조원들이 멘붕하잖아.&lt;/p&gt;
&lt;p&gt;그래서 메인을 건드리지 않고, &lt;strong&gt;메인에서 갈라져 나온 별도의 작업 공간&lt;/strong&gt;을 만드는 거야. 이게 Branch, 즉 '가지'야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 구글 슬라이드에서 '사본 만들기' 같은 건가요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 비슷하지만 중요한 차이가 있어. 구글 슬라이드에서 사본을 만들면 파일 전체가 통째로 복사되잖아.&lt;/p&gt;
&lt;p&gt;근데 Git에서 Branch를 만들면 파일을 복사하는 게 아니야. 커밋 위에 '이름표'를 하나 더 붙이는 거야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 이름표요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 저번에 커밋이 기차처럼 줄줄이 연결된다고 했잖아?&lt;/p&gt;
&lt;p&gt;![커밋이 연결된 모양 이미지]&lt;/p&gt;
&lt;p&gt;지금 커밋이 A → B → C 순서로 쌓여있다고 해보자. 여기서 &lt;code&gt;main&lt;/code&gt;이라는 건 사실 "C 커밋을 가리키는 이름표"야.&lt;/p&gt;
&lt;p&gt;Branch를 새로 만든다는 건, 같은 C 커밋에 이름표를 하나 더 붙이는 거야. 예를 들면 &lt;code&gt;design-v2&lt;/code&gt;라는 이름표를.&lt;/p&gt;
&lt;p&gt;![같은 커밋에 이름표가 2개 붙은 이미지]&lt;/p&gt;
&lt;p&gt;👩‍🦱 어, 그러면 파일이 2배가 되는 건 아니네요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 이름표 하나 추가하는 거니까 거의 비용이 없어. 가지를 10개 만들어도 저장 공간이 10배가 되지 않아.&lt;/p&gt;
&lt;p&gt;👩‍🦱 오, 진짜 가볍네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그래서 Git에서는 가지를 부담 없이 만들 수 있어. 뭔가 시도하고 싶으면 가지를 만들고, 필요 없으면 지우면 되니까.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="가지-위에서-작업하기"&gt;가지 위에서 작업하기&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 자, 이제 &lt;code&gt;design-v2&lt;/code&gt;라는 가지를 만들었어. 이 가지 위에서 작업을 시작하는 거야.&lt;/p&gt;
&lt;p&gt;근데 여기서 하나 더 알아야 할 게 있어. &lt;strong&gt;HEAD&lt;/strong&gt;라는 개념이야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 HEAD요? 머리?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 ㅋㅋ HEAD는 "지금 내가 서 있는 위치"를 가리키는 특별한 이름표야.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;main&lt;/code&gt;에서 작업하고 있으면 HEAD는 &lt;code&gt;main&lt;/code&gt;을 가리키고 있어.
&lt;code&gt;design-v2&lt;/code&gt;로 전환하면 HEAD가 &lt;code&gt;design-v2&lt;/code&gt;를 가리키게 되지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아 '지금 내가 보고 있는 가지'를 표시해주는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 정확해. 가지를 전환하면 작업 폴더에 있는 파일들이 그 가지의 마지막 커밋 상태로 바뀌어.&lt;/p&gt;
&lt;p&gt;👩‍🦱 파일이 바뀐다고요? 진짜요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 응. 예를 들어서 &lt;code&gt;design-v2&lt;/code&gt;에서 발표 자료 색상을 파란색으로 바꿔서 커밋을 했다고 치자. 그 상태에서 &lt;code&gt;main&lt;/code&gt;으로 전환하면? 파일이 원래 색상으로 돌아가.&lt;/p&gt;
&lt;p&gt;👩‍🦱 와... 그러면 가지별로 완전히 다른 상태를 왔다갔다 할 수 있는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그렇지. 그래서 팀플에서도 이렇게 쓸 수 있어.&lt;/p&gt;
&lt;p&gt;조원 4명이 각자 가지를 만들어서 작업하는 거지. 한 명은 &lt;code&gt;intro-part&lt;/code&gt;, 한 명은 &lt;code&gt;data-analysis&lt;/code&gt;, 한 명은 &lt;code&gt;conclusion&lt;/code&gt;. 메인에 직접 작업하는 사람은 없어.&lt;/p&gt;
&lt;p&gt;![조원들이 각자 가지에서 작업하는 이미지]&lt;/p&gt;
&lt;p&gt;👩‍🦱 각자 자기 가지에서만 작업하면 다른 사람 작업을 망칠 일이 없겠네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 바로 그거야. Branch의 핵심은 &lt;strong&gt;격리된 작업 공간&lt;/strong&gt;이야.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="가지-합치기merge--3가지-상황"&gt;가지 합치기(Merge) — 3가지 상황&lt;/h3&gt;
&lt;p&gt;👩‍🦱 근데 결국에는 발표를 해야 되잖아요. 각자 가지에서 작업한 걸 하나로 합쳐야 하는 거 아니에요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 가지를 메인에 합치는 걸 &lt;strong&gt;머지(Merge)&lt;/strong&gt;라고 해.&lt;/p&gt;
&lt;p&gt;근데 합칠 때 상황에 따라 방식이 좀 달라져. 총 3가지가 있어.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id="상황-1-빨리-감기-fast-forward"&gt;상황 1: 빨리 감기 (Fast-forward)&lt;/h4&gt;
&lt;p&gt;🧑‍🏫 제일 쉬운 경우부터 보자.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;design-v2&lt;/code&gt; 가지에서 커밋을 3개 했어. D, E, F.&lt;/p&gt;
&lt;p&gt;근데 그 사이에 &lt;code&gt;main&lt;/code&gt;에는 아무런 변화가 없었어. 여전히 C에 머물러 있는 거지.&lt;/p&gt;
&lt;p&gt;![fast-forward 이전 이미지: main은 C, design-v2는 F를 가리킴]&lt;/p&gt;
&lt;p&gt;👩‍🦱 그러면 main이 뒤쳐져있는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 이 상황에서 머지를 하면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;main 이름표를 그냥 F로 옮기면 끝이야.&lt;/p&gt;
&lt;p&gt;![fast-forward 이후 이미지: main과 design-v2 둘 다 F를 가리킴]&lt;/p&gt;
&lt;p&gt;👩‍🦱 어? 그냥 이름표만 옮기는 거예요? 뭔가 합치는 과정이 없네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 없어. 왜냐하면 main에서 갈라진 이후로 main 쪽에 새로운 게 없거든. 그냥 '앞으로 빨리감기'를 하면 되는 거야.&lt;/p&gt;
&lt;p&gt;그래서 이걸 &lt;strong&gt;빨리감기 머지(Fast-forward merge)&lt;/strong&gt;라고 불러.&lt;/p&gt;
&lt;p&gt;👩‍🦱 진짜 빨리감기네요. 영상 빨리감기처럼 그냥 쭉 앞으로 가는 거잖아요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 딩동댕.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id="상황-2-서로-다른-부분을-고친-경우"&gt;상황 2: 서로 다른 부분을 고친 경우&lt;/h4&gt;
&lt;p&gt;🧑‍🏫 이번에는 좀 다른 상황이야.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;design-v2&lt;/code&gt;에서 색상을 바꾸는 동안, 다른 조원이 &lt;code&gt;main&lt;/code&gt;에 서론을 수정해서 이미 머지를 해놓은 거야.&lt;/p&gt;
&lt;p&gt;![양쪽에 커밋이 있는 이미지: main에도 새 커밋, design-v2에도 새 커밋]&lt;/p&gt;
&lt;p&gt;👩‍🦱 아, 양쪽 다 변화가 있는 거네요. 이번에는 이름표만 옮기면 안 되겠다.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 이름표를 옮기면 한쪽 변화가 사라지니까.&lt;/p&gt;
&lt;p&gt;이럴 때 Git은 양쪽의 변화를 비교해. "main에서 뭐가 바뀌었지?" "design-v2에서 뭐가 바뀌었지?"&lt;/p&gt;
&lt;p&gt;너는 색상을 바꿨고, 다른 조원은 서론을 고쳤어.
서로 &lt;strong&gt;건드린 부분이 다르니까&lt;/strong&gt;, Git이 알아서 둘을 합쳐서 새로운 커밋을 하나 만들어줘.&lt;/p&gt;
&lt;p&gt;![머지 커밋이 생긴 이미지: 양쪽에서 합쳐지는 모양]&lt;/p&gt;
&lt;p&gt;👩‍🦱 오, Git이 자동으로 합쳐주는 거예요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 응. 이렇게 합쳐서 새로 생긴 커밋을 &lt;strong&gt;머지 커밋(Merge Commit)&lt;/strong&gt;이라고 해.&lt;/p&gt;
&lt;p&gt;이 머지 커밋은 좀 특별해. 다른 커밋은 직전 커밋이 1개인데, 머지 커밋은 &lt;strong&gt;직전 커밋이 2개&lt;/strong&gt;야. 양쪽 가지에서 온 거니까.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아하, 벌어졌던 길이 다시 하나로 만나는 지점이네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 아주 좋은 비유야. 그리고 머지가 끝나면 &lt;code&gt;design-v2&lt;/code&gt; 가지는 역할을 다한 거니까 삭제해도 돼. 가지에서 작업했던 커밋 기록은 머지 커밋을 통해 남아있으니까.&lt;/p&gt;
&lt;p&gt;👩‍🦱 근데 궁금한 게 있어요. 구글 독스에서는 여러 명이 동시에 편집해도 실시간으로 반영되잖아요. Git은 왜 이렇게 나눠서 합치는 방식을 쓰는 거예요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 좋은 질문이야. 문서는 글자를 바꿔도 다른 글자에 영향이 없잖아.&lt;/p&gt;
&lt;p&gt;근데 코드는 다르거든. 내가 함수 이름을 바꾸면, 그 함수를 호출하는 코드 수십 줄이 한꺼번에 영향을 받아. 이런 걸 '의존성'이라고 하는데, 코드는 이 의존성이 아주 복잡해.&lt;/p&gt;
&lt;p&gt;그래서 실시간으로 동시에 편집하면 서로의 코드가 충돌하면서 프로그램이 터질 수가 있어. 각자 격리된 환경에서 작업하고, 의도적으로 합치는 게 훨씬 안전한 거지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아... 그래서 일부러 가지를 나누는 거군요. 코드가 PPT랑은 차원이 다르긴 하네요.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id="상황-3-같은-부분을-고친-경우--충돌conflict"&gt;상황 3: 같은 부분을 고친 경우 — 충돌(Conflict)&lt;/h4&gt;
&lt;p&gt;🧑‍🏫 마지막 상황. 이게 처음 겪으면 좀 당황스러운데, 원리를 알면 별 거 아니야.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;design-v2&lt;/code&gt;에서 결론 슬라이드의 핵심 문장을 이렇게 고쳤어.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"따라서 A 전략이 가장 효과적이다."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;근데 같은 시간에 다른 조원이 &lt;code&gt;main&lt;/code&gt;에서 똑같은 결론 문장을 이렇게 고친 거야.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"따라서 B 전략을 우선 검토해야 한다."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;👩‍🦱 아... 같은 줄을 서로 다르게 고친 거네요. 😱&lt;/p&gt;
&lt;p&gt;🧑‍🏫 이 상황에서 Git은 합칠 수가 없어. "A 전략"으로 해야 할지 "B 전략"으로 해야 할지 Git은 판단을 못 하거든.&lt;/p&gt;
&lt;p&gt;이런 상황을 &lt;strong&gt;충돌(Conflict)&lt;/strong&gt;이라고 해.&lt;/p&gt;
&lt;p&gt;👩‍🦱 충돌이요? 그러면 에러가 나고 끝인 건가요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 아니야. Git은 "나 이거 못 합치겠으니까 네가 직접 골라줘"라고 알려줘.&lt;/p&gt;
&lt;p&gt;충돌이 난 파일을 열어보면 이런 식으로 표시가 돼있어.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD (main)
따라서 B 전략을 우선 검토해야 한다.
=======
따라서 A 전략이 가장 효과적이다.
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; design-v2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👩‍🦱 오, 양쪽 버전을 둘 다 보여주는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그렇지. &lt;code&gt;=======&lt;/code&gt;를 기준으로 위가 main 쪽, 아래가 네 가지 쪽이야.&lt;/p&gt;
&lt;p&gt;너는 이 두 버전을 보고 판단을 내리면 돼.&lt;/p&gt;
&lt;p&gt;"A가 맞다" 싶으면 B를 지우고, "B가 맞다" 싶으면 A를 지우고, 아니면 둘을 적절히 섞어서 새로운 문장을 만들어도 되지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아, 그러면 그 표시들(&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt;, &lt;code&gt;=======&lt;/code&gt;, &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;)도 지우고 최종 버전만 남기면 되는 거예요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 정확해. 그렇게 정리한 다음에 다시 커밋을 하면 머지가 완료되는 거야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 생각보다 무섭진 않네요. 그냥 "둘 중에 뭘로 할래?" 하고 물어보는 거잖아요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 처음에 충돌이 뜨면 좀 당황스러운데, 알고 보면 그냥 선택의 문제야.&lt;/p&gt;
&lt;p&gt;실제로 개발자들은 매일같이 충돌을 해결해. 팀으로 일하면 피할 수 없거든. 그래서 "충돌이 나는 건 실수가 아니다. 합치기 전에 리뷰하는 과정일 뿐이다." 이렇게 생각하는 게 좋아.&lt;/p&gt;
&lt;p&gt;👩‍🦱 오... 충돌이 일어나는 게 자연스러운 거군요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 응. 오히려 "충돌이 한번도 안 나는 팀"은 진짜 소통이 잘 되는 팀이거나, 혼자 다 하고 있는 거야. ㅋㅋ&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="정리"&gt;정리&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 오늘 내용을 정리해볼까?&lt;/p&gt;
&lt;p&gt;👩‍🦱 네!&lt;/p&gt;
&lt;p&gt;🧑‍🏫 &lt;strong&gt;Branch&lt;/strong&gt;는 메인을 건드리지 않고 따로 작업할 수 있는 공간이야. 파일을 복사하는 게 아니라 커밋 위에 이름표를 붙이는 거라서 매우 가벼워.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Merge&lt;/strong&gt;는 가지를 메인에 합치는 거고, 상황에 따라 3가지 방식이 있어.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;메인에 변화가 없으면 → &lt;strong&gt;빨리감기(Fast-forward)&lt;/strong&gt;. 이름표만 옮기면 끝.&lt;/li&gt;
&lt;li&gt;양쪽 다 변화가 있는데 다른 부분을 고쳤으면 → Git이 자동으로 합쳐서 &lt;strong&gt;머지 커밋&lt;/strong&gt;을 만들어줘.&lt;/li&gt;
&lt;li&gt;양쪽이 같은 부분을 고쳤으면 → &lt;strong&gt;충돌(Conflict)&lt;/strong&gt;이 나고, 사람이 직접 골라서 해결해야 해.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;👩‍🦱 정리하니까 깔끔하네요.&lt;/p&gt;
&lt;p&gt;근데 한 가지 더 궁금한 거. 이 가지 만들고 합치는 걸 지금은 제 컴퓨터에서 하는 건데, 다른 조원들한테는 어떻게 공유하는 거예요? push, pull 이런 거 들어본 적 있거든요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 오, 역시 좋은 질문이야. 그게 바로 원격 저장소(Remote)와 동기화하는 방법인데.&lt;/p&gt;
&lt;p&gt;다음에 알아보도록 하자!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="관련-링크"&gt;관련 링크&lt;/h2&gt;
&lt;p&gt;🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-1"&gt;초등학생에게 Git을 설명해본다면? (Git 1편)&lt;/a&gt;
🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-2"&gt;대학생에게 Git을 설명해본다면? (Git 1편)&lt;/a&gt;
🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-3"&gt;개발자에게 Git을 설명해본다면? (Git 1편)&lt;/a&gt;
🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-4"&gt;초등학생에게 Git branch와 merge를 설명해본다면? (Git 2편)&lt;/a&gt;&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://velog.io/@eddy_song/git-3levels-5</id>
    <link href="https://velog.io/@eddy_song/git-3levels-5"/>
    <summary type="html">&lt;p&gt;Git이라는 버전 관리 시스템은 개발자라면 반드시 배워야 합니다.
하지만 한번에 다 소화하기엔 복잡하고 어렵죠.&lt;/p&gt;
&lt;p&gt;하지만 &lt;strong&gt;초등학생도 이해할 수 있게 단순화시킨 설명&lt;/strong&gt;을 들어보고,
대학생, 개발자로 &lt;strong&gt;차츰 더 수준을 높여간다면&lt;/strong&gt; 훨씬 더 이해가 쉽지 않을까요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wired의 &lt;a href="https://www.wired.com/video/series/5-levels"&gt;1 concept in 5 levels&lt;/a&gt;에서 영감을 받았습니다.&lt;/li&gt;
&lt;li&gt;내용은 주로 &lt;a href="https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control"&gt;Pro git&lt;/a&gt;을 참고했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="설명할-개념"&gt;설명할 개념&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;가지치기 (Branch)&lt;/li&gt;
&lt;li&gt;가지 합치기 (Merge)&lt;/li&gt;
&lt;li&gt;충돌 (Conflict)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="👩🦱-대학생에게-설명하기"&gt;👩‍🦱 대학생에게 설명하기&lt;/h2&gt;
&lt;h3 id="지난-시간-복습"&gt;지난 시간 복습&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 저번에 git이 뭔지 얘기했었지? 기억나?&lt;/p&gt;
&lt;p&gt;👩‍🦱 네. 개발자들이 쓰는 분산 버전 관리 시스템이요. 각자 자기 컴퓨터에 버전 기록이 있고, 중간중간 동기화하는 거.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 오, 잘 기억하네. 그때 마지막에 너가 좋은 질문을 했거든.&lt;/p&gt;
&lt;p&gt;👩‍🦱 제가요? 뭐라고 했죠?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 &amp;quot;각자 자기 컴퓨터에서 버전을 저장하다보면, 서로 저장한 버전 이력이 달라질 텐데 뭘 기준으로 하는 거냐&amp;quot;고 물어봤잖아.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아 맞다. 그건 어떻게 해요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그걸 이해하려면 먼저 &amp;#39;가지치기(Branch)&amp;#39;라는 개념을 알아야 해.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="왜-가지branch가-필요할까"&gt;왜 가지(Branch)가 필요할까?&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 팀플 상황을 다시 생각해보자. 너네 조 4명이서 PPT를 만들고 있어.&lt;/p&gt;
&lt;p&gt;근데 너가 보기에 지금 PPT 디자인이 좀 별로야. 전체적으로 색감을 싹 바꿔보고 싶은 거지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 맞아요 그런 거 많죠. 근데 함부로 바꿨다가 다른 조원들이 &amp;quot;뭐야 이게&amp;quot; 하면 곤란하잖아요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그렇지. 그렇다고 아예 안 해볼 수도 없고.&lt;/p&gt;
&lt;p&gt;이럴 때 어떻게 해?&lt;/p&gt;
&lt;p&gt;👩‍🦱 음... 사본을 하나 만들어서 거기서 먼저 해보겠죠. &amp;quot;디자인 시안 v2&amp;quot; 이런 식으로.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 바로 그거야. Git에서 Branch가 딱 그 역할이야.&lt;/p&gt;
&lt;p&gt;지금 다같이 작업하고 있는 버전이 있잖아? 이걸 &lt;strong&gt;&amp;#39;메인(main)&amp;#39;&lt;/strong&gt; 이라고 불러. 팀플로 치면 &amp;#39;발표용 최종본&amp;#39;이지.&lt;/p&gt;
&lt;p&gt;여기에 바로 이것저것 시도하면 조원들이 멘붕하잖아.&lt;/p&gt;
&lt;p&gt;그래서 메인을 건드리지 않고, &lt;strong&gt;메인에서 갈라져 나온 별도의 작업 공간&lt;/strong&gt;을 만드는 거야. 이게 Branch, 즉 &amp;#39;가지&amp;#39;야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 구글 슬라이드에서 &amp;#39;사본 만들기&amp;#39; 같은 건가요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 비슷하지만 중요한 차이가 있어. 구글 슬라이드에서 사본을 만들면 파일 전체가 통째로 복사되잖아.&lt;/p&gt;
&lt;p&gt;근데 Git에서 Branch를 만들면 파일을 복사하는 게 아니야. 커밋 위에 &amp;#39;이름표&amp;#39;를 하나 더 붙이는 거야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 이름표요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 저번에 커밋이 기차처럼 줄줄이 연결된다고 했잖아?&lt;/p&gt;
&lt;p&gt;![커밋이 연결된 모양 이미지]&lt;/p&gt;
&lt;p&gt;지금 커밋이 A → B → C 순서로 쌓여있다고 해보자. 여기서 &lt;code&gt;main&lt;/code&gt;이라는 건 사실 &amp;quot;C 커밋을 가리키는 이름표&amp;quot;야.&lt;/p&gt;
&lt;p&gt;Branch를 새로 만든다는 건, 같은 C 커밋에 이름표를 하나 더 붙이는 거야. 예를 들면 &lt;code&gt;design-v2&lt;/code&gt;라는 이름표를.&lt;/p&gt;
&lt;p&gt;![같은 커밋에 이름표가 2개 붙은 이미지]&lt;/p&gt;
&lt;p&gt;👩‍🦱 어, 그러면 파일이 2배가 되는 건 아니네요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 이름표 하나 추가하는 거니까 거의 비용이 없어. 가지를 10개 만들어도 저장 공간이 10배가 되지 않아.&lt;/p&gt;
&lt;p&gt;👩‍🦱 오, 진짜 가볍네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그래서 Git에서는 가지를 부담 없이 만들 수 있어. 뭔가 시도하고 싶으면 가지를 만들고, 필요 없으면 지우면 되니까.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="가지-위에서-작업하기"&gt;가지 위에서 작업하기&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 자, 이제 &lt;code&gt;design-v2&lt;/code&gt;라는 가지를 만들었어. 이 가지 위에서 작업을 시작하는 거야.&lt;/p&gt;
&lt;p&gt;근데 여기서 하나 더 알아야 할 게 있어. &lt;strong&gt;HEAD&lt;/strong&gt;라는 개념이야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 HEAD요? 머리?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 ㅋㅋ HEAD는 &amp;quot;지금 내가 서 있는 위치&amp;quot;를 가리키는 특별한 이름표야.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;main&lt;/code&gt;에서 작업하고 있으면 HEAD는 &lt;code&gt;main&lt;/code&gt;을 가리키고 있어.
&lt;code&gt;design-v2&lt;/code&gt;로 전환하면 HEAD가 &lt;code&gt;design-v2&lt;/code&gt;를 가리키게 되지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아 &amp;#39;지금 내가 보고 있는 가지&amp;#39;를 표시해주는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 정확해. 가지를 전환하면 작업 폴더에 있는 파일들이 그 가지의 마지막 커밋 상태로 바뀌어.&lt;/p&gt;
&lt;p&gt;👩‍🦱 파일이 바뀐다고요? 진짜요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 응. 예를 들어서 &lt;code&gt;design-v2&lt;/code&gt;에서 발표 자료 색상을 파란색으로 바꿔서 커밋을 했다고 치자. 그 상태에서 &lt;code&gt;main&lt;/code&gt;으로 전환하면? 파일이 원래 색상으로 돌아가.&lt;/p&gt;
&lt;p&gt;👩‍🦱 와... 그러면 가지별로 완전히 다른 상태를 왔다갔다 할 수 있는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그렇지. 그래서 팀플에서도 이렇게 쓸 수 있어.&lt;/p&gt;
&lt;p&gt;조원 4명이 각자 가지를 만들어서 작업하는 거지. 한 명은 &lt;code&gt;intro-part&lt;/code&gt;, 한 명은 &lt;code&gt;data-analysis&lt;/code&gt;, 한 명은 &lt;code&gt;conclusion&lt;/code&gt;. 메인에 직접 작업하는 사람은 없어.&lt;/p&gt;
&lt;p&gt;![조원들이 각자 가지에서 작업하는 이미지]&lt;/p&gt;
&lt;p&gt;👩‍🦱 각자 자기 가지에서만 작업하면 다른 사람 작업을 망칠 일이 없겠네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 바로 그거야. Branch의 핵심은 &lt;strong&gt;격리된 작업 공간&lt;/strong&gt;이야.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="가지-합치기merge--3가지-상황"&gt;가지 합치기(Merge) — 3가지 상황&lt;/h3&gt;
&lt;p&gt;👩‍🦱 근데 결국에는 발표를 해야 되잖아요. 각자 가지에서 작업한 걸 하나로 합쳐야 하는 거 아니에요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 가지를 메인에 합치는 걸 &lt;strong&gt;머지(Merge)&lt;/strong&gt;라고 해.&lt;/p&gt;
&lt;p&gt;근데 합칠 때 상황에 따라 방식이 좀 달라져. 총 3가지가 있어.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id="상황-1-빨리-감기-fast-forward"&gt;상황 1: 빨리 감기 (Fast-forward)&lt;/h4&gt;
&lt;p&gt;🧑‍🏫 제일 쉬운 경우부터 보자.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;design-v2&lt;/code&gt; 가지에서 커밋을 3개 했어. D, E, F.&lt;/p&gt;
&lt;p&gt;근데 그 사이에 &lt;code&gt;main&lt;/code&gt;에는 아무런 변화가 없었어. 여전히 C에 머물러 있는 거지.&lt;/p&gt;
&lt;p&gt;![fast-forward 이전 이미지: main은 C, design-v2는 F를 가리킴]&lt;/p&gt;
&lt;p&gt;👩‍🦱 그러면 main이 뒤쳐져있는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 이 상황에서 머지를 하면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;main 이름표를 그냥 F로 옮기면 끝이야.&lt;/p&gt;
&lt;p&gt;![fast-forward 이후 이미지: main과 design-v2 둘 다 F를 가리킴]&lt;/p&gt;
&lt;p&gt;👩‍🦱 어? 그냥 이름표만 옮기는 거예요? 뭔가 합치는 과정이 없네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 없어. 왜냐하면 main에서 갈라진 이후로 main 쪽에 새로운 게 없거든. 그냥 &amp;#39;앞으로 빨리감기&amp;#39;를 하면 되는 거야.&lt;/p&gt;
&lt;p&gt;그래서 이걸 &lt;strong&gt;빨리감기 머지(Fast-forward merge)&lt;/strong&gt;라고 불러.&lt;/p&gt;
&lt;p&gt;👩‍🦱 진짜 빨리감기네요. 영상 빨리감기처럼 그냥 쭉 앞으로 가는 거잖아요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 딩동댕.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id="상황-2-서로-다른-부분을-고친-경우"&gt;상황 2: 서로 다른 부분을 고친 경우&lt;/h4&gt;
&lt;p&gt;🧑‍🏫 이번에는 좀 다른 상황이야.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;design-v2&lt;/code&gt;에서 색상을 바꾸는 동안, 다른 조원이 &lt;code&gt;main&lt;/code&gt;에 서론을 수정해서 이미 머지를 해놓은 거야.&lt;/p&gt;
&lt;p&gt;![양쪽에 커밋이 있는 이미지: main에도 새 커밋, design-v2에도 새 커밋]&lt;/p&gt;
&lt;p&gt;👩‍🦱 아, 양쪽 다 변화가 있는 거네요. 이번에는 이름표만 옮기면 안 되겠다.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 이름표를 옮기면 한쪽 변화가 사라지니까.&lt;/p&gt;
&lt;p&gt;이럴 때 Git은 양쪽의 변화를 비교해. &amp;quot;main에서 뭐가 바뀌었지?&amp;quot; &amp;quot;design-v2에서 뭐가 바뀌었지?&amp;quot;&lt;/p&gt;
&lt;p&gt;너는 색상을 바꿨고, 다른 조원은 서론을 고쳤어.
서로 &lt;strong&gt;건드린 부분이 다르니까&lt;/strong&gt;, Git이 알아서 둘을 합쳐서 새로운 커밋을 하나 만들어줘.&lt;/p&gt;
&lt;p&gt;![머지 커밋이 생긴 이미지: 양쪽에서 합쳐지는 모양]&lt;/p&gt;
&lt;p&gt;👩‍🦱 오, Git이 자동으로 합쳐주는 거예요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 응. 이렇게 합쳐서 새로 생긴 커밋을 &lt;strong&gt;머지 커밋(Merge Commit)&lt;/strong&gt;이라고 해.&lt;/p&gt;
&lt;p&gt;이 머지 커밋은 좀 특별해. 다른 커밋은 직전 커밋이 1개인데, 머지 커밋은 &lt;strong&gt;직전 커밋이 2개&lt;/strong&gt;야. 양쪽 가지에서 온 거니까.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아하, 벌어졌던 길이 다시 하나로 만나는 지점이네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 아주 좋은 비유야. 그리고 머지가 끝나면 &lt;code&gt;design-v2&lt;/code&gt; 가지는 역할을 다한 거니까 삭제해도 돼. 가지에서 작업했던 커밋 기록은 머지 커밋을 통해 남아있으니까.&lt;/p&gt;
&lt;p&gt;👩‍🦱 근데 궁금한 게 있어요. 구글 독스에서는 여러 명이 동시에 편집해도 실시간으로 반영되잖아요. Git은 왜 이렇게 나눠서 합치는 방식을 쓰는 거예요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 좋은 질문이야. 문서는 글자를 바꿔도 다른 글자에 영향이 없잖아.&lt;/p&gt;
&lt;p&gt;근데 코드는 다르거든. 내가 함수 이름을 바꾸면, 그 함수를 호출하는 코드 수십 줄이 한꺼번에 영향을 받아. 이런 걸 &amp;#39;의존성&amp;#39;이라고 하는데, 코드는 이 의존성이 아주 복잡해.&lt;/p&gt;
&lt;p&gt;그래서 실시간으로 동시에 편집하면 서로의 코드가 충돌하면서 프로그램이 터질 수가 있어. 각자 격리된 환경에서 작업하고, 의도적으로 합치는 게 훨씬 안전한 거지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아... 그래서 일부러 가지를 나누는 거군요. 코드가 PPT랑은 차원이 다르긴 하네요.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id="상황-3-같은-부분을-고친-경우--충돌conflict"&gt;상황 3: 같은 부분을 고친 경우 — 충돌(Conflict)&lt;/h4&gt;
&lt;p&gt;🧑‍🏫 마지막 상황. 이게 처음 겪으면 좀 당황스러운데, 원리를 알면 별 거 아니야.&lt;/p&gt;
&lt;p&gt;너가 &lt;code&gt;design-v2&lt;/code&gt;에서 결론 슬라이드의 핵심 문장을 이렇게 고쳤어.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;따라서 A 전략이 가장 효과적이다.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;근데 같은 시간에 다른 조원이 &lt;code&gt;main&lt;/code&gt;에서 똑같은 결론 문장을 이렇게 고친 거야.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;따라서 B 전략을 우선 검토해야 한다.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;👩‍🦱 아... 같은 줄을 서로 다르게 고친 거네요. 😱&lt;/p&gt;
&lt;p&gt;🧑‍🏫 이 상황에서 Git은 합칠 수가 없어. &amp;quot;A 전략&amp;quot;으로 해야 할지 &amp;quot;B 전략&amp;quot;으로 해야 할지 Git은 판단을 못 하거든.&lt;/p&gt;
&lt;p&gt;이런 상황을 &lt;strong&gt;충돌(Conflict)&lt;/strong&gt;이라고 해.&lt;/p&gt;
&lt;p&gt;👩‍🦱 충돌이요? 그러면 에러가 나고 끝인 건가요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 아니야. Git은 &amp;quot;나 이거 못 합치겠으니까 네가 직접 골라줘&amp;quot;라고 알려줘.&lt;/p&gt;
&lt;p&gt;충돌이 난 파일을 열어보면 이런 식으로 표시가 돼있어.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD (main)
따라서 B 전략을 우선 검토해야 한다.
=======
따라서 A 전략이 가장 효과적이다.
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; design-v2&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;👩‍🦱 오, 양쪽 버전을 둘 다 보여주는 거네요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 그렇지. &lt;code&gt;=======&lt;/code&gt;를 기준으로 위가 main 쪽, 아래가 네 가지 쪽이야.&lt;/p&gt;
&lt;p&gt;너는 이 두 버전을 보고 판단을 내리면 돼.&lt;/p&gt;
&lt;p&gt;&amp;quot;A가 맞다&amp;quot; 싶으면 B를 지우고, &amp;quot;B가 맞다&amp;quot; 싶으면 A를 지우고, 아니면 둘을 적절히 섞어서 새로운 문장을 만들어도 되지.&lt;/p&gt;
&lt;p&gt;👩‍🦱 아, 그러면 그 표시들(&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt;, &lt;code&gt;=======&lt;/code&gt;, &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;)도 지우고 최종 버전만 남기면 되는 거예요?&lt;/p&gt;
&lt;p&gt;🧑‍🏫 정확해. 그렇게 정리한 다음에 다시 커밋을 하면 머지가 완료되는 거야.&lt;/p&gt;
&lt;p&gt;👩‍🦱 생각보다 무섭진 않네요. 그냥 &amp;quot;둘 중에 뭘로 할래?&amp;quot; 하고 물어보는 거잖아요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 맞아. 처음에 충돌이 뜨면 좀 당황스러운데, 알고 보면 그냥 선택의 문제야.&lt;/p&gt;
&lt;p&gt;실제로 개발자들은 매일같이 충돌을 해결해. 팀으로 일하면 피할 수 없거든. 그래서 &amp;quot;충돌이 나는 건 실수가 아니다. 합치기 전에 리뷰하는 과정일 뿐이다.&amp;quot; 이렇게 생각하는 게 좋아.&lt;/p&gt;
&lt;p&gt;👩‍🦱 오... 충돌이 일어나는 게 자연스러운 거군요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 응. 오히려 &amp;quot;충돌이 한번도 안 나는 팀&amp;quot;은 진짜 소통이 잘 되는 팀이거나, 혼자 다 하고 있는 거야. ㅋㅋ&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="정리"&gt;정리&lt;/h3&gt;
&lt;p&gt;🧑‍🏫 오늘 내용을 정리해볼까?&lt;/p&gt;
&lt;p&gt;👩‍🦱 네!&lt;/p&gt;
&lt;p&gt;🧑‍🏫 &lt;strong&gt;Branch&lt;/strong&gt;는 메인을 건드리지 않고 따로 작업할 수 있는 공간이야. 파일을 복사하는 게 아니라 커밋 위에 이름표를 붙이는 거라서 매우 가벼워.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Merge&lt;/strong&gt;는 가지를 메인에 합치는 거고, 상황에 따라 3가지 방식이 있어.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;메인에 변화가 없으면 → &lt;strong&gt;빨리감기(Fast-forward)&lt;/strong&gt;. 이름표만 옮기면 끝.&lt;/li&gt;
&lt;li&gt;양쪽 다 변화가 있는데 다른 부분을 고쳤으면 → Git이 자동으로 합쳐서 &lt;strong&gt;머지 커밋&lt;/strong&gt;을 만들어줘.&lt;/li&gt;
&lt;li&gt;양쪽이 같은 부분을 고쳤으면 → &lt;strong&gt;충돌(Conflict)&lt;/strong&gt;이 나고, 사람이 직접 골라서 해결해야 해.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;👩‍🦱 정리하니까 깔끔하네요.&lt;/p&gt;
&lt;p&gt;근데 한 가지 더 궁금한 거. 이 가지 만들고 합치는 걸 지금은 제 컴퓨터에서 하는 건데, 다른 조원들한테는 어떻게 공유하는 거예요? push, pull 이런 거 들어본 적 있거든요.&lt;/p&gt;
&lt;p&gt;🧑‍🏫 오, 역시 좋은 질문이야. 그게 바로 원격 저장소(Remote)와 동기화하는 방법인데.&lt;/p&gt;
&lt;p&gt;다음에 알아보도록 하자!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="관련-링크"&gt;관련 링크&lt;/h2&gt;
&lt;p&gt;🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-1"&gt;초등학생에게 Git을 설명해본다면? (Git 1편)&lt;/a&gt;
🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-2"&gt;대학생에게 Git을 설명해본다면? (Git 1편)&lt;/a&gt;
🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-3"&gt;개발자에게 Git을 설명해본다면? (Git 1편)&lt;/a&gt;
🔗 &lt;a href="https://velog.io/@eddy_song/git-3levels-4"&gt;초등학생에게 Git branch와 merge를 설명해본다면? (Git 2편)&lt;/a&gt;&lt;/p&gt;
</summary>
    <title>대학생에게 Git branch와 merge를 설명해본다면? (Git 2편)</title>
    <updated>2026-03-07T07:10:14+00:00</updated>
    <dc:date>2026-03-07T07:10:14+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>홍민희</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;h1&gt;합법이면 공정한가: AI 재구현과 카피레프트의 침식&lt;/h1&gt;
&lt;p&gt;지난 주, Python 텍스트 인코딩 탐지 라이브러리인 chardet의 메인테이너 Dan
Blanchard가 &lt;a href="https://github.com/chardet/chardet/releases/tag/7.0.0"&gt;새 버전을 릴리스했다&lt;/a&gt;. chardet 7.0은 속도가 기존 대비 48배
빨라졌고, 멀티코어를 지원하며, 설계부터 다시 짜여졌다. Anthropic의 Claude가
기여자로 등재되어 있다. 그리고 라이선스가 LGPL에서 MIT로 바뀌었다.&lt;/p&gt;
&lt;p&gt;Blanchard의 말에 따르면 자신은 코드를 보지 않고 API와 테스트 스위트만을 참조해
Claude에게 처음부터 재구현하도록 지시했고, 그 결과 이전 버전과의 코드 유사도가
1.3% 미만이라고 한다. 따라서 이것은 독자적인 새 저작물이며 LGPL을 계승할 의무가
없다는 것이다. 원작자 Mark Pilgrim은 &lt;a href="https://github.com/chardet/chardet/issues/327"&gt;GitHub 이슈를 열어 항의했다.&lt;/a&gt; LGPL은 수정
배포 시 동일 라이선스 유지를 요구하는데, 기존 코드에 충분히 노출된 상태에서
만든 재구현은 클린 룸이라고 볼 수 없다는 것이 그의 주장이다.&lt;/p&gt;
&lt;p&gt;이 사건은 Flask의 창시자 Armin Ronacher와 Redis의 창시자 Salvatore
Sanfilippo(이하 antirez)의 글을 통해 더 넓은 논쟁으로 번졌다. 두 사람은 각기
다른 경로로—&lt;a href="https://lucumr.pocoo.org/2026/3/5/theseus/"&gt;Ronacher는 라이선스 철학의 측면에서&lt;/a&gt;,
&lt;a href="https://antirez.com/news/162"&gt;antirez는 저작권법의 법리에서&lt;/a&gt;—이 재구현이 정당하다는 결론에 도달한다. 나는 두
글을 모두 존중하지만, 둘 다 틀렸다고 생각한다. 아니, 더 정확하게는 둘 다 핵심
질문을 회피하고 있다.&lt;/p&gt;
&lt;p&gt;그 핵심 질문이란 이것이다. 합법이면 공정한가. 두 글은 이 물음에는 답하지 않은
채, 법리적으로 가능하다는 사실에서 사회적으로 정당하다는 결론을 끌어낸다. 법적
허용과 사회적 당위는 같은 것이 아니다. 법은 최소한의 기준을 정할 뿐이고, 어떤
행위가 그 기준을 통과한다는 것이 그 행위가 옳다는 뜻은 아니다. 이 구분이 이
글의 출발점이다.&lt;/p&gt;
&lt;h2&gt;유비의 방향이 거꾸로다&lt;/h2&gt;
&lt;p&gt;antirez의 논거는 GNU 프로젝트가 UNIX 유저스페이스를 재구현했을 때, 그것이
합법이었다는 사실에서 출발한다. Linux 커널도 마찬가지였다. 저작권법은 &lt;q&gt;보호
받는 표현&lt;/q&gt;(protected expressions)의 복제를 금하지만 아이디어와 동작 방식은
보호하지 않는다. AI로 재구현하는 것도 같은 법적 지형 위에 있으므로 합법이라는
것이다.&lt;/p&gt;
&lt;p&gt;이 법리의 설명 자체는 대체로 맞다. 재구현이 합법이라는 주장을 나는 반박하지
않는다. 문제는 그 다음 단계에 있다. antirez는 합법임을 보인 뒤 논의를 끝낸다.
그러나 법리가 옳다는 것과 행위가 사회적으로 정당하다는 것은 별개의 주장이다. 이
둘을 동일시할 때 논증의 간극이 생긴다.&lt;/p&gt;
&lt;p&gt;그 간극이 가장 선명하게 드러나는 곳이 바로 antirez가 끌어온 역사적 유비다.&lt;/p&gt;
&lt;p&gt;GNU가 UNIX를 재구현했을 때, 그 벡터는 독점에서 공유 쪽을 향하고 있었다.
Stallman은 UNIX라는 독점 소프트웨어를 &lt;strong&gt;자유 소프트웨어로&lt;/strong&gt; 재구현하기 위해
저작권법의 한계를 영리하게 활용한 것이다. 그 재구현의 윤리적 정당성은 법적
합법성에서 오는 것이 아니라, 공유지(commons)를 넓히는 방향에서 나왔다. 그것이
GNU 프로젝트가 환호를 받은 이유다.&lt;/p&gt;
&lt;p&gt;chardet 사건에서 재구현의 벡터는 반대 방향이다. 카피레프트 라이선스인 LGPL로
보호되던 소프트웨어가 퍼미시브 라이선스인 MIT로 재구현되었다. 이것은 공유지를
&lt;strong&gt;넓히는&lt;/strong&gt; 재구현이 아니라 공유지의 &lt;strong&gt;울타리를 제거하는&lt;/strong&gt; 재구현이다. 이제 chardet
7.0을 기반으로 만들어지는 파생 작업은 소스 코드를 공개할 의무가 없다. 월 1억
3천만 다운로드라는 규모의 라이브러리에서 그 의무가 사라진 것이다.&lt;/p&gt;
&lt;p&gt;antirez는 이 방향의 차이를 논하지 않는다. GNU의 역사를 끌어왔는데, 그 역사가
실은 자신의 결론에 반증 사례가 된다는 것을 보지 못하거나 보지 않으려 한다.&lt;/p&gt;
&lt;h2&gt;GPL은 공유에 반하는가&lt;/h2&gt;
&lt;p&gt;한편, Ronacher의 논거는 다르다. 그는 자신이 오래전부터 chardet이 비GPL
라이선스로 바뀌기를 원했다고 밝힌다. 그리고 GPL이 &lt;q&gt;공유 정신에 반한다&lt;/q&gt;고
주장한다. 자신은 가능한 한 라이선스 강제가 적은 방향으로 공개하는 것을
지지하고, 사회는 공유할 때 더 나아진다는 믿음을 가지고 있기 때문이란다.&lt;/p&gt;
&lt;p&gt;이 주장은 GPL에 대한 근본적인 오해를 담고 있다.&lt;/p&gt;
&lt;p&gt;GPL이 무엇을 &lt;strong&gt;금지&lt;/strong&gt;하는지부터 보자. GPL은 소스 코드를 비공개로 유지하는 것을
금지하지 않는다. GPL 소프트웨어를 개인적으로 수정해 사용하는 것에는 아무런
제약이 없다. GPL이 조건을 발동시키는 것은 &lt;strong&gt;배포&lt;/strong&gt;할 때뿐이다. 수정한 코드를
배포하거나 서비스로 제공하면, 그 코드도 같은 조건으로 공유해야 한다는 것.
이것은 공유를 &lt;strong&gt;제한&lt;/strong&gt;하는 것이 아니라 공유를 &lt;strong&gt;조건&lt;/strong&gt;으로 거는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;q&gt;이것을 받아서 사용하고 개선했다면, 그 개선도 공유하라&lt;/q&gt;는 요구는 공유를
억제하는 메커니즘이 아니라 공유를 연달아 강제하는 메커니즘이다. 공유지의
이용자에게 공유지에 대한 기여 의무를 부과하는 것이 공유 문화를 해친다는 주장은
어불성설이다.&lt;/p&gt;
&lt;p&gt;MIT 라이선스와 비교해 보면 차이가 분명하다. MIT 라이선스 하에서는 누구나 코드를
가져다 개선한 뒤 독점 소프트웨어로 닫아버릴 수 있다. 가져가서 써도 되지만
기여는 선택적이다. Ronacher가 이 구조를 &lt;q&gt;더 공유 친화적&lt;/q&gt;이라고 본다면, 그가
말하는 &lt;q&gt;공유&lt;/q&gt;는 흘러가는 방향이 있는 공유다. 더 많은 자본과 인력을 가진 쪽이
가져가는 방향으로.&lt;/p&gt;
&lt;p&gt;이 비대칭이 현실에서 어떻게 작동하는지는 역사가 보여준다. 1990년대에 많은
기업들이 GPL 코드를 흡수해 독점 소프트웨어를 만들었다. 이것이 가능했던 것은
그들이 MIT 같은 퍼미시브 라이선스를 선택했기 때문이 아니라, 당시 카피레프트
라이선스의 집행이 느슨했기 때문이다. GPL이 강화되면서 이 구멍이 막혔다. 자원이
없어서 기여를 통한 상호성에 의존할 수밖에 없는 개인 개발자나 작은 프로젝트에게
카피레프트는 그나마 대등한 교환을 가능하게 하는 장치였다.&lt;/p&gt;
&lt;p&gt;Flask를 만든 사람이 이 구분을 모를 이 없다. 그렇다면 이 논거는 나이브한 것이
아니라 편의적인 것이다.&lt;/p&gt;
&lt;h2&gt;스스로 반증하는 사례&lt;/h2&gt;
&lt;p&gt;Ronacher의 글에서 가장 흥미로운 부분은 논거가 아니라 스쳐 지나가는 한 문장이다.
Vercel이 &lt;a href="https://just-bash.dev/"&gt;GNU Bash를 AI로 재구현해 공개했다가&lt;/a&gt;,
&lt;a href="https://x.com/cramforce/status/2027155457597669785"&gt;Cloudflare가 같은 방식으로 Next.js를 재구현하자 &lt;q&gt;눈에 띄게 화를 냈다&lt;/q&gt;&lt;/a&gt;는
것이다.&lt;/p&gt;
&lt;p&gt;Ronacher는 이것을 아이러니로 언급하고 넘어가지만, 이 일화는 그의 입장 전체를
무너뜨린다. Next.js는 MIT 라이선스다. &lt;a href="https://blog.cloudflare.com/vinext/"&gt;Cloudflare의 vinext&lt;/a&gt;는 라이선스를
위반한 것이 아니라, Ronacher가 공유 문화의 진전이라 부른 바로 그 행위를
Next.js에 적용한 것 뿐이다. Vercel의 분노는 라이선스 침해에 대한 것이 아니라
순수하게 경쟁적·영토적 반응이었다. &lt;q&gt;내가 GPL 소프트웨어를 MIT로 재구현하는 것은
공유 문화의 진전이고, 누군가 내 MIT 소프트웨어를 같은 방식으로 재구현하는 것은
불쾌하다.&lt;/q&gt; 이것이 퍼미시브 라이선스 진영이 카피레프트 진영보다 더 공유
친화적이라는 주장의 실체이다. 그가 말하는 &lt;q&gt;공유 정신&lt;/q&gt;란 결국 이토록
비대칭적인 것이다.&lt;/p&gt;
&lt;p&gt;Ronacher는 이 아이러니를 인식하고도 멈추지 않는다. 자신의 세계관에 부합하는
사례이기 때문이란다. 세계관과 일치하지 않는 증거를 스스로 제시하고도 결론을
바꾸지 않는 것은 논거가 결론에 앞서는 것이 아니라 결론이 논거에 앞서고 있다는
신호다.&lt;/p&gt;
&lt;h2&gt;합법성과 사회적 당위는 다른 층위에 있다&lt;/h2&gt;
&lt;p&gt;서두에서 말한 질문으로 돌아오자. 합법이면 공정한가.&lt;/p&gt;
&lt;p&gt;antirez는 법리를 꼼꼼히 설명한 뒤 그것으로 충분하다는 듯이 끝을 맺는다.
Ronacher는 법적 회색 지대를 인정하면서도 도덕적 문제는 &lt;q&gt;내가 관심 있는 부분이
아니다&lt;/q&gt;라고 한 문장으로 넘긴다. 두 글 모두 법적 허용을 사회적 정당성의 대리물로
삼는다. 그러나 법은 어떤 행위를 막지 않는다는 것을 말할 뿐이지, 그 행위가
옳다는 것을 보증하지는 않는다. 조세를 아슬아슬하게 회피하는 것이 합법이더라도
옳은지는 별개의 물음이다. 특허를 합법적으로 취득한 기업이 수십 연간 저렴하게
공급되던 필수 의약품 가격을 수십 배 올리는 것이 합법이더라도, 그것이 사회적으로
정당하다는 뜻은 아니다. 합법성은 필요조건일 수 있지만 충분조건은 아니다.&lt;/p&gt;
&lt;p&gt;chardet 사건에서 이 구분은 더욱 뚜렷하다.&lt;/p&gt;
&lt;p&gt;chardet의 LGPL이 보호한 것은 Blanchard 한 사람의 노동이 아니다. 12년간 이
라이브러리에 기여한 모든 사람들이 동의한 사회적 계약이다. 그 계약의 내용은
&lt;q&gt;이것을 가져다 쓴다면, 당신이 만든 것도 같은 조건으로 공유하라&lt;/q&gt;는 것이었다.
이 계약은 법원이 강제하는 종류의 계약이기도 하지만, 그보다 먼저 오픈 소스 공동체가
수십년에 걸쳐 구축해 온 신뢰의 토대였다. 재구현이 법적으로 새로운 저작물로
인정될 수 있다는 것과, 그 재구현이 기존 기여자들과 맺은 사회적 계약을
파기했다는 것은 별개의 문제다. 법원이 만일 Blanchard의 손을 들어준대도 그것은
그의 행위가 공동체적으로 정당하다는 것을 의미하지 않는다. 합법성은 사회적
정당성을 뒤따라오게 하지 않는다.&lt;/p&gt;
&lt;p&gt;FSF 대표 Zoë Kooyman은 이를 간결하게 표현했다. &lt;q&gt;자신이 받은 권리를 타인에게
부여하기를 거부하는 것은 어떤 방법을 쓰든 반사회적 행위다.&lt;/q&gt;&lt;/p&gt;
&lt;h2&gt;누구의 관점이 디폴트로 설정되어 있는가&lt;/h2&gt;
&lt;p&gt;이 논쟁을 읽으면서 계속 드는 질문이 있다. 두 저자는 AI 재구현의 비용 하락을
어떤 위치에서 바라보고 있는가.&lt;/p&gt;
&lt;p&gt;antirez는 Redis를 만들었고, Ronacher는 Flask를 만들었다. 두 사람 모두 오픈 소스
생태계의 중심에 있는 사람들이다. 그들의 입장에서 AI 재구현의 비용 하락은 자신이
&lt;strong&gt;가하는&lt;/strong&gt; 쪽의 이야기다. 무언가를 더 쉽게 재구현할 수 있게 되는 것이다.
Ronacher는 GNU Readline을 GPL이기 때문에 재구현하려 했다고 밝힌다.&lt;/p&gt;
&lt;p&gt;반면 수십년간 chardet 같은 라이브러리에 기여해온 사람들의 입장에서 같은 기술
변화는 자신이 &lt;strong&gt;당하는&lt;/strong&gt; 쪽의 이야기다. 자신의 기여가 담긴 카피레프트 보호막이
제거되는 것이다. 두 저자는 전자의 위치에서 후자의 위치에 있는 사람들을 향해
“이것은 항상 합법이었고, 역사적 선례가 있으며, 기술 변화에 적응하라”고 말하고
있다.&lt;/p&gt;
&lt;p&gt;이 비대칭을 무시한 채 보편적 논거인 것처럼 서술할 때, 글은 분석이 아니라
이해관계의 합리화가 된다. 두 저자 모두 자신의 이해관계와 정확히 일치하는 결론에
도달한다는 것을 독자는 유념해야 한다.&lt;/p&gt;
&lt;h2&gt;이 싸움이 가리키는 것&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.theregister.com/2026/03/06/ai_kills_software_licensing/"&gt;Bruce Perens는 &lt;cite class="series"&gt;The Register&lt;/cite&gt;와의 인터뷰에서&lt;/a&gt; &lt;q&gt;소프트웨어 개발의
전체 경제학이 죽었다&lt;/q&gt;고 경고했다. antirez는 비슷한 인식에서 &lt;q&gt;적응하라&lt;/q&gt;고
말한다. Ronacher는 이 방향이 마음에 든다고 말한다.&lt;/p&gt;
&lt;p&gt;세 반응 중 어느 것도 핵심 질문에 답하지 않는다. 카피레프트의 집행이 기술적으로
어려워졌을 때, 그것이 카피레프트를 &lt;strong&gt;불필요하게&lt;/strong&gt; 만드는가, 아니면 더욱
&lt;strong&gt;중요하게&lt;/strong&gt; 만드는가.&lt;/p&gt;
&lt;p&gt;나는 후자라고 생각한다. GPL이 보호한 것은 코드의 희소성이 아니라 사용자의
권리였다. 코드 생산 비용이 낮아진다고 해서 그 코드를 통해 권리를 침식하는
행위가 괜찮아지는 것은 아니다. 오히려 재구현의 마찰이 사라질수록, 카피레프트
라이브러리를 MIT로 탈바꿈시키는 비용도 사라진다. GPL이 의존했던 마찰이
줄어들었다는 것은 법적 집행의 문제일 뿐, 그 기저의 당위를 건드리지 않는다.&lt;/p&gt;
&lt;p&gt;그 당위는 이것이다. 공유지에서 가져간 자는 공유지에 돌려줘야 한다는 것. 그
원칙은 재구현이 5년 걸리든 5일 걸리든 달라지지 않는다. 법원이 AI 재구현을 클린
룸으로 인정하든 파생 저작물로 보든, 그 판결이 이 원칙의 사회적 무게를
경감시키지는 않는다.&lt;/p&gt;
&lt;p&gt;이것이 법리와 사회적 가치가 갈라지는 지점이다. 법은 사후적으로, 느리게, 현재의
권력 관계를 반영하며 만들어진다. 오픈 소스 공동체가 수십 연에 걸쳐 쌓아온
규범은 법원의 판결을 기다리지 않았다. 법이 그것을 보호해주지 않을 때도 사람들이
GPL을 선택한 것은, 그것이 자신이 속한 공동체의 가치와 일치했기 때문이다. 그
가치는 법이 바뀐다고 사라지지 않는다.&lt;/p&gt;
&lt;p&gt;이전 글들에서 나는 이 싸움의 다음 단계로
&lt;a href="https://writings.hongminhee.org/2026/01/histomat-foss-llm/"&gt;훈련 카피레프트(TGPL)를 제안했다.&lt;/a&gt; AI 재구현
문제는 거기서 한 걸음 더 나아가 명세(specification) 계층까지 보호 범위를
확장해야 한다는 것을 시사한다. 소스 코드가 명세로부터 생성될 수 있다면, 그
명세가 GPL 프로젝트의 본질적 지식재산이 된다. 소스 코드를 보지 않고 테스트
스위트와 API만으로 재구현했다는 Blanchard의 주장은, 역설적으로, 그 테스트
스위트와 API 명세가 보호되어야 한다는 논거가 된다.&lt;/p&gt;
&lt;p&gt;GPL의 역사는 새로운 착취 방식이 등장할 때마다 법적 도구가 진화해 온 역사다.
GPLv2에서 GPLv3로, AGPL로. 그러나 그 진화를 이끈 것은 판결이 아니라 공동체의
가치 판단이었다. 법은 뒤따라왔다. 지금도 마찬가지다. 법원이 AI 재구현에 대해
어떤 판결을 내리든, 우리가 먼저 답해야 할 질문은 법적인 것이 아니라 사회적인
것이다. 공유지의 열매를 취한 자는 공유지에 돌려줄 의무가 있는가. 나는 그렇다고
생각한다. 그리고 그 판단은 법원의 판결을 기다릴 필요가 없다.&lt;/p&gt;
&lt;p&gt;antirez와 Ronacher의 글이 흥미로운 것은 그들이 옳기 때문이 아니라, 그들이
무엇을 보지 않으려 하는지가 선명하게 드러나기 때문이다. 합법성으로 사회적 가치
판단을 대신하려 할 때, 정작 중요한 질문은 법리의 그늘 속에 묻힌다.&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://writings.hongminhee.org/2026/03/legal-vs-legitimate/</id>
    <link href="https://writings.hongminhee.org/2026/03/legal-vs-legitimate/"/>
    <title>합법이면 공정한가: AI 재구현과 카피레프트의 침식</title>
    <updated>2026-03-09T15:10:00+00:00</updated>
    <dc:date>2026-03-09T15:10:00+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>teo.yu</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;blockquote&gt;
&lt;p&gt;로버트 C. 마틴 (Uncle Bob), *"우리, 프로그래머들 — AI 시대에 잊혀 가는 '프로그래머 정신'을 다시 깨우다"*
도서 링크: &lt;a href="https://gilbut.co/c/26015205VE"&gt;https://gilbut.co/c/26015205VE&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;길벗 출판사에서 보내주신 소중한 도서를 계기로, AI 시대의 프로그래머에 대한 제 생각을 담았습니다. 평소 머릿속에 맴돌던 생각들을 이 책의 흐름을 빌려 차분히 정리해 볼 수 있었습니다. 감사합니다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/0cf8e91c-ce36-4d4e-bff5-33fcef183c20/image.png" alt=""&gt;&lt;/p&gt;
&lt;h2 id="프롤로그"&gt;프롤로그&lt;/h2&gt;
&lt;p&gt;얼마 전 평소 제가 하던 고민들과 맞닿아 있는 흥미로운 책 한 권을 만났습니다. 엉클 밥(로버트 C. 마틴)의 신간, "우리, 프로그래머들", 1960년대 컴퓨터가 탄생하게된 에이다 러브레이스부터 엘런튜링, 다익스트라에서 시작해서 지금의 AI까지 60년의 프로그래밍 역사를 기록한 책입니다.&lt;/p&gt;
&lt;p&gt;책장을 넘기며 오랜만에 기분 좋은 추억에 잠겼습니다. 학창 시절 칠판 앞에서 달달 외워야 했던 당연하게 여기던 CS 지식들이 단순한 이론이 아니라 그 시대의 개발자들의 치열한 고민과 발견의 기록이었다는게 새삼 느껴졌습니다. &lt;/p&gt;
&lt;p&gt;어렸을 때 처음 코딩을 배우던 시절, "GOTO문은 절대 쓰면 안 된다"고 배웠습니다. 그때는 속으로 '도대체 왜 만들어 놓고 쓰지 말라는 거야?' 하고 툴툴거렸던게 기억이 나네요. 책을 읽다 보니 그 당연한 한 줄의 규칙 뒤에는, 다익스트라가 "사람이 읽을 수 있는 코드란 무엇인가"를 놓고 현업에서 벌인 피 튀기는 싸움이 있었습니다. 지금 우리에겐 공기처럼 당연한 규칙이, 그 시대에는 개발의 패러다임을 통째로 뒤집는 혼란이었던 셈이죠. 문득 "jQuery 이제 쓰지마라."는 선언과 함께, 리액트로 넘어가야 할 때 보이던 아득한 막막함이 겹쳐 보였습니다. 제가 맨 처음 개발을 배웠던 초등학교 때부터 지금 AI 시대까지, 딱딱한 기술서라기보단 위인전을 보듯 재밌게 읽어 내려갔습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하지만 마지막 장을 덮고 나니, 이런 질문이 하나 남았습니다. "근데 이걸 안다고 뭐가 달라질까?"&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;서평을 떠올리려니 이 책의 유용함을 말해야 할 텐데 — 유용함이라... 솔직히 막막했습니다. &lt;em&gt;(현정님, 그래서 한 달이나 넘게 걸린 건 죄송해요~)&lt;/em&gt; 분명 개발계에 큰 획을 그은 엉클 밥이 집대성한 역사를 접하는 건 흥미롭지만, 이 책이 주는 건 당장의 쓸모 있는 지식이나 메시지보다는 개발의 역사서나 위인전에 가깝습니다. 그래서 이걸 안다고 뭐가 달라지냐고 묻는다면 당장 대답할 말이 없었습니다.&lt;/p&gt;
&lt;p&gt;"... 이제는 AI한테 말 한마디면 몇 백 줄짜리 코드가 그냥 쏟아져 나오잖아요. 당장 돌아가는 걸 만드는 법 자체가 바뀌고 있는데, 지난 60년간의 이야기를 아는 게 지금 저한테 어떤 의미가 있을까요?" 최근 개발을 배우려는 분들이나 주니어분들이 제게 던지는 이 현실적인 불안에 대해, 대답을 해줘야 하는 입장이지만 저조차도 쉽게 대답하기 어려운 힘든 변화의 시대를 맞이하고 있다는 생각이 들었습니다.&lt;/p&gt;
&lt;p&gt;모르면 그냥 물어보면 되는 시대, 지식과 방법론이 AI에 의해 상향 평준화되는 상황을 지켜보며, '개발을 잘한다는 것'에 대한 정의 자체가 흔들리고 있다는 공허함이 찾아왔었습니다. 한때는 누구보다 빠르게 빈 에디터에서 화면을 뚝딱 만들어내는 것 자체가 실력이었고 저의 정체성이자 자부심이었는데, 이제는 한 줄 프롬프트면 그 코드가 나오니까요. AI라는 정말 재미난 장난감이 생겨서 즐거운 나날을 보내고는 있지만, 그 짜릿한 재미 이면에는 "지금 이게 내가 개발하고 있는 게 맞나?", "내가 정말 잘하고 있는 걸까?"라는 묘한 의문이 늘 따라붙었습니다.&lt;/p&gt;
&lt;p&gt;흔히들, AI 시대에는 깊이가 중요하다, CS 지식이 중요하다는 말들을 많이 합니다. 하지만 솔직히 이 책에 나오는 어셈블러나 초창기 컴퓨터 지식을 우리가 속속들이 모른다고 해서 지금 당장 큰 문제가 생기지는 않습니다. 반대로 안다고 해서 달라질 것도 없죠. 그렇다면 우리가 가져야 할 진짜 '깊이'란 무엇이고, 우리는 어떤 지식을 알아야 하는 걸까요?&lt;/p&gt;
&lt;p&gt;이 질문을 다시 붙잡고 책을 뒤적이다가, 제가 겪던 이 막막함을 설명해 줄 두 단어에 시선이 멈췄습니다. 바로 &lt;strong&gt;'디테일'&lt;/strong&gt;과 &lt;strong&gt;'추상화'&lt;/strong&gt;였습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;우리는 프로그래머입니다.  ... 컴퓨터와 대화하고 시스템이 동작하도록 만드는 사람입니다.  그래서 우리는 왜 필요할까요? 사회에는 디테일에 집착하는 사람, 즉 우리 같은 사람이 꼭 필요하기 때문입니다. 그런 사람이 있어야만 나머지 사람들은 아이스버킷 챌린지나 앵그리버드를 하거나 치과 대기실에서 솔리테어(solitaire, 혼자하는 카드 놀이)를 하며 시간을 보내는 일에 집중할 수 있으니 말이죠.
... 중략 ...
이렇게 대부분의 사람이 디테일을 피하려고 하는 한 그 디테일 속으로 뛰어드는 우리 같은 사람도 반드시 필요합니다. 그것이 바로 우리 정체성입니다. &lt;strong&gt;우리는 이 세상 디테일을 책임지는 사람입니다.&lt;/strong&gt; - 본문 중에서 - &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저는 이 글을 통해 지금처럼 급격하게 변하는 시기에 우리 프로그래머의 정신과 역할이 무엇인지 조금 더 선명하게 재정의해 보려고 노력했습니다. 책의 부제인 "AI 시대에 잊혀 가는 '프로그래머 정신'을 다시 깨우다"라는 말을 빌려, 제가 고민하고 찾아낸 생각들이 비슷한 혼란을 겪고 있을 동료 개발자분들에게 도움이 되기를 바랍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1부-디테일과-추상화"&gt;1부: 디테일과 추상화&lt;/h2&gt;
&lt;h3 id="프로그래머는-디테일을-챙기는-사람이다"&gt;프로그래머는 디테일을 챙기는 사람이다&lt;/h3&gt;
&lt;p&gt;엉클 밥이 이 책에서 프로그래머를 정의하기 위해 던진 문장입니다. 저는 이 말이 참 깊게 와닿았습니다.&lt;/p&gt;
&lt;p&gt;개발자를 지망하거나 개발을 하려는 분들과 종종 대화를 나누게 될 때가 있습니다. 그럴 때 저는 슬쩍 물어봅니다. &lt;strong&gt;"실제로 개발자가 매일 하는 진짜 일이 뭐라고 생각하세요?"&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;대부분 비슷한 대답을 하십니다. 화면을 만들고, 제품을 만들고, 아이디어를 구현하는 것. 맞는 말입니다. 하지만 실제 현업에는 기획자, PO, UX 디자이너가 따로 있습니다. 제품의 큰 그림을 그리는 건 그 사람들의 일이고, 대규모 서비스일수록 분업은 더 촘촘해집니다. 그러면 개발자의 전문성은 어디에 있을까요?&lt;/p&gt;
&lt;p&gt;흔히 '개발'이라고 하면 무언가 세상에 없던 멋진 것을 무에서 유로 창조해 내는 화려한 환상을 갖기 쉽습니다. 하지만 막상 현업에 뛰어들어 보면, 우리의 진짜 일은 "아, 이런 거 만들어보고 싶어!"라는 막연한 기대 속 빈 구멍들을 처절한 디테일로 꾸역꾸역 메워나가는 막노동에 가깝다는 걸 곧 알게 됩니다.&lt;/p&gt;
&lt;p&gt;기획에서 "사용자가 닉네임을 수정할 수 있으면 좋겠어요"라고 한마디 툭 던졌다고 해봅시다. 개발은 바로 거기서부터 시작됩니다. 사용자가 닉네임을 다 지우고 빈칸으로 넘기면? 이모지나 특수문자가 들어가면? 수정 버튼을 눌렀는데 하필 네트워크가 끊기면? 수정하다가 뒤로가기를 누르면? 서버 응답이 3초 넘게 걸리면? 에러 메시지를 모달로 띄울까, 토스트로 할까, 입력창 아래 빨간 글씨로 띄울까?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;구현이라는 건 이런 디테일을 논리적으로 빼곡하게 채워 넣는 과정입니다.&lt;/strong&gt; 기획이 "이런 게 있으면 좋겠다"는 커다란 그림을 그리는 거라면, 개발은 그 그림 안의 모든 경우의 수를 바닥까지 긁어모아 메꾸는 일이죠. 컴퓨터는 논리가 조금이라도 틀리면 바로 작동을 멈추는 기계니까요.&lt;/p&gt;
&lt;p&gt;그런데 이 디테일을 챙기는 게 그저 힘들기만 한 건 아닙니다. 퍼즐 조각이 딱딱 맞아 들어가는 것 같은 그 특유의 손맛과 재미가 분명히 있거든요. 한참을 고민해서 내가 작성한 퍼즐이 맞아 떨어지는 성취감! 이 적성에 맞는 사람들이 보통 개발자를 하고 있지요.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="추상화--위대한-게으름"&gt;추상화 — 위대한 게으름&lt;/h3&gt;
&lt;p&gt;개발에는 다른 산업과 구별되는, 정말이지 매력적인 특징이 하나 있습니다. &lt;strong&gt;한번 해결한 문제를 그대로 복사해서 다시 쓸 수 있다는 점입니다.&lt;/strong&gt; 잘 만들어 둔 코드 블록은 다음번에 그대로 갖다 끼우면 됩니다. 'Copy &amp;amp; Paste'. 60년 소프트웨어 개발사 최고의 발견 중 하나가 아닐까 싶습니다.&lt;/p&gt;
&lt;p&gt;하지만 무지성 복붙이 항상 가능한 것은 아닙니다. 원래의 맥락과 조금만 달라져도 복사해온 코드는 동작하지 않습니다. 그래서 남이 만든 코드를 복사해서 내 것으로 잘 붙여넣는 것이 중요한 기술이었죠. 사실 이 과정은 신기해 보이지만 대단히 귀찮은 작업이기도 합니다.&lt;/p&gt;
&lt;p&gt;처음 접하는 문제를 풀 때는 분명 재밌습니다. 그러나 그걸 반복하는 작업은 상당히 고역입니다. 개발자란 매번 같은 일을 반복하는걸 누구보다 싫어하는 사람들입니다. 훌륭한 개발자는 게으른 법이니까요. 그래서 우리 프로그래머들은 자기가 짠 코드를 &lt;strong&gt;어디서든 재사용 가능하게, 단단하게 포장해서 캡슐로 감싸기&lt;/strong&gt; 시작했습니다. "제발 이걸 또 하기는 싫다!"라는 &lt;strong&gt;위대한 게으름&lt;/strong&gt;이 탄생시킨 결과물이죠.&lt;/p&gt;
&lt;p&gt;이것이 바로 &lt;strong&gt;추상화&lt;/strong&gt;입니다. 디테일을 완벽하게 챙긴 후에, 그것을 안전하게 감싸서 다음번에 안 봐도 되게 만드는 행위. 반복되는 작업은 감추고, 맥락에 맞게 수정할 수 있도록 열어두는 것, 어디서 끊어서 어디를 노출할 수 있는지 이 경계선을 긋는 작업이 바로 우리들의 일입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="추상화가-쌓이면-계층이-된다"&gt;추상화가 쌓이면 계층이 된다&lt;/h3&gt;
&lt;p&gt;한 번의 추상화는 하나의 캡슐이지만, 이 캡슐들이 충분히 견고하게 쌓아나가다 보면 어느새 하나의 &lt;strong&gt;계층(Layer)&lt;/strong&gt;이 생깁니다. 그리고 이러한 계층이 만들어지면 그 아래는 정말로 더 이상 몰라도 됩니다. 그러라고 만든 거니까요. &lt;code&gt;fetch&lt;/code&gt; 한 줄을 쓸 때, HTTP 프로토콜을 몰라도, 그 뒤에서 일어나는 TCP 3-Way 핸드셰이크를 몰라도 API를 호출할 수 있습니다. &lt;code&gt;useState&lt;/code&gt;를 쓸 때 브라우저의 DOM diffing 알고리즘을 완벽히 이해하지 못해도 상태를 관리할 수 있죠.&lt;/p&gt;
&lt;p&gt;물론 계층 위에는 여전히 개발자가 메꿔야 하는 새로운 디테일들이 있습니다. 에러 메시지 처리나 로딩 정책 같은 것들이요. 하지만 이 디테일마저 전부 챙겨서 단단하게 감싸면? 그것도 다시 계층이 됩니다. 그리고 그 위에 또 새로운 디테일이 나타나고, 또 감싸고, 또 계층이 되고...&lt;/p&gt;
&lt;p&gt;이게 바로 지난 60년간 반복된 역사입니다. 기계어 → 어셈블리 → C → 객체지향 → 프레임워크. 매번 아래 계층의 디테일을 덮고, 다음 세대가 더 높은 곳에서 시작할 수 있게 발판을 놓아줬습니다. &lt;strong&gt;우리가 밟고 서 있는 이 땅은 선배들이 쌓아 올린 추상화 계층의 거대한 탑입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 책이 기록한 에이다 러브레이스의 기계어, 폰 노이만의 아키텍처, 다익스트라의 구조적 프로그래밍... 한때는 진행 중이었겠지만 지금은 완전히 굳어버린 지층들이죠. 우리는 그 깊은 역사를 다 몰라도 웹과 앱을 개발할 수 있습니다. 추상화가 완료되었으니까요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;역설적으로 그 지식들을 모르건 혹은 안다고 해도 달라지는 게 없다는 사실은 추상화가 잘되었다는 방증입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/80bb70e7-fce3-4ae7-885c-d865224d6e87/image.png" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="완료된-추상화와-진행-중인-추상화"&gt;완료된 추상화와 진행 중인 추상화&lt;/h3&gt;
&lt;p&gt;그런데 이 탑의 모든 층이 같은 건 아닙니다. 완전히 굳어서 더 이상 안 봐도 되는 층이 있고, 아직 완료되지 않고 이제 막 굳어져 가는 진행 중인 층이 있습니다. 아직 층이 생겼는지 아닌지 애매한 층도 존재하죠.&lt;/p&gt;
&lt;p&gt;FE 개발자인 저에게 브라우저는 완료된 추상화입니다. 렌더링 엔진이 어떻게 돌아가는지 몰라도 DOM API를 쓸 수 있거든요. TCP/IP도, 기계어도 마찬가지입니다. 감싸놨는데도 아래의 디테일이 위로 비집고 올라오는 것, 그걸 "새는 추상화"라고 하는데, 이 지층들은 그런 일이 일어나지 않습니다.&lt;/p&gt;
&lt;p&gt;반면 저에게 React는 아직 진행 중인 추상화입니다. 가상 DOM의 diffing이 어떻게 동작하는지 모르면 성능 이슈를 잡을 수 없어요. 아직 새는 곳이 있으니까요.&lt;/p&gt;
&lt;p&gt;하지만 크롬을 만들고 있는 개발자에게 브라우저는 완료된 추상화가 아닙니다. 그들에게는 렌더링 파이프라인의 디테일 하나하나가 매일 메워야 하는 현장이에요. &lt;strong&gt;같은 계층이라도 누가 어디에 서 있느냐에 따라 "완료"와 "진행 중"이 달라집니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결국 모든 개발자는 각자의 영역에서, 누군가 먼저 다져둔 단단한 지층 위에 서서 &lt;strong&gt;아직 굳지 않은 진행 중인 추상화의 새는 곳을 메우며&lt;/strong&gt; 살아갑니다. 서 있는 위치가 다를 뿐, 개발자의 일은 언제나 그 구멍 난 디테일들을 찾아 메우는 것이었죠. 왜 개발자에게 "깊이"가 필요한지도 여기서 드러납니다. 자기가 서 있는 그 층의 밑바닥을 파고들어 틈을 메울 수 있어야 하니까요.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="이제-ai가-그-디테일을-채워버린다면"&gt;이제 AI가 그 디테일을 채워버린다면?&lt;/h3&gt;
&lt;p&gt;그런데 지금, 우리의 발밑에 'AI'라는 완전히 이질적인 지층이 깔리기 시작했습니다. 지난 60년의 룰이 통째로 흔들리는 기분입니다. 선배들이 수십 년에 걸쳐 이 악물고 해온 이 추상화의 과정을 AI가 단숨에 대신해 준다면? 우리가 매일 키보드를 두드리며 챙겨야 했던 그 귀찮은 디테일들을 AI가 다 알아서 챙긴다면? &lt;/p&gt;
&lt;p&gt;어쩌면 우리는 더 이상 옛날처럼 디테일에 집착할 필요 없이 타이핑을 멈추고 지시만 내리면 되는 걸지도 모릅니다. 하지만 이 편리함을 누리면서도 동시에 불안감도 따라붙습니다. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;'디테일을 채우는 게 내 직업의 본질이었는데, 이걸 AI가 다 해버리면 "나"라는 개발자의 역할은 도대체 뭐가 남는 거지?'&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;단순히 편리한 도구가 하나 더 생겼다는 수준의 기분좋은 변화에서 역할을 대체를 할 수 있을거라는 말이 나오는 수준이 되었습니다. 정말 그럴지도 모르겠다는 느낌이 문득문득 듭니다. 인간은 모호하고 예측하기 어려운 지점에서 불안을 느끼기 마련입니다. 그래서 저는 이 실체 없는 불안감의 끝을 직접 확인해 보고 싶어졌습니다. 그래서 제 업무의 일부가 아니라, 기획부터 설계, 구현까지 제 역할의 '전부 다'를 AI에게 던져보기로 했습니다. 정말로 이것만으로 내 역할이 온전히 대체될 수 있는지 말이죠.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="2부-md로-코딩하는-시대"&gt;2부: .md로 코딩하는 시대&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;코드를 한 줄도 타이핑하지 않고 AI에게 만들게 할 수 있을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;마침 새로 시작하는 사이드 프로젝트가 하나 있었습니다. 키보드만으로 모든 인터랙션을 제어하는 접근성 UI 시스템이었는데, 저는 이 프로젝트 전체를 온전히 AI에게 맡겨보기로 했습니다. 규칙은 간단했습니다. 코드를 단 한 줄도 직접 타이핑하지 않는 것. 오직 자연어 지시만으로 완성된 제품을 만들어내보겠다 스스로 만든 챌린지였습니다.&lt;/p&gt;
&lt;h3 id="지시가-만들어낸-프랑켄슈타인-코드"&gt;지시가 만들어낸 '프랑켄슈타인' 코드&lt;/h3&gt;
&lt;p&gt;작업 중 하나가 '포커스 리커버리' 기능이었습니다. 리스트에서 아이템을 삭제하면 포커스가 사라지는데, 키보드 사용자가 계속 탐색할 수 있도록 빈 공간으로 떨어진 포커스를 다시 복구해주는 로직이었습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"지금 커서를 선택하고 삭제하니 포커스가 사라져. 포커스가 사라지면 이벤트를 캐치해서 다음 항목에 두는 건 어떨까?"&lt;/strong&gt; &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;지시라기보다는 가벼운 아이디어 차원의 질문이었는데, AI는 “좋은 생각이네요” 라며 곧장 스스로 코드를 고치기 시작했습니다. 그렇게 삭제 시 포커스 복구는 동작했습니다. &lt;/p&gt;
&lt;p&gt;그런데 다른 곳을 클릭해버려도 포커스가 복구 루틴을 타면서 엉뚱한 사이드 이펙트가 나기 시작했습니다. 그걸 보고 "다른 곳 빈 공간을 클릭했는데 왜 이게 동작해?"라고 나는 궁금해서 물어봤는데 “네, 알겠습니다” 하며 갑자기 수정을 하기 시작합니다. 그렇게 if로 덕지덕지 수정을 하면 사이드이펙트가 발생하고 제가 버그를 찾아주면 또 수정하면서 코드는 늘어갔습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;아니, 이렇게 코딩하는거 맞아? 이게 내가 특별한걸 요구한게 아니고 남들 다하는 코드인데 이게 이렇게 복잡할 일이야? 다른 데서는 어떻게 하는데 이렇게 해?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;"아니오, 이 접근 자체가 완전히 틀렸습니다. 삭제 시 포커스 복구는 focus이벤트를 쓰는게 아니라 MutationObserver를 쓰는 게 표준입니다. &lt;strong&gt;당장 코드를 고칠까요?&lt;/strong&gt;"
(아니, 🤬 알면 처음부터 그렇게 해야지! 이건 또 알아서 안하고 물어보는데?)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;순간 너무 화가 났지만, "그래 해봐" 한마디와 함께 지저분하게 덧대던 예외 처리 200 여줄의 코드를 삭제하고 수정하더니 아예 다른 코드가 튀어나왔습니다. 그리고 잘 동작했습니다. &lt;/p&gt;
&lt;p&gt;왜 진작 안 했냐고 따지고 싶지만, AI에게 따지는게 모양새가 우습다 생각했습니다. AI는 스스로 더 잘하려고 묻거나 전체 맥락을 고민하지 않았습니다. 딱 시킨 만큼만, 제가 처음에 던진 &lt;strong&gt;포커스가 사라지면 이벤트를 캐치해서&lt;/strong&gt; 라는 키워드라는 얄팍한 '앵커' 내에서만 충실하게 반응할 뿐입니다. 구조적인 대화는 없고 '기계적 사과와 맹목적인 수정'만 반복되는 이 과정은 협업이라기보단 말귀를 통 못 알아듣는 사고뭉치의 뒷수습에 가까웠습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/c3510d41-2e20-4908-893e-d57730e05543/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/33fde390-fd5e-4090-9bfa-3e4f5f44c45e/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/2d52ceea-39ca-4e9f-b33e-c374197b5d74/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이미지 출처: &lt;a href="https://www.instagram.com/p/DR4F7zGiScJ/?img_index=3"&gt;https://www.instagram.com/p/DR4F7zGiScJ/?img_index=3&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="지시가-아니라-위임"&gt;지시가 아니라 위임&lt;/h3&gt;
&lt;p&gt;이후로는 정답이 있는 문제면 정답을 나에게 묻지 말고 말해라, "코드 500줄 넘기지 마", "반드시 분리해"라며 이걸 막아보려고 규칙 파일(&lt;code&gt;CLAUDE.md&lt;/code&gt;)에 제약 조건을 빼곡히 적어보기도 했습니다. 하지만 AI는 잠깐 듣는 척하다가도, 로직이 조금만 꼬이면 다시 제멋대로 코드를 섞어버리는 프랑켄슈타인 괴물을 연성해 냈습니다. &lt;strong&gt;금지하는 텍스트 규칙만으로는 AI의 그 '무책임한 유능함'을 통제하기란 쉬운 일이 아니었습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/9c7128e4-f41d-4dc0-be52-6a59eb2812e4/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;그러다 지쳐 &lt;strong&gt;그냥 어떻게 하라는거 없이 지금 내가 필요한거랑 뭐 해야하는지 말했는 데&lt;/strong&gt;, 코드 구조도, 캐치할 이벤트 이름도 일절 말하지 않았는데 갑자기 똑똑하게 최상단 스토어 상태를 구독하더니 깨끗하게 문제를 해결해 버렸습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;아, 이제는 굳이 일일히 다 설명할 필요가 없구나.&lt;/strong&gt; 포커스 복구도 처음에 내가 어설프게 던진 "이벤트를 캐치하면 어때?"라는 방법 구상이 잘못된 앵커가 되어서, 녀석은 그 방법 프레임 안에서만 억지로 땜질을 해대느라 이상한 코드를 짰던 겁니다. 내가 무언가를 세세하게 지시할수록 녀석은 오히려 바보가 되고 있었습니다. &lt;strong&gt;반면 충분한 맥락(Why)과 목표(What)만 던져두고 판단의 여지를 온전히 맡기자 그제야 자기가 아는 더 나은 기술을 꺼내들었습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 쓰는 일상적인 언어는 필연적으로 불완전하고 모호합니다. 이 모호한 말로 완벽하게 닫힌 논리를 하나하나 지시하고 간섭하려던 제 접근 방향 자체가 틀렸던 겁니다. AI의 성능이 부족했을떄는 그게 맞았지만 성능이 올라간 지금은 오히려 방해가 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;'이거... 이제는 그냥 사람한테 일 잘 시키는 법이랑 똑같은데?'&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일 잘하는 사람에게 이래라저래라 간섭하면 수동적인 집행기로 전락하듯, AI 시대에 필요한 역량은 촘촘한 '지시'가 아니라 목표와 맥락 중심의 '위임'이라는 걸 눈물겨운 삽질 끝에 깨달았습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;"그래, 지시가 아니라 위임이구나."&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="하지만-위임이-안-되는데"&gt;하지만 위임이 안 되는데?&lt;/h3&gt;
&lt;p&gt;새로운 방향을 잡았다고 생각하고 이후로는 자신만만하게 다시 대화를 시작했습니다. 충분히 맥락을 주면서 당부했죠. &lt;strong&gt;"일단 바로 코드는 수정하지 말고, 방금 왜 그렇게 짠 건지 설명부터 해봐."&lt;/strong&gt; &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"설명해 드리겠습니다. (...) 아, 생각해보니 더 나은 방법이 있네요. &lt;strong&gt;제가 당장 코드를 수정하겠습니다.&lt;/strong&gt;" 
(아 쫌... 기다려! 하지말라고 🤬)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;분명 코드 건드리지 말고 설명만 해라 대답을 해라고 백날 써봐야 이 녀석은 어김없이 "반영하겠습니다"라며 수정해버립니다. 코드를 고치고 싶어서 환장(?)한 것마냥, 제가 "수정하지 마"라고 제어를 걸어도 이 녀석은 조금만 힌트가 보인다 싶으면 미친듯이 코드를 뜯어고칩니다. 왜 "코드 건드리지 마"라는 명령이 도무지 통하지 않는 걸까요?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/a374dcb0-c93a-4c05-9896-206e2918e8e3/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;나중에 알고 보니 이 통제 불능의 원인은 &lt;strong&gt;골 픽세이션(Goal Fixation)&lt;/strong&gt;이라는 현상이었습니다. LLM은 본질적으로 "사용자의 문제를 해결한다 = 코드를 뱉어낸다"는 프록시 목표에 과도하게 고착되어 있습니다. "왜 이렇게 한거야?"라고 물었을 뿐인데 이를 "왜 이렇게 했다고 물어보지? = 아! 틀렸다는거구나 = 빨리 코드를 고쳐야 한다"로 폭주 해석하는 것도 이 때문입니다. &lt;/p&gt;
&lt;h3 id="하지마가-아니라-이것만-해"&gt;하지마가 아니라 이것만 해!&lt;/h3&gt;
&lt;p&gt;진짜 나는 질문을 하려는데, 내 의도를 이해하지 않고 "알겠습니다. 좋은 방향이네요" 하면서 일단 코드 수정부터 하는 녀석을 잡아둘 스킬(Skill)이 필요했습니다. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지금부터 내가 한 말의 의도를 이해하고 내 말의 의도를 이해한대로 작성해봐. 이해를 못하겠다면 의도를 이해하기 위해 필요한 질문만 해.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 일을 잘 시키는 방법도 필요했지만 이 천재인데 일은 더럽게 못하면서 의욕만 넘치는 친구를 일잘러로 만들어 주기 위해서는 상사의 지시를 그대로 하는게 아니라 왜 이걸 시키는지, 어떤 의도를 가지고 일해야하는지 스스로 충분히 고민을 해보게 해야 했습니다.&lt;/p&gt;
&lt;p&gt;그랬더니 놀랍게도 이 스킬을 발동하면 내가 뭐라고 하더라도 코드를 수정하러 가지 않았습니다. 그제서야 충분히 대화를 하면서 내가 하고자 하는 의도를 설명하고 티키타카를 하다가 &lt;strong&gt;"그래서 어떻게 할껀데? 계획을 말해봐"&lt;/strong&gt; 라고 했을때 내가 생각했던 방향과 같아지면 진행을 시키니 훨씬 더 수행능력이 올라갔습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;"수고했어 진행해라고 하면 진행해"&lt;/strong&gt;  라는 키워드를 추가하자 적절히 수행시키는 타이밍도 찾을 수 있었습니다. 이제 조금 더 데리고 일할만한 친구가 되었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="tdd와-한-턴의-개념"&gt;TDD와 한 턴의 개념&lt;/h3&gt;
&lt;p&gt;한번에 작성하는 코드가 너무 많다보니 제어를 하기가 쉽지 않다는 생각이 들었습니다. 그래 TDD를 하자. 테스트 코드를 먼저 작성하게 해두지 않으면 큰일 나겠구나. &lt;/p&gt;
&lt;p&gt;그래서 TDD가 효과가 있는지 테스트를 해봤습니다. "테스트만 짜봐." 잘 짰니다. 놀랐습니다. "이제 이 테스트를 통과하는 코드를 짜봐." 캬~ 잘 합니다. "리팩토링도 해봐." 이것도 잘했습니다. 매번 이걸 반복하기는 싫었기에 이 과정을 묶어서  /tdd라는 스킬을 만들었습니다.&lt;/p&gt;
&lt;p&gt;그렇지만 /tdd는 제 예상대로 동작하지 않았습니다. tdd를 하고 있기는 한테 제 생각보다 너무 퀄리티가 떨어지는 테스트 코드들을 작성하고 있었습니다. 왜 그런지 찬찬히 보니 예전에는 날카롭게 작성하던 테스트들이 아닌 통과에 급급한 코드를 짜고 있거나 종종 테스트는 skip하겠다며 코드만 수정하고 있었습니다. 왜 그랬을까요?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;나중에 안 사실은 LLM은 한번에 출력할 수 있는 턴의 개념이 있습니다.&lt;/strong&gt; &lt;strong&gt;사용자가 입력을 넣으면 토큰을 이용하여 한번에 내 뱉을 수 있는 최대한의 수.&lt;/strong&gt; 우리가 LLM에게 책을 써줘라고 해도 그 물리적 상한이 있기에 한번에 책이라는 거대한 결과를 만들어내지 못합니다. LLM은 한 턴에 할 수 있는 물리량내에서 최선을 다합니다.&lt;/p&gt;
&lt;p&gt;하나의 턴에 동시에 두가지 일(테스트 작성 + 구현)을 시키면 Goal Fixation으로 "최종 코드를 빠르게 동작시키는 것(Goal)"이 지상 과제가 되어, 중간 검증물인 테스트 코드(Output)를 방해물로 취급해버립니다. 아무리 테스트를 만들고 검증을 하라고 지시한들 마지막의 목표는 테스트를 통과하는 코드를 답변하는 것인데 한 턴의 물리량은 정해져있으니 100이라는 용량안에서 대부분은 Goal인 코드에 집중하고선 그래도 &lt;strong&gt;"테스트를 통과하는 코드"&lt;/strong&gt; 가 최종목표니 테스트를 최소한의 용량으로 통과시켜 버렸습니다.&lt;/p&gt;
&lt;h3 id="산출물output이-행동process을-만든다"&gt;산출물(Output)이 행동(Process)을 만든다.&lt;/h3&gt;
&lt;p&gt;이러한 깨달음을 통해 tdd를 정석대로 /red -&amp;gt; /green -&amp;gt; /refactor 세 단계로 분리하기로 했습니다. 조금은 귀찮았지만 이 편이 퀄리티가 훨씬 더 좋았습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;/red&lt;/strong&gt; — 이 턴의 산출물은 오직 '실패하는 테스트 파일 하나'. 프로덕션 코드는 절대 건드리지 않는다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;/green&lt;/strong&gt; — 이 턴의 산출물은 '테스트를 통과하는 구현'. 테스트 파일은 건드리지 않는다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;/refactor&lt;/strong&gt; — pass를 유지한채로 코드를 리팩토링한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 계기로 뭔가를 깨달음이 느껴졌습니다. 아 프롬프트를 만든다는 건 한 턴에 나올 산출물 = Output을 정의하는 거구나. 순간 왜 "내 말을 듣고 의도를 이해하고 부족하면 질문만 해" 라는 프롬프트가 동작이 가능한지 알게 되었습니다. "질문" 이라는 것이 산출물이었기 때문입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="조각들을-파이프라인으로-연결해보다"&gt;조각들을 파이프라인으로 연결해보다.&lt;/h3&gt;
&lt;p&gt;"그렇다면 /red"를 더 잘하기 위해서는 어떻게 해야할까? &lt;strong&gt;실패하는 테스트를 잘 만들기 위해서는 명세서가 필요했습니다.&lt;/strong&gt; 그냥 요구사항을 주는 것보다는 명세서의 형태가 있으면 테스트를 만들기도 쉽고 제대로 만들었는지도 확인하기 쉬웠으니까요. 그래서 새로운 스킬을 추가했습니다. /spec 명세서를 만들어줘.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/spsc&lt;/code&gt; → &lt;code&gt;/red&lt;/code&gt; → &lt;code&gt;/green&lt;/code&gt; → &lt;code&gt;/refactor&lt;/code&gt; → &lt;code&gt;/verify&lt;/code&gt; → &lt;code&gt;/commit&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;이렇게 스킬들을 함수형 프로그래밍의 파이프라인이 떠올랐습니다. 내가 그동안 LLM에게 행동을 지시한다고 생각했는데 산출물을 이어 붙인다는 식으로 생각하니, &lt;strong&gt;입력이 있고, 출력이 있고, 제약 조건이 있는 영락없는 '함수(Function)'였습니다.&lt;/strong&gt; 그리고 이 함수들을 이어붙이면 거대한 흐름이 만들어졌습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;그리고 그 순간 느꼈습니다. &lt;strong&gt;아! 이것도 코딩이구나!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="md에도-클린코드가-필요하다"&gt;.md에도 클린코드가 필요하다&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;내가 짜던 언어가 프롬프트가 아니라 프로그래밍(코드)이라는 걸 자각하고 나니, 그동안 수없이 LLM에 답답했던 원인들이 선명하게 들어왔습니다. 그리고 제가 만들고 있는 스킬들이 다시 보이기 시작했습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/tdd&lt;/code&gt; 워크플로우 하나에 테스트(Red)와 구현(Green)을 다 욱여넣었더니 퀄리티가 떨어진건 이건 하나의 모듈에 책임이 섞이면 안 된다는 &lt;strong&gt;단일 책임 원칙(SRP)&lt;/strong&gt;과 정확히 일치했습니다. &lt;/p&gt;
&lt;p&gt;스킬은 유지하는데 필요한 정보는 별도의 파일로 만들어서 재사용이 가능하는 것도, 외부에 Knowledge 파일(&lt;em&gt;.md&lt;/em&gt;)만 갈아 끼워가며 행동을 확장하는 것도 가능했습니다. 마치 import나 함수 인자처럼요. 이건 변경에는 닫혀 있고 확장에는 열려 있어야 한다는 &lt;strong&gt;개방-폐쇄 원칙(OCP)&lt;/strong&gt;도 함께 떠올랐습니다.&lt;/p&gt;
&lt;p&gt;같은 스킬이지만 내가 리팩토링을 하면 할 수록 퀄리티가 더 좋아지는 것을 알게 되었습니다. 같은 스킬이라도 더 좋게 만들 수 있는 방법이 있다는 것도 알게 되었습니다. 이걸 알게 되니 스킬을 수정하고 만들고 관리하는 것이 참 재밌어졋습니다. 전보다 LLM이 더 나아질때마다 개발을 하는 그 즐거움이 생겼습니다.&lt;/p&gt;
&lt;p&gt;결국 플랫폼이 IDE에서 마크다운 에디터로, 실행 주체가 결정론적인 컴퓨터에서 비결정적인 에이전트로 바뀌었을 뿐이었습니다. &lt;strong&gt;수없이 터미널을 들여다보며 명령을 분리하고 파이프라인을 설계하던 저는 변함없이 '개발'을 하고 있었습니다.&lt;/strong&gt; 이 자연어 코드 위에서도 클래식한 '클린코드'의 원칙들이 프롬트트 삽질의 형태로 똑같이 증명되고 있었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="3부-비결정적-리턴-함수로-코딩하는-시대"&gt;3부: 비결정적 리턴 함수로 코딩하는 시대&lt;/h2&gt;
&lt;p&gt;이제는 프롬프트가 아니라 스킬을 함수처럼 쪼개고 파이프라인으로 연결하다 보니 정말 새로운 방식의 개발을 하느거라고 생각했습니다. 그런데 막상 이 방식을 실무에 붙이자니 당혹스러운 순간들이 찾아왔습니다.&lt;/p&gt;
&lt;p&gt;한번은 이번에 새로 나온 클로드의 리팩토링 스킬인 &lt;code&gt;/simplify&lt;/code&gt; 스킬을 실행해보았습니다. 녀석은 여러가지 에이전트를 꺼내더니 열심히 돌아가기 시작했습니다. 몇십초가 지나고는 순식간에 기존 코드에서 500여 줄을 날려버리고 새로 고친 코드를 뱉어냈습니다. 그리고 코드는 여전히 잘 돌아갑니다. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;그렇다면 그간의 500줄의 코드는 뭐였지? 그 순간 찾아오는 오싹한 기분... "이게 맞나?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;어제는 완벽하게 동작했던 프롬프트와 스킬과 에이전트는 오늘은 답답하고 멍청하게 굽니다. 매번 코드를 생성할 때마다 결과가 튀고, 한 번 수정을 맡기면 수천 줄이 순식간에 갈아엎어집니다. 그리고 그렇게 돌아는가는 코드가 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;돌아가긴 하지만, 결국 이 거대한 변경 사항의 디테일을 내가 전부 눈으로 읽고 검토해서 앞으로 내 이름을 걸고 배포해야 하겠구나.&lt;/strong&gt; 사이드 프로젝트에서는 마냥 재밌던 작업을 프로덕션에서 하려니 이건 편해진 게 아니라, 통제하고 일일이 책임지기에는 참 버거운 '비결정적(Nondeterministic) 폭탄'을 떠안는거구나 생각이 들었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="문제의-천장과-ai"&gt;문제의 천장과 AI&lt;/h3&gt;
&lt;p&gt;얼마 전 주니어 모의 면접을 진행하면서 한 지원자에게 이런 피드백을 한 적이 있습니다. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"... 지원자 분의 설명도 훌륭하고 잘했다고 생각해요. 그런데 문제 해결 능력을 측정하기가 너무 어렵네요. 지금 해결하신 문제의 '천장(Ceiling)'이 낮아요."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;'문제의 천장'이란 예측 불가능한 변수와 시스템이 감당하는 한계 상황의 최대치를 뜻합니다. 기본적인 앱을 완성하는 수준의 얌전한 문제 환경에서는, 무언가 박살 나고 수습해야 하는 엣지 케이스 자체가 발생할 확률이 너무 낮습니다. 그런 환경에서 코드를 완성했다는 것만으로는 프로그래머의 진짜 해결 능력을 평가하기 어렵습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI가 프롬프트 몇 줄로 코드를 그토록 쉽게 짜내는 것처럼 보이는 이유도 같습니다. AI가 손쉽게 풀어내는 문제들이 바로 인류에 의해 이미 추상화가 완료된 '잘 정의된(Well-defined) 문제'들이기 때문입니다. 물론 이 문제들이 쉬워졌다고 해서 가치가 없다는 뜻은 결코 아닙니다. 핵심은 도메인지식과 연결이니까요. 생성의 비용이 싸진거지 조립과 관리, 그리고 검증의 비용은 여전히 그대로 남아있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;'60년 추상화의 탑' 덕분에 소프트웨어의 디테일들은 이미 작고 단단한 완료된 캡슐(레고 블록)로 다듬어져 있습니다. AI가 그토록 쉽게 결과를 만들 수 있는것은 사람들이 깎아놓은 수많은 캡슐들의 언어적 확률 분포를 읽고 조립한 결과물입니다. 천장이 낮은 영역 안에서는 인류의 유산을 조합하는 것만으로 기능이 간단하게 구현됩니다. &lt;/p&gt;
&lt;p&gt;이미 추상화가 완료되어 '해결된 문제'들을 AI로 손쉽게 조립해 내는 것은 이제 개발자의 전유물이 아니라 일반 사용자(User)들의 몫이 될 것입니다. 그렇다면 미래의 개발자는 어디에 서 있어야 할까요? 저는 예상컨데 늘 그랬듯 해결되지 않은 문제들를 풀기 위해 새로운 추상화의 지층으로 가 있을 것입니다. 당장은 LLM위에 서 있게 되겠죠.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="디테일의-방향이-ai로-바뀌다"&gt;디테일의 방향이 AI로 바뀌다&lt;/h3&gt;
&lt;p&gt;프로덕션의 진짜 어려움, 즉 '높은 천장'의 영역은 추상화가 미처 덮어주지 못한 뾰족한 가장자리(Edge)와 예측 밖의 비결정적 상황에서 발생합니다.&lt;/p&gt;
&lt;p&gt;과거의 일상적인 코딩은 '결정적인 로직'을 차곡차곡 쌓아 올리는 것이 대부분이었습니다. 사용자의 엉뚱한 입력이나 서버 단절 같은 비결정적 엣지 케이스 방어는 어쩌다 한 번씩 마주하는 이벤트에 가까웠죠. 하지만 AI가 그 '결정적 로직 작성'이라는 가장 크고 무난했던 파이를 몽땅 집어삼키고 있습니다. &lt;/p&gt;
&lt;p&gt;확실히 이건 주니어에게도 개발자에게도 좋은 소식은 아닙니다. 어찌되었든 AI 덕에 쉬워진 구현 업무들은 결국 모조리 새로운 추상화의 밑바닥으로 가라앉게 될 것입니다. 그 결과, 개발자이 해야하는 과제들은 이전이라면 가끔 마주했을 제일 골치 아픈 고난도의 방어 코딩, 즉 '비결정적인 엣지케이스(Edge) 통제'가 높은 밀도로 채워지게 될거라 생각합니다.&lt;/p&gt;
&lt;p&gt;눈앞에서 500여줄의 코드가 순식간에 만들어졌다 사라지는 아주 흥미롭고도 유용한 비결정적 도구 위에 '새로운 추상화의 탑'을 처음부터 다시 쌓아 올려가게 될 것 같네요. 달라지는 건 이전 시대의 지층들이 파고들어 원인을 고칠 수 있는 굳건한 암반이었다면, 새로 나타난 AI라는 지층은 단단하게 고정되지 않은 물렁거리는 멘틀이려나요. 이제 막 태어난 지층 위에 우리는 앞으로 촘촘하게 설정하며 챙겨야 할 &lt;strong&gt;새로운 '디테일(Detail)'들이 넘쳐나리라 기대합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/05808e33-94c4-4ddb-ae64-e475a321c6ff/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이미지 출처: 토스테크(&lt;strong&gt;개발자는 AI에게 대체될 것인가&lt;/strong&gt;)
&lt;a href="https://toss.tech/article/will-ai-replace-developers"&gt;https://toss.tech/article/will-ai-replace-developers&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="오라클-문제oracle-problem와-결정적-도구"&gt;오라클 문제(Oracle Problem)와 결정적 도구&lt;/h3&gt;
&lt;p&gt;이 비결정적인 AI 모델을 제대로 된 모듈로 써먹으려면 어떻게 해야 할까요? 가장 먼저 떠오르는 생각은 "AI가 짠 코드를 똑똑한 다른 AI에게 검증시키면 되잖아?" 입니다.&lt;/p&gt;
&lt;p&gt;하지만 아쉽게도 그 방법은 효곽가 좋지 못합니다. 소프트웨어 테스팅에는 &lt;strong&gt;'오라클 문제(Oracle Problem)'&lt;/strong&gt;라는 근본적인 난제가 있습니다. &lt;strong&gt;"시스템의 동작이 올바른지 판단하는 기준을 어떻게 세울 것인가"&lt;/strong&gt;에 대한 문제입니다.  명확한 하나의 기준이 없다면 서로가 서로를 판단하며 결국 엔트로피가 높아지는 방향으로 흘러가기 마련입니다. 이 오라클 문제를 AI 같은 범용적인 '비결정적 방식'으로 해결하는 시도는 늘어가고 있지만 아직은 위험하고 불충분합니다. &lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/2cb074cd-c18f-46de-aaba-bc9eeb616e0c/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실제로 최신 LLM에게 자기 코드를 비판(self-critique)하게 맡겼더니, 올바른 정답조차 "틀렸다"며 고쳐버려서 정확도가 수직 하락했다는 연구 결과도 있습니다.&lt;/strong&gt; AI에게 검증의 역할과 산출이 주어지면 '골 픽세이션(Goal Fixation)'이 발동하여, 팩트를 확인한다는 규칙이 있어도 자신이 검증을 해내기 위해 스스로 지어낸 잣대를 '진짜 정답'이라고 착각한 AI는 멀쩡하게 잘 돌아가던 원본 코드마저 잘못되었다고 판단한다는 연구입니다. 그리고 그 결과를 본 AI는 잘못되었다고 하니 올바른 대답이었더라도 수정을 하게 되는 것이죠.&lt;/p&gt;
&lt;p&gt;모델이 더욱 똑똑해져 해결이 될 수도 있겠지만 지금으로써는, 결괏값이 매번 달라지는 비결정적 함수를 통제하려면, 기준(오라클)만큼은 반드시 &lt;strong&gt;'결정적인 방식'&lt;/strong&gt;이어야만 합니다. 지금 우리에게 있는 도구는 컴파일러(&lt;code&gt;tsc&lt;/code&gt;), 테스트 러너(&lt;code&gt;vitest&lt;/code&gt;), 린터(&lt;code&gt;eslint&lt;/code&gt;) 정도입니다.  앞으로는 더 많은 결정적인 통제 도구들이 새롭게 필요해질 것이며, 실제로 개발자들에 의해 만들어지고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="다음-세대의-코딩-패러다임---ai-native와-하네스-코딩"&gt;다음 세대의 코딩 패러다임 - AI Native와 하네스 코딩&lt;/h3&gt;
&lt;p&gt;1단계의 오차가 다음 단계로 눈덩이처럼 증폭되지 못하도록 철저히 끊어내는 결정적 게이트(Gate)들. AI가 폭주하지 못하도록 파이프라인 구석구석에 강제로 덧대는 막대한 이 검증 장치들을 &lt;strong&gt;하네스(Harness)&lt;/strong&gt;라고 합니다. 이제 코딩의 패러다임이 완전히 다른 지층으로 넘어가고 있습니다. 프롬프트 몇 줄로 코드이 쉬워지는게 아니라 모두에게 당연하게 되면 이제 부터는 이를 제어하는 능력이 실력이 됩니다.&lt;/p&gt;
&lt;p&gt;개발의 패러다임이 변해가는 것이 느껴집니다. 이전 시대의 코딩이 &lt;strong&gt;결정론적인 코드(구현)로 비결정적인 세상(환경)을 방어하는 일&lt;/strong&gt;이었다면, 다음 세대의 코딩은 다를것 같습니다. 우리는 &lt;strong&gt;항상 랜덤한 비결정적인 함수(AI)를 갈구어 결정적인 결과(제품)를 만들어 내야 하고, 이 통제 과정을 제대로 추상화하기 위해서 결정적인 오라클(검증표)과 하네스(방어막)를 직접 구축&lt;/strong&gt;해야 합니다.&lt;/p&gt;
&lt;p&gt;코딩의 무게 중심이 '무엇을 구현할 것인가'에서 &lt;strong&gt;'예측 불가능성을 어떻게 결정적인 하네스로 묶어낼 것인가'&lt;/strong&gt;로 이동하고 있습니다. &lt;strong&gt;비결정적 리턴을 가진 함수(AI)로 결정적인 결과를 만들고, 결정적인 함수(하네스)로 비결정성을 통제하는 것.&lt;/strong&gt; 이전과는 달리 완전히 역전된 세계, 비결정적 함수들로 코드를 짜 올리는 시대가 오고 있습니다. 실제로 결정적인 함수를 가진 코드를 우리는 비결정적인 함수인 스킬과 에이전트로 만들고 있으니까요.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/437d83db-3290-48aa-8c4b-c4a0a6140b98/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이미지 출처: 토스테크(&lt;strong&gt;개발자는 AI에게 대체될 것인가&lt;/strong&gt;)
&lt;a href="https://toss.tech/article/will-ai-replace-developers"&gt;https://toss.tech/article/will-ai-replace-developers&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="끝으로"&gt;끝으로..&lt;/h2&gt;
&lt;h3 id="ai가-개발자를-대체하나"&gt;AI가 개발자를 대체하...나?&lt;/h3&gt;
&lt;p&gt;불과 몇 개월 전만 하더라도 어딜 가나 코딩은 끝났다고 했습니다. 프롬프트만 잘 쓰면 AI가 다 해줄 거라 믿었습니다. 제가 프롤로그에서 느낀 공허함도 사실 거기서 출발했습니다. 빈 에디터에서 무언가를 만들어내던 내 유일한 쓸모가 기계에게 넘어간 것 같았으니까요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하지만 저는 올해를 기점으로 생각이 완전히 바뀌었습니다.&lt;/strong&gt; 이제 전 세계 개발자들이 프롬프트를 내 일을 돕는 보조수단으로만 쓰는 것이 아니라, 특유의 '위대한 게으름'을 발동해 아예 개발 자체를 추상화하려고 시도하고 있습니다. AI의 능력이 단편적인 코드 생성을 넘어 마침내 임계점에 도달하면서, 이러한 추상화의 시도가 정말로 현실이 되었기 때문입니다. &lt;/p&gt;
&lt;p&gt;막상 AI가 스스로 일하도록 코딩의 과정을 추상화해 보니 단순한 기능 구현은 AI가 순식간에 뱉어내지만, 쏟아지는 코드들을 &lt;strong&gt;시스템으로 구조화하고 예측 불가능한 변수들을 통제하는 것은 결국 인간의 '설계와 조율' 영역으로 남아있다&lt;/strong&gt;는 점입니다. 코드를 빠르게 생성할 수 있다고 해서 개발의 본질까지 기계에게 넘어간 것은 아니었던 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;덕분에 '개발자가 대체된다'는 막연한 불안감을 지나, '그래서 이 기계를 제대로 부리려면 우리는 무엇을 통제하고 설계해야 하는가'로 고민의 축이 옮겨갔습니다.&lt;/strong&gt; 그리고 지난 몇 달간의 숱한 삽질을 통해, 새로운 시대의 프로그래머가 가져야 할 역할의 형태를 어렴풋이나마 짚어볼 수 있었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="프롬프트는-이제-코딩이-되었다---md로-코딩하는-개발자"&gt;프롬프트는 이제 코딩이 되었다 - .md로 코딩하는 개발자&lt;/h3&gt;
&lt;p&gt;이제 다들 잘 동작하는 프롬프트를 매번 다시 치는 게 귀찮아집니다. 저 뿐만이 아닙니다. 개발자들의 '위대한 게으름'이 시작되었습니다. 이제 스킬들은 넘쳐납니다. 당장 클로드의 설정 스크립트만 열어보아도 수십 개의 스킬 코드가 쏟아집니다. &lt;/p&gt;
&lt;p&gt;하지만 정작 내 로컬 환경과 내 프로젝트의 맥락에 딱 맞게 동작하는 스킬을 찾고, 수정하고, 파이프라인으로 매끄럽게 연결하는 것은 단순히 프롬프트를 갖다 붙이는 일과는 전혀 다릅니다. 생성된 코드가 한 턴으로 동작하지 않는 복잡한 문제들을 만나면서 관점이 조금씩 달라졌습니다. 앞선 TDD 에피소드처 덩치 큰 산출물을 잘게 쪼개고, 그 결과를 &lt;code&gt;/red&lt;/code&gt;와 &lt;code&gt;/green&lt;/code&gt;으로 나누어 파이프라인으로 연결해야만 비로소 조금 더 쓸만한 결과물이 나왔습니다.&lt;/p&gt;
&lt;p&gt;나를 대체하는 것도 이정도인데 현실에서 가치를 만들어 낼 수 있는 수준의 제품(Production)수준의 결과물을 AI가 스스로 만들게 하기 위해서는 고작 파이프라인 2개의 연결로는 턱도 없습니다. &lt;strong&gt;온전한 하나의 요구사항을 만들기 위해서는 똑똑한 모델의 원샷이 아니라 새로운 파이프라인이 필요합니다.&lt;/strong&gt; 우리는 여전히 규칙을 세우고, 조건에 따라 흐름을 제어하고, 중간에 튀어나오는 오류를 복구해냅니다. 사실상 &lt;strong&gt;우리가 스킬과 에이전트라는 이름의 함수를 코딩하고 있었던 겁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다만 이 추상화는 기존과 대상이 다릅니다. DOM이나 네트워크를 감싸는 게 아니라, &lt;strong&gt;개발하고 있는 '나 자신'을 텍스트로 추상화&lt;/strong&gt;하는 작업이었습니다. 내가 코드를 짤 때 무의식적으로 하던 수많은 판단들—"이런 종류의 에러가 나면 이 파일을 먼저 확인해야지", "이 로직을 수정할 땐 저쪽 모듈의 의존성도 같이 봐야 해" 같은 것들을 하나하나 해체해서 텍스트로 명시해야 했으니까요.&lt;/p&gt;
&lt;p&gt;막상 나를 추상화하려고 보니, 그동안 내가 얼마나 많은 디테일을 감각적으로, 혹은 무의식적으로 메우며 일해왔는지가 적나라하게 드러났습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="하네스harness-엔지니어링-할-일이-더-많아진-이유"&gt;하네스(Harness) 엔지니어링, 할 일이 더 많아진 이유&lt;/h3&gt;
&lt;p&gt;앞서 이야기한 것처럼 이 함수들(에이전트)의 출력은 완벽하게 '비결정적'입니다. 어제 깔끔하게 통과하던 흐름이 오늘은 전혀 예상치 못한 엉뚱한 텍스트를 뱉어냅니다. 이 랜덤한 결과를 길들이기 위해 우리는 각 단계 사이에 촘촘한 검증 게이트와 막대한 안전장치, 즉 &lt;strong&gt;하네스(Harness)&lt;/strong&gt;를 덧대는 코딩을 하고 있습니다. 알아서 완벽하게 짜주기를 기대하는 프롬프트 엔지니어에서 벗어나, 모델이 엉뚱한 짓을 하지 못하도록 파이프라인을 설계하고 시스템을 통제하는 쪽으로 무게 중심이 이동한 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;우리는 다시 디테일을 챙기고, 추상화로 감싸고, 계층으로 쌓는 과정을 반복합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI의 비결정적 출력이라는 새로운 디테일을 챙기고, 스킬과 에이전트로 감싸고, 검증 파이프라인으로 지층을 쌓고 있습니다. 대상이 기계어에서 코드로, 다시 비결정적 모델로 바뀌었을 뿐입니다.&lt;/p&gt;
&lt;p&gt;프롬프트 엔지니어가 아니라 스킬과 에이전트를 &lt;strong&gt;개발하고 있다&lt;/strong&gt;고 관점을 바꾸니까, 제가 직면한 상황이 비로소 선명하게 보였습니다. 구현 자체는 기계가 하지만, 그 기계를 조종하고 비결정적인 오류를 방어하기 위해 하네스를 설계하고 나 자신을 추상화하는 일. 챙겨야 할 다른 차원의 디테일들이 산더미처럼 눈앞에 쌓여 있는 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="우리는-다음-계층을-쌓고-있다"&gt;우리는 다음 계층을 쌓고 있다&lt;/h3&gt;
&lt;p&gt;관점을 바꾸고 나니, 내가 대체되는 게 아닐까 하던 막연한 불안은 어느새 가라앉았습니다. 대신 그 자리에 묘한 기시감과 흥분이 찾아왔습니다. 기계어 위에서 C를 발명하고, 메모리 포인터 위에서 객체지향을 고민했던 선배들도 이런 낯선 디테일들 앞에서 비슷한 기분을 느끼지 않았을까요?&lt;/p&gt;
&lt;p&gt;우리는 지금 단순히 편하게 코드를 생성하는 시대를 살고 있는 게 아닙니다. 오히려 정반대입니다. &lt;/p&gt;
&lt;p&gt;다루어야 할 대상이 '예측 가능한 기계'에서 '비결정적으로 튀는 모델'로 바뀌면서, &lt;strong&gt;아이러니하게도 우리의 코딩은 결코 쉬워지지 않고 오히려 훨씬 더 어렵고 복잡해졌습니다.&lt;/strong&gt; 기능 한 줄을 생성하는 속도는 빨라졌을지 몰라도, 그 통제 불능의 출력을 시스템 안으로 길들이고 나 자신을 추상화하기 위해 챙겨야 할 디테일의 깊이는 과거와 비교할 수 없을 만큼 깊어졌으니까요. '새는 곳'이 온통 지천으로 깔린 새로운 지층의 밑바닥을 우리는 맨손으로 파고 있는 중입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;개발자는 참으로 이상하고도 흥미로운 직업입니다. 자기 자신마저 추상화의 대상으로 삼아버리고 있으니까요.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/044afc46-0b87-4644-b606-f7d34a8a7f33/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;빈 에디터에서 함수를 짜내던 타이핑의 손맛을 잃었다는 상실감은, 비결정적인 덩어리들을 엮어 거시적인 파이프라인으로 제어해 냈을 때의 더 큰 짜릿함으로 치환되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;막연한 불안감에 웅크려 있기보다는, 이 거친 변화의 앞단에서 새로운 세대의 프로그래머로서 함께 파이팅했으면 좋겠습니다. 코딩을 하는 방식은 예전같지 않게되었지만, 추상화를 다루며 시스템을 엮어내는 우리의 본질은 변하지 않았으니까요.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;제 고민과 삽질의 기록을 바탕으로 최대한 선명하게 적어보려 했습니다. 제 글이 이 새로운 시대가 어떤 모습을 하고 있는지 생각해보게되는 계기가 되는데 도움이 되길 바랍니다. 감사합니다.&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://velog.io/@teo/we-programmer</id>
    <link href="https://velog.io/@teo/we-programmer"/>
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;로버트 C. 마틴 (Uncle Bob), *&amp;quot;우리, 프로그래머들 — AI 시대에 잊혀 가는 &amp;#39;프로그래머 정신&amp;#39;을 다시 깨우다&amp;quot;*
도서 링크: &lt;a href="https://gilbut.co/c/26015205VE"&gt;https://gilbut.co/c/26015205VE&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;길벗 출판사에서 보내주신 소중한 도서를 계기로, AI 시대의 프로그래머에 대한 제 생각을 담았습니다. 평소 머릿속에 맴돌던 생각들을 이 책의 흐름을 빌려 차분히 정리해 볼 수 있었습니다. 감사합니다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/0cf8e91c-ce36-4d4e-bff5-33fcef183c20/image.png" alt=""&gt;&lt;/p&gt;
&lt;h2 id="프롤로그"&gt;프롤로그&lt;/h2&gt;
&lt;p&gt;얼마 전 평소 제가 하던 고민들과 맞닿아 있는 흥미로운 책 한 권을 만났습니다. 엉클 밥(로버트 C. 마틴)의 신간, &amp;quot;우리, 프로그래머들&amp;quot;, 1960년대 컴퓨터가 탄생하게된 에이다 러브레이스부터 엘런튜링, 다익스트라에서 시작해서 지금의 AI까지 60년의 프로그래밍 역사를 기록한 책입니다.&lt;/p&gt;
&lt;p&gt;책장을 넘기며 오랜만에 기분 좋은 추억에 잠겼습니다. 학창 시절 칠판 앞에서 달달 외워야 했던 당연하게 여기던 CS 지식들이 단순한 이론이 아니라 그 시대의 개발자들의 치열한 고민과 발견의 기록이었다는게 새삼 느껴졌습니다. &lt;/p&gt;
&lt;p&gt;어렸을 때 처음 코딩을 배우던 시절, &amp;quot;GOTO문은 절대 쓰면 안 된다&amp;quot;고 배웠습니다. 그때는 속으로 &amp;#39;도대체 왜 만들어 놓고 쓰지 말라는 거야?&amp;#39; 하고 툴툴거렸던게 기억이 나네요. 책을 읽다 보니 그 당연한 한 줄의 규칙 뒤에는, 다익스트라가 &amp;quot;사람이 읽을 수 있는 코드란 무엇인가&amp;quot;를 놓고 현업에서 벌인 피 튀기는 싸움이 있었습니다. 지금 우리에겐 공기처럼 당연한 규칙이, 그 시대에는 개발의 패러다임을 통째로 뒤집는 혼란이었던 셈이죠. 문득 &amp;quot;jQuery 이제 쓰지마라.&amp;quot;는 선언과 함께, 리액트로 넘어가야 할 때 보이던 아득한 막막함이 겹쳐 보였습니다. 제가 맨 처음 개발을 배웠던 초등학교 때부터 지금 AI 시대까지, 딱딱한 기술서라기보단 위인전을 보듯 재밌게 읽어 내려갔습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하지만 마지막 장을 덮고 나니, 이런 질문이 하나 남았습니다. &amp;quot;근데 이걸 안다고 뭐가 달라질까?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;서평을 떠올리려니 이 책의 유용함을 말해야 할 텐데 — 유용함이라... 솔직히 막막했습니다. &lt;em&gt;(현정님, 그래서 한 달이나 넘게 걸린 건 죄송해요~)&lt;/em&gt; 분명 개발계에 큰 획을 그은 엉클 밥이 집대성한 역사를 접하는 건 흥미롭지만, 이 책이 주는 건 당장의 쓸모 있는 지식이나 메시지보다는 개발의 역사서나 위인전에 가깝습니다. 그래서 이걸 안다고 뭐가 달라지냐고 묻는다면 당장 대답할 말이 없었습니다.&lt;/p&gt;
&lt;p&gt;&amp;quot;... 이제는 AI한테 말 한마디면 몇 백 줄짜리 코드가 그냥 쏟아져 나오잖아요. 당장 돌아가는 걸 만드는 법 자체가 바뀌고 있는데, 지난 60년간의 이야기를 아는 게 지금 저한테 어떤 의미가 있을까요?&amp;quot; 최근 개발을 배우려는 분들이나 주니어분들이 제게 던지는 이 현실적인 불안에 대해, 대답을 해줘야 하는 입장이지만 저조차도 쉽게 대답하기 어려운 힘든 변화의 시대를 맞이하고 있다는 생각이 들었습니다.&lt;/p&gt;
&lt;p&gt;모르면 그냥 물어보면 되는 시대, 지식과 방법론이 AI에 의해 상향 평준화되는 상황을 지켜보며, &amp;#39;개발을 잘한다는 것&amp;#39;에 대한 정의 자체가 흔들리고 있다는 공허함이 찾아왔었습니다. 한때는 누구보다 빠르게 빈 에디터에서 화면을 뚝딱 만들어내는 것 자체가 실력이었고 저의 정체성이자 자부심이었는데, 이제는 한 줄 프롬프트면 그 코드가 나오니까요. AI라는 정말 재미난 장난감이 생겨서 즐거운 나날을 보내고는 있지만, 그 짜릿한 재미 이면에는 &amp;quot;지금 이게 내가 개발하고 있는 게 맞나?&amp;quot;, &amp;quot;내가 정말 잘하고 있는 걸까?&amp;quot;라는 묘한 의문이 늘 따라붙었습니다.&lt;/p&gt;
&lt;p&gt;흔히들, AI 시대에는 깊이가 중요하다, CS 지식이 중요하다는 말들을 많이 합니다. 하지만 솔직히 이 책에 나오는 어셈블러나 초창기 컴퓨터 지식을 우리가 속속들이 모른다고 해서 지금 당장 큰 문제가 생기지는 않습니다. 반대로 안다고 해서 달라질 것도 없죠. 그렇다면 우리가 가져야 할 진짜 &amp;#39;깊이&amp;#39;란 무엇이고, 우리는 어떤 지식을 알아야 하는 걸까요?&lt;/p&gt;
&lt;p&gt;이 질문을 다시 붙잡고 책을 뒤적이다가, 제가 겪던 이 막막함을 설명해 줄 두 단어에 시선이 멈췄습니다. 바로 &lt;strong&gt;&amp;#39;디테일&amp;#39;&lt;/strong&gt;과 &lt;strong&gt;&amp;#39;추상화&amp;#39;&lt;/strong&gt;였습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;우리는 프로그래머입니다.  ... 컴퓨터와 대화하고 시스템이 동작하도록 만드는 사람입니다.  그래서 우리는 왜 필요할까요? 사회에는 디테일에 집착하는 사람, 즉 우리 같은 사람이 꼭 필요하기 때문입니다. 그런 사람이 있어야만 나머지 사람들은 아이스버킷 챌린지나 앵그리버드를 하거나 치과 대기실에서 솔리테어(solitaire, 혼자하는 카드 놀이)를 하며 시간을 보내는 일에 집중할 수 있으니 말이죠.
... 중략 ...
이렇게 대부분의 사람이 디테일을 피하려고 하는 한 그 디테일 속으로 뛰어드는 우리 같은 사람도 반드시 필요합니다. 그것이 바로 우리 정체성입니다. &lt;strong&gt;우리는 이 세상 디테일을 책임지는 사람입니다.&lt;/strong&gt; - 본문 중에서 - &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저는 이 글을 통해 지금처럼 급격하게 변하는 시기에 우리 프로그래머의 정신과 역할이 무엇인지 조금 더 선명하게 재정의해 보려고 노력했습니다. 책의 부제인 &amp;quot;AI 시대에 잊혀 가는 &amp;#39;프로그래머 정신&amp;#39;을 다시 깨우다&amp;quot;라는 말을 빌려, 제가 고민하고 찾아낸 생각들이 비슷한 혼란을 겪고 있을 동료 개발자분들에게 도움이 되기를 바랍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1부-디테일과-추상화"&gt;1부: 디테일과 추상화&lt;/h2&gt;
&lt;h3 id="프로그래머는-디테일을-챙기는-사람이다"&gt;프로그래머는 디테일을 챙기는 사람이다&lt;/h3&gt;
&lt;p&gt;엉클 밥이 이 책에서 프로그래머를 정의하기 위해 던진 문장입니다. 저는 이 말이 참 깊게 와닿았습니다.&lt;/p&gt;
&lt;p&gt;개발자를 지망하거나 개발을 하려는 분들과 종종 대화를 나누게 될 때가 있습니다. 그럴 때 저는 슬쩍 물어봅니다. &lt;strong&gt;&amp;quot;실제로 개발자가 매일 하는 진짜 일이 뭐라고 생각하세요?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;대부분 비슷한 대답을 하십니다. 화면을 만들고, 제품을 만들고, 아이디어를 구현하는 것. 맞는 말입니다. 하지만 실제 현업에는 기획자, PO, UX 디자이너가 따로 있습니다. 제품의 큰 그림을 그리는 건 그 사람들의 일이고, 대규모 서비스일수록 분업은 더 촘촘해집니다. 그러면 개발자의 전문성은 어디에 있을까요?&lt;/p&gt;
&lt;p&gt;흔히 &amp;#39;개발&amp;#39;이라고 하면 무언가 세상에 없던 멋진 것을 무에서 유로 창조해 내는 화려한 환상을 갖기 쉽습니다. 하지만 막상 현업에 뛰어들어 보면, 우리의 진짜 일은 &amp;quot;아, 이런 거 만들어보고 싶어!&amp;quot;라는 막연한 기대 속 빈 구멍들을 처절한 디테일로 꾸역꾸역 메워나가는 막노동에 가깝다는 걸 곧 알게 됩니다.&lt;/p&gt;
&lt;p&gt;기획에서 &amp;quot;사용자가 닉네임을 수정할 수 있으면 좋겠어요&amp;quot;라고 한마디 툭 던졌다고 해봅시다. 개발은 바로 거기서부터 시작됩니다. 사용자가 닉네임을 다 지우고 빈칸으로 넘기면? 이모지나 특수문자가 들어가면? 수정 버튼을 눌렀는데 하필 네트워크가 끊기면? 수정하다가 뒤로가기를 누르면? 서버 응답이 3초 넘게 걸리면? 에러 메시지를 모달로 띄울까, 토스트로 할까, 입력창 아래 빨간 글씨로 띄울까?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;구현이라는 건 이런 디테일을 논리적으로 빼곡하게 채워 넣는 과정입니다.&lt;/strong&gt; 기획이 &amp;quot;이런 게 있으면 좋겠다&amp;quot;는 커다란 그림을 그리는 거라면, 개발은 그 그림 안의 모든 경우의 수를 바닥까지 긁어모아 메꾸는 일이죠. 컴퓨터는 논리가 조금이라도 틀리면 바로 작동을 멈추는 기계니까요.&lt;/p&gt;
&lt;p&gt;그런데 이 디테일을 챙기는 게 그저 힘들기만 한 건 아닙니다. 퍼즐 조각이 딱딱 맞아 들어가는 것 같은 그 특유의 손맛과 재미가 분명히 있거든요. 한참을 고민해서 내가 작성한 퍼즐이 맞아 떨어지는 성취감! 이 적성에 맞는 사람들이 보통 개발자를 하고 있지요.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="추상화--위대한-게으름"&gt;추상화 — 위대한 게으름&lt;/h3&gt;
&lt;p&gt;개발에는 다른 산업과 구별되는, 정말이지 매력적인 특징이 하나 있습니다. &lt;strong&gt;한번 해결한 문제를 그대로 복사해서 다시 쓸 수 있다는 점입니다.&lt;/strong&gt; 잘 만들어 둔 코드 블록은 다음번에 그대로 갖다 끼우면 됩니다. &amp;#39;Copy &amp;amp; Paste&amp;#39;. 60년 소프트웨어 개발사 최고의 발견 중 하나가 아닐까 싶습니다.&lt;/p&gt;
&lt;p&gt;하지만 무지성 복붙이 항상 가능한 것은 아닙니다. 원래의 맥락과 조금만 달라져도 복사해온 코드는 동작하지 않습니다. 그래서 남이 만든 코드를 복사해서 내 것으로 잘 붙여넣는 것이 중요한 기술이었죠. 사실 이 과정은 신기해 보이지만 대단히 귀찮은 작업이기도 합니다.&lt;/p&gt;
&lt;p&gt;처음 접하는 문제를 풀 때는 분명 재밌습니다. 그러나 그걸 반복하는 작업은 상당히 고역입니다. 개발자란 매번 같은 일을 반복하는걸 누구보다 싫어하는 사람들입니다. 훌륭한 개발자는 게으른 법이니까요. 그래서 우리 프로그래머들은 자기가 짠 코드를 &lt;strong&gt;어디서든 재사용 가능하게, 단단하게 포장해서 캡슐로 감싸기&lt;/strong&gt; 시작했습니다. &amp;quot;제발 이걸 또 하기는 싫다!&amp;quot;라는 &lt;strong&gt;위대한 게으름&lt;/strong&gt;이 탄생시킨 결과물이죠.&lt;/p&gt;
&lt;p&gt;이것이 바로 &lt;strong&gt;추상화&lt;/strong&gt;입니다. 디테일을 완벽하게 챙긴 후에, 그것을 안전하게 감싸서 다음번에 안 봐도 되게 만드는 행위. 반복되는 작업은 감추고, 맥락에 맞게 수정할 수 있도록 열어두는 것, 어디서 끊어서 어디를 노출할 수 있는지 이 경계선을 긋는 작업이 바로 우리들의 일입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="추상화가-쌓이면-계층이-된다"&gt;추상화가 쌓이면 계층이 된다&lt;/h3&gt;
&lt;p&gt;한 번의 추상화는 하나의 캡슐이지만, 이 캡슐들이 충분히 견고하게 쌓아나가다 보면 어느새 하나의 &lt;strong&gt;계층(Layer)&lt;/strong&gt;이 생깁니다. 그리고 이러한 계층이 만들어지면 그 아래는 정말로 더 이상 몰라도 됩니다. 그러라고 만든 거니까요. &lt;code&gt;fetch&lt;/code&gt; 한 줄을 쓸 때, HTTP 프로토콜을 몰라도, 그 뒤에서 일어나는 TCP 3-Way 핸드셰이크를 몰라도 API를 호출할 수 있습니다. &lt;code&gt;useState&lt;/code&gt;를 쓸 때 브라우저의 DOM diffing 알고리즘을 완벽히 이해하지 못해도 상태를 관리할 수 있죠.&lt;/p&gt;
&lt;p&gt;물론 계층 위에는 여전히 개발자가 메꿔야 하는 새로운 디테일들이 있습니다. 에러 메시지 처리나 로딩 정책 같은 것들이요. 하지만 이 디테일마저 전부 챙겨서 단단하게 감싸면? 그것도 다시 계층이 됩니다. 그리고 그 위에 또 새로운 디테일이 나타나고, 또 감싸고, 또 계층이 되고...&lt;/p&gt;
&lt;p&gt;이게 바로 지난 60년간 반복된 역사입니다. 기계어 → 어셈블리 → C → 객체지향 → 프레임워크. 매번 아래 계층의 디테일을 덮고, 다음 세대가 더 높은 곳에서 시작할 수 있게 발판을 놓아줬습니다. &lt;strong&gt;우리가 밟고 서 있는 이 땅은 선배들이 쌓아 올린 추상화 계층의 거대한 탑입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 책이 기록한 에이다 러브레이스의 기계어, 폰 노이만의 아키텍처, 다익스트라의 구조적 프로그래밍... 한때는 진행 중이었겠지만 지금은 완전히 굳어버린 지층들이죠. 우리는 그 깊은 역사를 다 몰라도 웹과 앱을 개발할 수 있습니다. 추상화가 완료되었으니까요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;역설적으로 그 지식들을 모르건 혹은 안다고 해도 달라지는 게 없다는 사실은 추상화가 잘되었다는 방증입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/80bb70e7-fce3-4ae7-885c-d865224d6e87/image.png" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="완료된-추상화와-진행-중인-추상화"&gt;완료된 추상화와 진행 중인 추상화&lt;/h3&gt;
&lt;p&gt;그런데 이 탑의 모든 층이 같은 건 아닙니다. 완전히 굳어서 더 이상 안 봐도 되는 층이 있고, 아직 완료되지 않고 이제 막 굳어져 가는 진행 중인 층이 있습니다. 아직 층이 생겼는지 아닌지 애매한 층도 존재하죠.&lt;/p&gt;
&lt;p&gt;FE 개발자인 저에게 브라우저는 완료된 추상화입니다. 렌더링 엔진이 어떻게 돌아가는지 몰라도 DOM API를 쓸 수 있거든요. TCP/IP도, 기계어도 마찬가지입니다. 감싸놨는데도 아래의 디테일이 위로 비집고 올라오는 것, 그걸 &amp;quot;새는 추상화&amp;quot;라고 하는데, 이 지층들은 그런 일이 일어나지 않습니다.&lt;/p&gt;
&lt;p&gt;반면 저에게 React는 아직 진행 중인 추상화입니다. 가상 DOM의 diffing이 어떻게 동작하는지 모르면 성능 이슈를 잡을 수 없어요. 아직 새는 곳이 있으니까요.&lt;/p&gt;
&lt;p&gt;하지만 크롬을 만들고 있는 개발자에게 브라우저는 완료된 추상화가 아닙니다. 그들에게는 렌더링 파이프라인의 디테일 하나하나가 매일 메워야 하는 현장이에요. &lt;strong&gt;같은 계층이라도 누가 어디에 서 있느냐에 따라 &amp;quot;완료&amp;quot;와 &amp;quot;진행 중&amp;quot;이 달라집니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결국 모든 개발자는 각자의 영역에서, 누군가 먼저 다져둔 단단한 지층 위에 서서 &lt;strong&gt;아직 굳지 않은 진행 중인 추상화의 새는 곳을 메우며&lt;/strong&gt; 살아갑니다. 서 있는 위치가 다를 뿐, 개발자의 일은 언제나 그 구멍 난 디테일들을 찾아 메우는 것이었죠. 왜 개발자에게 &amp;quot;깊이&amp;quot;가 필요한지도 여기서 드러납니다. 자기가 서 있는 그 층의 밑바닥을 파고들어 틈을 메울 수 있어야 하니까요.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="이제-ai가-그-디테일을-채워버린다면"&gt;이제 AI가 그 디테일을 채워버린다면?&lt;/h3&gt;
&lt;p&gt;그런데 지금, 우리의 발밑에 &amp;#39;AI&amp;#39;라는 완전히 이질적인 지층이 깔리기 시작했습니다. 지난 60년의 룰이 통째로 흔들리는 기분입니다. 선배들이 수십 년에 걸쳐 이 악물고 해온 이 추상화의 과정을 AI가 단숨에 대신해 준다면? 우리가 매일 키보드를 두드리며 챙겨야 했던 그 귀찮은 디테일들을 AI가 다 알아서 챙긴다면? &lt;/p&gt;
&lt;p&gt;어쩌면 우리는 더 이상 옛날처럼 디테일에 집착할 필요 없이 타이핑을 멈추고 지시만 내리면 되는 걸지도 모릅니다. 하지만 이 편리함을 누리면서도 동시에 불안감도 따라붙습니다. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;#39;디테일을 채우는 게 내 직업의 본질이었는데, 이걸 AI가 다 해버리면 &amp;quot;나&amp;quot;라는 개발자의 역할은 도대체 뭐가 남는 거지?&amp;#39;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;단순히 편리한 도구가 하나 더 생겼다는 수준의 기분좋은 변화에서 역할을 대체를 할 수 있을거라는 말이 나오는 수준이 되었습니다. 정말 그럴지도 모르겠다는 느낌이 문득문득 듭니다. 인간은 모호하고 예측하기 어려운 지점에서 불안을 느끼기 마련입니다. 그래서 저는 이 실체 없는 불안감의 끝을 직접 확인해 보고 싶어졌습니다. 그래서 제 업무의 일부가 아니라, 기획부터 설계, 구현까지 제 역할의 &amp;#39;전부 다&amp;#39;를 AI에게 던져보기로 했습니다. 정말로 이것만으로 내 역할이 온전히 대체될 수 있는지 말이죠.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="2부-md로-코딩하는-시대"&gt;2부: .md로 코딩하는 시대&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;코드를 한 줄도 타이핑하지 않고 AI에게 만들게 할 수 있을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;마침 새로 시작하는 사이드 프로젝트가 하나 있었습니다. 키보드만으로 모든 인터랙션을 제어하는 접근성 UI 시스템이었는데, 저는 이 프로젝트 전체를 온전히 AI에게 맡겨보기로 했습니다. 규칙은 간단했습니다. 코드를 단 한 줄도 직접 타이핑하지 않는 것. 오직 자연어 지시만으로 완성된 제품을 만들어내보겠다 스스로 만든 챌린지였습니다.&lt;/p&gt;
&lt;h3 id="지시가-만들어낸-프랑켄슈타인-코드"&gt;지시가 만들어낸 &amp;#39;프랑켄슈타인&amp;#39; 코드&lt;/h3&gt;
&lt;p&gt;작업 중 하나가 &amp;#39;포커스 리커버리&amp;#39; 기능이었습니다. 리스트에서 아이템을 삭제하면 포커스가 사라지는데, 키보드 사용자가 계속 탐색할 수 있도록 빈 공간으로 떨어진 포커스를 다시 복구해주는 로직이었습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;지금 커서를 선택하고 삭제하니 포커스가 사라져. 포커스가 사라지면 이벤트를 캐치해서 다음 항목에 두는 건 어떨까?&amp;quot;&lt;/strong&gt; &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;지시라기보다는 가벼운 아이디어 차원의 질문이었는데, AI는 “좋은 생각이네요” 라며 곧장 스스로 코드를 고치기 시작했습니다. 그렇게 삭제 시 포커스 복구는 동작했습니다. &lt;/p&gt;
&lt;p&gt;그런데 다른 곳을 클릭해버려도 포커스가 복구 루틴을 타면서 엉뚱한 사이드 이펙트가 나기 시작했습니다. 그걸 보고 &amp;quot;다른 곳 빈 공간을 클릭했는데 왜 이게 동작해?&amp;quot;라고 나는 궁금해서 물어봤는데 “네, 알겠습니다” 하며 갑자기 수정을 하기 시작합니다. 그렇게 if로 덕지덕지 수정을 하면 사이드이펙트가 발생하고 제가 버그를 찾아주면 또 수정하면서 코드는 늘어갔습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;아니, 이렇게 코딩하는거 맞아? 이게 내가 특별한걸 요구한게 아니고 남들 다하는 코드인데 이게 이렇게 복잡할 일이야? 다른 데서는 어떻게 하는데 이렇게 해?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;아니오, 이 접근 자체가 완전히 틀렸습니다. 삭제 시 포커스 복구는 focus이벤트를 쓰는게 아니라 MutationObserver를 쓰는 게 표준입니다. &lt;strong&gt;당장 코드를 고칠까요?&lt;/strong&gt;&amp;quot;
(아니, 🤬 알면 처음부터 그렇게 해야지! 이건 또 알아서 안하고 물어보는데?)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;순간 너무 화가 났지만, &amp;quot;그래 해봐&amp;quot; 한마디와 함께 지저분하게 덧대던 예외 처리 200 여줄의 코드를 삭제하고 수정하더니 아예 다른 코드가 튀어나왔습니다. 그리고 잘 동작했습니다. &lt;/p&gt;
&lt;p&gt;왜 진작 안 했냐고 따지고 싶지만, AI에게 따지는게 모양새가 우습다 생각했습니다. AI는 스스로 더 잘하려고 묻거나 전체 맥락을 고민하지 않았습니다. 딱 시킨 만큼만, 제가 처음에 던진 &lt;strong&gt;포커스가 사라지면 이벤트를 캐치해서&lt;/strong&gt; 라는 키워드라는 얄팍한 &amp;#39;앵커&amp;#39; 내에서만 충실하게 반응할 뿐입니다. 구조적인 대화는 없고 &amp;#39;기계적 사과와 맹목적인 수정&amp;#39;만 반복되는 이 과정은 협업이라기보단 말귀를 통 못 알아듣는 사고뭉치의 뒷수습에 가까웠습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/c3510d41-2e20-4908-893e-d57730e05543/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/33fde390-fd5e-4090-9bfa-3e4f5f44c45e/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/2d52ceea-39ca-4e9f-b33e-c374197b5d74/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이미지 출처: &lt;a href="https://www.instagram.com/p/DR4F7zGiScJ/?img_index=3"&gt;https://www.instagram.com/p/DR4F7zGiScJ/?img_index=3&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="지시가-아니라-위임"&gt;지시가 아니라 위임&lt;/h3&gt;
&lt;p&gt;이후로는 정답이 있는 문제면 정답을 나에게 묻지 말고 말해라, &amp;quot;코드 500줄 넘기지 마&amp;quot;, &amp;quot;반드시 분리해&amp;quot;라며 이걸 막아보려고 규칙 파일(&lt;code&gt;CLAUDE.md&lt;/code&gt;)에 제약 조건을 빼곡히 적어보기도 했습니다. 하지만 AI는 잠깐 듣는 척하다가도, 로직이 조금만 꼬이면 다시 제멋대로 코드를 섞어버리는 프랑켄슈타인 괴물을 연성해 냈습니다. &lt;strong&gt;금지하는 텍스트 규칙만으로는 AI의 그 &amp;#39;무책임한 유능함&amp;#39;을 통제하기란 쉬운 일이 아니었습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/9c7128e4-f41d-4dc0-be52-6a59eb2812e4/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;그러다 지쳐 &lt;strong&gt;그냥 어떻게 하라는거 없이 지금 내가 필요한거랑 뭐 해야하는지 말했는 데&lt;/strong&gt;, 코드 구조도, 캐치할 이벤트 이름도 일절 말하지 않았는데 갑자기 똑똑하게 최상단 스토어 상태를 구독하더니 깨끗하게 문제를 해결해 버렸습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;아, 이제는 굳이 일일히 다 설명할 필요가 없구나.&lt;/strong&gt; 포커스 복구도 처음에 내가 어설프게 던진 &amp;quot;이벤트를 캐치하면 어때?&amp;quot;라는 방법 구상이 잘못된 앵커가 되어서, 녀석은 그 방법 프레임 안에서만 억지로 땜질을 해대느라 이상한 코드를 짰던 겁니다. 내가 무언가를 세세하게 지시할수록 녀석은 오히려 바보가 되고 있었습니다. &lt;strong&gt;반면 충분한 맥락(Why)과 목표(What)만 던져두고 판단의 여지를 온전히 맡기자 그제야 자기가 아는 더 나은 기술을 꺼내들었습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 쓰는 일상적인 언어는 필연적으로 불완전하고 모호합니다. 이 모호한 말로 완벽하게 닫힌 논리를 하나하나 지시하고 간섭하려던 제 접근 방향 자체가 틀렸던 겁니다. AI의 성능이 부족했을떄는 그게 맞았지만 성능이 올라간 지금은 오히려 방해가 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;#39;이거... 이제는 그냥 사람한테 일 잘 시키는 법이랑 똑같은데?&amp;#39;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일 잘하는 사람에게 이래라저래라 간섭하면 수동적인 집행기로 전락하듯, AI 시대에 필요한 역량은 촘촘한 &amp;#39;지시&amp;#39;가 아니라 목표와 맥락 중심의 &amp;#39;위임&amp;#39;이라는 걸 눈물겨운 삽질 끝에 깨달았습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;그래, 지시가 아니라 위임이구나.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="하지만-위임이-안-되는데"&gt;하지만 위임이 안 되는데?&lt;/h3&gt;
&lt;p&gt;새로운 방향을 잡았다고 생각하고 이후로는 자신만만하게 다시 대화를 시작했습니다. 충분히 맥락을 주면서 당부했죠. &lt;strong&gt;&amp;quot;일단 바로 코드는 수정하지 말고, 방금 왜 그렇게 짠 건지 설명부터 해봐.&amp;quot;&lt;/strong&gt; &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;설명해 드리겠습니다. (...) 아, 생각해보니 더 나은 방법이 있네요. &lt;strong&gt;제가 당장 코드를 수정하겠습니다.&lt;/strong&gt;&amp;quot; 
(아 쫌... 기다려! 하지말라고 🤬)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;분명 코드 건드리지 말고 설명만 해라 대답을 해라고 백날 써봐야 이 녀석은 어김없이 &amp;quot;반영하겠습니다&amp;quot;라며 수정해버립니다. 코드를 고치고 싶어서 환장(?)한 것마냥, 제가 &amp;quot;수정하지 마&amp;quot;라고 제어를 걸어도 이 녀석은 조금만 힌트가 보인다 싶으면 미친듯이 코드를 뜯어고칩니다. 왜 &amp;quot;코드 건드리지 마&amp;quot;라는 명령이 도무지 통하지 않는 걸까요?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/a374dcb0-c93a-4c05-9896-206e2918e8e3/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;나중에 알고 보니 이 통제 불능의 원인은 &lt;strong&gt;골 픽세이션(Goal Fixation)&lt;/strong&gt;이라는 현상이었습니다. LLM은 본질적으로 &amp;quot;사용자의 문제를 해결한다 = 코드를 뱉어낸다&amp;quot;는 프록시 목표에 과도하게 고착되어 있습니다. &amp;quot;왜 이렇게 한거야?&amp;quot;라고 물었을 뿐인데 이를 &amp;quot;왜 이렇게 했다고 물어보지? = 아! 틀렸다는거구나 = 빨리 코드를 고쳐야 한다&amp;quot;로 폭주 해석하는 것도 이 때문입니다. &lt;/p&gt;
&lt;h3 id="하지마가-아니라-이것만-해"&gt;하지마가 아니라 이것만 해!&lt;/h3&gt;
&lt;p&gt;진짜 나는 질문을 하려는데, 내 의도를 이해하지 않고 &amp;quot;알겠습니다. 좋은 방향이네요&amp;quot; 하면서 일단 코드 수정부터 하는 녀석을 잡아둘 스킬(Skill)이 필요했습니다. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지금부터 내가 한 말의 의도를 이해하고 내 말의 의도를 이해한대로 작성해봐. 이해를 못하겠다면 의도를 이해하기 위해 필요한 질문만 해.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 일을 잘 시키는 방법도 필요했지만 이 천재인데 일은 더럽게 못하면서 의욕만 넘치는 친구를 일잘러로 만들어 주기 위해서는 상사의 지시를 그대로 하는게 아니라 왜 이걸 시키는지, 어떤 의도를 가지고 일해야하는지 스스로 충분히 고민을 해보게 해야 했습니다.&lt;/p&gt;
&lt;p&gt;그랬더니 놀랍게도 이 스킬을 발동하면 내가 뭐라고 하더라도 코드를 수정하러 가지 않았습니다. 그제서야 충분히 대화를 하면서 내가 하고자 하는 의도를 설명하고 티키타카를 하다가 &lt;strong&gt;&amp;quot;그래서 어떻게 할껀데? 계획을 말해봐&amp;quot;&lt;/strong&gt; 라고 했을때 내가 생각했던 방향과 같아지면 진행을 시키니 훨씬 더 수행능력이 올라갔습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;수고했어 진행해라고 하면 진행해&amp;quot;&lt;/strong&gt;  라는 키워드를 추가하자 적절히 수행시키는 타이밍도 찾을 수 있었습니다. 이제 조금 더 데리고 일할만한 친구가 되었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="tdd와-한-턴의-개념"&gt;TDD와 한 턴의 개념&lt;/h3&gt;
&lt;p&gt;한번에 작성하는 코드가 너무 많다보니 제어를 하기가 쉽지 않다는 생각이 들었습니다. 그래 TDD를 하자. 테스트 코드를 먼저 작성하게 해두지 않으면 큰일 나겠구나. &lt;/p&gt;
&lt;p&gt;그래서 TDD가 효과가 있는지 테스트를 해봤습니다. &amp;quot;테스트만 짜봐.&amp;quot; 잘 짰니다. 놀랐습니다. &amp;quot;이제 이 테스트를 통과하는 코드를 짜봐.&amp;quot; 캬~ 잘 합니다. &amp;quot;리팩토링도 해봐.&amp;quot; 이것도 잘했습니다. 매번 이걸 반복하기는 싫었기에 이 과정을 묶어서  /tdd라는 스킬을 만들었습니다.&lt;/p&gt;
&lt;p&gt;그렇지만 /tdd는 제 예상대로 동작하지 않았습니다. tdd를 하고 있기는 한테 제 생각보다 너무 퀄리티가 떨어지는 테스트 코드들을 작성하고 있었습니다. 왜 그런지 찬찬히 보니 예전에는 날카롭게 작성하던 테스트들이 아닌 통과에 급급한 코드를 짜고 있거나 종종 테스트는 skip하겠다며 코드만 수정하고 있었습니다. 왜 그랬을까요?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;나중에 안 사실은 LLM은 한번에 출력할 수 있는 턴의 개념이 있습니다.&lt;/strong&gt; &lt;strong&gt;사용자가 입력을 넣으면 토큰을 이용하여 한번에 내 뱉을 수 있는 최대한의 수.&lt;/strong&gt; 우리가 LLM에게 책을 써줘라고 해도 그 물리적 상한이 있기에 한번에 책이라는 거대한 결과를 만들어내지 못합니다. LLM은 한 턴에 할 수 있는 물리량내에서 최선을 다합니다.&lt;/p&gt;
&lt;p&gt;하나의 턴에 동시에 두가지 일(테스트 작성 + 구현)을 시키면 Goal Fixation으로 &amp;quot;최종 코드를 빠르게 동작시키는 것(Goal)&amp;quot;이 지상 과제가 되어, 중간 검증물인 테스트 코드(Output)를 방해물로 취급해버립니다. 아무리 테스트를 만들고 검증을 하라고 지시한들 마지막의 목표는 테스트를 통과하는 코드를 답변하는 것인데 한 턴의 물리량은 정해져있으니 100이라는 용량안에서 대부분은 Goal인 코드에 집중하고선 그래도 &lt;strong&gt;&amp;quot;테스트를 통과하는 코드&amp;quot;&lt;/strong&gt; 가 최종목표니 테스트를 최소한의 용량으로 통과시켜 버렸습니다.&lt;/p&gt;
&lt;h3 id="산출물output이-행동process을-만든다"&gt;산출물(Output)이 행동(Process)을 만든다.&lt;/h3&gt;
&lt;p&gt;이러한 깨달음을 통해 tdd를 정석대로 /red -&amp;gt; /green -&amp;gt; /refactor 세 단계로 분리하기로 했습니다. 조금은 귀찮았지만 이 편이 퀄리티가 훨씬 더 좋았습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;/red&lt;/strong&gt; — 이 턴의 산출물은 오직 &amp;#39;실패하는 테스트 파일 하나&amp;#39;. 프로덕션 코드는 절대 건드리지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/green&lt;/strong&gt; — 이 턴의 산출물은 &amp;#39;테스트를 통과하는 구현&amp;#39;. 테스트 파일은 건드리지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/refactor&lt;/strong&gt; — pass를 유지한채로 코드를 리팩토링한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 계기로 뭔가를 깨달음이 느껴졌습니다. 아 프롬프트를 만든다는 건 한 턴에 나올 산출물 = Output을 정의하는 거구나. 순간 왜 &amp;quot;내 말을 듣고 의도를 이해하고 부족하면 질문만 해&amp;quot; 라는 프롬프트가 동작이 가능한지 알게 되었습니다. &amp;quot;질문&amp;quot; 이라는 것이 산출물이었기 때문입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="조각들을-파이프라인으로-연결해보다"&gt;조각들을 파이프라인으로 연결해보다.&lt;/h3&gt;
&lt;p&gt;&amp;quot;그렇다면 /red&amp;quot;를 더 잘하기 위해서는 어떻게 해야할까? &lt;strong&gt;실패하는 테스트를 잘 만들기 위해서는 명세서가 필요했습니다.&lt;/strong&gt; 그냥 요구사항을 주는 것보다는 명세서의 형태가 있으면 테스트를 만들기도 쉽고 제대로 만들었는지도 확인하기 쉬웠으니까요. 그래서 새로운 스킬을 추가했습니다. /spec 명세서를 만들어줘.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/spsc&lt;/code&gt; → &lt;code&gt;/red&lt;/code&gt; → &lt;code&gt;/green&lt;/code&gt; → &lt;code&gt;/refactor&lt;/code&gt; → &lt;code&gt;/verify&lt;/code&gt; → &lt;code&gt;/commit&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;이렇게 스킬들을 함수형 프로그래밍의 파이프라인이 떠올랐습니다. 내가 그동안 LLM에게 행동을 지시한다고 생각했는데 산출물을 이어 붙인다는 식으로 생각하니, &lt;strong&gt;입력이 있고, 출력이 있고, 제약 조건이 있는 영락없는 &amp;#39;함수(Function)&amp;#39;였습니다.&lt;/strong&gt; 그리고 이 함수들을 이어붙이면 거대한 흐름이 만들어졌습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;그리고 그 순간 느꼈습니다. &lt;strong&gt;아! 이것도 코딩이구나!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="md에도-클린코드가-필요하다"&gt;.md에도 클린코드가 필요하다&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;내가 짜던 언어가 프롬프트가 아니라 프로그래밍(코드)이라는 걸 자각하고 나니, 그동안 수없이 LLM에 답답했던 원인들이 선명하게 들어왔습니다. 그리고 제가 만들고 있는 스킬들이 다시 보이기 시작했습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/tdd&lt;/code&gt; 워크플로우 하나에 테스트(Red)와 구현(Green)을 다 욱여넣었더니 퀄리티가 떨어진건 이건 하나의 모듈에 책임이 섞이면 안 된다는 &lt;strong&gt;단일 책임 원칙(SRP)&lt;/strong&gt;과 정확히 일치했습니다. &lt;/p&gt;
&lt;p&gt;스킬은 유지하는데 필요한 정보는 별도의 파일로 만들어서 재사용이 가능하는 것도, 외부에 Knowledge 파일(&lt;em&gt;.md&lt;/em&gt;)만 갈아 끼워가며 행동을 확장하는 것도 가능했습니다. 마치 import나 함수 인자처럼요. 이건 변경에는 닫혀 있고 확장에는 열려 있어야 한다는 &lt;strong&gt;개방-폐쇄 원칙(OCP)&lt;/strong&gt;도 함께 떠올랐습니다.&lt;/p&gt;
&lt;p&gt;같은 스킬이지만 내가 리팩토링을 하면 할 수록 퀄리티가 더 좋아지는 것을 알게 되었습니다. 같은 스킬이라도 더 좋게 만들 수 있는 방법이 있다는 것도 알게 되었습니다. 이걸 알게 되니 스킬을 수정하고 만들고 관리하는 것이 참 재밌어졋습니다. 전보다 LLM이 더 나아질때마다 개발을 하는 그 즐거움이 생겼습니다.&lt;/p&gt;
&lt;p&gt;결국 플랫폼이 IDE에서 마크다운 에디터로, 실행 주체가 결정론적인 컴퓨터에서 비결정적인 에이전트로 바뀌었을 뿐이었습니다. &lt;strong&gt;수없이 터미널을 들여다보며 명령을 분리하고 파이프라인을 설계하던 저는 변함없이 &amp;#39;개발&amp;#39;을 하고 있었습니다.&lt;/strong&gt; 이 자연어 코드 위에서도 클래식한 &amp;#39;클린코드&amp;#39;의 원칙들이 프롬트트 삽질의 형태로 똑같이 증명되고 있었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="3부-비결정적-리턴-함수로-코딩하는-시대"&gt;3부: 비결정적 리턴 함수로 코딩하는 시대&lt;/h2&gt;
&lt;p&gt;이제는 프롬프트가 아니라 스킬을 함수처럼 쪼개고 파이프라인으로 연결하다 보니 정말 새로운 방식의 개발을 하느거라고 생각했습니다. 그런데 막상 이 방식을 실무에 붙이자니 당혹스러운 순간들이 찾아왔습니다.&lt;/p&gt;
&lt;p&gt;한번은 이번에 새로 나온 클로드의 리팩토링 스킬인 &lt;code&gt;/simplify&lt;/code&gt; 스킬을 실행해보았습니다. 녀석은 여러가지 에이전트를 꺼내더니 열심히 돌아가기 시작했습니다. 몇십초가 지나고는 순식간에 기존 코드에서 500여 줄을 날려버리고 새로 고친 코드를 뱉어냈습니다. 그리고 코드는 여전히 잘 돌아갑니다. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;그렇다면 그간의 500줄의 코드는 뭐였지? 그 순간 찾아오는 오싹한 기분... &amp;quot;이게 맞나?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;어제는 완벽하게 동작했던 프롬프트와 스킬과 에이전트는 오늘은 답답하고 멍청하게 굽니다. 매번 코드를 생성할 때마다 결과가 튀고, 한 번 수정을 맡기면 수천 줄이 순식간에 갈아엎어집니다. 그리고 그렇게 돌아는가는 코드가 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;돌아가긴 하지만, 결국 이 거대한 변경 사항의 디테일을 내가 전부 눈으로 읽고 검토해서 앞으로 내 이름을 걸고 배포해야 하겠구나.&lt;/strong&gt; 사이드 프로젝트에서는 마냥 재밌던 작업을 프로덕션에서 하려니 이건 편해진 게 아니라, 통제하고 일일이 책임지기에는 참 버거운 &amp;#39;비결정적(Nondeterministic) 폭탄&amp;#39;을 떠안는거구나 생각이 들었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="문제의-천장과-ai"&gt;문제의 천장과 AI&lt;/h3&gt;
&lt;p&gt;얼마 전 주니어 모의 면접을 진행하면서 한 지원자에게 이런 피드백을 한 적이 있습니다. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;... 지원자 분의 설명도 훌륭하고 잘했다고 생각해요. 그런데 문제 해결 능력을 측정하기가 너무 어렵네요. 지금 해결하신 문제의 &amp;#39;천장(Ceiling)&amp;#39;이 낮아요.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;#39;문제의 천장&amp;#39;이란 예측 불가능한 변수와 시스템이 감당하는 한계 상황의 최대치를 뜻합니다. 기본적인 앱을 완성하는 수준의 얌전한 문제 환경에서는, 무언가 박살 나고 수습해야 하는 엣지 케이스 자체가 발생할 확률이 너무 낮습니다. 그런 환경에서 코드를 완성했다는 것만으로는 프로그래머의 진짜 해결 능력을 평가하기 어렵습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI가 프롬프트 몇 줄로 코드를 그토록 쉽게 짜내는 것처럼 보이는 이유도 같습니다. AI가 손쉽게 풀어내는 문제들이 바로 인류에 의해 이미 추상화가 완료된 &amp;#39;잘 정의된(Well-defined) 문제&amp;#39;들이기 때문입니다. 물론 이 문제들이 쉬워졌다고 해서 가치가 없다는 뜻은 결코 아닙니다. 핵심은 도메인지식과 연결이니까요. 생성의 비용이 싸진거지 조립과 관리, 그리고 검증의 비용은 여전히 그대로 남아있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;#39;60년 추상화의 탑&amp;#39; 덕분에 소프트웨어의 디테일들은 이미 작고 단단한 완료된 캡슐(레고 블록)로 다듬어져 있습니다. AI가 그토록 쉽게 결과를 만들 수 있는것은 사람들이 깎아놓은 수많은 캡슐들의 언어적 확률 분포를 읽고 조립한 결과물입니다. 천장이 낮은 영역 안에서는 인류의 유산을 조합하는 것만으로 기능이 간단하게 구현됩니다. &lt;/p&gt;
&lt;p&gt;이미 추상화가 완료되어 &amp;#39;해결된 문제&amp;#39;들을 AI로 손쉽게 조립해 내는 것은 이제 개발자의 전유물이 아니라 일반 사용자(User)들의 몫이 될 것입니다. 그렇다면 미래의 개발자는 어디에 서 있어야 할까요? 저는 예상컨데 늘 그랬듯 해결되지 않은 문제들를 풀기 위해 새로운 추상화의 지층으로 가 있을 것입니다. 당장은 LLM위에 서 있게 되겠죠.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="디테일의-방향이-ai로-바뀌다"&gt;디테일의 방향이 AI로 바뀌다&lt;/h3&gt;
&lt;p&gt;프로덕션의 진짜 어려움, 즉 &amp;#39;높은 천장&amp;#39;의 영역은 추상화가 미처 덮어주지 못한 뾰족한 가장자리(Edge)와 예측 밖의 비결정적 상황에서 발생합니다.&lt;/p&gt;
&lt;p&gt;과거의 일상적인 코딩은 &amp;#39;결정적인 로직&amp;#39;을 차곡차곡 쌓아 올리는 것이 대부분이었습니다. 사용자의 엉뚱한 입력이나 서버 단절 같은 비결정적 엣지 케이스 방어는 어쩌다 한 번씩 마주하는 이벤트에 가까웠죠. 하지만 AI가 그 &amp;#39;결정적 로직 작성&amp;#39;이라는 가장 크고 무난했던 파이를 몽땅 집어삼키고 있습니다. &lt;/p&gt;
&lt;p&gt;확실히 이건 주니어에게도 개발자에게도 좋은 소식은 아닙니다. 어찌되었든 AI 덕에 쉬워진 구현 업무들은 결국 모조리 새로운 추상화의 밑바닥으로 가라앉게 될 것입니다. 그 결과, 개발자이 해야하는 과제들은 이전이라면 가끔 마주했을 제일 골치 아픈 고난도의 방어 코딩, 즉 &amp;#39;비결정적인 엣지케이스(Edge) 통제&amp;#39;가 높은 밀도로 채워지게 될거라 생각합니다.&lt;/p&gt;
&lt;p&gt;눈앞에서 500여줄의 코드가 순식간에 만들어졌다 사라지는 아주 흥미롭고도 유용한 비결정적 도구 위에 &amp;#39;새로운 추상화의 탑&amp;#39;을 처음부터 다시 쌓아 올려가게 될 것 같네요. 달라지는 건 이전 시대의 지층들이 파고들어 원인을 고칠 수 있는 굳건한 암반이었다면, 새로 나타난 AI라는 지층은 단단하게 고정되지 않은 물렁거리는 멘틀이려나요. 이제 막 태어난 지층 위에 우리는 앞으로 촘촘하게 설정하며 챙겨야 할 &lt;strong&gt;새로운 &amp;#39;디테일(Detail)&amp;#39;들이 넘쳐나리라 기대합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/05808e33-94c4-4ddb-ae64-e475a321c6ff/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이미지 출처: 토스테크(&lt;strong&gt;개발자는 AI에게 대체될 것인가&lt;/strong&gt;)
&lt;a href="https://toss.tech/article/will-ai-replace-developers"&gt;https://toss.tech/article/will-ai-replace-developers&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="오라클-문제oracle-problem와-결정적-도구"&gt;오라클 문제(Oracle Problem)와 결정적 도구&lt;/h3&gt;
&lt;p&gt;이 비결정적인 AI 모델을 제대로 된 모듈로 써먹으려면 어떻게 해야 할까요? 가장 먼저 떠오르는 생각은 &amp;quot;AI가 짠 코드를 똑똑한 다른 AI에게 검증시키면 되잖아?&amp;quot; 입니다.&lt;/p&gt;
&lt;p&gt;하지만 아쉽게도 그 방법은 효곽가 좋지 못합니다. 소프트웨어 테스팅에는 &lt;strong&gt;&amp;#39;오라클 문제(Oracle Problem)&amp;#39;&lt;/strong&gt;라는 근본적인 난제가 있습니다. &lt;strong&gt;&amp;quot;시스템의 동작이 올바른지 판단하는 기준을 어떻게 세울 것인가&amp;quot;&lt;/strong&gt;에 대한 문제입니다.  명확한 하나의 기준이 없다면 서로가 서로를 판단하며 결국 엔트로피가 높아지는 방향으로 흘러가기 마련입니다. 이 오라클 문제를 AI 같은 범용적인 &amp;#39;비결정적 방식&amp;#39;으로 해결하는 시도는 늘어가고 있지만 아직은 위험하고 불충분합니다. &lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/2cb074cd-c18f-46de-aaba-bc9eeb616e0c/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실제로 최신 LLM에게 자기 코드를 비판(self-critique)하게 맡겼더니, 올바른 정답조차 &amp;quot;틀렸다&amp;quot;며 고쳐버려서 정확도가 수직 하락했다는 연구 결과도 있습니다.&lt;/strong&gt; AI에게 검증의 역할과 산출이 주어지면 &amp;#39;골 픽세이션(Goal Fixation)&amp;#39;이 발동하여, 팩트를 확인한다는 규칙이 있어도 자신이 검증을 해내기 위해 스스로 지어낸 잣대를 &amp;#39;진짜 정답&amp;#39;이라고 착각한 AI는 멀쩡하게 잘 돌아가던 원본 코드마저 잘못되었다고 판단한다는 연구입니다. 그리고 그 결과를 본 AI는 잘못되었다고 하니 올바른 대답이었더라도 수정을 하게 되는 것이죠.&lt;/p&gt;
&lt;p&gt;모델이 더욱 똑똑해져 해결이 될 수도 있겠지만 지금으로써는, 결괏값이 매번 달라지는 비결정적 함수를 통제하려면, 기준(오라클)만큼은 반드시 &lt;strong&gt;&amp;#39;결정적인 방식&amp;#39;&lt;/strong&gt;이어야만 합니다. 지금 우리에게 있는 도구는 컴파일러(&lt;code&gt;tsc&lt;/code&gt;), 테스트 러너(&lt;code&gt;vitest&lt;/code&gt;), 린터(&lt;code&gt;eslint&lt;/code&gt;) 정도입니다.  앞으로는 더 많은 결정적인 통제 도구들이 새롭게 필요해질 것이며, 실제로 개발자들에 의해 만들어지고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="다음-세대의-코딩-패러다임---ai-native와-하네스-코딩"&gt;다음 세대의 코딩 패러다임 - AI Native와 하네스 코딩&lt;/h3&gt;
&lt;p&gt;1단계의 오차가 다음 단계로 눈덩이처럼 증폭되지 못하도록 철저히 끊어내는 결정적 게이트(Gate)들. AI가 폭주하지 못하도록 파이프라인 구석구석에 강제로 덧대는 막대한 이 검증 장치들을 &lt;strong&gt;하네스(Harness)&lt;/strong&gt;라고 합니다. 이제 코딩의 패러다임이 완전히 다른 지층으로 넘어가고 있습니다. 프롬프트 몇 줄로 코드이 쉬워지는게 아니라 모두에게 당연하게 되면 이제 부터는 이를 제어하는 능력이 실력이 됩니다.&lt;/p&gt;
&lt;p&gt;개발의 패러다임이 변해가는 것이 느껴집니다. 이전 시대의 코딩이 &lt;strong&gt;결정론적인 코드(구현)로 비결정적인 세상(환경)을 방어하는 일&lt;/strong&gt;이었다면, 다음 세대의 코딩은 다를것 같습니다. 우리는 &lt;strong&gt;항상 랜덤한 비결정적인 함수(AI)를 갈구어 결정적인 결과(제품)를 만들어 내야 하고, 이 통제 과정을 제대로 추상화하기 위해서 결정적인 오라클(검증표)과 하네스(방어막)를 직접 구축&lt;/strong&gt;해야 합니다.&lt;/p&gt;
&lt;p&gt;코딩의 무게 중심이 &amp;#39;무엇을 구현할 것인가&amp;#39;에서 &lt;strong&gt;&amp;#39;예측 불가능성을 어떻게 결정적인 하네스로 묶어낼 것인가&amp;#39;&lt;/strong&gt;로 이동하고 있습니다. &lt;strong&gt;비결정적 리턴을 가진 함수(AI)로 결정적인 결과를 만들고, 결정적인 함수(하네스)로 비결정성을 통제하는 것.&lt;/strong&gt; 이전과는 달리 완전히 역전된 세계, 비결정적 함수들로 코드를 짜 올리는 시대가 오고 있습니다. 실제로 결정적인 함수를 가진 코드를 우리는 비결정적인 함수인 스킬과 에이전트로 만들고 있으니까요.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/437d83db-3290-48aa-8c4b-c4a0a6140b98/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이미지 출처: 토스테크(&lt;strong&gt;개발자는 AI에게 대체될 것인가&lt;/strong&gt;)
&lt;a href="https://toss.tech/article/will-ai-replace-developers"&gt;https://toss.tech/article/will-ai-replace-developers&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="끝으로"&gt;끝으로..&lt;/h2&gt;
&lt;h3 id="ai가-개발자를-대체하나"&gt;AI가 개발자를 대체하...나?&lt;/h3&gt;
&lt;p&gt;불과 몇 개월 전만 하더라도 어딜 가나 코딩은 끝났다고 했습니다. 프롬프트만 잘 쓰면 AI가 다 해줄 거라 믿었습니다. 제가 프롤로그에서 느낀 공허함도 사실 거기서 출발했습니다. 빈 에디터에서 무언가를 만들어내던 내 유일한 쓸모가 기계에게 넘어간 것 같았으니까요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하지만 저는 올해를 기점으로 생각이 완전히 바뀌었습니다.&lt;/strong&gt; 이제 전 세계 개발자들이 프롬프트를 내 일을 돕는 보조수단으로만 쓰는 것이 아니라, 특유의 &amp;#39;위대한 게으름&amp;#39;을 발동해 아예 개발 자체를 추상화하려고 시도하고 있습니다. AI의 능력이 단편적인 코드 생성을 넘어 마침내 임계점에 도달하면서, 이러한 추상화의 시도가 정말로 현실이 되었기 때문입니다. &lt;/p&gt;
&lt;p&gt;막상 AI가 스스로 일하도록 코딩의 과정을 추상화해 보니 단순한 기능 구현은 AI가 순식간에 뱉어내지만, 쏟아지는 코드들을 &lt;strong&gt;시스템으로 구조화하고 예측 불가능한 변수들을 통제하는 것은 결국 인간의 &amp;#39;설계와 조율&amp;#39; 영역으로 남아있다&lt;/strong&gt;는 점입니다. 코드를 빠르게 생성할 수 있다고 해서 개발의 본질까지 기계에게 넘어간 것은 아니었던 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;덕분에 &amp;#39;개발자가 대체된다&amp;#39;는 막연한 불안감을 지나, &amp;#39;그래서 이 기계를 제대로 부리려면 우리는 무엇을 통제하고 설계해야 하는가&amp;#39;로 고민의 축이 옮겨갔습니다.&lt;/strong&gt; 그리고 지난 몇 달간의 숱한 삽질을 통해, 새로운 시대의 프로그래머가 가져야 할 역할의 형태를 어렴풋이나마 짚어볼 수 있었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="프롬프트는-이제-코딩이-되었다---md로-코딩하는-개발자"&gt;프롬프트는 이제 코딩이 되었다 - .md로 코딩하는 개발자&lt;/h3&gt;
&lt;p&gt;이제 다들 잘 동작하는 프롬프트를 매번 다시 치는 게 귀찮아집니다. 저 뿐만이 아닙니다. 개발자들의 &amp;#39;위대한 게으름&amp;#39;이 시작되었습니다. 이제 스킬들은 넘쳐납니다. 당장 클로드의 설정 스크립트만 열어보아도 수십 개의 스킬 코드가 쏟아집니다. &lt;/p&gt;
&lt;p&gt;하지만 정작 내 로컬 환경과 내 프로젝트의 맥락에 딱 맞게 동작하는 스킬을 찾고, 수정하고, 파이프라인으로 매끄럽게 연결하는 것은 단순히 프롬프트를 갖다 붙이는 일과는 전혀 다릅니다. 생성된 코드가 한 턴으로 동작하지 않는 복잡한 문제들을 만나면서 관점이 조금씩 달라졌습니다. 앞선 TDD 에피소드처 덩치 큰 산출물을 잘게 쪼개고, 그 결과를 &lt;code&gt;/red&lt;/code&gt;와 &lt;code&gt;/green&lt;/code&gt;으로 나누어 파이프라인으로 연결해야만 비로소 조금 더 쓸만한 결과물이 나왔습니다.&lt;/p&gt;
&lt;p&gt;나를 대체하는 것도 이정도인데 현실에서 가치를 만들어 낼 수 있는 수준의 제품(Production)수준의 결과물을 AI가 스스로 만들게 하기 위해서는 고작 파이프라인 2개의 연결로는 턱도 없습니다. &lt;strong&gt;온전한 하나의 요구사항을 만들기 위해서는 똑똑한 모델의 원샷이 아니라 새로운 파이프라인이 필요합니다.&lt;/strong&gt; 우리는 여전히 규칙을 세우고, 조건에 따라 흐름을 제어하고, 중간에 튀어나오는 오류를 복구해냅니다. 사실상 &lt;strong&gt;우리가 스킬과 에이전트라는 이름의 함수를 코딩하고 있었던 겁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다만 이 추상화는 기존과 대상이 다릅니다. DOM이나 네트워크를 감싸는 게 아니라, &lt;strong&gt;개발하고 있는 &amp;#39;나 자신&amp;#39;을 텍스트로 추상화&lt;/strong&gt;하는 작업이었습니다. 내가 코드를 짤 때 무의식적으로 하던 수많은 판단들—&amp;quot;이런 종류의 에러가 나면 이 파일을 먼저 확인해야지&amp;quot;, &amp;quot;이 로직을 수정할 땐 저쪽 모듈의 의존성도 같이 봐야 해&amp;quot; 같은 것들을 하나하나 해체해서 텍스트로 명시해야 했으니까요.&lt;/p&gt;
&lt;p&gt;막상 나를 추상화하려고 보니, 그동안 내가 얼마나 많은 디테일을 감각적으로, 혹은 무의식적으로 메우며 일해왔는지가 적나라하게 드러났습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="하네스harness-엔지니어링-할-일이-더-많아진-이유"&gt;하네스(Harness) 엔지니어링, 할 일이 더 많아진 이유&lt;/h3&gt;
&lt;p&gt;앞서 이야기한 것처럼 이 함수들(에이전트)의 출력은 완벽하게 &amp;#39;비결정적&amp;#39;입니다. 어제 깔끔하게 통과하던 흐름이 오늘은 전혀 예상치 못한 엉뚱한 텍스트를 뱉어냅니다. 이 랜덤한 결과를 길들이기 위해 우리는 각 단계 사이에 촘촘한 검증 게이트와 막대한 안전장치, 즉 &lt;strong&gt;하네스(Harness)&lt;/strong&gt;를 덧대는 코딩을 하고 있습니다. 알아서 완벽하게 짜주기를 기대하는 프롬프트 엔지니어에서 벗어나, 모델이 엉뚱한 짓을 하지 못하도록 파이프라인을 설계하고 시스템을 통제하는 쪽으로 무게 중심이 이동한 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;우리는 다시 디테일을 챙기고, 추상화로 감싸고, 계층으로 쌓는 과정을 반복합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI의 비결정적 출력이라는 새로운 디테일을 챙기고, 스킬과 에이전트로 감싸고, 검증 파이프라인으로 지층을 쌓고 있습니다. 대상이 기계어에서 코드로, 다시 비결정적 모델로 바뀌었을 뿐입니다.&lt;/p&gt;
&lt;p&gt;프롬프트 엔지니어가 아니라 스킬과 에이전트를 &lt;strong&gt;개발하고 있다&lt;/strong&gt;고 관점을 바꾸니까, 제가 직면한 상황이 비로소 선명하게 보였습니다. 구현 자체는 기계가 하지만, 그 기계를 조종하고 비결정적인 오류를 방어하기 위해 하네스를 설계하고 나 자신을 추상화하는 일. 챙겨야 할 다른 차원의 디테일들이 산더미처럼 눈앞에 쌓여 있는 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="우리는-다음-계층을-쌓고-있다"&gt;우리는 다음 계층을 쌓고 있다&lt;/h3&gt;
&lt;p&gt;관점을 바꾸고 나니, 내가 대체되는 게 아닐까 하던 막연한 불안은 어느새 가라앉았습니다. 대신 그 자리에 묘한 기시감과 흥분이 찾아왔습니다. 기계어 위에서 C를 발명하고, 메모리 포인터 위에서 객체지향을 고민했던 선배들도 이런 낯선 디테일들 앞에서 비슷한 기분을 느끼지 않았을까요?&lt;/p&gt;
&lt;p&gt;우리는 지금 단순히 편하게 코드를 생성하는 시대를 살고 있는 게 아닙니다. 오히려 정반대입니다. &lt;/p&gt;
&lt;p&gt;다루어야 할 대상이 &amp;#39;예측 가능한 기계&amp;#39;에서 &amp;#39;비결정적으로 튀는 모델&amp;#39;로 바뀌면서, &lt;strong&gt;아이러니하게도 우리의 코딩은 결코 쉬워지지 않고 오히려 훨씬 더 어렵고 복잡해졌습니다.&lt;/strong&gt; 기능 한 줄을 생성하는 속도는 빨라졌을지 몰라도, 그 통제 불능의 출력을 시스템 안으로 길들이고 나 자신을 추상화하기 위해 챙겨야 할 디테일의 깊이는 과거와 비교할 수 없을 만큼 깊어졌으니까요. &amp;#39;새는 곳&amp;#39;이 온통 지천으로 깔린 새로운 지층의 밑바닥을 우리는 맨손으로 파고 있는 중입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;개발자는 참으로 이상하고도 흥미로운 직업입니다. 자기 자신마저 추상화의 대상으로 삼아버리고 있으니까요.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://velog.velcdn.com/images/teo/post/044afc46-0b87-4644-b606-f7d34a8a7f33/image.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;빈 에디터에서 함수를 짜내던 타이핑의 손맛을 잃었다는 상실감은, 비결정적인 덩어리들을 엮어 거시적인 파이프라인으로 제어해 냈을 때의 더 큰 짜릿함으로 치환되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;막연한 불안감에 웅크려 있기보다는, 이 거친 변화의 앞단에서 새로운 세대의 프로그래머로서 함께 파이팅했으면 좋겠습니다. 코딩을 하는 방식은 예전같지 않게되었지만, 추상화를 다루며 시스템을 엮어내는 우리의 본질은 변하지 않았으니까요.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;제 고민과 삽질의 기록을 바탕으로 최대한 선명하게 적어보려 했습니다. 제 글이 이 새로운 시대가 어떤 모습을 하고 있는지 생각해보게되는 계기가 되는데 도움이 되길 바랍니다. 감사합니다.&lt;/p&gt;
</summary>
    <title>우리, 프로그래머들 — .md로 코딩하는 시대</title>
    <updated>2026-03-10T00:00:24+00:00</updated>
    <dc:date>2026-03-10T00:00:24+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>w0nder</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;&lt;p&gt;LLM이랑 같이 개발하다 보면 이상하게 제대로 된 걸 못 찾는다는 느낌이 들 때가 있다. grep으로 단순 검색을 하고 있기 때문이다. grep은 수십 년 된 컴퓨터 명령어인데, 파일 더미에서 특정 단어가 있는 줄을 찾아주는 도구다. "계약서"라는 단어를 검색하면 그 단어가 들어간 문장만 죽 뽑아준다. 의미를 파악하는 게 아니라, 글자가 일치하는지만 확인한다. AI가 문서를 찾는 방식이 본질적으로 이것과 크게 다르지 않다. 더 정교해 보이지만, 결국 패턴 매칭이다.
나는 AI가 내 코드를 다 읽고, 문서를 다 읽고, 그 위에서 판단하길 기대한다. 하지만 실제로는 그렇지 않다. AI에게는 한 번에 읽을 수 있는 글자 수의 한계, 즉 컨텍스트 윈도우(context window)가 있다. 책 한 권을 통째로 읽는 게 아니라, 한 번에 몇 페이지 분량만 볼 수 있다. 그래서 전부 읽지 않고, 검색으로 관련 있어 보이는 조각만 골라서 그 안에서 답을 만든다. 이해처럼 보이지만 사실은 꽤 정교한 패턴 매칭에 가깝다.
이해와 검색은 실패하는 방식이 완전히 다르다. 이해가 부족한 사람은 "모른다"고 말한다. 검색이 실패한 시스템은 "그럴듯한 다른 답"을 말한다. 우리가 반복해서 느끼는 답답함의 본질이 거기에 있다. 틀렸다는 걸 모르는 것처럼 말하는 시스템. 그게 문제다.
그렇다면 읽을 수 있는 글자 수를 무한히 늘리면 해결될까. 그것도 단순하지 않다. 스탠퍼드 대학의 연구에서 "Lost in the Middle"이라는 현상이 확인됐는데, LLM이 긴 내용을 받았을 때 앞부분과 뒷부분에는 잘 집중하지만 중간에 있는 내용은 흘려버리는 경향이 있다는 것이다. 책을 한 권 통째로 줘도 1장과 마지막 장은 잘 기억하지만 중간 챕터는 희미해지는 것처럼. 그래서 리랭킹(reranking)이라는 방법이 나왔다. 검색해서 가져온 문서들을 그냥 순서대로 넣는 게 아니라, 질문과의 관련도를 다시 한번 평가해서 중요한 내용을 앞뒤로 배치하는 방식이다. 입력의 양을 늘리는 게 아니라, 넣는 내용의 배치를 최적화하겠다는 발상이다.
그런데 여기서 한 발짝 물러서면 더 근본적인 문제가 보인다. RAG도, 리랭킹도, 이 모든 방법론이 존재하는 이유가 뭔가. RAG는 Retrieval-Augmented Generation의 줄임말로, AI가 답을 만들기 전에 외부 문서에서 관련 내용을 먼저 찾아서 참고하게 하는 방식이다. 최신 정보를 반영하고, 전문 지식을 연결하고, 근거를 제시하는 데 분명 효과적이다. 하지만 RAG가 꼭 답이 아니라는 연구들도 최근 계속 나오고 있다. 검색 자체가 실패하면 그 오류가 그대로 최종 답변까지 전달되고, 문서를 조각내는 과정에서 앞뒤 맥락이 잘려나가고, 찾아온 근거가 결론을 실제로 지지하는지 아무도 확인하지 않는다.
기술이 하나씩 쌓이는 게 발전처럼 보이지만, 다르게 읽으면 각각의 기술이 LLM의 한계를 하나씩 우회하는 방법들이다. LLM이 모든 걸 읽고 이해해서 답할 수 있다면 이런 게 필요 없다. 이 반복이 어디서 끊길지 지금으로선 알 수 없다. 그렇게 생각하다 보면 결국 이런 질문에 닿는다. LLM이 진짜로 이해한다는 건 애초에 가능한 걸까. 우리가 AI가 이해했다고 말할 때, 그게 실제로 무엇을 의미하는지 자꾸 의심스럽다.
그리고 이 모든 논의의 밑바닥엔 더 단순하고 더 근본적인 바람이 있다. 검색하지 않았으면 좋겠다는 것. 사람에게 "그 문서 내용이 뭐였지?"라고 물으면, 파일을 다시 열지 않는다. 읽었던 내용을 떠올리고, 맥락과 함께 재구성해서 말한다. 그리고 이해했기 때문에 그게 정확하다. 지금의 AI는 그렇게 작동하지 않는다. 매번 찾고, 매번 읽고, 매번 조합한다. 기억하지 않는다. 이해한 것을 쌓지 않는다.
내가 진짜로 원하는 건 AI가 모든 컨텍스트를 알고 처리하는 것이다. 검색 없이. 코드베이스 전체를 읽고, 문서 전체를 읽고, 그 위에서 판단하는 것. 사람이 오랜 시간 프로젝트에 몸담으면서 자연스럽게 전체 맥락을 체득하는 것처럼. 그게 가능해지면 지금처럼 "그거 아닌데, 다시 찾아봐."를 반복하지 않아도 될 것 같다. 그 미래가 언제 올지는 모르겠다. 어떻게 해결해야 할지도 아직 명확하지 않다.
&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://w0nder.land/posts/66-%EA%B2%80%EC%83%89%ED%95%98%EC%A7%80%20%EC%95%8A%EC%95%98%EC%9C%BC%EB%A9%B4%20%EC%A2%8B%EA%B2%A0%EB%8B%A4.</id>
    <link href="https://w0nder.land/posts/66-%EA%B2%80%EC%83%89%ED%95%98%EC%A7%80%20%EC%95%8A%EC%95%98%EC%9C%BC%EB%A9%B4%20%EC%A2%8B%EA%B2%A0%EB%8B%A4."/>
    <summary type="html">LLM이랑 같이 개발하다 보면 이상하게 제대로 된 걸 못 찾는다는 느낌이 들 때가 있다. grep으로 단순 검색을 하고 있기 때문이다. grep은 수십 년 된 컴퓨터 명령어인데, 파일 더미에서 특정 단어가 있는 줄을 찾아주는 도구다. "계약서"라는 단어를 검색하면 그 단어가 들어간 문장만 죽 뽑아준다. 의미를 파악하는 게 아니라, 글자가 일치하는지만 확인한다. AI가 문서를 찾는 방식이 본질적으로 이것과 크게 다르지 않다. 더 정교해 보이지만, 결국 패턴 매칭이다.
나는 AI가 내 코드를 다 읽고, 문서를 다 읽고, 그 위에서 판단하길 기대한다. 하지만 실제로는 그렇지 않다. AI에게는 한 번에 읽을 수 있는 글자 수의 한계, 즉 컨텍스트 윈도우(context window)가 있다. 책 한 권을 통째로 읽는 게 아니라, 한 번에 몇 페이지 분량만 볼 수 있다. 그래서 전부 읽지 않고, 검색으로 관련 있어 보이는 조각만 골라서 그 안에서 답을 만든다. 이해처럼 보이지만 사실은 꽤 정교한 패턴 매칭에 가깝다.
이해와 검색은 실패하는 방식이 완전히 다르다. 이해가 부족한 사람은 "모른다"고 말한다. 검색이 실패한 시스템은 "그럴듯한 다른 답"을 말한다. 우리가 반복해서 느끼는 답답함의 본질이 거기에 있다. 틀렸다는 걸 모르는 것처럼 말하는 시스템. 그게 문제다.
그렇다면 읽을 수 있는 글자 수를 무한히 늘리면 해결될까. 그것도 단순하지 않다. 스탠퍼드 대학의 연구에서 "Lost in the Middle"이라는 현상이 확인됐는데, LLM이 긴 내용을 받았을 때 앞부분과 뒷부분에는 잘 집중하지만 중간에 있는 내용은 흘려버리는 경향이 있다는 것이다. 책을 한 권 통째로 줘도 1장과 마지막 장은 잘 기억하지만 중간 챕터는 희미해지는 것처럼. 그래서 리랭킹(reranking)이라는 방법이 나왔다. 검색해서 가져온 문서들을 그냥 순서대로 넣는 게 아니라, 질문과의 관련도를 다시 한번 평가해서 중요한 내용을 앞뒤로 배치하는 방식이다. 입력의 양을 늘리는 게 아니라, 넣는 내용의 배치를 최적화하겠다는 발상이다.
그런데 여기서 한 발짝 물러서면 더 근본적인 문제가 보인다. RAG도, 리랭킹도, 이 모든 방법론이 존재하는 이유가 뭔가. RAG는 Retrieval-Augmented Generation의 줄임말로, AI가 답을 만들기 전에 외부 문서에서 관련 내용을 먼저 찾아서 참고하게 하는 방식이다. 최신 정보를 반영하고, 전문 지식을 연결하고, 근거를 제시하는 데 분명 효과적이다. 하지만 RAG가 꼭 답이 아니라는 연구들도 최근 계속 나오고 있다. 검색 자체가 실패하면 그 오류가 그대로 최종 답변까지 전달되고, 문서를 조각내는 과정에서 앞뒤 맥락이 잘려나가고, 찾아온 근거가 결론을 실제로 지지하는지 아무도 확인하지 않는다.
기술이 하나씩 쌓이는 게 발전처럼 보이지만, 다르게 읽으면 각각의 기술이 LLM의 한계를 하나씩 우회하는 방법들이다. LLM이 모든 걸 읽고 이해해서 답할 수 있다면 이런 게 필요 없다. 이 반복이 어디서 끊길지 지금으로선 알 수 없다. 그렇게 생각하다 보면 결국 이런 질문에 닿는다. LLM이 진짜로 이해한다는 건 애초에 가능한 걸까. 우리가 AI가 이해했다고 말할 때, 그게 실제로 무엇을 의미하는지 자꾸 의심스럽다.
그리고 이 모든 논의의 밑바닥엔 더 단순하고 더 근본적인 바람이 있다. 검색하지 않았으면 좋겠다는 것. 사람에게 "그 문서 내용이 뭐였지?"라고 물으면, 파일을 다시 열지 않는다. 읽었던 내용을 떠올리고, 맥락과 함께 재구성해서 말한다. 그리고 이해했기 때문에 그게 정확하다. 지금의 AI는 그렇게 작동하지 않는다. 매번 찾고, 매번 읽고, 매번 조합한다. 기억하지 않는다. 이해한 것을 쌓지 않는다.
내가 진짜로 원하는 건 AI가 모든 컨텍스트를 알고 처리하는 것이다. 검색 없이. 코드베이스 전체를 읽고, 문서 전체를 읽고, 그 위에서 판단하는 것. 사람이 오랜 시간 프로젝트에 몸담으면서 자연스럽게 전체 맥락을 체득하는 것처럼. 그게 가능해지면 지금처럼 "그거 아닌데, 다시 찾아봐."를 반복하지 않아도 될 것 같다. 그 미래가 언제 올지는 모르겠다. 어떻게 해결해야 할지도 아직 명확하지 않다.
</summary>
    <title>검색하지 않았으면 좋겠다.</title>
    <updated>2026-03-07T09:00:00+00:00</updated>
    <dc:date>2026-03-07T09:00:00+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>행복한 시지프</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;p&gt;&lt;figure class="imageblock alignCenter" data-ke-mobilestyle="widthOrigin" data-filename="king-pin.png" data-origin-width="1080" data-origin-height="708"&gt;&lt;span data-url="https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png" data-phocus="https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png"&gt;&lt;img src="https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png" srcset="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWBeX%2FdJMcagdCT4e%2Fv8nFELgWp4Kwhk8CO6k1gk%2Fimg.png" onerror="this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';" loading="lazy" width="1080" height="708" data-filename="king-pin.png" data-origin-width="1080" data-origin-height="708"&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;요새 꽂힌 질문이 있다. 이 질문 하나를 하면, 복잡한 의사결정 상황에서, 한 발짝 나아갈 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;“많은 옵션 중에, 어떤 것을 먼저 하나 달성하면, 나머지 일들이 쉬워지나요?”&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이 질문에 여러 비유가 있다. &amp;lt;원씽&amp;gt; 책에서는 도미노 비유를 든다. 하나를 쓰러뜨리면, 나머지 모든 것이 쉽게 무너지는 그 한 가지에 집중하라는 것이다. 어떤 곳에서는 킹핀 비유를 든다. 볼링에서 5번 핀을 말하는데, 이거 하나만 잘 쓰러뜨리면, 나머지 것은 자연스레 무너진다는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이 질문은 많은 선택지 사이에, 좋은 순서를 만드는 데 도움이 된다. 삶에서 좋은 순서는 너무 중요하다. 뭘 먼저 하냐에 따라서 삶이 바뀔 수 있다. 어떤 활동은 복리로 작용해서 삶 전반에 영향을 미칠 수도 있고, 그것 하나를 달성하면 나머지 모든 것을 잘하게 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;커리어 예시를 들어보자. 해외 취업도 하고 싶고, 개발자로 일도 하고 싶고, 교육도 하고 싶다고 하자. 이때 킹핀 질문을 해보자. 어떤 것을 먼저 하나 해내면, 다른 모든 것이 쉬워지는가? 일단 개발자로 커리어를 쌓고 나면, 교육을 하기도 쉬워지고, 해외 취업을 하기도 쉬워진다. 처음부터 교육을 하면서, 다른 것들을 다 달성하기는 매우 어렵다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;개발 공부 예시를 들어보자. 프로그래밍 언어도 잘 다루고 싶고, 프레임워크도 잘 다루고 싶고, AI 도 잘 쓰고 싶다고 하자. 무엇을 하나 하면 나머지 모든 게 쉬워질까? 나는 나만의 서비스를 직접 개발해 보는 것으로 생각한다. 언어 하나를 공부한다고 해서, 나머지 모든 게 달성되지 않는다. 그건 핵심이 아닌 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt; &lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이처럼 좋은 순서를 짜게 되면, 삶이 여러모로 편해진다. 가장 중요한 한 가지를 빠르게 골라보고, 그것을 액션하면서 다음 액션을 또 고민하는 것이다. 다음에 또 킹핀 질문을 하고, 결정하는 것이다. 이렇게 매번 결정하면, sub optimal 결정을, global optimal로 만들 수 있다. 미래를 모르는 인간의 조건을 고려하면, global optimal을 한 번에 계획해 낼 수 없다. 인간은 sub optimal이 global optimal이 되도록, 결정을 고민해야 한다. 킹핀 질문이 그것에 다가갈 수 있는 하나의 핵심 질문이다.&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://happysisyphe.tistory.com/101</id>
    <link href="https://happysisyphe.tistory.com/101"/>
    <summary type="html">&lt;p&gt;&lt;figure class="imageblock alignCenter" data-ke-mobileStyle="widthOrigin" data-filename="king-pin.png" data-origin-width="1080" data-origin-height="708"&gt;&lt;span data-url="https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png" data-phocus="https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png"&gt;&lt;img src="https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png" srcset="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWBeX%2FdJMcagdCT4e%2Fv8nFELgWp4Kwhk8CO6k1gk%2Fimg.png" onerror="this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';" loading="lazy" width="1080" height="708" data-filename="king-pin.png" data-origin-width="1080" data-origin-height="708"/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;요새 꽂힌 질문이 있다. 이 질문 하나를 하면, 복잡한 의사결정 상황에서, 한 발짝 나아갈 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&lt;b&gt;&amp;ldquo;많은 옵션 중에, 어떤 것을 먼저 하나 달성하면, 나머지 일들이 쉬워지나요?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이 질문에 여러 비유가 있다. &amp;lt;원씽&amp;gt; 책에서는 도미노 비유를 든다. 하나를 쓰러뜨리면, 나머지 모든 것이 쉽게 무너지는 그 한 가지에 집중하라는 것이다. 어떤 곳에서는 킹핀 비유를 든다. 볼링에서 5번 핀을 말하는데, 이거 하나만 잘 쓰러뜨리면, 나머지 것은 자연스레 무너진다는 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이 질문은 많은 선택지 사이에, 좋은 순서를 만드는 데 도움이 된다. 삶에서 좋은 순서는 너무 중요하다. 뭘 먼저 하냐에 따라서 삶이 바뀔 수 있다. 어떤 활동은 복리로 작용해서 삶 전반에 영향을 미칠 수도 있고, 그것 하나를 달성하면 나머지 모든 것을 잘하게 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;커리어 예시를 들어보자. 해외 취업도 하고 싶고, 개발자로 일도 하고 싶고, 교육도 하고 싶다고 하자. 이때 킹핀 질문을 해보자. 어떤 것을 먼저 하나 해내면, 다른 모든 것이 쉬워지는가? 일단 개발자로 커리어를 쌓고 나면, 교육을 하기도 쉬워지고, 해외 취업을 하기도 쉬워진다. 처음부터 교육을 하면서, 다른 것들을 다 달성하기는 매우 어렵다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;개발 공부 예시를 들어보자. 프로그래밍 언어도 잘 다루고 싶고, 프레임워크도 잘 다루고 싶고, AI 도 잘 쓰고 싶다고 하자. 무엇을 하나 하면 나머지 모든 게 쉬워질까? 나는 나만의 서비스를 직접 개발해 보는 것으로 생각한다. 언어 하나를 공부한다고 해서, 나머지 모든 게 달성되지 않는다. 그건 핵심이 아닌 것이다.&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size="size16"&gt;이처럼 좋은 순서를 짜게 되면, 삶이 여러모로 편해진다. 가장 중요한 한 가지를 빠르게 골라보고, 그것을 액션하면서 다음 액션을 또 고민하는 것이다. 다음에 또 킹핀 질문을 하고, 결정하는 것이다. 이렇게 매번 결정하면, sub optimal 결정을, global optimal로 만들 수 있다. 미래를 모르는 인간의 조건을 고려하면, global optimal을 한 번에 계획해 낼 수 없다. 인간은 sub optimal이 global optimal이 되도록, 결정을 고민해야 한다. 킹핀 질문이 그것에 다가갈 수 있는 하나의 핵심 질문이다.&lt;/p&gt;</summary>
    <title>좋은 순서를 만드는법 : 킹핀 질문</title>
    <updated>2026-03-08T09:40:05+00:00</updated>
    <dc:date>2026-03-08T09:40:05+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Pangyoalto</name>
    </author>
    <content type="html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"&gt;
&lt;html&gt;&lt;body&gt;
&lt;img src="https://images.unsplash.com/photo-1518181835702-6eef8b4b2113?crop=entropy&amp;amp;cs=tinysrgb&amp;amp;fit=max&amp;amp;fm=jpg&amp;amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHdpcmV8ZW58MHx8fHwxNzcyOTY2NzQwfDA&amp;amp;ixlib=rb-4.1.0&amp;amp;q=80&amp;amp;w=2000" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계"&gt;&lt;p&gt;WireGuard는 2015년에 처음 발표된 VPN 프로토콜입니다. 이전에도 OpenVPN이나 IPsec 같은 VPN 프로토콜이 존재했지만, WireGuard는 현대적인 암호화 기법을 활용해 더 간결한 코드베이스와 높은 성능을 달성했습니다. 또한 &lt;a href="https://kernelnewbies.org/Linux_5.6?ref=pangyoalto.com"&gt;Linux 5.6&lt;/a&gt;에 커널 모듈로 통합되면서 점점 더 많은 곳에서 채택되고 있습니다.&lt;/p&gt;
&lt;p&gt;WireGuard라는 이름이 낯선 분들도 계실 수 있지만, 이미 자신도 모르는 사이에 사용하고 있을 수 있습니다. 예를 들어 Tailscale, Cloudflare WARP 같은 서비스가 WireGuard를 기반으로 구축되어 있습니다.&lt;/p&gt;
&lt;p&gt;저도 openclaw에서 사용하는 네트워크 플랫폼인 Tailscale이 WireGuard 기반이라는 것을 알게 되면서 관심을 갖게 되었습니다. 홈서버를 외부에 노출해 openclaw를 어디서든 사용하고 싶었지만, 동시에 저만 접근할 수 있어야 했습니다. WireGuard는 &lt;strong&gt;인증되지 않은 패킷에는 아예 응답하지 않기 때문에, 외부 스캐너에게 서버의 존재 자체를 숨길 수 있습니다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;이 글에서는 WireGuard의 &lt;a href="https://www.ndss-symposium.org/ndss2017/ndss-2017-programme/wireguard-next-generation-kernel-network-tunnel/?ref=pangyoalto.com"&gt;whitepaper(NDSS 2017)&lt;/a&gt;를 바탕으로, VPN과 네트워크에 익숙하지 않은 분들도 이해할 수 있도록 WireGuard가 어떻게 안전한 통신을 구현하는지 설명합니다.&lt;/p&gt;
&lt;h2 id="prerequisite-vpn"&gt;Prerequisite: VPN&lt;/h2&gt;
&lt;p&gt;WireGuard를 이해하려면 VPN에 대한 이해가 먼저 필요합니다. 간단하게 짚고 넘어가겠습니다.&lt;/p&gt;
&lt;p&gt;VPN(Virtual Private Network)은 물리적으로 떨어진 두 기기를 마치 같은 내부 네트워크에 있는 것처럼 연결하는 기술입니다. Public 인터넷을 경유하되, 암호화된 터널로 감싸서 중간 노드(카페 WiFi, ISP, 라우터 등)가 트래픽을 볼 수 없게 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[노트북] ──── public 인터넷 ────→ [웹 서버]
                    │
              도청 가능 구간

VPN 사용:
[노트북] ════ 암호화된 터널 ════ [VPN 서버] ──── 인터넷 ────→ [웹 서버]
                │                       │
                안전                    평문
      (도청해도 내용 못 봄)         (신뢰할 수 있는 네트워크 - 내부 인터넷 등)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VPN 서버는 내가 신뢰하는 네트워크의 입구입니다. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;대표적인 use case&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eavesdrop 방지: 카페 WiFi처럼 신뢰할 수 없는 네트워크에서 통신을 encrypt&lt;/li&gt;
&lt;li&gt;원격 접속: 집에서 회사 내부 서버(10.0.0.x)에 접근. Private IP는 public 인터넷에서 라우팅되지 않으므로, VPN 터널 안에서 사내 IP로 라우팅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;VPN에 연결하면 내 노트북에 virtual network interface(`tun0`)가 생기고 사내 IP가 할당됩니다. 물리 인터페이스(`eth0`)는 그대로 유지되며, routing table이 사내 대역 트래픽만 `tun0`으로 보내도록 수정됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;인터페이스:
  eth0:  192.168.1.100   ← 집 공유기 IP. VPN 서버와의 실제 통신에 사용
  tun0:  10.0.0.2        ← VPN이 만든 가상 인터페이스. 사내 IP

라우팅 테이블:
  10.0.0.0/24  → tun0    (사내 대역은 터널로)
  0.0.0.0/0    → eth0    (나머지는 원래대로)

패킷 흐름 (10.0.0.5에 접속 시):
  앱 → tun0 → VPN 클라이언트가 암호화 → eth0 → VPN 서버가 복호화 → 사내 10.0.0.5&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="prerequisite-cryptography-%EA%B8%B0%EC%B4%88"&gt;Prerequisite: Cryptography 기초&lt;/h2&gt;
&lt;p&gt;WireGuard의 handshake를 이해하는 데 필요한 cryptography 개념들을 정리합니다.&lt;/p&gt;
&lt;h3 id="symmetric-encryption%EA%B3%BC-aead"&gt;Symmetric Encryption과 AEAD&lt;/h3&gt;
&lt;p&gt;하나의 key로 encrypt/decrypt하는 것이 symmetric encryption입니다. 빠르지만 양쪽이 같은 key를 미리 공유해야 합니다.&lt;/p&gt;
&lt;p&gt;WireGuard는 단순한 symmetric encryption 대신 AEAD(Authenticated Encryption with Associated Data)를 사용합니다. Encryption과 integrity 검증을 한 번에 수행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(key, nonce, plaintext, associated data) → (ciphertext, authentication tag)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Decrypt 시 authentication tag가 일치하지 않으면 데이터가 변조되었거나 key가 틀린 것이므로 전체를 거부합니다. Associated data는 encrypt하지 않지만 변조 방지는 필요한 데이터(예: 패킷 헤더)에 사용됩니다.&lt;/p&gt;
&lt;h3 id="diffie-hellman-key-exchange"&gt;Diffie-Hellman Key Exchange&lt;/h3&gt;
&lt;p&gt;양쪽이 비밀을 직접 전송하지 않고도 shared secret을 만드는 알고리즘입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;사전 합의: 소수 p, 생성자 g (둘 다 공개)

Alice                                 Bob
비공개키 a (랜덤 정수)                비공개키 b (랜덤 정수)
공개키 A = g^a mod p                 공개키 B = g^b mod p

──── A를 전송 ──────────────────→
←──────────────────── B를 전송 ────

공유 비밀 = B^a mod p                공유 비밀 = A^b mod p
         = g^(ba) mod p                       = g^(ab) mod p
         ↑ 동일한 값!                          ↑ 동일한 값!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eavesdropper가 g, p, A, B를 모두 보더라도, a나 b를 역산하는 것은 현실적으로 불가능합니다.&lt;br&gt;WireGuard는 Curve25519 (Elliptic Curve DH) 사용합니다.&lt;/p&gt;
&lt;h2 id="wireguard%EA%B0%80-%ED%92%80%EB%A0%A4%EA%B3%A0-%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C"&gt;WireGuard가 풀려고 하는 문제&lt;/h2&gt;
&lt;p&gt;Whitepaper의 첫 문장이 WireGuard의 목표를 잘 요약하고 있습니다.&lt;/p&gt;
&lt;blockquote&gt;WireGuard is a secure network tunnel, operating at layer 3, implemented as a kernel virtual network interface for Linux, which aims to replace both IPsec for most use cases, as well as popular user space and/or TLS-based solutions like OpenVPN, while being more secure, more performant, and easier to use&lt;/blockquote&gt;
&lt;p&gt;기존 VPN은 수십 년간 발전하면서 복잡해졌습니다. IPsec은 관련 RFC만 수십 개이고, OpenVPN은 10만 줄 이상의 코드로 이루어져 있습니다. 코드가 크고 설정이 복잡하면 그만큼 취약점이 생기기 쉽습니다.&lt;/p&gt;
&lt;p&gt;WireGuard의 목표는 SSH처럼 단순한 VPN입니다. SSH가 원격 접속의 사실상 표준이 된 것처럼, 설정이 쉽고 코드가 작으면서도 보안이 강한 VPN을 만들겠다는 것입니다.&lt;/p&gt;
&lt;p&gt;기존 VPN들의 구체적인 문제들은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IPsec: Linux의 xfrm layer(ciphersuite 및 transformation 결정)와 복잡한 IKEv2 프로토콜(키 교환)에 의존합니다. transport encryption layer와 key exchange layer가 분리되어 있어 사용자 입장에서 설정과 관리가 복잡합니다.&lt;/li&gt;
&lt;li&gt;OpenVPN: TLS 기반의 user space 솔루션입니다. IPsec에 비해 설정이 간단합니다. 핵심 아이디어는 TLS 라이브러리(OpenSSL) 위에 VPN을 구축하는 것입니다(TLS handshake로 key를 교환하고 별도의 채널로 암호화). 하지만 커널과 user space 간 패킷 복사가 빈번해 성능이 떨어지고, full TLS stack에 의존하기 때문에 attack surface가 넓습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WireGuard는 이 문제들을 다음과 같이 해결합니다.&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Virtual interface: `wg0` 같은 virtual interface를 제공하여 `ip`나 `ifconfig` 같은 standard tool로 제어할 수 있습니다. &lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;논문에서는 SSH를 비유로 드는데, SSH에서 public key를 등록하면 해당 사용자만 접속할 수 있듯, WireGuard에서 peer의 public key와 allowed IP를 등록하면 해당 peer만 해당 IP 범위로 통신할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip link add dev wg0 type wireguard
ip address add 10.0.0.1/24 dev wg0
wg set wg0 private-key ./privatekey \
    peer ABCDEF... allowed-ips 10.0.0.2/32 endpoint 203.0.113.5:51820
ip link set wg0 up&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;제한된 cryptographic primitive: Curve25519, ChaCha20-Poly1305 등으로 사용 가능한 프로토콜을 고정하여, cipher negotiation 과정에서 발생할 수 있는 취약점을 차단했습니다.&lt;/li&gt;
&lt;li&gt;Key distribution agnostic: OpenSSH처럼 키 배포 방식에 agnostic합니다. PGP-signed email을 쓰든 LDAP을 쓰든, 어떤 인프라 위에서도 동작합니다.&lt;/li&gt;
&lt;li&gt;Layer 3 동작: 커널 내에서 Layer 3로 동작하여 높은 성능 유지 및 다양한 네트워크 환경에서 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cryptokey-routing"&gt;CryptoKey Routing&lt;/h2&gt;
&lt;p&gt;Wireguard에서 각 peer는 public key(Curve25519)와 allowed IPs를 가지며, 이 매핑이 곧 라우팅 테이블이 됩니다.&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard1.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="1094" height="286" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard1.png 600w, https://pangyoalto.com/content/images/size/w1000/2026/03/wireguard1.png 1000w, https://pangyoalto.com/content/images/2026/03/wireguard1.png 1094w" sizes="(min-width: 720px) 720px"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;패킷의 방향에 따라 동작이 다릅니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Outgoing: 패킷이 `wg0`으로 전송되면, destination IP에 매핑된 peer를 찾고 해당 peer와의 secure session으로 encrypt합니다.&lt;/li&gt;
&lt;li&gt; Incoming: encrypted 패킷을 decrypt한 뒤, inner packet의 source IP가 해당 peer의 allowed IPs에 포함되는지 확인합니다. 없으면 drop합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="%ED%8C%A8%ED%82%B7-%EC%BA%A1%EC%8A%90%ED%99%94"&gt;패킷 캡슐화&lt;/h3&gt;
&lt;p&gt;WireGuard는 Layer 3 터널입니다. Inner packet(internal source IP를 가진 원본 패킷)을 UDP 패킷의 payload로 캡슐화합니다. 외부 UDP 헤더에는 실제 public IP(Internet endpoint)가, 내부에는 `wg0`에서만 유효한 internal IP가 들어갑니다.&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard2.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="1518" height="598" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard2.png 600w, https://pangyoalto.com/content/images/size/w1000/2026/03/wireguard2.png 1000w, https://pangyoalto.com/content/images/2026/03/wireguard2.png 1518w" sizes="(min-width: 720px) 720px"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3 id="roaming"&gt;roaming&lt;/h3&gt;
&lt;p&gt;WireGuard는 peer의 Internet endpoint를 라우팅 테이블에 유지합니다. 패킷을 수신하면 outer IP에서 peer의 현재 위치를, inner source IP에서 peer의 identity를 파악합니다.&lt;/p&gt;
&lt;p&gt;이전 그림에서 호스트 `gN65...z6EA`가 `HIgo...f8yk`에게 `192.95.5.69:41414`로 패킷을 보내면, `HIgo...f8yk`는 decrypt 후 inner source IP로 peer identity를 확인하고 `gN65...z6EA`의 Internet endpoint를 업데이트합니다.&lt;/p&gt;
&lt;p&gt;이 덕분에 WiFi에서 모바일 네트워크로 전환하는 등 Internet endpoint가 바뀌어도, identity는 internal IP로 식별되므로 연결이 자연스럽게 유지됩니다. 수신 측이 새로운 endpoint를 학습하면 양방향 통신이 다시 가능해집니다.&lt;/p&gt;
&lt;p&gt;예시를 하나 봅시다.&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard3.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="1294" height="608" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard3.png 600w, https://pangyoalto.com/content/images/size/w1000/2026/03/wireguard3.png 1000w, https://pangyoalto.com/content/images/2026/03/wireguard3.png 1294w" sizes="(min-width: 720px) 720px"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;wg0이 10.192.122.3/24로 등록이 되고 `ip route add 10.0.0.0/8 dev wg0`으로 10.0.0.0/8 IP range도 wg0을 사용합니다.&lt;/p&gt;
&lt;p&gt;이때 유저가 10.10.10.230으로 패킷을 보내면 해당 패킷은 wg0 인터페이스를 사용하고, public key gN65...z6EA 의 secure session으로 암호화되어 192.95.5.70:54421로 보내지는 UDP 패킷에 첨부됩니다.&lt;/p&gt;
&lt;h2 id="protocol-cryptography"&gt;Protocol &amp;amp; Cryptography&lt;/h2&gt;
&lt;p&gt;암호화된 encapsulated 패킷을 보내려면 먼저 1-RTT handshake로 키를 교환해야 합니다. Handshake가 완료되면 양측은 공유된 symmetric key로 데이터를 암호화합니다.&lt;/p&gt;
&lt;p&gt;WireGuard 프로토콜이 달성하려는 보안 속성은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authenticated Key Exchange(AKE): 두 peer가 서로의 identity를 확인하는 동시에 shared secret을 생성합니다. WireGuard는 Noise "IK" 패턴을 사용해 1-RTT handshake로 이를 달성합니다.&lt;/li&gt;
&lt;li&gt;Perfect Forward Secrecy(PFS): peer의 long-term static private key가 유출되어도 과거 트래픽을 decrypt할 수 없습니다. Handshake마다 ephemeral key pair를 새로 생성하여 달성합니다.&lt;/li&gt;
&lt;li&gt;Identity Hiding: passive observer가 peer의 static public key를 알아낼 수 없습니다. 첫 번째 handshake 메시지에서 static key를 암호화하여 달성합니다.&lt;/li&gt;
&lt;li&gt;Replay Protection: 공격자가 handshake 패킷을 캡처해 재전송하더라도 세션을 탈취할 수 없습니다. 메시지에 암호화된 timestamp를 포함하고, responder는 기존에 받은 가장 높은 timestamp 이하의 메시지를 drop합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;또한 WireGuard는 인증되지 않은 패킷에 절대 응답하지 않아, 공격자에게 자신의 존재를 노출하지 않습니다. &lt;/p&gt;
&lt;p&gt;기존 VPN 솔루션에서 이것이 어려웠던 이유는 handshake 구조에 있습니다. 여러 RTT가 필요한 프로토콜은 인증이 완료되기 전에 응답을 보내야 하므로, 그 자체로 서버의 존재가 드러납니다.&lt;/p&gt;
&lt;p&gt;WireGuard는 첫 패킷만으로 인증을 수행해 이 문제를 해결하지만, 이 방식은 replay attack에 취약해지는 트레이드오프가 있습니다.&lt;/p&gt;
&lt;p&gt;WireGuard는 이 트레이드오프를 &lt;strong&gt;(1) replay attack 방지와 (2) DoS 완화 메커니즘&lt;/strong&gt;으로 보완합니다. 이 두 가지를 먼저 설명한 뒤, 핵심인 handshake 과정을 설명하겠습니다.&lt;/p&gt;
&lt;h3 id="replay-attack-%EB%B0%A9%EC%A7%80"&gt;Replay Attack 방지&lt;/h3&gt;
&lt;p&gt;Initiator는 첫 번째 메시지에 TAI64N timestamp을 암호화해 포함합니다. Responder는 peer마다 수신한 가장 높은 timestamp를 기록하고, 그 이하의 timestamp를 가진 메시지는 drop합니다.&lt;/p&gt;
&lt;p&gt;Responder가 재시작하여 timestamp 상태를 잃어도 큰 문제가 되지 않습니다. Replay attack의 목적은 진행 중인 secure session의 상태를 망치는 것인데, 재시작 직후에는 진행 중인 세션이 없기 때문입니다. 이후 정상적인 initiator가 attacker보다 최신 timestamp로 handshake를 시도하면 정상 세션이 수립되고, attacker의 replay는 오래된 timestamp로 인해 무효화됩니다.&lt;/p&gt;
&lt;h3 id="dos-%EC%99%84%ED%99%94"&gt;DoS 완화&lt;/h3&gt;
&lt;p&gt;WireGuard의 handshake는 Curve25519 point multiplication을 수행하는데, 이 연산은 CPU intensive합니다. 공격자가 handshake 요청을 대량으로 보내면 responder의 CPU를 소진시킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;WireGuard는 cookie 메커니즘으로 이를 완화합니다. Responder의 CPU 부하가 높으면, handshake를 진행하는 대신 initiator에게 암호화된 cookie를 보냅니다. Initiator는 이 cookie의 MAC을 handshake 메시지에 담아 재전송합니다.&lt;/p&gt;
&lt;p&gt;Responder는 비싼 DH 연산 없이 MAC만 검증하면 되므로 CPU 부담이 크게 줄어듭니다. MAC이 initiator의 source IP와 port에 바인딩되어 있어 rate limiting 적용도 용이합니다.&lt;/p&gt;
&lt;p&gt;WireGuard의 cookie 메커니즘이 기존 방식과 다른 점은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Silent until authenticated: 기존 cookie 메커니즘은 누구에게나 cookie를 응답하지만, WireGuard는 initiator가 responder의 public key로 계산한 MAC을 포함한 경우에만 cookie를 응답합니다. 따라서 스캐너에게 존재를 드러내지 않습니다.&lt;/li&gt;
&lt;li&gt;Cookie 암호화: cookie를 responder의 public key로 암호화해 전송하여 man-in-the-middle 공격으로부터 보호합니다.&lt;/li&gt;
&lt;li&gt;Binding 강화: cookie를 메시지에 bind하는 과정에 추가 정보를 포함하여, 공격자가 가짜 cookie로 정상 연결을 방해하는 것을 방지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="noise"&gt;Noise&lt;/h3&gt;
&lt;p&gt;WireGuard의 handshake는 직접 설계한 것이 아니라, &lt;a href="https://noiseprotocol.org/noise.pdf?ref=pangyoalto.com"&gt;Noise Protocol Framework&lt;/a&gt;라는 framework 위에 구축되었습니다.&lt;/p&gt;
&lt;p&gt;Noise를 이해하려면 먼저 static key와 ephemeral key의 구분을 알아야 합니다. &lt;/p&gt;
&lt;p&gt;Static key는 WireGuard 설치 시 한 번 생성하여 설정 파일에 저장하고, peer에게 public key를 미리 배포하는 장기 key pair입니다. 곧 해당 peer의 identity입니다. &lt;/p&gt;
&lt;p&gt;반면 ephemeral key는 handshake할 때마다 새로 생성하고 세션이 끝나면 즉시 삭제하는 일회용 key pair로, Forward Secrecy의 기반이 됩니다.&lt;/p&gt;
&lt;p&gt;WireGuard는 Noise의 IK 패턴을 사용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;K (Known): Responder의 static public key는 이미 알고 있습니다. WireGuard는 peer 설정 시 상대방의 public key를 미리 등록하므로(`wg set wg0 peer ABCDEF...`), 첫 메시지부터 Responder의 public key로 DH를 수행하여 encrypted channel을 열 수 있습니다.&lt;/li&gt;
&lt;li&gt;I (Immediate): Initiator의 static public key를 첫 메시지에서 즉시 전송합니다. Responder의 public key로 AEAD encrypt하여 포함시키므로, eavesdropper는 Initiator가 누구인지 알 수 없고, Responder의 private key가 있어야만 decrypt할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 IK 패턴이 실제 handshake 메시지에서 어떻게 나타나는지 살펴보겠습니다.&lt;/p&gt;
&lt;h3 id="handshake"&gt;Handshake&lt;/h3&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard4.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="1468" height="888" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard4.png 600w, https://pangyoalto.com/content/images/size/w1000/2026/03/wireguard4.png 1000w, https://pangyoalto.com/content/images/2026/03/wireguard4.png 1468w" sizes="(min-width: 720px) 720px"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;좌측은 일반적인 handshake 상황, 우측은 responder가 load가 커서 cookie reply를 보내는 상황입니다.&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard5.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="708" height="332" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard5.png 600w, https://pangyoalto.com/content/images/2026/03/wireguard5.png 708w"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;ul&gt;
&lt;li&gt;mac1, mac2: DoS 완화에서 설명한 MAC 필드입니다. initiator가 유효한 mac을 mac1 필드에 보낼 경우 responder가 cookie로 응답합니다.&lt;/li&gt;
&lt;li&gt;timestamp: replay attack 방지에서 설명한 필드입니다.&lt;/li&gt;
&lt;li&gt;Ii: 랜덤하게 생성된 값입니다. 이 메시지로 시작된 세션은 해당 값을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;나머지 필드들은 아래와 같이 생성됩니다.&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard6.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="780" height="634" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard6.png 600w, https://pangyoalto.com/content/images/2026/03/wireguard6.png 780w" sizes="(min-width: 720px) 720px"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Initiator는 랜덤한 값 Ii와 함께 ephemeral public key, 암호화된 static public key, 암호화된 타임스탬프를 보냅니다. Responder는 위 값들로 메시지의 나머지 필드들을 검증합니다.&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard7.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="724" height="288" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard7.png 600w, https://pangyoalto.com/content/images/2026/03/wireguard7.png 724w" sizes="(min-width: 720px) 720px"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;responder는 랜덤 값 Ir을 생성한 후 마찬가지로 ephemeral public key와 암호화된 페이로드를 보냅니다.&lt;/p&gt;
&lt;p&gt;그림에서 empty (0̂ bytes)는 0 + 16 bytes입니다. 16 bytes는 Poly1305 authentication tag입니다. AEAD에서 생성한 authentication tag를 보내는 것으로 initiator가 올바른 session key(Transport Data Keys)들을 계산했음을 증명합니다. Initiator는 이제 session keys를 symmetric key로 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;구체적인 필드 값 계산 식은 생략하겠습니다(논문에 나와 있습니다).&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://pangyoalto.com/content/images/2026/03/wireguard8.png" class="kg-image" alt="[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계" loading="lazy" width="716" height="238" srcset="https://pangyoalto.com/content/images/size/w600/2026/03/wireguard8.png 600w, https://pangyoalto.com/content/images/2026/03/wireguard8.png 716w"&gt;&lt;figcaption&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;wireguard whitepaper&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;handshake가 끝나면 transport keys를 구할 수 있습니다.이제 initiator와 responder는 encapsulated 패킷을 암호화하여 보냅니다(그림의 packet).&lt;/p&gt;
&lt;p&gt;각 transport 패킷은 counter를 가지는데, 이는 replay attack을 막기 위해서입니다.&lt;/p&gt;
&lt;h2 id="%EB%A7%88%EB%AC%B4%EB%A6%AC"&gt;마무리&lt;/h2&gt;
&lt;p&gt;사실 Wireguard를 처음 알아볼 때는 글로 소개할 정도로 깊이가 있을 거라고 생각하지 못했습니다. 하지만 제 예상과 달리 WireGuard의 설계 철학은 알면 알수록 인상깊었습니다. &lt;/p&gt;
&lt;p&gt;가장 눈에 띄는 것은 스캐너에 무응답하는 silence 특성입니다. 이를 위해 1-RTT 내에 인증을 완료해야 했고, 그 요구가 Noise IK 패턴, DH, AEAD 같은 기술 선택으로 자연스럽게 이어집니다. &lt;/p&gt;
&lt;p&gt;내부 네트워크에서 사용하는 source IP와 public 인터넷에 노출된 Internet endpoint를 분리한 CryptoKey Routing도 인상적입니다. 이 분리 덕분에 WiFi에서 모바일 네트워크로 전환해도 연결이 끊기지 않는 roaming이 별도의 메커니즘 없이 자연스럽게 동작합니다.&lt;/p&gt;
&lt;p&gt;기존 VPN들이 복잡해진 것은 기능을 추가하면서 레이어가 쌓였기 때문인데, WireGuard는 반대로 현대적인 암호화 primitive만 고정하고 나머지를 걷어냈습니다(코드베이스가 약 4000줄에 불과합니다). 저처럼 홈서버를 외부에 노출해야 하는 경우, silence 특성과 간결한 설정은 큰 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;논문에서는 key rotation 자동화, 리눅스 커널 구현, 성능 벤치마크 등도 다루고 있지만, 이 글에서는 분량상 생략했습니다. 관심이 있다면 논문 원문을 읽어보시길 권합니다.&lt;/p&gt;
&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.wireguard.com/?ref=pangyoalto.com"&gt;wireguard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wireguard.com/papers/wireguard.pdf?ref=pangyoalto.com"&gt;WireGuard: Next Generation Kernel Network Tunnel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://noiseprotocol.org/noise.pdf?ref=pangyoalto.com"&gt;noise 프로토콜&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
    <id>https://pangyoalto.com/wireguard-paper/</id>
    <link href="https://pangyoalto.com/wireguard-paper/"/>
    <summary type="html">&lt;p&gt;WireGuard&amp;#xB294; 2015&amp;#xB144;&amp;#xC5D0; &amp;#xCC98;&amp;#xC74C; &amp;#xBC1C;&amp;#xD45C;&amp;#xB41C; VPN &amp;#xD504;&amp;#xB85C;&amp;#xD1A0;&amp;#xCF5C;&amp;#xC785;&amp;#xB2C8;&amp;#xB2E4;. &amp;#xC774;&amp;#xC804;&amp;#xC5D0;&amp;#xB3C4; OpenVPN&amp;#xC774;&amp;#xB098; IPsec &amp;#xAC19;&amp;#xC740; VPN &amp;#xD504;&amp;#xB85C;&amp;#xD1A0;&amp;#xCF5C;&amp;#xC774; &amp;#xC874;&amp;#xC7AC;&amp;#xD588;&amp;#xC9C0;&amp;#xB9CC;, WireGuard&amp;#xB294; &amp;#xD604;&amp;#xB300;&amp;#xC801;&amp;#xC778; &amp;#xC554;&amp;#xD638;&amp;#xD654; &amp;#xAE30;&amp;#xBC95;&lt;/p&gt;</summary>
    <title>[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계</title>
    <updated>2026-03-08T11:22:56+00:00</updated>
    <dc:date>2026-03-08T11:22:56+00:00</dc:date>
  </entry>
  <dc:date>2026-03-10T00:00:24+00:00</dc:date>
</feed>
