<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>디버깅 on monkshark.dev</title><link>https://monkshark.github.io/tags/%EB%94%94%EB%B2%84%EA%B9%85/</link><description>Recent content in 디버깅 on monkshark.dev</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Mon, 04 May 2026 10:00:00 +0900</lastBuildDate><atom:link href="https://monkshark.github.io/tags/%EB%94%94%EB%B2%84%EA%B9%85/index.xml" rel="self" type="application/rss+xml"/><item><title>#2 - 파일 트리, 그리고 두 번의 미끄럼</title><link>https://monkshark.github.io/p/page-ide-file-tree/</link><pubDate>Mon, 04 May 2026 10:00:00 +0900</pubDate><guid>https://monkshark.github.io/p/page-ide-file-tree/</guid><description>&lt;p&gt;파일을 트리로 보고 싶었다. 첫 마일스톤의 PAGE 화면은 에디터 패널 하나가 전부였다. 단일 파일을 열어 두는 데는 충분하지만, 프로젝트를 훑으면서 옮겨다닐 수가 없다. 좌측에 파일 트리를 붙이는 게 다음 단계였다.&lt;/p&gt;
&lt;h2 id="모델은-한-줄"&gt;&lt;a href="#%eb%aa%a8%eb%8d%b8%ec%9d%80-%ed%95%9c-%ec%a4%84" class="header-anchor"&gt;&lt;/a&gt;모델은 한 줄
&lt;/h2&gt;&lt;p&gt;펼친 폴더의 집합 하나가 트리의 상태 전부다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;listTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expanded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;):&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;expanded&lt;/code&gt; 만 들고 있으면 그때그때 디스크를 다시 읽어 평탄화된 행 리스트를 돌려준다. UI 는 LazyColumn 에 그대로 붙이면 끝. 익스팬드/콜랩스 상태가 디스크와 분리돼 있어 테스트도 깔끔하다 — &lt;code&gt;@TempDir&lt;/code&gt; 하나로 7 케이스를 다 돌렸다.&lt;/p&gt;
&lt;p&gt;여기까지는 한 시간도 안 걸렸다. 미끄러진 건 UI 가 켜진 다음이었다.&lt;/p&gt;
&lt;h2 id="첫-번째-미끄럼-클릭-한-번이-edt-를-죽인다"&gt;&lt;a href="#%ec%b2%ab-%eb%b2%88%ec%a7%b8-%eb%af%b8%eb%81%84%eb%9f%bc-%ed%81%b4%eb%a6%ad-%ed%95%9c-%eb%b2%88%ec%9d%b4-edt-%eb%a5%bc-%ec%a3%bd%ec%9d%b8%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;첫 번째 미끄럼: 클릭 한 번이 EDT 를 죽인다
&lt;/h2&gt;&lt;p&gt;파일 트리에서 PNG 를 한 번 잘못 클릭했다. 그 뒤로는 폴더를 눌러도 아무 반응이 없다. 콘솔을 보니:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;AWT-EventQueue-0&amp;#34;&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MalformedInputException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ne"&gt;Input&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3362&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileDocument&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileDocument&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;파일을 UTF-8 로 디코드하려다 실패. 그 예외가 클릭 핸들러에서 그대로 위로 던져졌고, AWT EventQueue 에서 잡힌 뒤 EDT 가 굳었다. 그래서 그 다음 클릭들도 전부 무반응.&lt;/p&gt;
&lt;p&gt;수정은 단순했다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;loadOrNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;호출 측에서 null 이면 그냥 아무 것도 안 한다. 바이너리 파일을 누르면 조용히 무시. 의도대로다.&lt;/p&gt;
&lt;p&gt;다만 진짜 교훈은 코드에 있지 않았다. &lt;strong&gt;EDT 에서 한 번 던진 예외는 단발 사고가 아니다.&lt;/strong&gt; 그 다음 모든 입력이 같이 죽는다. UI 코드에서 IO 를 얼마나 무방비하게 호출하고 있었는지 그제서야 보였다. 클릭 핸들러는 가벼운 컴포지션 같지만, 그 안에서 호출되는 모든 함수가 EDT 의 신뢰를 건드린다.&lt;/p&gt;
&lt;h2 id="두-번째-미끄럼-path-가-iterable-이다"&gt;&lt;a href="#%eb%91%90-%eb%b2%88%ec%a7%b8-%eb%af%b8%eb%81%84%eb%9f%bc-path-%ea%b0%80-iterable-%ec%9d%b4%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;두 번째 미끄럼: Path 가 Iterable 이다
&lt;/h2&gt;&lt;p&gt;UTF-8 을 막고 다시 띄웠다. 이번엔 폴더 클릭 자체가 작동을 안 한다. 첫 클릭은 펼쳐지는데, 두 번째 클릭부터 접히질 않는다.&lt;/p&gt;
&lt;p&gt;토글 함수는 한 줄이었다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;toggleExpanded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;expanded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;expanded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;expanded&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;expanded&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;println&lt;/code&gt; 을 박았다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[tree] toggle ...\.idea before=1 after=6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[tree] toggle ...\.idea before=6 after=6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[tree] toggle ...\.idea before=6 after=6
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;첫 클릭에 set size 가 1 에서 6 으로 뛰었다. 다섯 개나 들어갔다. 어디서 다섯이?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:\Users\manne\Desktop\hansol_hs_java_app\.idea&lt;/code&gt; 의 path 컴포넌트가 정확히 다섯이다. &lt;code&gt;Users&lt;/code&gt;, &lt;code&gt;manne&lt;/code&gt;, &lt;code&gt;Desktop&lt;/code&gt;, &lt;code&gt;hansol_hs_java_app&lt;/code&gt;, &lt;code&gt;.idea&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java.nio.file.Path&lt;/code&gt; 는 &lt;code&gt;Iterable&amp;lt;Path&amp;gt;&lt;/code&gt; 를 구현한다. 이름 컴포넌트들을 순회하는 인터페이스다. 그래서 &lt;code&gt;Set&amp;lt;Path&amp;gt;.plus(Path)&lt;/code&gt; 가 element 오버로드 (&lt;code&gt;plus(T)&lt;/code&gt;) 가 아니라 iterable 오버로드 (&lt;code&gt;plus(Iterable&amp;lt;T&amp;gt;)&lt;/code&gt;) 로 결합됐다. 두 오버로드가 모두 적용 가능할 때 Kotlin 이 어느 쪽을 우선하는지에 대해서는 stdlib 의 &lt;code&gt;@HidesMembers&lt;/code&gt; 와 overload resolution 규칙을 더 파봐야 정확히 답할 수 있겠다. 다만 동작 결과는 분명하다 — 한 번의 토글이 path 의 모든 name 컴포넌트를 set 에 풀어 넣는다.&lt;/p&gt;
&lt;p&gt;두 번째 클릭부터는 더 황당했다. &lt;code&gt;p in before&lt;/code&gt; 는 false 를 돌려준다 — 풀어넣은 건 name 조각들이고, p 는 전체 경로이기 때문이다. 그래서 또 else 분기를 타고 &lt;code&gt;before + p&lt;/code&gt; 를 호출. 이미 set 에 들어 있는 name 조각들이라 dedupe 돼서 size 는 그대로 6. 무한 no-op.&lt;/p&gt;
&lt;p&gt;수정도 한 줄이었다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;expanded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;expanded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;expanded&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;expanded&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;인자를 명시적인 set 으로 감싸면 element 오버로드와의 충돌 자체가 사라진다. set 두 개의 union 또는 difference 로만 결합된다.&lt;/p&gt;
&lt;p&gt;이건 Kotlin 의 함정으로만 묶기에는 좀 더 보편적이다. 어떤 타입이 우연히도 &lt;code&gt;Iterable&amp;lt;자기자신&amp;gt;&lt;/code&gt; 을 구현할 때, 그 타입을 컬렉션 연산자에 넣으면 의미가 무너진다. Java 에서도 똑같은 패턴이 있다. 상속이 의미를 침범하는 사례다.&lt;/p&gt;
&lt;h2 id="사이드바-리사이즈는-의외로-짧았다"&gt;&lt;a href="#%ec%82%ac%ec%9d%b4%eb%93%9c%eb%b0%94-%eb%a6%ac%ec%82%ac%ec%9d%b4%ec%a6%88%eb%8a%94-%ec%9d%98%ec%99%b8%eb%a1%9c-%ec%a7%a7%ec%95%98%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;사이드바 리사이즈는 의외로 짧았다
&lt;/h2&gt;&lt;p&gt;사이드바와 에디터 사이 1dp 구분선을 잡고 끌면 폭이 바뀐다. Compose Desktop 에서는 이게 거의 코드 한 블록.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ResizeHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onDeltaDp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="py"&gt;density&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDensity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Modifier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointerHoverIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PointerIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPredefinedCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;E_RESIZE_CURSOR&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointerInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;detectHorizontalDragGestures&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;onDeltaDp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;density&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toDp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* 1dp 라인 */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;delta 만 위로 올리고, 호출 측에서 &lt;code&gt;(현재 + delta).coerceIn(160.dp, 600.dp)&lt;/code&gt; 로 클램프. AWT 커서 상수가 그대로 먹는 것도 좋았다 — Compose Desktop 의 &lt;code&gt;PointerIcon(java.awt.Cursor)&lt;/code&gt; 오버로드가 둘을 이어 준다.&lt;/p&gt;
&lt;h2 id="회고"&gt;&lt;a href="#%ed%9a%8c%ea%b3%a0" class="header-anchor"&gt;&lt;/a&gt;회고
&lt;/h2&gt;&lt;p&gt;파일 트리는 &amp;ldquo;보이는 작은 기능&amp;quot;의 전형이다. UI 만 갈아끼우면 끝일 것 같은. 두 번 미끄러지고서야 보이는 것들이 있었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클릭 핸들러는 EDT 와 직결돼 있다. 그 안에서 IO 예외를 그대로 위로 던지면 EDT 자체가 굳는다. UI 의 모든 상호작용이 같이 무너진다.&lt;/li&gt;
&lt;li&gt;표준 라이브러리에서도 &amp;ldquo;원소처럼 보이지만 실은 컬렉션이기도 한&amp;rdquo; 타입은 흔하다. &lt;code&gt;Path&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt; (의 Char 시퀀스), 다양한 트리 노드들. 컬렉션 연산자에 인자를 넣을 땐 그 타입의 인터페이스를 한 번 더 의심해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이번 라운드의 코드 변경량은 462 라인 추가, 34 라인 삭제. 그 중 절반 이상이 트리 모델/패널이고, 결정적인 두 줄은 &lt;code&gt;loadOrNull&lt;/code&gt; 과 &lt;code&gt;setOf(p)&lt;/code&gt; 였다.&lt;/p&gt;</description></item></channel></rss>