<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>개발수양록</title>
    <link>https://blog.benelog.net/</link>
    <atom:link href="https://blog.benelog.net//feed.xml" rel="self" type="application/rss+xml" />
    <description>S/W 개발에 관한 정상혁의 글 모음</description>
    <language>ko</language>
    <pubDate>Sun, 12 Apr 2026 09:03:08 +0000</pubDate>
    <lastBuildDate>Sun, 12 Apr 2026 09:03:08 +0000</lastBuildDate>

    <item>
      <title>Linux 바이너리 역어셈블로 커널 모듈 패치 확인하기</title>
      <link>https://blog.benelog.net//linux-binary-disassembly.html</link>
      <pubDate>Sun, 12 Apr 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">linux-binary-disassembly.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#역어셈블의_기본_개념&quot;&gt;1. 역어셈블의 기본 개념&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#elf과_ko&quot;&gt;1.1. ELF과 .ko&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#핵심_도구&quot;&gt;1.2. 핵심 도구&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#x86_64_호출_규약_system_v_amd64_abi&quot;&gt;1.3. x86-64 호출 규약 (System V AMD64 ABI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#자주_보이는_x86_64_명령어&quot;&gt;1.4. 자주 보이는 x86-64 명령어&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#상수로_호출_지점_찾기&quot;&gt;1.5. 상수로 호출 지점 찾기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#활용_예시_커널_모듈_바이너리_비교&quot;&gt;2. 활용 예시: 커널 모듈 바이너리 비교&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#문제_상황&quot;&gt;2.1. 문제 상황&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#왜_github_소스_비교_대신_역어셈블을_선택했나&quot;&gt;2.2. 왜 GitHub 소스 비교 대신 역어셈블을 선택했나&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#모듈_추출과_역어셈블&quot;&gt;2.3. 모듈 추출과 역어셈블&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#핵심_차이_찾기&quot;&gt;2.4. 핵심 차이 찾기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#재배치_정보로_call_대상_확인&quot;&gt;2.5. 재배치 정보로 call 대상 확인&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#주의할_점&quot;&gt;3. 주의할 점&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#참고_자료&quot;&gt;4. 참고 자료&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;소스 코드를 구할 수 없는 Linux 바이너리를 분석해야 할 때가 있습니다. 이 글에서는 &lt;code&gt;objdump&lt;/code&gt; 기반의 역어셈블 기법을 먼저 정리하고, 활용 예시로 Ubuntu 패키지의 &lt;code&gt;intel_cvs.ko&lt;/code&gt; 커널 모듈과 DKMS 패치 버전을 비교한 사례의 핵심을 설명합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;역어셈블의_기본_개념&quot;&gt;1. 역어셈블의 기본 개념&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;elf과_ko&quot;&gt;1.1. ELF과 .ko&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Linux의 실행 파일과 공유 라이브러리, 커널 모듈은 모두 ELF(Executable and Linkable Format) 포맷입니다. 특히 커널 모듈(&lt;code&gt;.ko&lt;/code&gt;)은 &lt;strong&gt;relocatable object&lt;/strong&gt; 파일로, 커널에 로드되는 시점에 주소가 확정됩니다. 따라서 역어셈블에서 &lt;code&gt;call&lt;/code&gt; 대상이 `0x0000`으로 보이는 것은 비정상이 아니라 재배치가 아직 적용되지 않았기 때문입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ELF는 섹션 단위로 구성됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.text&lt;/code&gt; — 코드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.rodata&lt;/code&gt; — 읽기 전용 데이터, 문자열 리터럴&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.data&lt;/code&gt; — 전역 변수&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.symtab&lt;/code&gt; — 심볼 테이블&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;최근 Ubuntu OEM 커널은 모듈을 zstd로 압축하므로 `zstd -d`로 먼저 해제해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;핵심_도구&quot;&gt;1.2. 핵심 도구&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 75%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;도구&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;objdump&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;ELF 역어셈블. `-d -M intel`이 가장 많이 쓰는 조합&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;nm&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;심볼 테이블 조회. 함수 주소와 이름 확인&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;readelf&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;ELF 헤더, 섹션, 재배치 정보 확인&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;strings&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;바이너리 안의 문자열 추출. 힌트 얻기 용도&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;modinfo&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;커널 모듈 메타데이터&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;pahole&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;DWARF 디버그 정보에서 구조체 레이아웃 추출&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;x86_64_호출_규약_system_v_amd64_abi&quot;&gt;1.3. x86-64 호출 규약 (System V AMD64 ABI)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;역어셈블을 읽기 위해 가장 먼저 알아야 하는 것은 호출 규약입니다. Linux는 System V AMD64 ABI를 따르며, 함수 인자는 정해진 순서의 레지스터로 전달됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 60%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;인자 순서&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;레지스터&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;1번째&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;rdi&lt;/code&gt; / &lt;code&gt;edi&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;2번째&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;rsi&lt;/code&gt; / &lt;code&gt;esi&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;3번째&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;rdx&lt;/code&gt; / &lt;code&gt;edx&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;4번째&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;rcx&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;5번째&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;r8&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;6번째&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;r9&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;반환값&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;rax&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들어 다음 시퀀스는 &lt;code&gt;foo(0x805, NULL, 0)&lt;/code&gt; 호출과 정확히 대응됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;asm&quot;&gt;xor    edx, edx            ; 3번째 인자: 0
xor    esi, esi            ; 2번째 인자: NULL
mov    edi, 0x805          ; 1번째 인자: 0x805
call   foo&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;자주_보이는_x86_64_명령어&quot;&gt;1.4. 자주 보이는 x86-64 명령어&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 75%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;명령어&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;mov&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;값 복사. &lt;code&gt;mov edi, 0x805&lt;/code&gt; → &lt;code&gt;edi = 0x805&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;movzx&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;작은 값을 0 확장하여 복사. 1바이트를 32비트로 읽을 때&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;lea&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;주소 계산만 수행 (메모리 접근 없음). 포인터 전달 용도&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;test reg, reg&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;같은 레지스터로 AND. 0인지 검사&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;cmp&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;뺄셈 후 플래그만 설정&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;je&lt;/code&gt; / &lt;code&gt;jne&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;조건 점프 (equal / not equal)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;xor reg, reg&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;레지스터를 0으로 초기화 (`mov reg, 0`보다 짧음)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;call&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;함수 호출&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;상수로_호출_지점_찾기&quot;&gt;1.5. 상수로 호출 지점 찾기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;소스에서 &lt;code&gt;#define&lt;/code&gt; 값을 알고 있으면 역어셈블에서 해당 상수를 검색해 호출 지점을 좁힐 수 있습니다. 다만 같은 값이 다른 맥락(예: 길이, 인덱스)에서 쓰일 수 있으므로, 그 상수가 &lt;strong&gt;어느 레지스터에 들어가는지&lt;/strong&gt;와 주변 &lt;code&gt;call&lt;/code&gt; 대상을 함께 확인해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;활용_예시_커널_모듈_바이너리_비교&quot;&gt;2. 활용 예시: 커널 모듈 바이너리 비교&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;문제_상황&quot;&gt;2.1. 문제 상황&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ubuntu의 &lt;code&gt;linux-modules-vision-*-oem&lt;/code&gt; 패키지에 포함된 `intel_cvs.ko`가 Intel 업스트림의 특정 버그 픽스를 반영하고 있는지 확인해야 했습니다. 패키지 changelog에는 어느 커밋까지 포함되었는지 명시되어 있지 않고, DKMS로 설치한 패치 버전과 어떻게 다른지를 확인해야 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;왜_github_소스_비교_대신_역어셈블을_선택했나&quot;&gt;2.2. 왜 GitHub 소스 비교 대신 역어셈블을 선택했나&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Intel의 업스트림 저장소가 GitHub에 공개되어 있으므로 소스 레벨 `diff`가 더 쉬운 선택처럼 보입니다. 그런데도 역어셈블을 택한 이유는 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;어떤 커밋에서 빌드되었는지 모른다.&lt;/strong&gt; Ubuntu OEM 패키지의 changelog에는 Intel 업스트림의 어떤 시점 스냅샷인지가 명시되어 있지 않았습니다. 임의의 커밋에서 찍은 소스와 비교하면 &quot;차이&quot;가 진짜 패치 누락 때문인지 단순한 버전 갭 때문인지 구분되지 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu가 자체 패치를 얹었을 수 있다.&lt;/strong&gt; 패키지 빌드는 업스트림 tarball에 distro 패치를 덧붙이는 경우가 흔합니다. 업스트림 소스만으로는 실제로 빌드된 결과를 재현할 수 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;바이너리가 ground truth다.&lt;/strong&gt; 커널이 실제로 로드하는 것은 &lt;code&gt;.ko&lt;/code&gt; 바이너리입니다. &quot;이 시스템에서 지금 돌고 있는 코드에 해당 가드가 있는가&quot;라는 질문의 답은 바이너리 안에만 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;확인하려는 범위가 작다.&lt;/strong&gt; 특정 한 가지 패치(특정 상수 호출 앞의 분기 유무)만 확인하면 충분했습니다. 소스 트리 전체를 받아 빌드 환경을 맞추는 수고보다 &lt;code&gt;objdump&lt;/code&gt; 한 번이 더 빠른 상황이었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;즉, 소스 비교는 &quot;업스트림에 그 패치가 존재하는가&quot;에 답하지만, 이번에 필요한 답은 &quot;내 시스템에 설치된 바이너리에 그 패치가 반영되어 있는가&quot;였고, 이 질문에는 바이너리를 직접 보는 것이 가장 확실한 방법이었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;모듈_추출과_역어셈블&quot;&gt;2.3. 모듈 추출과 역어셈블&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;# 압축 해제
zstd -d -c /lib/modules/$(uname -r)/ubuntu/vision/intel_cvs.ko.zst &amp;gt; /tmp/pkg.ko
zstd -d -c /lib/modules/$(uname -r)/updates/dkms/intel_cvs.ko.zst &amp;gt; /tmp/dkms.ko

# 동일성 확인
md5sum /tmp/pkg.ko /tmp/dkms.ko

# 관심 함수만 Intel 문법으로 역어셈블
objdump -d -M intel --no-show-raw-insn /tmp/pkg.ko \
  | sed -n &apos;/&amp;lt;cvs_common_probe&amp;gt;:/,/^$/p&apos; &amp;gt; /tmp/pkg_probe.asm

objdump -d -M intel --no-show-raw-insn /tmp/dkms.ko \
  | sed -n &apos;/&amp;lt;cvs_common_probe&amp;gt;:/,/^$/p&apos; &amp;gt; /tmp/dkms_probe.asm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;-M intel`은 `mov dst, src&lt;/code&gt; 순서의 Intel 문법을 사용합니다 (기본은 `mov src, dst`의 AT&amp;amp;T 문법). `--no-show-raw-insn`은 기계어 바이트를 생략해 가독성을 높입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;핵심_차이_찾기&quot;&gt;2.4. 핵심 차이 찾기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;확인하고 싶은 패치는 아래 형태의 가드였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;c&quot;&gt;if (icvs-&amp;gt;magic_num_support) {
    ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;SET_HOST_IDENTIFIER = 0x0805`는 첫 번째 인자이므로 `mov edi, 0x805&lt;/code&gt; 패턴으로 역어셈블에서 검색할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;DKMS 버전 (가드 있음)&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;asm&quot;&gt;movzx  eax, BYTE PTR [r12+0x4c]  ; icvs-&amp;gt;magic_num_support 로드
and    ebx, 0x1
test   bl, bl                    ; 0인지 검사
je     12c7                      ; 0이면 호출 건너뜀
xor    edx, edx                  ; len = 0
xor    esi, esi                  ; buf = NULL
mov    edi, 0x805                ; SET_HOST_IDENTIFIER
call   cvs_write_i2c&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;패키지 버전 (가드 없음)&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;asm&quot;&gt;xor    edx, edx
xor    esi, esi
mov    edi, 0x805
call   cvs_write_i2c             ; 무조건 실행됨&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;mov edi, 0x805&lt;/code&gt; 바로 앞에 &lt;code&gt;test&lt;/code&gt; + `je`로 된 분기가 있느냐 없느냐가 두 바이너리의 실질적 차이였습니다. 즉 패키지 버전에는 해당 패치가 반영되어 있지 않다는 결론이 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;재배치_정보로_call_대상_확인&quot;&gt;2.5. 재배치 정보로 call 대상 확인&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;.ko`의 `call&lt;/code&gt; 대상이 &lt;code&gt;0x0000`이거나 어색한 주소로 보일 때는 `-r&lt;/code&gt; 옵션으로 재배치 정보를 함께 출력하면 원래 타깃 심볼을 알 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;objdump -d -r -M intel /tmp/pkg.ko&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;127f: call   1284 &amp;lt;cvs_common_probe+0x104&amp;gt;
      1280: R_X86_64_PLT32  cvs_write_i2c-0x4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;주의할_점&quot;&gt;3. 주의할 점&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;컴파일러 최적화&lt;/strong&gt;로 같은 소스여도 명령어 순서와 레지스터 선택이 달라집니다. 정확히 같은 시퀀스가 아니라 &lt;strong&gt;논리적 흐름(분기 구조)&lt;/strong&gt; 을 비교해야 합니다. DKMS 빌드와 패키지 빌드의 GCC 버전이 같으면 차이가 적고, 다르면 코드 구조 자체가 꽤 달라 보일 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;재배치 주소 차이는 무시&lt;/strong&gt;해야 합니다. &lt;code&gt;diff`를 돌리면 `je 1553&lt;/code&gt; vs `je 12c7`처럼 점프 대상 주소가 대량으로 다르게 나오는데, 이것은 함수 배치 차이일 뿐이고 주목해야 할 것은 명령어 자체의 추가/삭제입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;상수 검색의 false positive&lt;/strong&gt;: &lt;code&gt;mov edi, 0x805`가 반드시 원하는 호출은 아닐 수 있습니다. 어떤 함수 안에 있는지, 직후의 `call&lt;/code&gt; 대상이 무엇인지로 맥락을 확인해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 당연한 이야기지만, 소스를 구할 수 있다면 역어셈블보다 `diff`가 훨씬 빠르고 정확합니다. 역어셈블은 소스를 확보할 수 없을 때의 최후 수단입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고_자료&quot;&gt;4. 참고 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.egr.unlv.edu/~ed/x86.html&quot;&gt;x86-64 Assembly Language Programming with Ubuntu&lt;/a&gt; — x86-64 어셈블리 입문 교재 (무료 PDF)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.felixcloutier.com/x86/&quot;&gt;Intel x86-64 Instruction Set Reference&lt;/a&gt; — Intel 공식 매뉴얼 웹 버전&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.osdev.org/System_V_ABI&quot;&gt;System V ABI - OSDev Wiki&lt;/a&gt; — 호출 규약 요약&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sourceware.org/binutils/docs/binutils/&quot;&gt;GNU Binutils Documentation&lt;/a&gt; — &lt;code&gt;objdump&lt;/code&gt;, &lt;code&gt;nm&lt;/code&gt;, &lt;code&gt;readelf&lt;/code&gt; 공식 문서&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://refspecs.linuxfoundation.org/elf/elf.pdf&quot;&gt;ELF Format 공식 스펙&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ghidra-sre.org/&quot;&gt;Ghidra&lt;/a&gt; — NSA가 공개한 역공학 도구. `objdump`보다 훨씬 강력한 디컴파일과 크로스 레퍼런스 제공&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://godbolt.org/&quot;&gt;Compiler Explorer (Godbolt)&lt;/a&gt; — C 코드가 어떤 어셈블리로 컴파일되는지 실시간 확인&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 포스트는 Claude Code가 정상혁의 지시에 따라서 작성했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Claude Code의 Prompt Cache 버그 (v2.1.69~v2.1.89)</title>
      <link>https://blog.benelog.net//claude-code-cache-bug.html</link>
      <pubDate>Fri, 3 Apr 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">claude-code-cache-bug.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#버그_요약&quot;&gt;1. 버그 요약&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#원인_resume_시_메시지_구조_불일치&quot;&gt;2. 원인: Resume 시 메시지 구조 불일치&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#근본_원인&quot;&gt;2.1. 근본 원인&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#mcp_서버_deferred_tools와의_관계&quot;&gt;2.2. MCP 서버, deferred tools와의 관계&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#부수적_문제_매_턴_tool_schema_재직렬화&quot;&gt;2.3. 부수적 문제: 매 턴 tool schema 재직렬화&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#영향_범위&quot;&gt;3. 영향 범위&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#비용_영향&quot;&gt;4. 비용 영향&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#수정_내용_v2_1_90&quot;&gt;5. 수정 내용 (v2.1.90)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#영향_받았는지_확인하는_방법&quot;&gt;6. 영향 받았는지 확인하는 방법&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#resume_사용_여부_확인&quot;&gt;6.1. Resume 사용 여부 확인&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#api_사용량_확인&quot;&gt;6.2. API 사용량 확인&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#캐시_메트릭_모니터링&quot;&gt;6.3. 캐시 메트릭 모니터링&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#참고_자료&quot;&gt;7. 참고 자료&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sidebarblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;title&quot;&gt;변경 이력&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2026-04-06: Prompt Cache TTL 기본값(5분)과 1시간 옵션에 대한 설명 추가&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2026년 3월 초부터 약 한 달간 Claude Code에 Prompt Cache가 제대로 작동하지 않는 버그가 있었습니다.
&lt;a href=&quot;claude-code-cost.html&quot;&gt;Claude Code 토큰 비용과 프롬프트 캐싱&lt;/a&gt;에서 정리했듯이 Prompt Caching은 입력 토큰 비용을 90%까지 절약하는 핵심 수단인데, 이 캐시가 작동하지 않으면 비용이 크게 증가합니다.
이 글에서는 해당 기간에 발생한 캐시 버그의 원인과 영향을 정리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;버그_요약&quot;&gt;1. 버그 요약&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 75%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;항목&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;영향 버전&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;v2.1.69 (2026-03-05) ~ v2.1.89&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;수정 버전&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;v2.1.90 (2026-04-01)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;근본 원인&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;v2.1.69에서 도입된 &lt;code&gt;deferred_tools_delta`로 인해 resume 시 `messages[0]&lt;/code&gt; 구조가 새 세션과 달라짐&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;영향 대상&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;--resume&lt;/code&gt; 또는 `/resume`으로 세션을 재개하는 사용자&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;GitHub Issue&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/34629&quot;&gt;#34629&lt;/a&gt;, &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/28899&quot;&gt;#28899&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;원인_resume_시_메시지_구조_불일치&quot;&gt;2. 원인: Resume 시 메시지 구조 불일치&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;근본_원인&quot;&gt;2.1. 근본 원인&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;v2.1.69에서 도입된 &lt;code&gt;deferred_tools_delta&lt;/code&gt; 기능이 원인입니다.
이 기능은 deferred tools 목록, MCP 서버 설정, skills 정보를 메시지에 첨부하는 방식을 변경했는데, 새 세션과 재개 세션에서 `messages[0]`의 구조가 달라지는 문제가 생겼습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;세션 유형&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;messages[0] 내용&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;크기&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;새 세션&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;deferred tools 목록, MCP 설정, skills, 날짜 컨텍스트&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;~13.4KB&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;재개 세션&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;날짜 컨텍스트만 포함 (나머지는 messages 끝에 추가)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;~352B&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Prompt Cache는 prefix의 해시값으로 캐시를 조회합니다.
`messages[0]`의 내용이 13.4KB에서 352B로 바뀌면 해시값이 완전히 달라지므로, 이전 세션에서 쌓은 캐시를 전혀 재사용할 수 없었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;mcp_서버_deferred_tools와의_관계&quot;&gt;2.2. MCP 서버, deferred tools와의 관계&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 버그는 MCP 서버나 deferred tools를 사용하는 환경에서 더 큰 영향을 받았습니다.
다만 이것은 MCP 서버의 tool schema가 불안정해서가 아니라, deferred tools/MCP 설정/skills 정보가 resume 시 `messages[0]`이 아닌 다른 위치에 배치되어 prefix가 달라지기 때문입니다.
MCP 서버가 매번 완전히 동일한 schema를 반환하더라도, resume만 하면 메시지 구조가 달라져서 캐시 미스가 발생했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;v2.1.90의 수정 내용도 이를 뒷받침합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fixed &lt;code&gt;--resume&lt;/code&gt; causing a full prompt-cache miss on the first request for users with deferred tools, MCP servers, or custom agents&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;부수적_문제_매_턴_tool_schema_재직렬화&quot;&gt;2.3. 부수적 문제: 매 턴 tool schema 재직렬화&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;근본 원인과 별개로, Claude Code가 매 턴마다 MCP tool schema를 `JSON.stringify`로 재계산하여 캐시 키를 만드는 비효율도 있었습니다.
이것은 캐시 미스를 유발하는 직접적인 원인은 아니었지만 성능 오버헤드를 발생시켰고, v2.1.90에서 tool schema를 한 번만 계산하고 재사용하도록 개선되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;영향_범위&quot;&gt;3. 영향 범위&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 버그는 &lt;code&gt;--resume&lt;/code&gt; (CLI 플래그)과 &lt;code&gt;/resume&lt;/code&gt; (슬래시 커맨드) 모두에 해당합니다.
두 방식 모두 세션 재개 시 동일한 메시지 구조 불일치가 발생하기 때문입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;단, Prompt Cache의 TTL이 기본 5분 &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt; 이므로 실질적인 비용 손해가 발생하는 경우는 제한적입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 66.6667%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;시나리오&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;영향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;일반 연속 세션 (resume 없이)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;영향 없음. `messages[0]`이 세션 내내 안정적으로 유지&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;5분 이내에 resume&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시가 살아있어야 하는데 prefix 불일치로 캐시 미스 발생 → &lt;strong&gt;비용 손해&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;5분 이후에 resume&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;어차피 TTL 만료로 캐시 소멸 → 버그와 무관하게 비용 동일&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;--print --resume&lt;/code&gt; 자동화&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;매 호출이 새 프로세스이므로 캐시를 전혀 재활용하지 못함 → &lt;strong&gt;가장 큰 피해&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;비용_영향&quot;&gt;4. 비용 영향&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정상 상태에서는 캐시가 쌓이면서 턴이 진행될수록 메시지당 비용이 감소합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;정상 (v2.1.68):  메시지 1 → $0.15,  메시지 4 → $0.02 (캐시 적중률 증가)
버그 (v2.1.69+): 모든 메시지 → $0.35~0.40 (매번 전체 히스토리를 Cache Write)&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;--print --resume&lt;/code&gt; 워크플로우에서는 &lt;code&gt;cache_read&lt;/code&gt; 토큰이 ~11,000(시스템 프롬프트 분량)에 머물고, &lt;code&gt;cache_creation&lt;/code&gt; 토큰이 300,000 이상으로 치솟는 현상이 관찰되었습니다.
메시지당 약 &lt;strong&gt;20배의 비용 증가&lt;/strong&gt;가 보고되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;수정_내용_v2_1_90&quot;&gt;5. 수정 내용 (v2.1.90)&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;v2.1.90 (2026-04-01)에서 다음과 같이 수정되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;메시지 구조 정규화&lt;/strong&gt;: 세션 재개 시 `messages[0]`에 deferred tools, MCP 설정, skills를 새 세션과 동일한 위치에 삽입하도록 변경&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;tool schema 재직렬화 제거&lt;/strong&gt;: MCP tool schema를 한 번만 계산하고 캐시하여, 매 턴 `JSON.stringify`를 호출하지 않도록 성능 개선&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재 버전 확인:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;claude --version&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;v2.1.90 이상이면 수정된 상태입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;영향_받았는지_확인하는_방법&quot;&gt;6. 영향 받았는지 확인하는 방법&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2026년 3월 5일~4월 1일 사이에 Claude Code를 사용했다면, 다음 방법으로 영향 여부를 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;resume_사용_여부_확인&quot;&gt;6.1. Resume 사용 여부 확인&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 버그는 resume 시에만 발생하므로, 해당 기간에 resume을 사용했는지가 핵심입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;resume_슬래시_커맨드_확인&quot;&gt;6.1.1. /resume (슬래시 커맨드) 확인&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대화형 세션 안에서 `/resume`을 입력한 기록은 `~/.claude/history.jsonl`에 남습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Claude Code 프롬프트&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;~/.claude/history.jsonl에서 2026년 3월 5일~4월 1일 사이의 /resume 기록을 찾아줘.
각 resume 시점에 대해, 같은 프로젝트의 ~/.claude/projects/ 아래 JSONL 세션 로그에서
직전 세션의 마지막 timestamp와의 시간 간격을 계산해줘.
5분 이내에 resume한 경우가 있는지 확인하고 싶어.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;resume_cli_플래그_확인&quot;&gt;6.1.2. --resume (CLI 플래그) 확인&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;`claude --resume`처럼 CLI 인자로 실행한 경우는 `history.jsonl`에 기록되지 않습니다.
이 경우는 shell history에서 확인합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;bash 사용자&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;grep &quot;claude.*--resume&quot; ~/.bash_history&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;zsh 사용자&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;grep &quot;claude.*--resume&quot; ~/.zsh_history&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;bash는 기본적으로 명령 실행 시각을 기록하지 않으므로 &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;, &lt;code&gt;--resume&lt;/code&gt; 사용 여부만 확인할 수 있고 5분 간격 분석은 어렵습니다.
zsh는 &lt;code&gt;EXTENDED_HISTORY&lt;/code&gt; 옵션이 켜져 있으면 `~/.zsh_history`에 타임스탬프가 함께 기록됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;--resume`을 주로 자동화 스크립트(&lt;/code&gt;--print --resume`)에서 사용했다면 해당 스크립트의 실행 로그를 확인하는 것이 더 정확합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;5분 이내에 resume한 기록이 없다면, Prompt Cache의 TTL(5분)이 만료된 후 재개한 것이므로 이 버그로 인한 추가 비용은 발생하지 않은 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;api_사용량_확인&quot;&gt;6.2. API 사용량 확인&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anthropic Console에서 해당 기간의 토큰 사용량을 확인합니다.
평소 대비 `cache_creation_input_tokens`가 비정상적으로 높고 `cache_read_input_tokens`가 낮다면 이 버그의 영향을 받았을 가능성이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;캐시_메트릭_모니터링&quot;&gt;6.3. 캐시 메트릭 모니터링&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;세션 중 API 응답의 캐시 관련 토큰을 확인합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cache_creation_input_tokens&lt;/code&gt;: 대화가 진행될수록 &lt;strong&gt;감소&lt;/strong&gt;해야 정상&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cache_read_input_tokens&lt;/code&gt;: 대화가 진행될수록 &lt;strong&gt;증가&lt;/strong&gt;해야 정상&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;버그 발생 시에는 `cache_read_input_tokens`가 낮은 값에 머물고, `cache_creation_input_tokens`가 계속 높게 유지됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고_자료&quot;&gt;7. 참고 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/34629&quot;&gt;Prompt cache regression in --print --resume since v2.1.69 - GitHub Issue #34629&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/28899&quot;&gt;Prompt caching not working - GitHub Issue #28899&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://piunikaweb.com/2026/03/31/claude-cache-bugs-tokens-20x-more-anthropic-investigating/&quot;&gt;Anthropic Looking Into Cache Bugs - PiunikaWeb&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Anthropic API는 기본 5분(&lt;code&gt;ephemeral&lt;/code&gt;)과 1시간(&lt;code&gt;&quot;ttl&quot;: &quot;1h&quot;&lt;/code&gt;) 두 가지 TTL 옵션을 제공합니다. 1시간 옵션은 cache write 비용이 기본 입력 토큰의 2배(5분은 1.25배)로 더 높습니다. Claude Code에서는 구독 플랜에 따라 자동 결정되며(Max 플랜: 1시간, Pro 플랜/API 키: 5분), 사용자가 직접 설정할 수는 없습니다. 자세한 내용은 &lt;a href=&quot;https://platform.claude.com/docs/en/build-with-claude/prompt-caching&quot;&gt;Anthropic 공식 문서&lt;/a&gt;를 참고하세요.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. &lt;code&gt;HISTTIMEFORMAT`을 설정하면 `history&lt;/code&gt; 명령에서 시각이 표시되지만, &lt;code&gt;~/.bash_history&lt;/code&gt; 파일 자체에는 타임스탬프가 저장되지 않습니다.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Claude Code 비용 최적화를 위한 지표 수집</title>
      <link>https://blog.benelog.net//claude-code-optimize.html</link>
      <pubDate>Wed, 1 Apr 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">claude-code-optimize.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#수집해야_할_지표&quot;&gt;1. 수집해야 할 지표&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#컨텍스트_관리_효율성&quot;&gt;1.1. 컨텍스트 관리 효율성&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#도구_사용_패턴&quot;&gt;1.2. 도구 사용 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#세션_수명_주기&quot;&gt;1.3. 세션 수명 주기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#api_호출과_비용&quot;&gt;1.4. API 호출과 비용&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#지표별_수집_방법&quot;&gt;2. 지표별 수집 방법&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#hook으로_수집하는_지표&quot;&gt;2.1. Hook으로 수집하는 지표&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#opentelemetry로_수집하는_지표&quot;&gt;2.2. OpenTelemetry로 수집하는 지표&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#수집_방법_요약&quot;&gt;2.3. 수집 방법 요약&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#개인_분석_로컬_로그로_셀프_리포트_만들기&quot;&gt;3. 개인 분석: 로컬 로그로 셀프 리포트 만들기&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#분석_스크립트_예시&quot;&gt;3.1. 분석 스크립트 예시&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#수집_데이터_저장소&quot;&gt;4. 수집 데이터 저장소&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#저장소_구성&quot;&gt;4.1. 저장소 구성&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#저장소_선택지&quot;&gt;4.2. 저장소 선택지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#hook_데이터_중앙_수집&quot;&gt;4.3. Hook 데이터 중앙 수집&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#권고_가이드_생성_로직&quot;&gt;5. 권고 가이드 생성 로직&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#컨텍스트_관리_권고&quot;&gt;5.1. 컨텍스트 관리 권고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#도구_사용_권고&quot;&gt;5.2. 도구 사용 권고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#모델_선택_권고&quot;&gt;5.3. 모델 선택 권고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#비용_효율_권고&quot;&gt;5.4. 비용 효율 권고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#리포트_예시&quot;&gt;5.5. 리포트 예시&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#주의할_점&quot;&gt;6. 주의할 점&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#참고_자료&quot;&gt;7. 참고 자료&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code를 효율적으로 사용하고 있는지 개인적으로나 팀 차원에서 자주 돌아보고 개선 시도를 한다면, 같은 작업을 더 적은 비용으로 할 수 있습니다.
이 글에서는 Claude Code의 Hook과 OpenTelemetry를 활용하여 사용 지표를 수집하고, 수집된 데이터로 개인별 개선 권고 리포트를 만드는 방법을 정리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;수집해야_할_지표&quot;&gt;1. 수집해야 할 지표&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;비용 최적화 관점에서 의미 있는 지표는 크게 네 영역으로 나뉩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;컨텍스트_관리_효율성&quot;&gt;1.1. 컨텍스트 관리 효율성&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;컨텍스트가 가득 차서 자동 압축(autocompact)이 발생하면, 그만큼 한 세션에서 많은 토큰을 소비했다는 신호입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 60%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;지표&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 시작 유형 (&lt;code&gt;source&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;startup&lt;/code&gt;(신규), &lt;code&gt;resume&lt;/code&gt;(재개), &lt;code&gt;compact&lt;/code&gt;(압축 후), &lt;code&gt;clear&lt;/code&gt;(&lt;code&gt;/clear&lt;/code&gt; 후) 비율로 사용 습관을 파악&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;자동 압축 빈도&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션당 autocompact 횟수가 많으면 작업 단위를 더 작게 분리할 필요가 있음&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;압축 간격&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;압축 사이 시간이 짧을수록 컨텍스트를 빠르게 소진하고 있다는 의미&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;도구_사용_패턴&quot;&gt;1.2. 도구 사용 패턴&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code는 파일 읽기, 편집, 명령 실행 등 도구를 호출할 때마다 API 호출이 발생합니다.
도구 사용 방식에 따라 같은 작업의 토큰 소비량이 크게 달라집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 60%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;지표&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;도구별 호출 빈도&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;어떤 도구를 주로 쓰는지 파악. &lt;code&gt;Bash`로 `cat&lt;/code&gt;, &lt;code&gt;grep`을 반복 호출하면 전용 도구(`Read&lt;/code&gt;, &lt;code&gt;Grep&lt;/code&gt;)보다 토큰을 더 소비&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;도구 호출 실패율&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;실패 후 재시도는 토큰 낭비. 반복 실패 패턴은 사용자 교육이 필요한 영역&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;서브에이전트 생성 빈도&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;Agent&lt;/code&gt; 도구로 서브에이전트를 생성하면 추가 API 호출이 발생. 불필요한 서브에이전트 생성은 비용 증가 요인&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;세션_수명_주기&quot;&gt;1.3. 세션 수명 주기&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 60%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;지표&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 길이 (시작~종료 시간)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션이 길수록 컨텍스트가 누적되어 턴당 입력 토큰이 증가 &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 종료 사유&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;정상 종료, 비정상 종료, rate limit 등의 비율&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;api_호출과_비용&quot;&gt;1.4. API 호출과 비용&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;가장 직접적인 비용 지표입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 60%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;지표&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;API 호출 횟수&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션당, 프롬프트당 API 호출 수&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;호출당 토큰 수&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;입력/출력/캐시 읽기/캐시 쓰기 토큰&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;호출당 비용 (USD)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;모델별 단가 적용된 실제 비용&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;모델별 사용 비율&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Opus vs Sonnet vs Haiku. Opus는 Sonnet 대비 5배 비용&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시 히트율&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시 읽기 토큰 비율이 높을수록 비용 효율적&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;지표별_수집_방법&quot;&gt;2. 지표별 수집 방법&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;수집 방법은 크게 두 가지입니다. &lt;strong&gt;Hook&lt;/strong&gt;은 사용 행동 패턴을, &lt;strong&gt;OpenTelemetry&lt;/strong&gt;는 정확한 비용 데이터를 수집합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;hook으로_수집하는_지표&quot;&gt;2.1. Hook으로 수집하는 지표&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code의 &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/hooks&quot;&gt;Hook&lt;/a&gt;은 특정 이벤트 발생 시 셸 스크립트를 실행하는 기능입니다. 비용 데이터에는 직접 접근할 수 없지만, 사용 행동 패턴을 기록하는 데 적합합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Hook 이벤트&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;수집 가능한 지표&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;SessionStart&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 시작 유형(&lt;code&gt;source&lt;/code&gt;), 사용 모델(&lt;code&gt;model&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;source&lt;/code&gt; 값: &lt;code&gt;startup&lt;/code&gt;, &lt;code&gt;resume&lt;/code&gt;, &lt;code&gt;compact&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;SessionEnd&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 종료 시각, 종료 사유&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 시작 시각과 조합하면 세션 길이 산출&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;PreCompact&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;압축 발생 시각, 자동/수동 여부&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;`matcher: auto`로 자동 압축만 필터링 가능&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;PreToolUse&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;도구 이름, 호출 시각&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;고빈도 이벤트이므로 성능 영향 고려 필요&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;PostToolUseFailure&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;실패한 도구, 에러 내용&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;반복 실패 패턴 분석용&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;SubagentStart&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;서브에이전트 생성 시각&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;불필요한 서브에이전트 생성 패턴 분석&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;수집_스크립트_예시&quot;&gt;2.1.1. 수집 스크립트 예시&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;하나의 스크립트에서 이벤트 유형별로 분기하여 JSONL 형식으로 기록합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;~/.claude/scripts/log-usage.sh&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;#!/bin/bash
INPUT=$(cat)
EVENT=$(echo &quot;$INPUT&quot; | jq -r &apos;.hook_event_name&apos;)
SESSION=$(echo &quot;$INPUT&quot; | jq -r &apos;.session_id&apos;)
TIMESTAMP=$(date -u &apos;+%Y-%m-%dT%H:%M:%SZ&apos;)
USER=$(whoami)

LOG_DIR=&quot;$HOME/.claude/usage-logs&quot;
mkdir -p &quot;$LOG_DIR&quot;

case &quot;$EVENT&quot; in
  SessionStart)
    SOURCE=$(echo &quot;$INPUT&quot; | jq -r &apos;.source&apos;)
    MODEL=$(echo &quot;$INPUT&quot; | jq -r &apos;.model&apos;)
    echo &quot;{\&quot;ts\&quot;:\&quot;$TIMESTAMP\&quot;,\&quot;user\&quot;:\&quot;$USER\&quot;,\&quot;event\&quot;:\&quot;session_start\&quot;,\&quot;session\&quot;:\&quot;$SESSION\&quot;,\&quot;source\&quot;:\&quot;$SOURCE\&quot;,\&quot;model\&quot;:\&quot;$MODEL\&quot;}&quot; \
      &amp;gt;&amp;gt; &quot;$LOG_DIR/events.jsonl&quot;
    ;;
  SessionEnd)
    echo &quot;{\&quot;ts\&quot;:\&quot;$TIMESTAMP\&quot;,\&quot;user\&quot;:\&quot;$USER\&quot;,\&quot;event\&quot;:\&quot;session_end\&quot;,\&quot;session\&quot;:\&quot;$SESSION\&quot;}&quot; \
      &amp;gt;&amp;gt; &quot;$LOG_DIR/events.jsonl&quot;
    ;;
  PreCompact)
    echo &quot;{\&quot;ts\&quot;:\&quot;$TIMESTAMP\&quot;,\&quot;user\&quot;:\&quot;$USER\&quot;,\&quot;event\&quot;:\&quot;compact\&quot;,\&quot;session\&quot;:\&quot;$SESSION\&quot;}&quot; \
      &amp;gt;&amp;gt; &quot;$LOG_DIR/events.jsonl&quot;
    ;;
  PreToolUse)
    TOOL=$(echo &quot;$INPUT&quot; | jq -r &apos;.tool_name&apos;)
    echo &quot;{\&quot;ts\&quot;:\&quot;$TIMESTAMP\&quot;,\&quot;user\&quot;:\&quot;$USER\&quot;,\&quot;event\&quot;:\&quot;tool_use\&quot;,\&quot;session\&quot;:\&quot;$SESSION\&quot;,\&quot;tool\&quot;:\&quot;$TOOL\&quot;}&quot; \
      &amp;gt;&amp;gt; &quot;$LOG_DIR/events.jsonl&quot;
    ;;
  PostToolUseFailure)
    TOOL=$(echo &quot;$INPUT&quot; | jq -r &apos;.tool_name&apos;)
    echo &quot;{\&quot;ts\&quot;:\&quot;$TIMESTAMP\&quot;,\&quot;user\&quot;:\&quot;$USER\&quot;,\&quot;event\&quot;:\&quot;tool_fail\&quot;,\&quot;session\&quot;:\&quot;$SESSION\&quot;,\&quot;tool\&quot;:\&quot;$TOOL\&quot;}&quot; \
      &amp;gt;&amp;gt; &quot;$LOG_DIR/events.jsonl&quot;
    ;;
  SubagentStart)
    echo &quot;{\&quot;ts\&quot;:\&quot;$TIMESTAMP\&quot;,\&quot;user\&quot;:\&quot;$USER\&quot;,\&quot;event\&quot;:\&quot;subagent\&quot;,\&quot;session\&quot;:\&quot;$SESSION\&quot;}&quot; \
      &amp;gt;&amp;gt; &quot;$LOG_DIR/events.jsonl&quot;
    ;;
esac&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;settings_json_설정&quot;&gt;2.1.2. settings.json 설정&lt;/h4&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;~/.claude/settings.json&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;{
  &quot;hooks&quot;: {
    &quot;SessionStart&quot;: [
      { &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;~/.claude/scripts/log-usage.sh&quot; }] }
    ],
    &quot;SessionEnd&quot;: [
      { &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;~/.claude/scripts/log-usage.sh&quot; }] }
    ],
    &quot;PreCompact&quot;: [
      { &quot;matcher&quot;: &quot;auto&quot;,
        &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;~/.claude/scripts/log-usage.sh&quot; }] }
    ],
    &quot;PreToolUse&quot;: [
      { &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;~/.claude/scripts/log-usage.sh&quot; }] }
    ],
    &quot;PostToolUseFailure&quot;: [
      { &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;~/.claude/scripts/log-usage.sh&quot; }] }
    ],
    &quot;SubagentStart&quot;: [
      { &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;~/.claude/scripts/log-usage.sh&quot; }] }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;opentelemetry로_수집하는_지표&quot;&gt;2.2. OpenTelemetry로 수집하는 지표&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hook에서는 API 호출 횟수, 토큰 수, 비용 같은 핵심 비용 데이터에 접근할 수 없습니다.
이 데이터는 Claude Code의 OpenTelemetry 연동을 통해 수집할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code는 &lt;code&gt;claude_code.api_request&lt;/code&gt; 이벤트를 OTLP로 내보냅니다. 이 이벤트에는 다음 필드가 포함됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 66.6667%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;필드&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;model&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;사용된 모델 (예: &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;cost_usd&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;해당 API 호출의 비용 (USD)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;input_tokens&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;입력 토큰 수&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;output_tokens&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;출력 토큰 수&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;cache_read_tokens&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시에서 읽은 토큰 수&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;cache_creation_tokens&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시에 새로 쓴 토큰 수&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;duration_ms&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;API 응답 시간&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;event.sequence&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 내 API 호출 순번 (순서 보장용)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;prompt.id&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;사용자 프롬프트 단위 UUID (한 프롬프트에서 여러 API 호출이 발생할 수 있음)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;활성화_방법&quot;&gt;2.2.1. 활성화 방법&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;환경 변수를 설정하면 Claude Code가 OTLP 프로토콜로 텔레메트리를 전송합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개발자 PC의 셸 프로파일(&lt;code&gt;~/.bashrc&lt;/code&gt;, &lt;code&gt;~/.zshrc&lt;/code&gt; 등)에 추가하거나, 전사 dotfiles 관리 도구로 배포할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;수집_방법_요약&quot;&gt;2.3. 수집 방법 요약&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;지표&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Hook&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;OpenTelemetry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 시작/종료, 압축 빈도&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;도구 사용 패턴, 실패율&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;API 호출 횟수&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;토큰 수 (입력/출력/캐시)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;호출당 비용 (USD)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;모델별 사용 비율&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;개인_분석_로컬_로그로_셀프_리포트_만들기&quot;&gt;3. 개인 분석: 로컬 로그로 셀프 리포트 만들기&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전사 수집 인프라가 없더라도, 앞서 설명한 Hook 스크립트로 로컬에 쌓인 &lt;code&gt;~/.claude/usage-logs/events.jsonl&lt;/code&gt; 파일만으로 개인 사용 패턴을 분석할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;분석_스크립트_예시&quot;&gt;3.1. 분석 스크립트 예시&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;`jq`와 셸 명령만으로 주요 지표를 산출하는 스크립트입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;~/.claude/scripts/self-report.sh&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;#!/bin/bash
LOG=&quot;$HOME/.claude/usage-logs/events.jsonl&quot;

if [ ! -f &quot;$LOG&quot; ]; then
  echo &quot;로그 파일이 없습니다: $LOG&quot;
  exit 1
fi

# 분석 기간 (기본: 최근 7일)
SINCE=$(date -u -d &apos;7 days ago&apos; &apos;+%Y-%m-%dT00:00:00Z&apos; 2&amp;gt;/dev/null \
     || date -u -v-7d &apos;+%Y-%m-%dT00:00:00Z&apos;)  # macOS 호환
DATA=$(jq -c --arg since &quot;$SINCE&quot; &apos;select(.ts &amp;gt;= $since)&apos; &quot;$LOG&quot;)

echo &quot;=== 셀프 사용 리포트 (최근 7일) ===&quot;
echo &quot;&quot;

# 세션 수
SESSIONS=$(echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;session_start&quot;) | .session&apos; | sort -u | wc -l)
echo &quot;[세션 요약]&quot;
echo &quot;  세션 수: $SESSIONS&quot;

# 세션 시작 유형 비율
echo &quot;  시작 유형:&quot;
echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;session_start&quot;) | .source&apos; \
  | sort | uniq -c | sort -rn | while read count source; do
    echo &quot;    $source: $count&quot;
  done

# autocompact 횟수
COMPACTS=$(echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;compact&quot;)&apos; | wc -l)
echo &quot;  자동 압축 횟수: $COMPACTS (세션당 평균: $(echo &quot;scale=1; $COMPACTS / $SESSIONS&quot; | bc 2&amp;gt;/dev/null || echo &quot;N/A&quot;))&quot;
echo &quot;&quot;

# 도구 사용 패턴
echo &quot;[도구 사용]&quot;
TOTAL_TOOLS=$(echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;tool_use&quot;)&apos; | wc -l)
echo &quot;  총 도구 호출: $TOTAL_TOOLS&quot;
echo &quot;  상위 도구:&quot;
echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;tool_use&quot;) | .tool&apos; \
  | sort | uniq -c | sort -rn | head -10 | while read count tool; do
    pct=$(echo &quot;scale=0; $count * 100 / $TOTAL_TOOLS&quot; | bc 2&amp;gt;/dev/null || echo &quot;?&quot;)
    echo &quot;    $tool: $count (${pct}%)&quot;
  done

# Bash로 cat/grep/find 호출 비율 (도구 이름만으로는 Bash 내부 명령을 알 수 없으므로 Bash 비율만 표시)
BASH_COUNT=$(echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;tool_use&quot; and .tool==&quot;Bash&quot;)&apos; | wc -l)
if [ &quot;$TOTAL_TOOLS&quot; -gt 0 ]; then
  BASH_PCT=$(echo &quot;scale=0; $BASH_COUNT * 100 / $TOTAL_TOOLS&quot; | bc 2&amp;gt;/dev/null || echo &quot;?&quot;)
  echo &quot;  Bash 도구 비율: ${BASH_PCT}% (30% 이상이면 전용 도구 활용 권장)&quot;
fi

# 실패율
FAILS=$(echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;tool_fail&quot;)&apos; | wc -l)
if [ &quot;$TOTAL_TOOLS&quot; -gt 0 ]; then
  FAIL_PCT=$(echo &quot;scale=1; $FAILS * 100 / $TOTAL_TOOLS&quot; | bc 2&amp;gt;/dev/null || echo &quot;?&quot;)
  echo &quot;  도구 호출 실패: $FAILS (실패율: ${FAIL_PCT}%)&quot;
fi

# 서브에이전트
SUBAGENTS=$(echo &quot;$DATA&quot; | jq -r &apos;select(.event==&quot;subagent&quot;)&apos; | wc -l)
echo &quot;  서브에이전트 생성: $SUBAGENTS (세션당 평균: $(echo &quot;scale=1; $SUBAGENTS / $SESSIONS&quot; | bc 2&amp;gt;/dev/null || echo &quot;N/A&quot;))&quot;
echo &quot;&quot;

# 간단한 권고
echo &quot;[자동 권고]&quot;
COMPACT_AVG=$(echo &quot;scale=1; $COMPACTS / $SESSIONS&quot; | bc 2&amp;gt;/dev/null || echo &quot;0&quot;)
if [ &quot;$(echo &quot;$COMPACT_AVG &amp;gt;= 3&quot; | bc 2&amp;gt;/dev/null)&quot; = &quot;1&quot; ]; then
  echo &quot;  ⚠ 세션당 autocompact가 ${COMPACT_AVG}회입니다. 작업을 더 작은 단위로 분리하세요.&quot;
fi
if [ &quot;$(echo &quot;$BASH_PCT &amp;gt;= 30&quot; | bc 2&amp;gt;/dev/null)&quot; = &quot;1&quot; ]; then
  echo &quot;  ⚠ Bash 도구 비율이 ${BASH_PCT}%입니다. Read, Grep, Glob 전용 도구를 활용하세요.&quot;
fi
if [ &quot;$(echo &quot;$FAIL_PCT &amp;gt; 15&quot; | bc 2&amp;gt;/dev/null)&quot; = &quot;1&quot; ]; then
  echo &quot;  ⚠ 도구 호출 실패율이 ${FAIL_PCT}%입니다. 반복 실패하는 도구의 사용법을 점검하세요.&quot;
fi
SUBAGENT_AVG=$(echo &quot;scale=1; $SUBAGENTS / $SESSIONS&quot; | bc 2&amp;gt;/dev/null || echo &quot;0&quot;)
if [ &quot;$(echo &quot;$SUBAGENT_AVG &amp;gt; 5&quot; | bc 2&amp;gt;/dev/null)&quot; = &quot;1&quot; ]; then
  echo &quot;  ⚠ 세션당 서브에이전트가 평균 ${SUBAGENT_AVG}회입니다. 간단한 검색은 직접 도구를 호출하세요.&quot;
fi
echo &quot;  (비용 관련 권고는 OpenTelemetry 데이터가 필요합니다)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스크립트 실행 결과 예시:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;=== 셀프 사용 리포트 (최근 7일) ===

[세션 요약]
  세션 수: 15
  시작 유형:
    startup: 10
    resume: 3
    compact: 2
  자동 압축 횟수: 5 (세션당 평균: 0.3)

[도구 사용]
  총 도구 호출: 312
  상위 도구:
    Read: 89 (28%)
    Edit: 67 (21%)
    Bash: 58 (18%)
    Grep: 45 (14%)
    Glob: 32 (10%)
    Write: 12 (3%)
    Agent: 9 (2%)
  Bash 도구 비율: 18% (30% 이상이면 전용 도구 활용 권장)
  도구 호출 실패: 8 (실패율: 2.5%)
  서브에이전트 생성: 9 (세션당 평균: 0.6)

[자동 권고]
  (비용 관련 권고는 OpenTelemetry 데이터가 필요합니다)&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 스크립트는 별도의 인프라 없이도 개인이 즉시 실행하여 자신의 사용 습관을 점검할 수 있습니다. 팀 평균과의 비교가 필요 없는 개인 수준의 개선에는 이것만으로 충분합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;수집_데이터_저장소&quot;&gt;4. 수집 데이터 저장소&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다수 사용자의 데이터를 분석하려면 중앙 저장소가 필요합니다. 조직 규모와 기존 인프라에 따라 선택지가 달라집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;저장소_구성&quot;&gt;4.1. 저장소 구성&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;개발자 PC                         중앙 수집                    분석/리포트
┌──────────────┐                ┌─────────────────┐         ┌──────────────┐
│ Claude Code  │                │                 │         │              │
│  ├ Hook      │── JSONL 전송 ─▶│  저장소          │────────▶│  분석 파이프  │
│  └ OTel      │── OTLP ──────▶│                 │         │  라인        │
└──────────────┘                └─────────────────┘         └──────────────┘&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;저장소_선택지&quot;&gt;4.2. 저장소 선택지&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 16.6666%;&quot;&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 16.6668%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;방식&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;장점&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;단점&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;적합한 규모&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;파일 기반 (JSONL + S3)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;구축 비용 최소, 별도 인프라 불필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;실시간 쿼리 어려움, 분석 시 별도 로딩 필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;소규모 (10명 이하)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;OpenTelemetry Collector + Clickhouse&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;OTLP 네이티브 수집, 대량 시계열 데이터에 강점&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Clickhouse 운영 부담&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;중규모 (10~100명)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Grafana + Loki/Tempo&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;기존 모니터링 인프라 활용 가능, 대시보드 내장&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;학습 곡선, 커스텀 리포트 생성이 번거로움&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;모니터링 인프라가 이미 있는 조직&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;BigQuery / Athena&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;SQL로 자유로운 분석, 대규모 데이터 처리&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;클라우드 비용, 초기 파이프라인 구축&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;대규모 (100명 이상)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;hook_데이터_중앙_수집&quot;&gt;4.3. Hook 데이터 중앙 수집&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hook은 기본적으로 개발자 PC의 로컬 파일에 기록합니다. 중앙으로 모으려면 추가 처리가 필요합니다. 몇 가지 방법이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cron + rsync/scp&lt;/strong&gt;: 주기적으로 로컬 JSONL을 중앙 서버나 S3로 전송&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hook 스크립트에서 직접 전송&lt;/strong&gt;: `curl`로 수집 API에 POST (단, Hook 실행 시간이 세션 성능에 영향을 줄 수 있으므로 백그라운드 전송 권장)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fluentd/Fluent Bit&lt;/strong&gt;: 로컬 JSONL 파일을 tail하여 중앙 저장소로 전송&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OpenTelemetry 데이터는 OTLP 엔드포인트를 중앙 Collector로 지정하면 별도 처리 없이 수집됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;권고_가이드_생성_로직&quot;&gt;5. 권고 가이드 생성 로직&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;수집된 데이터를 분석하여 개인별 개선 권고를 생성하는 로직입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;컨텍스트_관리_권고&quot;&gt;5.1. 컨텍스트 관리 권고&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3334%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;조건&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;판정&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;권고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션당 autocompact &amp;gt;= 3회&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;컨텍스트 과다 사용&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;작업을 더 작은 단위로 분리하세요. 한 세션에서 하나의 목적만 달성하는 것이 비용 효율적입니다.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;compact 간격 &amp;lt; 10분&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;빠른 컨텍스트 소진&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;프롬프트를 더 간결하게 작성하거나, 불필요한 파일 읽기를 줄여보세요.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;source=resume&lt;/code&gt; 비율 &amp;lt; 10%&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션 재개를 활용하지 않음&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;이전 세션을 재개(&lt;code&gt;claude --resume&lt;/code&gt;)하면 컨텍스트를 처음부터 다시 구성하지 않아도 됩니다.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;도구_사용_권고&quot;&gt;5.2. 도구 사용 권고&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3334%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;조건&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;판정&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;권고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Bash로 &lt;code&gt;cat&lt;/code&gt;/&lt;code&gt;grep&lt;/code&gt;/&lt;code&gt;find&lt;/code&gt; 호출 비율 &amp;gt; 30%&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;전용 도구 미활용&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;Bash 대신 Read, Grep, Glob 등 전용 도구를 사용하면 토큰을 절약할 수 있습니다.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;도구 호출 실패율 &amp;gt; 15%&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;비효율적 도구 사용&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;도구 호출 실패가 잦습니다. 에러 메시지를 확인하고 사용법을 점검해보세요.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;세션당 서브에이전트 &amp;gt; 5회&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;서브에이전트 남용 가능성&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;서브에이전트 생성을 줄이고, 간단한 검색은 직접 도구를 호출하는 것이 비용 효율적입니다.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;모델_선택_권고&quot;&gt;5.3. 모델 선택 권고&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3334%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;조건&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;판정&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;권고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Opus 사용 비율 &amp;gt; 50%&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;고비용 모델 과다 사용&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;단순 편집이나 검색 작업에는 Sonnet을 사용하세요. Opus는 Sonnet 대비 5배 비용입니다.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;서브에이전트에서 Opus 사용&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;서브에이전트 비용 과다&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;서브에이전트는 Sonnet이나 Haiku로 충분한 경우가 많습니다.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;비용_효율_권고&quot;&gt;5.4. 비용 효율 권고&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 항목은 OpenTelemetry 데이터가 있어야 산출 가능합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 33.3334%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;조건&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;판정&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;권고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시 히트율 &amp;lt; 50%&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시 활용 부족&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;세션 중간에 모델을 전환하거나 CLAUDE.md를 수정하면 캐시가 무효화됩니다. 한 세션에서는 하나의 모델을 유지하세요.&quot; &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;프롬프트당 API 호출 &amp;gt; 20회&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;한 프롬프트에 과도한 작업 요청&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;하나의 프롬프트에 여러 작업을 한꺼번에 요청하면 API 호출이 급증합니다. 작업을 나누어 요청하세요.&quot;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;일일 비용이 팀 평균의 2배 이상&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;비용 이상치&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;개별 상담 대상. 사용 패턴 상세 분석 필요&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;리포트_예시&quot;&gt;5.5. 리포트 예시&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 로직을 조합하면 다음과 같은 개인별 주간 리포트를 생성할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;=== 주간 사용 리포트 (2026-03-24 ~ 2026-03-30) ===
사용자: hong

[요약]
  세션 수: 23
  총 API 호출: 847
  총 비용: $42.30
  주요 모델: Sonnet 68%, Opus 32%

[개선 권고]
  1. 컨텍스트 관리
     - 세션당 평균 autocompact: 3.2회 (팀 평균: 1.1회)
     → 작업을 더 작은 단위로 분리하세요.

  2. 도구 사용
     - Bash로 grep/cat 호출: 전체 도구 사용의 41%
     → Read, Grep 전용 도구를 직접 사용하면 토큰을 절약할 수 있습니다.

  3. 모델 선택
     - Opus 사용 비율: 32% (팀 평균: 15%)
     → 단순 작업에서는 Sonnet으로 전환을 권장합니다.
     → 예상 절감: 주당 ~$8.50

[잘하고 있는 점]
  - 캐시 히트율 87% (팀 평균: 72%)
  - 도구 호출 실패율 3% (팀 평균: 8%)&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;주의할_점&quot;&gt;6. 주의할 점&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PreToolUse는 고빈도 이벤트&lt;/strong&gt;입니다. Hook 스크립트의 실행 시간이 길면 세션 응답 속도에 영향을 줄 수 있습니다. 스크립트를 가볍게 유지하거나, 샘플링을 적용하거나, 백그라운드로 기록하는 방식을 고려해야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;전사 배포가 필요&lt;/strong&gt;합니다. Hook 설정과 환경 변수를 각 개발자 PC에 배포해야 합니다. Enterprise managed config, dotfiles 저장소, 또는 온보딩 스크립트를 활용할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hook으로는 정확한 토큰 수와 비용을 알 수 없습니다&lt;/strong&gt;. 비용 분석이 목적이라면 OpenTelemetry는 필수입니다. Hook만으로는 사용 행동 패턴 분석에 한정됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;개인정보 고려&lt;/strong&gt;가 필요합니다. 프롬프트 내용이나 코드를 수집하는 것이 아니라 메타데이터(이벤트 유형, 도구 이름, 타임스탬프)만 수집하지만, 수집 목적과 범위를 사전에 공유하고 동의를 구하는 것이 바람직합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고_자료&quot;&gt;7. 참고 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/hooks&quot;&gt;Claude Code Hooks - Anthropic Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/monitoring-usage&quot;&gt;Monitoring Usage - Anthropic Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://opentelemetry.io/docs/collector/&quot;&gt;OpenTelemetry Collector&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. 턴당 입력 토큰의 이차 증가에 대해서는 &lt;a href=&quot;claude-code-cost.html&quot;&gt;Claude Code 토큰 비용과 프롬프트 캐싱&lt;/a&gt;을 참고
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. Prompt Caching의 상세 원리는 &lt;a href=&quot;claude-code-cost.html&quot;&gt;Claude Code 토큰 비용과 프롬프트 캐싱&lt;/a&gt;을 참고
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Claude Code Hook으로 quota 전환 자동화하기</title>
      <link>https://blog.benelog.net//claude-code-quota-switch.html</link>
      <pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">claude-code-quota-switch.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#배경&quot;&gt;배경&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#전체_구성&quot;&gt;전체 구성&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#필요_환경&quot;&gt;필요 환경&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#원리_quota_소진_시_claude_code의_동작&quot;&gt;원리: Quota 소진 시 Claude Code의 동작&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#실행_흐름&quot;&gt;실행 흐름&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#구현과_테스트_프롬프트&quot;&gt;구현과 테스트 프롬프트&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#구현_프롬프트&quot;&gt;구현 프롬프트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#테스트_프롬프트&quot;&gt;테스트 프롬프트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#사용_방법&quot;&gt;사용 방법&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#1단계_quota_a_소진&quot;&gt;1단계: quota A 소진&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2단계_quota_b로_전환&quot;&gt;2단계: quota B로 전환&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3단계_자동_복귀&quot;&gt;3단계: 자동 복귀&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#생성된_코드_분석&quot;&gt;생성된 코드 분석&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#quota_소진_감지_및_초기화_시간_자동_기록&quot;&gt;Quota 소진 감지 및 초기화 시간 자동 기록&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#초기화_시간_수동_기록_fallback&quot;&gt;초기화 시간 수동 기록 (fallback)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#자동_복귀&quot;&gt;자동 복귀&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#settings_json_hook_설정&quot;&gt;settings.json hook 설정&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#한계&quot;&gt;한계&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#macos&quot;&gt;macOS에서의 차이점&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;배경&quot;&gt;배경&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code를 여러 quota로 사용하는 경우가 있습니다. 예를 들면 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Max plan을 2개를 구독해서 번갈아 가면서 쓰는 경우&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;같은 이메일 계정에 여러 organization이 연결되어 각각 별도의 quota가 있는 경우&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Team plan과 Enterprise plan을 동시에 구독하는 경우&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 상황에서 한쪽 quota가 소진되면 다른 쪽으로 전환해서 쓸 수 있습니다. 문제는 quota A가 소진되어 quota B로 전환한 뒤, quota A가 초기화되는 시점에 다시 돌아오는 걸 잊기 쉽다는 것입니다. Claude Code의 &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/hooks&quot;&gt;Hook&lt;/a&gt; 기능을 활용하면 이 전환을 자동화할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 글은 Claude Code에게 프롬프트를 내려서 hook 스크립트를 생성하고, 생성된 코드를 분석하는 방식으로 구성됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;전체_구성&quot;&gt;전체 구성&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;필요_환경&quot;&gt;필요 환경&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Claude Code v2.1.78(2026년 3월 18일 릴리즈) 이상 (&lt;code&gt;StopFailure&lt;/code&gt; hook 이벤트 지원)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; 설치 필요&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;원리_quota_소진_시_claude_code의_동작&quot;&gt;원리: Quota 소진 시 Claude Code의 동작&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;사용자에게_보여주는_메시지&quot;&gt;사용자에게 보여주는 메시지&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code에서는 API quota를 소진 시 초기화 예정 시간을 포함한 메시지를 안내합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;5시간 한도 소진 시&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;You&apos;ve hit your limit · resets 2pm (Asia/Seoul)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;주간 한도 소진 시에는 날짜도 포함됩니다:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;주간 한도 소진 시&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;You&apos;ve hit your limit · resets Feb 20, 5pm (Asia/Seoul)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;메시지 형식은 Claude Code 버전에 따라 달라져 왔습니다. &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/5977&quot;&gt;과거&lt;/a&gt;에는 &lt;code&gt;Claude usage limit reached. Your limit will reset at 2pm (America/New_York)&lt;/code&gt; 형식이었고, &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/6457&quot;&gt;중간&lt;/a&gt;에 &lt;code&gt;5-hour limit reached · resets 12pm&lt;/code&gt; 을 거쳐, &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/24428&quot;&gt;현재&lt;/a&gt;의 &lt;code&gt;You&amp;#8217;ve hit your limit · resets &amp;#8230;&amp;#8203;&lt;/code&gt; 형식이 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;stopfailure_hook_이벤트&quot;&gt;StopFailure hook 이벤트&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Quota 소진 시 발생하는 hook 이벤트는 &lt;code&gt;StopFailure&lt;/code&gt; 입니다 (2026년 3월 18일 릴리즈된 v2.1.78에서 도입). &lt;code&gt;Notification&lt;/code&gt; 이나 &lt;code&gt;Stop&lt;/code&gt; 은 quota 소진 시 발생하지 않습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;&lt;code&gt;StopFailure&lt;/code&gt; 이벤트의 JSON 입력 (실제 기록된 데이터)&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;{
  &quot;session_id&quot;: &quot;5cbd3730-...&quot;,
  &quot;transcript_path&quot;: &quot;/home/user/.claude/projects/.../abc123.jsonl&quot;,
  &quot;cwd&quot;: &quot;/home/user/project&quot;,
  &quot;hook_event_name&quot;: &quot;StopFailure&quot;,
  &quot;error&quot;: &quot;rate_limit&quot;,
  &quot;last_assistant_message&quot;: &quot;You&apos;ve hit your limit · resets 11pm (Asia/Seoul)&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;초기화 시간 전용 필드는 없지만 (&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/36056&quot;&gt;#36056&lt;/a&gt;, &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/30784&quot;&gt;#30784&lt;/a&gt;에서 기능 요청 중), &lt;code&gt;last_assistant_message&lt;/code&gt; 필드에 초기화 시간이 포함된 메시지가 담겨 옵니다. `on-rate-limit.sh`에서 이 필드를 grep하여 초기화 시간을 추출합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;실행_흐름&quot;&gt;실행 흐름&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래는 quota 전환 자동화의 전체 흐름입니다. 스크립트 3개와 hook 2개로 구성됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;[Quota A 소진]
    │
    ▼
StopFailure hook (rate_limit) ──→ on-rate-limit.sh
    │                                  │
    │                          last_assistant_message에서 초기화 시간 파싱
    │                                  │
    │                                  ▼
    │                          quota-switch-reset-time 파일에 기록
    │
    ▼
[사용자가 /logout 후 Quota B로 로그인]
    │
    ▼
[Quota B로 작업 계속]
    │
    ▼
[초기화 시간 경과 후 새 세션 or /clear]
    │
    ▼
SessionStart hook ──→ check-quota-switch.sh
                          │
                  시간 비교: 현재 &amp;gt;= 초기화 시간?
                          │
                    Yes ──┤
                          │
                  credentials 삭제 + 세션 블로킹
                          │
                          ▼
                  [Claude Code 재시작 → Quota A로 로그인]&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;스크립트&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;역할&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;호출 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;on-rate-limit.sh&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Hook 입력의 &lt;code&gt;last_assistant_message&lt;/code&gt; 에서 초기화 시간을 파싱하여 기록&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;StopFailure&lt;/code&gt; hook (&lt;code&gt;rate_limit&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;check-quota-switch.sh&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;초기화 시간이 지났으면 credentials를 삭제하여 재로그인 유도&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;SessionStart&lt;/code&gt; hook (&lt;code&gt;startup&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;record-quota-reset.sh&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;초기화 시간을 수동으로 기록 (자동 파싱 실패 시 fallback)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;사용자가 직접 실행&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;구현과_테스트_프롬프트&quot;&gt;구현과 테스트 프롬프트&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;구현_프롬프트&quot;&gt;구현 프롬프트&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같은 프롬프트로 Claude Code에게 구현을 요청합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code를 quota A, quota B 두 개로 쓰고 있어. Quota A가 소진되면 quota B로 전환해서 쓰다가, quota A의 초기화 시간이 지난 후 새 세션이나 /clear 시에 자동으로 quota B를 logout시켜서 quota A로 복귀하고 싶어. 아래 요구사항대로 구현해줘.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;quota 소진 감지: StopFailure hook에 rate_limit matcher를 걸어서 소진 시점을 감지해줘. 스크립트 이름은 on-rate-limit.sh로 해줘.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;초기화 시간 자동 기록: on-rate-limit.sh에서 hook 입력의 last_assistant_message 필드에서 &quot;resets TIME (TIMEZONE)&quot; 패턴을 파싱하여 초기화 시간을 ~/.claude/quota-switch-reset-time에 epoch으로 저장해줘.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;초기화 시간 수동 기록(fallback): 자동 파싱이 실패할 경우를 대비해 record-quota-reset.sh도 만들어줘. &quot;4h&quot;, &quot;30m&quot; 같은 상대 시간과 절대 시간을 지원해야해.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;자동 복귀: SessionStart hook에서 check-quota-switch.sh를 실행해서, 현재 시간이 초기화 시간을 지났으면 credentials를 삭제하여 재로그인을 유도해줘. startup과 clear 두 matcher 모두 등록해줘.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code는 이 요청을 받아 세 개의 스크립트 파일을 생성하고, `~/.claude/settings.json`에 hook 설정까지 직접 등록했습니다. 사용자가 `settings.json`을 수동으로 편집할 필요 없이, 프롬프트 하나로 스크립트 작성과 hook 등록이 모두 완료됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;테스트_프롬프트&quot;&gt;테스트 프롬프트&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;실제 quota를 소진하지 않고도 hook과 스크립트가 정상 동작하는지 검증할 수 있습니다. 테스트 역시 Claude Code에게 요청하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;~/.claude/settings.json에 등록된 on-rate-limit.sh와 check-quota-switch.sh를 실제 quota 소진 없이 테스트하고 싶어. on-rate-limit.sh는 hook JSON 입력을 직접 구성해서 stdin으로 넘기는 방식으로 테스트해줘. &quot;resets 2pm (Asia/Seoul)&quot; 같은 5시간 한도 메시지와 &quot;resets Feb 20, 5pm (Asia/Seoul)&quot; 같은 주간 한도 메시지 두 가지를 모두 테스트해줘. 각 테스트 후 hook-events.log와 quota-switch-reset-time 파일을 확인해줘. check-quota-switch.sh는 과거 시간의 epoch을 기록한 뒤 실행해서 credentials 삭제 동작을 검증해줘. 단, 실제 credentials가 삭제되므로 테스트 전에 백업하고 테스트 후 복원해줘.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code는 이 요청에 따라 hook JSON을 직접 구성하여 `on-rate-limit.sh`에 전달하고, 결과를 확인하는 과정을 수행합니다. 예를 들어 5시간 한도 메시지 테스트는 다음과 같이 실행됩니다:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;echo &apos;{
  &quot;hook_event_name&quot;: &quot;StopFailure&quot;,
  &quot;error&quot;: &quot;rate_limit&quot;,
  &quot;last_assistant_message&quot;: &quot;You&apos;\&apos;&apos;ve hit your limit · resets 2pm (Asia/Seoul)&quot;
}&apos; | ~/.claude/scripts/on-rate-limit.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트가 성공하면 &lt;code&gt;~/.claude/hook-events.log&lt;/code&gt; 에 &quot;Auto-recorded quota reset time&quot;이 기록되고, &lt;code&gt;~/.claude/quota-switch-reset-time&lt;/code&gt; 파일에 epoch 값이 저장됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;사용_방법&quot;&gt;사용 방법&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;1단계_quota_a_소진&quot;&gt;1단계: quota A 소진&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Quota A가 소진되면 &lt;code&gt;StopFailure&lt;/code&gt; hook이 자동으로 &lt;code&gt;on-rate-limit.sh&lt;/code&gt; 를 실행합니다. Hook 입력의 &lt;code&gt;last_assistant_message&lt;/code&gt; 에서 초기화 시간을 파싱하여 `~/.claude/quota-switch-reset-time`에 기록합니다. `~/.claude/hook-events.log`에서 &quot;Auto-recorded quota reset time&quot;이 기록되었는지 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자동 파싱에 실패한 경우, 로그에 실패 사유가 기록됩니다. 이때는 수동으로 쿼터가 초기화될 시간을 기록합니다:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;record-quota-reset.sh 수동 실행 예시&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;# 절대 시간
~/.claude/scripts/record-quota-reset.sh &quot;2026-03-29 09:00&quot;

# 상대 시간
~/.claude/scripts/record-quota-reset.sh 4h
~/.claude/scripts/record-quota-reset.sh 30m&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;2단계_quota_b로_전환&quot;&gt;2단계: quota B로 전환&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code에서 &lt;code&gt;/logout&lt;/code&gt; 을 실행한 후 quota B 계정으로 재로그인합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;3단계_자동_복귀&quot;&gt;3단계: 자동 복귀&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;기록된 초기화 시간이 지난 후에 Claude Code를 새로 시작하거나 &lt;code&gt;/clear&lt;/code&gt; 를 실행하면 &lt;code&gt;SessionStart&lt;/code&gt; hook이 동작합니다. Quota B의 credentials가 자동으로 제거되고, Claude Code를 재시작하면 quota A로 로그인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;생성된_코드_분석&quot;&gt;생성된 코드 분석&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 코드는 Linux 환경에서 생성된 것입니다. macOS에서의 차이점은 &lt;a href=&quot;#macos&quot;&gt;macOS에서의 차이점&lt;/a&gt;를 참고하세요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;quota_소진_감지_및_초기화_시간_자동_기록&quot;&gt;Quota 소진 감지 및 초기화 시간 자동 기록&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;~/.claude/scripts/on-rate-limit.sh&lt;/code&gt; 는 &lt;code&gt;StopFailure&lt;/code&gt; hook에서 실행되는 스크립트입니다. Quota 소진 시 hook 입력의 &lt;code&gt;last_assistant_message&lt;/code&gt; 필드에서 초기화 시간을 파싱하여 기록합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;on-rate-limit.sh&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;#!/bin/bash
# StopFailure hook (matcher: rate_limit)
# 1. Log the event for diagnostics
# 2. Extract reset time from last_assistant_message (hook JSON input)

LOG_FILE=&quot;$HOME/.claude/hook-events.log&quot;
INPUT=$(cat)

# Step 1: Log the event
echo &quot;=== $(date &apos;+%Y-%m-%d %H:%M:%S&apos;) ===&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
echo &quot;$INPUT&quot; | jq . &amp;gt;&amp;gt; &quot;$LOG_FILE&quot; 2&amp;gt;/dev/null || echo &quot;$INPUT&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
echo &quot;&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;

# Step 2: Extract reset time from last_assistant_message
LAST_MSG=$(echo &quot;$INPUT&quot; | jq -r &apos;.last_assistant_message // empty&apos;)
if [ -z &quot;$LAST_MSG&quot; ]; then
    echo &quot;No last_assistant_message in hook input&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
    exit 0
fi

RESET_LINE=$(echo &quot;$LAST_MSG&quot; | grep -oP \
    &apos;resets?\s+(at\s+)?(\w+\s+\d+,?\s+)?\d{1,2}(:\d{2})?\s*[ap]m(\s*\([^)]+\))?&apos;)

if [ -z &quot;$RESET_LINE&quot; ]; then
    echo &quot;No reset time found in message: $LAST_MSG&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
    exit 0
fi

echo &quot;Found reset info: $RESET_LINE&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;

# Step 3: Parse the reset time
TIME_PART=$(echo &quot;$RESET_LINE&quot; | grep -oP &apos;\d{1,2}(:\d{2})?\s*[ap]m&apos;)
DATE_PART=$(echo &quot;$RESET_LINE&quot; | grep -oP &apos;(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}&apos; | head -1)
TZ_PART=$(echo &quot;$RESET_LINE&quot; | grep -oP &apos;\(([^)]+)\)&apos; | tr -d &apos;()&apos;)

# Convert 12-hour to 24-hour
HOUR=$(echo &quot;$TIME_PART&quot; | grep -oP &apos;^\d{1,2}&apos;)
MINUTE=$(echo &quot;$TIME_PART&quot; | grep -oP &apos;:\K\d{2}&apos; || echo &quot;00&quot;)
AMPM=$(echo &quot;$TIME_PART&quot; | grep -oP &apos;[ap]m&apos;)

if [ &quot;$AMPM&quot; = &quot;pm&quot; ] &amp;amp;&amp;amp; [ &quot;$HOUR&quot; -ne 12 ]; then
    HOUR=$((HOUR + 12))
elif [ &quot;$AMPM&quot; = &quot;am&quot; ] &amp;amp;&amp;amp; [ &quot;$HOUR&quot; -eq 12 ]; then
    HOUR=0
fi
TIME_24=$(printf &quot;%02d:%s&quot; &quot;$HOUR&quot; &quot;$MINUTE&quot;)

# Build datetime string
TODAY=$(date &quot;+%Y-%m-%d&quot;)
if [ -n &quot;$DATE_PART&quot; ]; then
    YEAR=$(date &quot;+%Y&quot;)
    DATETIME=&quot;$DATE_PART $YEAR $TIME_24&quot;
else
    DATETIME=&quot;$TODAY $TIME_24&quot;
fi

# Convert to epoch
if [ -n &quot;$TZ_PART&quot; ]; then
    RESET_EPOCH=$(TZ=&quot;$TZ_PART&quot; date -d &quot;$DATETIME&quot; +%s 2&amp;gt;/dev/null)
else
    RESET_EPOCH=$(date -d &quot;$DATETIME&quot; +%s 2&amp;gt;/dev/null)
fi

if [ -z &quot;$RESET_EPOCH&quot; ]; then
    echo &quot;Failed to parse reset time: $DATETIME (TZ=$TZ_PART)&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
    exit 0
fi

# If parsed time is in the past, assume next day (5-hour limit case)
NOW_EPOCH=$(date +%s)
if [ &quot;$RESET_EPOCH&quot; -le &quot;$NOW_EPOCH&quot; ] &amp;amp;&amp;amp; [ -z &quot;$DATE_PART&quot; ]; then
    RESET_EPOCH=$((RESET_EPOCH + 86400))
fi

# Step 4: Record the reset time
RESET_FILE=&quot;$HOME/.claude/quota-switch-reset-time&quot;
echo &quot;$RESET_EPOCH&quot; &amp;gt; &quot;$RESET_FILE&quot;
RESET_DISPLAY=$(date -d &quot;@$RESET_EPOCH&quot; &quot;+%Y-%m-%d %H:%M:%S&quot;)
echo &quot;Auto-recorded quota reset time: $RESET_DISPLAY&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;

exit 0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;last_assistant_message&lt;/code&gt; 필드에서 &lt;code&gt;resets TIME (TIMEZONE)&lt;/code&gt; 패턴을 grep하여 초기화 시간을 추출하는 원리는 &lt;a href=&quot;#stopfailure_hook_이벤트&quot;&gt;StopFailure hook 이벤트&lt;/a&gt;를 참고하세요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;초기화_시간_수동_기록_fallback&quot;&gt;초기화 시간 수동 기록 (fallback)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;~/.claude/scripts/record-quota-reset.sh&lt;/code&gt; 는 자동 파싱이 실패했을 때 초기화 시간을 수동으로 기록하는 스크립트입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;record-quota-reset.sh&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;#!/bin/bash
# Record when quota A resets so the hook can switch back from quota B
# Usage: record-quota-reset.sh &quot;2026-03-29 09:00&quot;
#        record-quota-reset.sh 4h    (4 hours from now)
#        record-quota-reset.sh 30m   (30 minutes from now)

RESET_FILE=&quot;$HOME/.claude/quota-switch-reset-time&quot;

if [ -z &quot;$1&quot; ]; then
    echo &quot;Usage: $0 &amp;lt;datetime or duration&amp;gt;&quot;
    echo &quot;  Examples:&quot;
    echo &quot;    $0 &apos;2026-03-29 09:00&apos;   # absolute time&quot;
    echo &quot;    $0 4h                    # 4 hours from now&quot;
    echo &quot;    $0 30m                   # 30 minutes from now&quot;
    exit 1
fi

INPUT=&quot;$1&quot;

# Parse duration format (e.g., 4h, 30m)
if [[ &quot;$INPUT&quot; =~ ^([0-9]+)h$ ]]; then
    HOURS=&quot;${BASH_REMATCH[1]}&quot;
    RESET_EPOCH=$(date -d &quot;+${HOURS} hours&quot; +%s)
    RESET_DISPLAY=$(date -d &quot;+${HOURS} hours&quot; &quot;+%Y-%m-%d %H:%M:%S&quot;)
elif [[ &quot;$INPUT&quot; =~ ^([0-9]+)m$ ]]; then
    MINS=&quot;${BASH_REMATCH[1]}&quot;
    RESET_EPOCH=$(date -d &quot;+${MINS} minutes&quot; +%s)
    RESET_DISPLAY=$(date -d &quot;+${MINS} minutes&quot; &quot;+%Y-%m-%d %H:%M:%S&quot;)
else
    # Absolute datetime
    RESET_EPOCH=$(date -d &quot;$INPUT&quot; +%s 2&amp;gt;/dev/null)
    if [ $? -ne 0 ]; then
        echo &quot;Error: Cannot parse datetime &apos;$INPUT&apos;&quot;
        exit 1
    fi
    RESET_DISPLAY=$(date -d &quot;$INPUT&quot; &quot;+%Y-%m-%d %H:%M:%S&quot;)
fi

echo &quot;$RESET_EPOCH&quot; &amp;gt; &quot;$RESET_FILE&quot;
echo &quot;Quota A reset time recorded: $RESET_DISPLAY&quot;
echo &quot;Saved to: $RESET_FILE&quot;
echo &quot;&quot;
echo &quot;Now run /logout in Claude Code and log in with quota B.&quot;
echo &quot;When the reset time arrives, the next session start or /clear will auto-logout quota B.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;절대 시간(&lt;code&gt;&quot;2026-03-29 09:00&quot;&lt;/code&gt;)과 상대 시간(&lt;code&gt;4h&lt;/code&gt;, &lt;code&gt;30m&lt;/code&gt;) 두 가지 형식을 지원합니다. 내부적으로는 Unix epoch 타임스탬프로 변환하여 &lt;code&gt;~/.claude/quota-switch-reset-time&lt;/code&gt; 파일에 저장합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;자동_복귀&quot;&gt;자동 복귀&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;~/.claude/scripts/check-quota-switch.sh&lt;/code&gt; 는 SessionStart hook에서 실행되는 스크립트입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;check-quota-switch.sh&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;#!/bin/bash
# SessionStart hook: check if quota A has reset
# If so, remove credentials to force re-login with quota A

RESET_FILE=&quot;$HOME/.claude/quota-switch-reset-time&quot;
CRED_FILE=&quot;$HOME/.claude/.credentials.json&quot;

# No reset time recorded, nothing to do
if [ ! -f &quot;$RESET_FILE&quot; ]; then
    exit 0
fi

RESET_EPOCH=$(cat &quot;$RESET_FILE&quot;)
NOW_EPOCH=$(date +%s)

if [ &quot;$NOW_EPOCH&quot; -ge &quot;$RESET_EPOCH&quot; ]; then
    # Time to switch back to quota A
    RESET_DISPLAY=$(date -d &quot;@$RESET_EPOCH&quot; &quot;+%Y-%m-%d %H:%M:%S&quot;)

    # Backup quota B credentials and remove current credentials
    if [ -f &quot;$CRED_FILE&quot; ]; then
        cp &quot;$CRED_FILE&quot; &quot;$HOME/.claude/.credentials.quota-b.bak&quot;
        rm -f &quot;$CRED_FILE&quot;
    fi

    # Remove reset time file
    rm -f &quot;$RESET_FILE&quot;

    # Exit with code 2 to block session and show message
    echo &quot;Quota A has reset (reset time: $RESET_DISPLAY).&quot; &amp;gt;&amp;amp;2
    echo &quot;Quota B credentials have been removed.&quot; &amp;gt;&amp;amp;2
    echo &quot;Please restart Claude Code to log in with quota A.&quot; &amp;gt;&amp;amp;2
    exit 2
fi

# Not yet time to switch
exit 0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 스크립트의 핵심 동작은 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;기록된 초기화 시간 파일이 없으면 `exit 0`으로 아무 동작 없이 종료합니다. 평소에는 hook이 세션 시작을 방해하지 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;현재 시간이 초기화 시간 이상이면 전환을 실행합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;~/.claude/.credentials.json&lt;/code&gt; 을 &lt;code&gt;~/.claude/.credentials.quota-b.bak&lt;/code&gt; 으로 백업한 후 삭제합니다. Credentials 파일이 없으면 Claude Code가 다음 시작 시 로그인을 요청합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`exit 2`로 세션을 블로킹하고 stderr로 안내 메시지를 출력합니다. Claude Code hook에서 exit code 2는 세션 진행을 차단하고 stderr 메시지를 사용자에게 보여주는 약속된 규칙입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;settings_json_hook_설정&quot;&gt;settings.json hook 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;~/.claude/settings.json&lt;/code&gt; 에 &lt;code&gt;StopFailure`와 `SessionStart&lt;/code&gt; hook을 등록합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;{
  &quot;hooks&quot;: {
    &quot;StopFailure&quot;: [
      {
        &quot;matcher&quot;: &quot;rate_limit&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;/home/user/.claude/scripts/on-rate-limit.sh&quot;
          }
        ]
      }
    ],
    &quot;SessionStart&quot;: [
      {
        &quot;matcher&quot;: &quot;startup&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;/home/user/.claude/scripts/check-quota-switch.sh&quot;
          }
        ]
      },
      {
        &quot;matcher&quot;: &quot;clear&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;/home/user/.claude/scripts/check-quota-switch.sh&quot;
          }
        ]
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;&quot;matcher&quot;: &quot;rate_limit&quot;&lt;/code&gt; 으로 quota 소진 이벤트만 필터링합니다. &lt;code&gt;SessionStart&lt;/code&gt; 이벤트는 &lt;code&gt;startup&lt;/code&gt; (새 세션 시작)과 &lt;code&gt;clear&lt;/code&gt; (&lt;code&gt;/clear&lt;/code&gt; 실행) 두 경우 모두 같은 스크립트를 실행하도록 설정했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;한계&quot;&gt;한계&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;credentials 저장소 의존&lt;/strong&gt;: Linux에서는 &lt;code&gt;~/.claude/.credentials.json&lt;/code&gt; 파일을, macOS에서는 Keychain을 직접 조작하는 방식이므로, Claude Code의 내부 인증 구조가 변경되면 동작하지 않을 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;완전 자동 로그인은 불가&lt;/strong&gt;: Credentials 제거 후 재시작 시 로그인 화면이 나타나지만, quota A 계정을 자동으로 선택해주지는 않습니다. 사용자가 직접 quota A 계정으로 로그인해야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;세션 중간 전환은 지원하지 않음&lt;/strong&gt;: &lt;code&gt;SessionStart&lt;/code&gt; hook은 새 세션 시작이나 &lt;code&gt;/clear&lt;/code&gt; 시에만 동작합니다. 세션 중간에 초기화 시간이 지나더라도 현재 세션에서는 전환이 일어나지 않습니다. 매 프롬프트마다 체크하는 방식(&lt;code&gt;UserPromptSubmit&lt;/code&gt; hook)도 가능하지만, 세션 중간에 credentials가 제거되면 진행 중인 작업과 컨텍스트가 유실되므로 권장하지 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;메시지 형식 의존&lt;/strong&gt;: &lt;code&gt;on-rate-limit.sh`의 파싱은 `last_assistant_message&lt;/code&gt; 필드의 &lt;code&gt;resets TIME (TIMEZONE)&lt;/code&gt; 패턴에 의존합니다. Claude Code 버전에 따라 메시지 형식이 달라져 왔으므로, 향후 형식이 변경되면 파싱이 실패할 수 있습니다. 실패 시 &lt;code&gt;~/.claude/hook-events.log&lt;/code&gt; 에 원인이 기록되며, `record-quota-reset.sh`로 수동 기록할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;macos&quot;&gt;macOS에서의 차이점&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code는 실행 환경의 OS를 인식합니다. macOS에서 같은 요청을 하면 &lt;code&gt;date&lt;/code&gt; 명령어 부분이 BSD 문법으로 생성됩니다. 주요 차이는 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;용도&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Linux (GNU date)&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;macOS (BSD date)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;상대 시간 (4시간 후)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;date -d &quot;+4 hours&quot; +%s&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;date -v+4H +%s&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;절대 시간 파싱&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;date -d &quot;2026-03-29 09:00&quot; +%s&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;date -j -f &quot;%Y-%m-%d %H:%M&quot; &quot;2026-03-29 09:00&quot; +%s&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;epoch → 표시 형식&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;date -d &quot;@$EPOCH&quot; &quot;+%Y-%m-%d %H:%M:%S&quot;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;date -r $EPOCH &quot;+%Y-%m-%d %H:%M:%S&quot;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 외에도 macOS에서는 다음 차이가 있습니다:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;grep -oP&lt;/code&gt; (PCRE)가 macOS 기본 grep에서 지원되지 않습니다. &lt;code&gt;brew install grep&lt;/code&gt; 후 `ggrep`을 사용하거나, 정규식을 POSIX 호환으로 변경해야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;macOS에서 Claude Code의 credentials는 &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/security#credential-storage&quot;&gt;macOS Keychain에 저장&lt;/a&gt;됩니다. &lt;code&gt;~/.claude/.credentials.json&lt;/code&gt; 파일이 존재하지 않으므로, &lt;code&gt;check-quota-switch.sh&lt;/code&gt;에서 파일을 삭제하는 방식이 동작하지 않습니다. macOS에서 프롬프트를 내리면 &lt;code&gt;security delete-generic-password&lt;/code&gt; 명령으로 Keychain에서 credentials를 삭제하는 스크립트가 생성됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Go 코드에 품질 도구 적용하기</title>
      <link>https://blog.benelog.net//go-quality-tools.html</link>
      <pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">go-quality-tools.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#gofmt_코드_포맷팅&quot;&gt;1. gofmt: 코드 포맷팅&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#goimports_import_정리&quot;&gt;2. goimports: import 정리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#go_vet_정적_분석&quot;&gt;3. go vet: 정적 분석&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#golangci_lint_통합_린터&quot;&gt;4. golangci-lint: 통합 린터&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#설정_파일&quot;&gt;4.1. 설정 파일&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#기본_활성화_린터_default_standard&quot;&gt;4.2. 기본 활성화 린터 (&lt;code&gt;default: standard&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#makefile로_통합&quot;&gt;5. Makefile로 통합&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#참고_자료&quot;&gt;6. 참고 자료&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Go는 언어 차원에서 코드 포맷팅 도구(&lt;code&gt;gofmt&lt;/code&gt;)를 제공하고, 표준 도구 체인에 정적 분석(&lt;code&gt;go vet&lt;/code&gt;)이 포함되어 있습니다.
이런 도구들을 프로젝트 초기에 설정해두면 코드 리뷰에서 스타일 논쟁을 줄이고, 흔한 실수를 빌드 전에 잡을 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 글에서는 Go로 개발하는 프로젝트에서 기본적으로 갖추면 좋은 도구들과 설정 방법을 정리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;gofmt_코드_포맷팅&quot;&gt;1. gofmt: 코드 포맷팅&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Go 커뮤니티에서는 &lt;code&gt;gofmt&lt;/code&gt; 이 표준 포맷터입니다.
탭/스페이스, 중괄호 위치 같은 스타일 논쟁 없이 모든 Go 코드가 동일한 포맷을 따르게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;gofmt -w .&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;-w&lt;/code&gt; 플래그는 파일을 직접 수정합니다. &lt;code&gt;-l&lt;/code&gt; 플래그를 쓰면 포맷이 맞지 않는 파일 목록만 출력합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;gofmt -l .&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;포맷이 맞지 않는 파일이 있으면 파일 경로가 출력되고, 모두 맞으면 아무것도 출력되지 않습니다.
CI에서는 이 출력을 검사해서 포맷팅되지 않은 코드가 머지되는 것을 막을 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;goimports_import_정리&quot;&gt;2. goimports: import 정리&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/golang.org/x/tools/cmd/goimports&quot;&gt;goimports&lt;/a&gt;는 `gofmt`의 기능에 더해 import 문을 자동으로 정리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;사용하지 않는 import를 제거&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;필요한 import를 자동 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;import를 표준 라이브러리 / 그 외 패키지 2그룹으로 정렬 (&lt;code&gt;-local&lt;/code&gt; 플래그를 쓰면 로컬 패키지를 별도 그룹으로 분리 가능)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;go install golang.org/x/tools/cmd/goimports@latest
goimports -w .&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;gofmt&lt;/code&gt; 대신 `goimports`만 실행해도 포맷팅까지 함께 처리됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;go_vet_정적_분석&quot;&gt;3. go vet: 정적 분석&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;`go vet`은 Go 표준 도구 체인에 포함된 정적 분석 도구입니다.
컴파일러가 잡지 못하는 의심스러운 코드를 검출합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;go vet ./...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대표적으로 잡아주는 문제들은 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;fmt.Printf`의 포맷 문자열과 인자 불일치 (`printf&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;도달할 수 없는 코드 (&lt;code&gt;unreachable&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sync.Mutex&lt;/code&gt; 등 잠금을 값으로 복사하는 코드 (&lt;code&gt;copylocks&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;context.WithCancel`의 cancel 함수를 호출하지 않는 경로 (`lostcancel&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;별도 설치 없이 바로 사용할 수 있으므로, 모든 Go 프로젝트에서 기본으로 실행하는 것이 좋습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;golangci_lint_통합_린터&quot;&gt;4. golangci-lint: 통합 린터&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://golangci-lint.run/&quot;&gt;golangci-lint&lt;/a&gt;는 여러 린터를 하나의 도구로 통합해서 실행해주는 린터 러너입니다.
개별 린터를 따로 설치하고 실행할 필요 없이, 설정 파일 하나로 원하는 린터를 선택해서 쓸 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;## 바이너리 설치 (go install은 공식적으로 권장되지 않음)
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin
golangci-lint run ./...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;설정_파일&quot;&gt;4.1. 설정 파일&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;프로젝트 루트에 &lt;code&gt;.golangci.yml&lt;/code&gt; 파일을 만들어 설정합니다.
아래는 golangci-lint v2 기준 설정 예시입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;.golangci.yml&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;version: &quot;2&quot;

linters:
  default: standard
  enable:
    - misspell

formatters:
  enable:
    - gofmt
    - goimports&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;v2에서는 `version: &quot;2&quot;`를 반드시 명시해야 합니다.
`default: standard`로 설정하면 errcheck, govet, ineffassign, staticcheck, unused 5개 린터가 기본으로 활성화됩니다.
추가로 필요한 린터만 `enable`에 넣으면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;v2의 또 다른 변경점은 gofmt, goimports 같은 포맷터가 &lt;code&gt;linters`가 아닌 `formatters&lt;/code&gt; 섹션으로 분리된 것입니다.
기존 v1 설정 파일이 있다면 &lt;code&gt;golangci-lint migrate&lt;/code&gt; 명령으로 자동 변환할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;기본_활성화_린터_default_standard&quot;&gt;4.2. 기본 활성화 린터 (&lt;code&gt;default: standard&lt;/code&gt;)&lt;/h3&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 75%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;린터&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;errcheck&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;에러 반환값을 무시하는 코드 검출&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;govet&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;`go vet`과 동일한 정적 분석&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;ineffassign&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;이후에 사용되지 않는 변수 할당 검출&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;staticcheck&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Go 코드의 버그, 성능 문제, 스타일 문제를 종합적으로 검사 (gosimple, stylecheck 포함)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;unused&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;사용되지 않는 변수, 함수, 타입 검출&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 중 `errcheck`은 특히 중요합니다. Go에서 에러를 반환값으로 처리하는 관례상, 에러를 무시하는 코드는 런타임에 예상치 못한 동작을 일으킬 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;makefile로_통합&quot;&gt;5. Makefile로 통합&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위 도구들을 개별로 실행하는 것은 번거로우므로, Makefile에 통합하면 편리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Makefile&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;makefile&quot;&gt;.PHONY: fmt lint test check ci

fmt:
	goimports -w .

lint:
	golangci-lint run ./...

test:
	go test ./...

check: fmt lint test

ci: lint test&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;check`와 `ci&lt;/code&gt; 두 가지 타겟을 나눈 이유가 있습니다.
&lt;code&gt;fmt&lt;/code&gt; 타겟은 파일을 직접 수정하므로 로컬에서 커밋 전에 실행하기에 편리하지만, CI 환경에서는 파일을 수정하면 안 됩니다.
&lt;code&gt;ci&lt;/code&gt; 타겟은 `golangci-lint`의 gofmt, goimports 린터가 포맷 위반을 검출만 하고 파일을 수정하지 않으므로, CI에서 안전하게 쓸 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;로컬: &lt;code&gt;make check&lt;/code&gt; (포맷 자동 수정 + 린트 + 테스트)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CI: &lt;code&gt;make ci&lt;/code&gt; (린트 + 테스트, 파일 수정 없음)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;`go vet`을 별도 타겟으로 두지 않은 이유는, golangci-lint에 govet 린터가 포함되어 있어서 `make lint`에서 이미 실행되기 때문입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코드 수정 후 커밋하기 전에 `make check`를 실행하는 습관을 들이면, CI에서 실패하는 상황을 줄일 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고_자료&quot;&gt;6. 참고 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://go.dev/blog/gofmt&quot;&gt;go fmt your code - The Go Blog&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/golang.org/x/tools/cmd/goimports&quot;&gt;goimports - pkg.go.dev&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/cmd/vet&quot;&gt;go vet - pkg.go.dev&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://golangci-lint.run/&quot;&gt;golangci-lint 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(이 글은 개인 라이센스로 구매한 Claude Code의 도움을 받아 작성되었습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Claude Code의 &apos;/model opusplan&amp;#8217;과 showClearContextOnPlanAccept 옵션</title>
      <link>https://blog.benelog.net//claude-code-opusplan.html</link>
      <pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">claude-code-opusplan.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#model_opusplan&quot;&gt;/model opusplan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#showclearcontextonplanaccept_설정&quot;&gt;showClearContextOnPlanAccept 설정&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#showclearcontextonplanaccept은_기본값_변경_이력&quot;&gt;showClearContextOnPlanAccept은 기본값 변경 이력&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#plan_실행_전_컨텍스트_클리어의_트레이드오프&quot;&gt;Plan 실행 전 컨텍스트 클리어의 트레이드오프&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;model_opusplan&quot;&gt;/model opusplan&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;/model opusplan&lt;/code&gt; 을 선택하면 Opus로 plan을 세우고 Sonnet으로 실행하는 전략을 쓸 수 있습니다. 복잡한 작업에서 Opus의 추론 능력으로 계획을 세우고, 실행은 비용이 낮은 Sonnet으로 처리하는 방식입니다.
그런데 같은 세션 안에서 모델이 전환되면, 그때까지 쌓인 히스토리 전체가 캐시 미스로 재과금됩니다. 턴이 많이 진행된 후에 전환할수록 삼각수 효과로 누적된 토큰이 크기 때문에 비용 타격이 큽니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock tip&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Tip&lt;/div&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
토큰 비용의 삼각수 효과와 Prompt Caching에 대한 자세한 설명은 &lt;a href=&quot;claude-code-cost.html&quot;&gt;Claude Code 토큰 비용과 프롬프트 캐싱&lt;/a&gt;을 참고하세요.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;showclearcontextonplanaccept_설정&quot;&gt;showClearContextOnPlanAccept 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;세션 중간에 모델이 전환될 때 발생하는 부정적인 효과를 줄이는데 도움이 될 수도 있는 옵션이 있습니다. &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/settings&quot;&gt;Claude Code 설정&lt;/a&gt;의 &lt;code&gt;showClearContextOnPlanAccept&lt;/code&gt; 옵션이 &lt;code&gt;true&lt;/code&gt; 이면, plan 승인 시 컨텍스트를 클리어할지 선택하는 옵션이 나타납니다.
&apos;Yes, clear context(??% used) and by pass permissions&apos; 를 선택하면, 컨텍스트를 클리어하면 planning 단계에서 쌓인 히스토리가 제거된 상태에서 실행이 시작되므로, 모델 전환으로 인한 캐시 무효화 비용을 피할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 선택지가 나오도록 활성화하려면 &lt;code&gt;~/.claude/settings.json&lt;/code&gt; 에 다음과 같이 추가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;{
  &quot;preferences&quot;: {
    &quot;showClearContextOnPlanAccept&quot;: true
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;showclearcontextonplanaccept은_기본값_변경_이력&quot;&gt;showClearContextOnPlanAccept은 기본값 변경 이력&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md&quot;&gt;Claude Code의 CHANGELOG&lt;/a&gt;에 따르면 showClearContextOnPlanAccept은 아래와 같은 이력을 가지고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;v2.1.0(2026-01-07) : &quot;Clear context&quot; 옵션이 plan 승인 시 기본 선택으로 처음 도입&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;v2.1.2(2026-01-09) : Shift+Tab 단축키로 이 옵션을 건너뛸 수 있는 기능도 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;v2.1.81(2026-03-20) : 기본값이 &lt;code&gt;false&lt;/code&gt; 로 변경&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서 2026년 3월 말 현 시점에서는 showClearContextOnPlanAccept 속성값을 직접 추가하지 않으면 &quot;clear context&quot; 옵션이 나타나지 않습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;plan_실행_전_컨텍스트_클리어의_트레이드오프&quot;&gt;Plan 실행 전 컨텍스트 클리어의 트레이드오프&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 옵션은 opusplan이 아닌 경우에도 유용할 때도 있습니다. 같은 모델로 plan mode를 사용하더라도, plan 단계에서 많은 턴이 쌓였다면 컨텍스트를 클리어하고 실행을 시작하면 이후 턴의 입력 토큰 크기를 줄일 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 컨텍스트 클리어에는 트레이드오프가 있습니다. Plan 파일 자체는 디스크(&lt;code&gt;~/.claude/plans/&lt;/code&gt;)에 저장되어 보존되지만, planning 중에 읽은 파일 내용이나 탐색 결과는 모두 사라집니다. 따라서 실행 단계에서 필요한 파일을 plan을 가이드 삼아 다시 읽어야 하며, 이 과정에서 추가 토큰이 소비됩니다. planning 중의 논의 내용 중 plan 텍스트에 반영되지 않은 부분도 유실됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Plan 실행 전에 컨텍스트 클리어에 대해서 아래와 같은 부정적인 사용자 피드백이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/18523&quot;&gt;#18523&lt;/a&gt; Plan 승인 대화상자의 기본 옵션을 설정할 수 있게 해달라는 요청. 컨텍스트를 유지해야 복잡한 문제에서 더 나은 결정을 내릴 수 있고, auto-compact가 이미 컨텍스트를 관리하므로 강제 클리어가 불필요하다는 의견.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/18878&quot;&gt;#18878&lt;/a&gt; &quot;Clear context&quot; 기본 옵션을 비활성화하거나 순서를 변경할 수 있게 해달라는 요청. plan에 포함되지 않은 대화 맥락이 유실되는 문제를 지적하며, 파괴적인 동작이 기본값이 되어서는 안 된다는 의견.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/25734&quot;&gt;#25734&lt;/a&gt; &quot;Clear context and implement&quot;가 기본 옵션인 것은 파괴적이고 되돌릴 수 없다는 문제 제기. 터미널 스크롤백까지 지워져 복구가 불가능하며, 가장 파괴적인 옵션이 기본값이 되어서는 안 된다는 의견.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Claude Code 토큰 비용과 프롬프트 캐싱</title>
      <link>https://blog.benelog.net//claude-code-cost.html</link>
      <pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">claude-code-cost.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#과금의_기본_단위_토큰&quot;&gt;과금의 기본 단위: 토큰&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#모델별_가격표&quot;&gt;모델별 가격표&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#prompt_caching의_효과&quot;&gt;Prompt Caching의 효과&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#직접_시뮬레이션해보기&quot;&gt;직접 시뮬레이션해보기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#참고_자료&quot;&gt;참고 자료&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Code는 Anthropic의 API를 호출하는 CLI 도구입니다.
Enterprise Plan에서는 API 사용량에 따라 과금되므로, 과금 구조를 이해하면 같은 작업을 더 적은 비용으로 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;과금의_기본_단위_토큰&quot;&gt;과금의 기본 단위: 토큰&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude API는 토큰 단위로 과금됩니다. 토큰 종류별로 가격이 다릅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 75%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;토큰 종류&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Input 토큰&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;모델에게 보내는 모든 텍스트 (시스템 프롬프트, 대화 히스토리, 도구 정의 등)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Output 토큰&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;모델이 생성한 응답 텍스트&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Thinking 토큰&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Extended thinking 모드에서 모델이 내부적으로 추론하는 데 쓰는 토큰. Output과 같은 가격으로 과금&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;모델별_가격표&quot;&gt;모델별 가격표&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2026년 3월 기준으로 백만(1M) 토큰당 가격은 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;모델&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Input&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Output&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Cache Write&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Cache Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Sonnet 4/4.6&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$3&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$15&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$3.75&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$0.30&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Opus 4/4.6&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$15&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$75&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$18.75&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$1.50&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Haiku 4.5&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$0.80&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$4&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$1.00&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;$0.08&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Opus는 Sonnet 대비 모든 항목이 5배입니다. Haiku는 Sonnet의 약 1/4 수준입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;prompt_caching의_효과&quot;&gt;Prompt Caching의 효과&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude API는 stateless입니다. 매 턴마다 시스템 프롬프트, 도구 정의, 이전 대화 히스토리를 모두 다시 보내야 합니다.
여기서 &apos;턴&amp;#8217;은 사용자가 한 번 입력하고 Claude가 한 번 답변하는 단위가 아닙니다. Claude Code는 agentic하게 동작하기 때문에, 사용자가 한 번 입력하더라도 Claude가 파일 읽기, 편집, 명령 실행 등 도구를 호출할 때마다 API 호출이 발생합니다. 즉 &lt;strong&gt;한 번의 사용자 입력이 여러 턴&lt;/strong&gt;에 해당할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;턴이 진행될수록 입력 토큰이 누적되어 증가합니다. 공식으로 표현하면 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;턴 n의 입력 토큰:
  input(n) = B + n·T + (O + Th)·(n − 1)

  B  = 시스템 프롬프트 + 도구 정의 (매 턴 고정)
  T  = 턴당 새 사용자 입력 토큰
  O  = 턴당 출력 토큰 (히스토리 누적)
  Th = 턴당 thinking 토큰 (히스토리 누적)

N턴까지의 총 입력 토큰:
  총 입력 = N·B + T·N(N+1)/2 + (O+Th)·N(N−1)/2&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;N(N+1)/2&lt;/code&gt; 는 삼각수 공식입니다. 즉 총 입력 토큰은 턴 수에 대해 &lt;strong&gt;이차(quadratic)로 증가&lt;/strong&gt;합니다.
10턴 대화의 뒷쪽 턴은 앞쪽 턴보다 훨씬 많은 입력 토큰을 소비하며, 대화가 길어질수록 이 격차는 더 벌어집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Prompt Caching은 이 삼각수 효과를 완화하는 핵심 수단입니다.
이전 턴까지의 히스토리는 대부분 캐시에 남아있으므로, 캐시가 적중하면 반복 전송되는 입력 토큰의 비용이 정가의 10%로 줄어듭니다.
캐싱 없이는 뒷쪽 턴의 비용이 급격히 올라가지만, 캐싱이 잘 적중하면 누적 비용 곡선을 크게 낮출 수 있습니다.
캐시가 잘 적중하면 입력 토큰 비용을 90%까지 절약할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 50%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;캐시 유형&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;설명&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;가격(Input 대비)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Cache Write&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;새로운 내용을 캐시에 저장&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;125%&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Cache Read&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시된 내용을 재사용&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;10%&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Cache Miss&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;캐시 만료 후 다시 저장&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;125% (Cache Write와 동일)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;캐시 히트율을 높이기 위해서 다음을 의식해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5분 안에 다음 턴 진행&lt;/strong&gt;: 입력을 보내고 나서 다음 턴까지 5분 이내로 유지하면 캐시가 살아있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;세션 중간에 모델을 전환하지 않기&lt;/strong&gt;: 모델을 바꾸면 캐시가 완전히 초기화됩니다. 하나의 대화 세션에서는 가능하면 하나의 모델을 유지합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;가급적 대화 중 CLAUDE.md를 수정하지 않기&lt;/strong&gt;: CLAUDE.md는 시스템 프롬프트의 일부로 매 턴 전송됩니다. 대화 중에 이 파일을 수정하면 시스템 프롬프트의 prefix가 달라져 기존의 모든 캐시가 무효화됩니다. 특히 턴이 많이 진행된 세션에서는 삼각수 효과로 쌓인 대량의 히스토리 토큰이 전부 Cache Write(125%)로 재과금되어 비용 타격이 큽니다. 수정이 필요하다면 새 세션에서 시작하는 것이 비용 면에서 유리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;직접_시뮬레이션해보기&quot;&gt;직접 시뮬레이션해보기&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 내용을 바탕으로 자신의 사용 패턴에 맞는 비용을 직접 계산해볼 수 있는 시뮬레이터를 Claude Code로 만들었습니다.
세션 내의 턴 수, 토큰량, 캐시 히트율 등을 조절하면서 모델별 비용 차이와 누적 비용 곡선을 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;claude-code-cost-simulation.html&quot;&gt;Claude Code Token Cost Simulator로 이동 →&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고_자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/costs&quot;&gt;Claude Code Costs - Anthropic Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching&quot;&gt;Prompt Caching - Anthropic Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/about-claude/models&quot;&gt;Models &amp;amp; Pricing - Anthropic Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Ubuntu에서의 OEM 커널 설치</title>
      <link>https://blog.benelog.net//oem-kernel.html</link>
      <pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">oem-kernel.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#ubuntu_설치_시점에_따른_경험_차이&quot;&gt;Ubuntu 설치 시점에 따른 경험 차이&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#oem_커널_vs_hwe_커널&quot;&gt;OEM 커널 vs HWE 커널&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#oem_커널이_자동으로_선택되는_원리&quot;&gt;OEM 커널이 자동으로 선택되는 원리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#oem_커널을_수동으로_설치제거&quot;&gt;OEM 커널을 수동으로 설치/제거&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#참고_자료&quot;&gt;참고 자료&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ubuntu 공식 홈페이지에서 ISO를 받아 같은 방법으로 설치했음에도 설치 시점에 따라서 Lenovo ThinkPad에는 OEM 커널이, Dell XPS에는 HWE 커널이 설치되는 경우를 경험했습니다.
이 글에서는 두 커널의 차이와 자동 선택되는 원리 등을 정리합니다.
현상의 분석과 이 글의 작성에는 Claude Code의 도움을 받았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;ubuntu_설치_시점에_따른_경험_차이&quot;&gt;Ubuntu 설치 시점에 따른 경험 차이&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 Lenovo와 Dell 2개의 모델에  Ubuntu를 설치해서 사용하고 있습니다.
2024년 11월에 Dell XPS 13 9350 모델에 Ubuntu 24.04를 설치했을 때는 웹캠이 제대로 동작하지 않았습니다.
2025년 8월경 Lenovo ThinkPad X1 Carbon Gen 13에 Ubuntu 24.04를 설치했을 때는 웹캠이 바로 잡혔습니다.
이 둘의 차이는 당시 Dell에서는 HWE 커널이 설치된 반면, Lenovo에서는 OEM 커널이 설치되었기 때문입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2026년 2월 현재에는 2개의 모델이 모두 Ubuntu 공식 인증기기로 등록되어 있어서 OEM 커널이 설치될 것으로 예상됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ubuntu.com/certified/202407-34214&quot;&gt;Dell XPS 13 9350&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ubuntu.com/certified/202411-36001&quot;&gt;Lenovo ThinkPad X1 Carbon Gen 13 LNL&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;oem_커널_vs_hwe_커널&quot;&gt;OEM 커널 vs HWE 커널&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ubuntu 24.04 LTS에서 최신 하드웨어 지원을 위해 제공하는 커널은 크게 두 가지입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;항목&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;OEM 커널&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;HWE 커널&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;패키지명 예&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;linux-oem-24.04d&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;linux-generic-hwe-24.04&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;커널 접미사 예&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;6.17.0-1011-oem&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;6.17.0-14-generic&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;대상&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;특정 OEM 파트너 기기 (Lenovo, Dell 등)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;일반 사용자 전체&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;포함 패치&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;해당 기기 전용 드라이버·패치 (카메라, 지문인식, 열 관리 등)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;범용 최신 하드웨어 지원&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;관리 주체&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Canonical OEM 팀 + OEM 파트너&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Canonical / Ubuntu 커뮤니티&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;업데이트 경로&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;OEM 파트너 저장소 (&lt;code&gt;oem.archive.ubuntu.com&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;기본 Ubuntu 저장소&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;HWE(Hardware Enablement) 커널은 LTS 버전에서 새 하드웨어를 지원하기 위해 최신 업스트림 커널을 백포팅한 것입니다.
OEM 커널은 여기에 더해 특정 기기에서만 필요한 패치가 추가로 적용됩니다.
즉, OEM 커널은 하드웨어 제조사와 Canonical이 협력하여, 해당 기기에서 &quot;설치 후 바로 동작(out-of-the-box)&quot;하는 경험을 제공하기 위한 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Intel Meteor Lake / Lunar Lake CPU를 탑재한 기기들은 HWE 커널에서 완전히 지원되지 않은 하드웨어가 있었습니다.
대표적인 예가 내장 카메라(MIPI/IPU7)와 일부 지문인식 센서입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;oem_커널이_자동으로_선택되는_원리&quot;&gt;OEM 커널이 자동으로 선택되는 원리&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ubuntu 인스톨러(Subiquity)는 설치 중에 &lt;code&gt;ubuntu-drivers&lt;/code&gt; 도구를 호출하여 현재 하드웨어를 감지합니다.
이때 DMI(System Management BIOS) 정보를 읽어 Canonical OEM 파트너 목록과 대조합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들어 Lenovo ThinkPad X1 Carbon Gen 13의 경우, 아래와 같은 메타패키지가 매칭됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Package: oem-sutton-dacia-meta
Modaliases: meta(dmi:*bvnLENOVO:bvrN4B*:pvrThinkPad*)
Depends: linux-oem-24.04c, oem-sutton-meta
Ubuntu-Oem-Kernel-Flavour: oem
Description: hardware support for Lenovo ThinkPad X1 2-in-1 G10, X1 Carbon G13&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;Modaliases&lt;/code&gt; 필드의 패턴(&lt;code&gt;dmi:*bvnLENOVO:bvrN4B*:pvrThinkPad*&lt;/code&gt;)이 현재 기기의 DMI 정보와 일치하면,
해당 메타패키지와 함께 OEM 커널이 자동으로 설치됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설치 흐름을 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Ubuntu 설치 중
└─ ubuntu-drivers 가 DMI 정보 확인
   └─ OEM 메타패키지 패턴 매칭
      └─ oem-sutton-dacia-meta 설치
         └─ linux-oem-24.04d 의존성으로 OEM 커널 설치
            └─ → 부팅 커널: 6.17.0-1011-oem&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;어떤 커널이 설치될지는 &lt;strong&gt;하드웨어가 Canonical OEM 파트너 목록에 등록되어 있느냐&lt;/strong&gt;에 달려 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음과 같이 Intel Ultra 7 258V CPU를 쓰는 기기라도 차이가 생길 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;기기&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;OEM 메타패키지&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;설치되는 커널&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Lenovo ThinkPad X1 Carbon G13&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;oem-sutton-dacia-meta&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;linux-oem-24.04d&lt;/code&gt; (OEM 커널)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Dell XPS 13 9350&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;oem-somerville-tyrogue-meta&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;linux-oem-24.04d&lt;/code&gt; (OEM 커널)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;OEM 파트너 목록에 없는 기기&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;없음&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;code&gt;linux-generic-hwe-24.04&lt;/code&gt; (HWE 커널)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래서 설치 시기와 모델에 따라 어느 커널이 설치 될지가 달랐던 것입니다.
&lt;a href=&quot;https://ubuntu.com/certified&quot;&gt;우분투 공식 인증기기&lt;/a&gt;라면 OEM 커널이 설치시에 바로 감지되어 설치될 가능성이 높습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 명령을 참고로 알아두시면 기기에 대한 정보를 파악하는데 도움이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ubuntu-drivers list-oem&lt;/code&gt; : 기기에 감지된 OEM 메타패키지&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sudo dmidecode -s system-product-name&lt;/code&gt; :  기기의 정확한 모델명&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;oem_커널을_수동으로_설치제거&quot;&gt;OEM 커널을 수동으로 설치/제거&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재 어떤 커널이 설치되어 있는지는 다음 명령으로 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;설치된 커널과 OEM 메타패키지 확인&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;# 현재 부팅된 커널
uname -r

# 설치된 커널 패키지 목록
dpkg -l | grep linux-image | grep &apos;^ii&apos;

# ubuntu-drivers가 감지하는 OEM 메타패키지 확인
ubuntu-drivers list-oem&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우분투 초기 설치 시점에 HWE 커널이 자동 선택되었지만, OEM 커널로 전환을 하고 싶다면 아래와 같이 수동으로 설치할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;OEM 커널 수동 설치&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;# 설치 가능한 OEM 메타패키지 목록 검색
apt search linux-oem-24.04

# OEM 커널 수동 설치 (24.04 기준)
sudo apt install linux-oem-24.04d&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞으로 업데이트에도 HWE 커널이 설치되지 않도록 하려면, HWE 커널의 메타 패키지를 제거하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;HWE 커널 제거&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;sudo apt remove linux-headers-generic-hwe-24.04&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;또는 HWE 커널의 메타 패키지를 보류(hold) 상태로 설정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;HWE 커널 보류 설정&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;sudo apt-mark hold linux-headers-generic-hwe-24.04&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;반대로 OEM 커널 없이 HWE 커널만 사용하고 싶다면, OEM 메타패키지를 제거하면 됩니다.
단, 해당 하드웨어에서 필요한 드라이버가 빠질 수 있으므로 주의가 필요합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고_자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.ubuntu.com/Kernel/OEMKernel&quot;&gt;Ubuntu OEM Kernel — Ubuntu Wiki&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ubuntu.com/certified&quot;&gt;Ubuntu Certified Hardware&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.ubuntu.com/IntelMIPICamera&quot;&gt;Intel MIPI Camera — Ubuntu Wiki&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Ubuntu 24.04 설치</title>
      <link>https://blog.benelog.net//installing-ubuntu-24.04.html</link>
      <pubDate>Thu, 9 Oct 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">installing-ubuntu-24.04.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;새 컴퓨터를 받아서 설치한 프로그램들을 정리해봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Ubuntu는 영문판으로 설치&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Desktop&lt;/code&gt;, &lt;code&gt;Download&lt;/code&gt; 같은 디렉토리가 영문으로 나오는 것이 편리해서&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;한글_설정&quot;&gt;한글 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;sudo apt install ibus-hangul&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;Settings&lt;/code&gt; 에서&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Regision &amp;amp; Language&lt;/code&gt; 에서 &lt;code&gt;Manage Installed Lanauges&lt;/code&gt; 에 &apos;Korean&apos; 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Keyboard&lt;/code&gt; &amp;gt; &lt;code&gt;Input Sources&lt;/code&gt; 에 &lt;code&gt;Korean(Hangul)&lt;/code&gt; 추가하고 &quot;&amp;#8230;&amp;#8203;&quot; 눌러서 &lt;code&gt;Preference&lt;/code&gt; 선택&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Hangul toggle key&lt;/code&gt; 에 &lt;code&gt;Shift + Space&lt;/code&gt; 지정&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;하드웨어_설정&quot;&gt;하드웨어 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;키보드_설정&quot;&gt;키보드 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;일부 키보드의 경우 F1~F12가 잘 인식되지 않은 경우도 있음. 해당 장비가 애플 키보드 타입으로 인식되고 자동 모드가 Fn가 기본적으로 눌러져있는 것으로 인식되기 때문. hid_apple 모듈의 fnmode 변수 설정으로 이 문제를 해결할 수 있음.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;현재 모드 확인 : &lt;code&gt;lsmod | grep hid_&lt;/code&gt; (디폴트는 자동 모드인 3)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;보통 2로 모드 설정을 하면 잘 되고, 그래도 안되면 disable인 3으로&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;일시적으로 설정 값이 변경하여 테스트 : &lt;code&gt;echo 2 | sudo tee /sys/module/hid_apple/parameters/fnmode&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;키보드 펑션키 모드 변경이 재부팅 후에도 적용되도록&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;echo &quot;options hid_apple fnmode=2&quot; | sudo tee /etc/modprobe.d/hid_apple.conf
sudo update-initramfs -u&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고자료&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sunyzero.tistory.com/290&quot; class=&quot;bare&quot;&gt;https://sunyzero.tistory.com/290&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://mathncode.tistory.com/25&quot; class=&quot;bare&quot;&gt;https://mathncode.tistory.com/25&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;웹_캠_설정&quot;&gt;웹 캠 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Intel Ultra 7 258v CPU를 쓰는 Dell XPS 13 plus 9350에서는 웹캠을 위한 드라이버 설치를 따로 해줘야했음.
같은 CPU를 쓰는 Lenovo Thinkpad X1 Carbon G13는 기본 설치만으로 잘 되었음.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dell XPS 13 plus 9350에서 웹캠이 안 잡힐 때 참고한 자료&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.ubuntu.com/IntelMIPICamera&quot; class=&quot;bare&quot;&gt;https://wiki.ubuntu.com/IntelMIPICamera&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.ubuntu.com/Dell&quot; class=&quot;bare&quot;&gt;https://wiki.ubuntu.com/Dell&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;cli_도구&quot;&gt;CLI 도구&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;terminator&quot;&gt;Terminator&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여러 창을 띄우기에 편한 터미널 프로그램&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;sudo apt install terminator&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;curl&quot;&gt;curl&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;sudo apt install curl&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;ifconfig&quot;&gt;ifconfig&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;sudo apt install net-tools&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;ssh_설정&quot;&gt;ssh 설정&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;sudo vi /etc/ssh/ssh_config&lt;/code&gt; 하여 아래를 마지막에 추가&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;HostkeyAlgorithms ssh-dss,ssh-rsa
KexAlgorithms +diffie-hellman-group1-sha1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://kr.analysisman.com/2018/08/linux-ssh.html&quot; class=&quot;bare&quot;&gt;https://kr.analysisman.com/2018/08/linux-ssh.html&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;git_gitk&quot;&gt;git / gitk&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;sudo apt-get install git gitk&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;github_ssh_설정&quot;&gt;GitHub SSH 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent&quot; class=&quot;bare&quot;&gt;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;oh_myzsh&quot;&gt;oh-myzsh&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo apt install zsh
curl -L http://install.ohmyz.sh | sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh&quot; class=&quot;bare&quot;&gt;https://github.com/robbyrussell/oh-my-zsh&lt;/a&gt; 참고 )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;terminator의 &lt;code&gt;Preference&lt;/code&gt; &amp;gt; &lt;code&gt;Profile&lt;/code&gt; &amp;gt; &lt;code&gt;Command&lt;/code&gt; 에서&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Run a custom command instead of my shell&lt;/code&gt; 체크&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Custom commands&lt;/code&gt; : &lt;code&gt;zsh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;scm_breeze&quot;&gt;scm_breeze&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;zsh에서 실행할 때의 기준&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;git clone https://github.com/scmbreeze/scm_breeze.git ~/.scm_breeze
~/.scm_breeze/install.sh
source &quot;${ZDOTDIR:-$HOME}/.zshrc
bash
source ~/.bashrc
exit&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( &lt;a href=&quot;https://github.com/ndbroadbent/scm_breeze&quot; class=&quot;bare&quot;&gt;https://github.com/ndbroadbent/scm_breeze&lt;/a&gt; 참고 )&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;개발_환경&quot;&gt;개발 환경&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여러 JDK를 설치하고 관리하는 방법은 SDKMAN + direnv를 추천 ( &lt;a href=&quot;https://blog.benelog.net/installing-jdk.html&quot; class=&quot;bare&quot;&gt;https://blog.benelog.net/installing-jdk.html&lt;/a&gt; 참조)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;sdkman&quot;&gt;SDKMAN&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;curl -s &quot;https://get.sdkman.io&quot; | bash
source &quot;$HOME/.sdkman/bin/sdkman-init.sh&quot;
sdk install java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sdkman.io/install&quot; class=&quot;bare&quot;&gt;https://sdkman.io/install&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;direnv&quot;&gt;direnv&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sudo apt install direnv&lt;/code&gt; 설치&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;~/.bashrc&lt;/code&gt; 와 &lt;code&gt;~/.zshrc&lt;/code&gt; 에 &lt;code&gt;eval &quot;$(direnv hook bash)&quot;&lt;/code&gt; 추가&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://direnv.net/docs/hook.html&quot; class=&quot;bare&quot;&gt;https://direnv.net/docs/hook.html&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;intellij&quot;&gt;IntelliJ&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo apt install fuse&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jetbrains.com/ko-kr/toolbox-app/&quot; class=&quot;bare&quot;&gt;https://www.jetbrains.com/ko-kr/toolbox-app/&lt;/a&gt; 에서 Toolbox로 설치&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Terminal에서 zsh 지정&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Tools&lt;/code&gt; &amp;gt; &lt;code&gt;Application Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Shell Path&lt;/code&gt; : &lt;code&gt;/bin/zsh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Plugin 설치&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7391-asciidoc&quot; class=&quot;bare&quot;&gt;https://plugins.jetbrains.com/plugin/7391-asciidoc&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/1065-checkstyle-idea&quot; class=&quot;bare&quot;&gt;https://plugins.jetbrains.com/plugin/1065-checkstyle-idea&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;node_js&quot;&gt;Node.js&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;nvm_설치&quot;&gt;nvm 설치&lt;/h4&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;~/.bash_profile, ~/.zshrc, ~/.profile,  ~/.bashrc 등에 추가&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;export NVM_DIR=&quot;$([ -z &quot;${XDG_CONFIG_HOME-}&quot; ] &amp;amp;&amp;amp; printf %s &quot;${HOME}/.nvm&quot; || printf %s &quot;${XDG_CONFIG_HOME}/nvm&quot;)&quot;
[ -s &quot;$NVM_DIR/nvm.sh&quot; ] &amp;amp;&amp;amp; \. &quot;$NVM_DIR/nvm.sh&quot; # This loads nvm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nvm-sh/nvm#installing-and-updating&quot; class=&quot;bare&quot;&gt;https://github.com/nvm-sh/nvm#installing-and-updating&lt;/a&gt; 참고&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;정보_관리_프로그램&quot;&gt;정보 관리 프로그램&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;크롬&quot;&gt;크롬&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;obsidian&quot;&gt;Obsidian&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;snap으로 설치하면 한글 문제가 있음. Appimage로 설치&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;pinta&quot;&gt;Pinta&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;snap install pinta&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;vidcutter&quot;&gt;VidCutter&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;snap install vidcutter&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ozmartian/vidcutter&quot; class=&quot;bare&quot;&gt;https://github.com/ozmartian/vidcutter&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;shotcut&quot;&gt;ShotCut&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;snap install shotcut --classic&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://shotcut.org/&quot; class=&quot;bare&quot;&gt;https://shotcut.org/&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;기타_참고&quot;&gt;기타 참고&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이전 버전에서 참고한 자료&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;무선_인터넷_안_잡힐_때&quot;&gt;무선 인터넷 안 잡힐 때&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;rfkill list all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://askubuntu.com/questions/380586/wireless-is-disabled-by-hardware-switch-wifi-doesnt-work-hard-blocked-yes&quot; class=&quot;bare&quot;&gt;http://askubuntu.com/questions/380586/wireless-is-disabled-by-hardware-switch-wifi-doesnt-work-hard-blocked-yes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://askubuntu.com/questions/847250/new-ubuntu-16-04-with-no-wifi/847282&quot; class=&quot;bare&quot;&gt;http://askubuntu.com/questions/847250/new-ubuntu-16-04-with-no-wifi/847282&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;nginx_설치_후_자동으로_시작_안하게&quot;&gt;NGINX 설치 후 자동으로 시작 안하게&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;sudo update-rc.d -f nginx disable&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;secure_boot&quot;&gt;Secure Boot&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.ubuntu.com/UEFI/SecureBoot&quot; class=&quot;bare&quot;&gt;https://wiki.ubuntu.com/UEFI/SecureBoot&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>카멜 케이스를 쓸 때 약어 표기</title>
      <link>https://blog.benelog.net//acronym-camel-case.html</link>
      <pubDate>Sat, 2 Dec 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">acronym-camel-case.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(이 글에서는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Camel_case&quot;&gt;위키페디아의 정의에 따라&lt;/a&gt; &apos;카멜 케이스&amp;#8217;를 UpperCamelCase와 lowerCamelCase를 포괄하는 개념으로 사용했습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자바나 코틀린에서 약어(acronym)가 클래스명이나 메서드명의 일부로 들어갈 때 최대 두 글자까지만 대문자로 표기하는 방식을 권장합니다.
비슷하게 카멜 케이스를 쓰는 자바스크립트와 같은 다른 언어에서도 이 관례가 무난하다고 생각합니다.
기존의 바꿀 수 없는 코드나 외부 라이브러리 사용 등으로 어쩔 수 없는 경우에는 전부 대문자로 쓰더라도 이를 최소화하는 노력을 하고 프로젝트 내부 문서로도 예외의 경우로 명시하면 좋습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 관례는 약어 전체를 대문자로 표기하는 관례에 비해서 다음과 같은 장점이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;연속된 약어가 조합될 경우 단어 사이의 구문이 더 잘 인지됩니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;예: &lt;code&gt;XMLRPCAPIURL&lt;/code&gt; vs &lt;code&gt;XmlRpcApiUrl&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;일관성을 유지하기가 더 쉽습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;전부 대문자로 표기해야할 단어들을 따로 기억할 필요가 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;사람마다 다르게 판단할 여지가 적은 단순한 규칙입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;영향력이 큰 책이나 가이드에서 지지받고 있는 규칙입니다. 새로 합류한 구성원도 따를 가능성이 높습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자바와 코틀린으로 나누어서 이 관례에 대해 참고할 수 있는 자료를 정리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;자바의_관례&quot;&gt;자바의 관례&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDK 내부에서는 이 관례가 통일되어 있지 않습니다.
다음과 같이 약어를 쓸 때 전부 대문자와 첫 글자만 대문자 표기가 혼재하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;java.net.HttpURLConnection (한 클래스 이름에서도 Http는 첫글자만 대문자, URL은 다 대문자로 표기했습니다.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;javax.xml.bind.annotation.XmlElement&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Effective Java의 3판에서는 약어라도 첫글자만 대문자로 하는 방식에 대한 지지도를 더 높인 느낌입니다. 2판의 &lt;code&gt;Upppercase may be more common&lt;/code&gt; 문구가 `Some programmers still use uppercase`로 바뀌었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;:&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/effective-java/camel-casing.png&quot; alt=&quot;camel-casing&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3판에서의 해당 단락의 전체 내용은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Item 68: Adhere to generally accepted naming conventions (원서 289~290 페이지)&lt;/div&gt;
&lt;blockquote&gt;
There is some disagreements as to whether acronyms should be uppercase or have only their first letter capitalized. While some programmers stil use uppercase, a strong argument can be made in favor of capitalizing only the first letter: even if multiple acronyms occur back-to-back, you can still tell where one word starts and the next word ends. Which class name would you rather see, HTTPURL or HttpUrl?
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;아이템 68: 일반적으로 통용되는 명명 규칙을 따르라 (번역서 382 페이지)&lt;/div&gt;
&lt;blockquote&gt;
약자의 경우 첫 글자만 대문자로 할지 전체를 대문자로 할지는 살짝 논란이 있다. 전체를 대문자로 쓰는 프로그래머도 있지만, 그래도 첫 글자만 대문자로 하는 쪽이 훨씬 많다. HttpUrl처럼 여러 약자가 혼합된 경우라도 각 약자의 시작과 끝을 명확히 알 수 있기 때문이다. (HTTPURL과 비교해보자)
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자바 초창기에 비해 점점 첫 글자만 대문자로 쓰는 점유율이 높아졌다고 해석될만도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링 프레임워크에서도 초창기에는 두 방식이 혼재하다가 점점 첫글자만 대문자로 쓰는 관례로 바뀐 것으로 보입니다.
URL 단어 표기는 다음과 같은 예시가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java&quot;&gt;URLEditor&lt;/a&gt; : 2002년 작성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-core/src/main/java/org/springframework/core/io/UrlResource.java&quot;&gt;UrlResource&lt;/a&gt; : 2003년 작성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/UrlTag.java&quot;&gt;UrlTag&lt;/a&gt; : 2009년 작성 추정&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;DAO, XML, SQL 등은 모두 처음부터 첫글자만 대문자로 표기된 것으로 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/JdbcDaoSupport.java&quot;&gt;JdbcDaoSupport&lt;/a&gt; : 2003년 처음 작성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-jdbc/src/main/java/org/springframework/jdbc/support/xml/SqlXmlValue.java&quot;&gt;SqlXmlValue&lt;/a&gt; : 2008년 작성 추정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-oxm/src/main/java/org/springframework/oxm/XmlMappingException.java&quot;&gt;XmlMappingException&lt;/a&gt; : 2009년 작성 추정&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;코틀린의_관례&quot;&gt;코틀린의 관례&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코틀린 언어 사이트의 코딩 컨벤션 가이드에서도 약어의 표기 방식에 언급하고 있습니다.
약어가 두 글자라면 다 대문자로 쓰고, 길어지면 한 글자만 대문자로 쓰라고 권장하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://kotlinlang.org/docs/coding-conventions.html#choose-good-names&quot; class=&quot;bare&quot;&gt;https://kotlinlang.org/docs/coding-conventions.html#choose-good-names&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Choose Good names&lt;/div&gt;
&lt;blockquote&gt;
When using an acronym as part of a declaration name, capitalize it if it consists of two letters (IOStream); capitalize only the first letter if it is longer (XmlFormatter, HttpInputStream).
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;좋은 이름을 선택해라&lt;/div&gt;
&lt;blockquote&gt;
선언된 이름의 일부에 약어가 들어 갈 때는 두 개의 문자로 구성된 약어는 대문자로 쓰고(IOStream) 긴 문자는 첫 글자만 대소문자로 쓰세요. (XmlFormatter, HttpInputStream).
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;언어 공식 사이트의 가이드에 있는 내용이므로 보편적으로 인지되고 유지될 가능성이 높은 관례입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>필독 개발자 온보딩 가이드</title>
      <link>https://blog.benelog.net//missing-readme.html</link>
      <pubDate>Thu, 23 Nov 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">missing-readme.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#감상&quot;&gt;감상&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#의견_메모&quot;&gt;의견 메모&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#추천자료&quot;&gt;추천자료&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;https://image.yes24.com/goods/119108069/XL&quot; alt=&quot;Cover&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;이미지 출처 : &lt;a href=&quot;https://www.yes24.com/Product/Goods/119108069&quot; class=&quot;bare&quot;&gt;https://www.yes24.com/Product/Goods/119108069&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;각 장의 참고문헌 목록을 옮겨 정리하는 것에 대해서는 책만 출판사(&lt;a href=&quot;https://www.onlybook.co.kr)에&quot; class=&quot;bare&quot;&gt;https://www.onlybook.co.kr)에&lt;/a&gt; 연락해서 게재 허락을 구했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;감상&quot;&gt;감상&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(내돈내산 후기입니다)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;많지 않은 분량으로 실무 개발자의 폭넓은 업무를 다루면서 결코 얄팍하지 않은 지식을 전달하는 책입니다.
협업, 설계, 구현, 테스트, 문서화, 긴급 대응, 경력 관리 등 다양한 분야에 대한 알찬 조언을 책 한권에 눌러 담았고 더 많은 분량을 학습하고 싶은 독자를 위한 참고자료들도 매 장마다 소개하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책은 신입 개발자를 대상으로 한 책으로 홍보되고 있지만 경력이 많은 개발자가 읽을만한 가치도 충분합니다. 저도 과거의 경험을 돌아보면서 생각을 정리할 수 있었습니다. 스스로 절실하게 얻은 교훈이 이 책에도 적혀 있는 보면 반가웠고 깊은 공감이 되는 문장들도 많이 만날수 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들면, Dark launching이나 Feature toggle 같이 제 경험에서 유용했던 기법이 개발 실무의 액기스를 전하는 이 책에서도 소개되니 반갑고 안심이 되었습니다. 긴급 대응을 위한 당번(on call) 제도와 같이 더 개선을 해야 할 요소도 독서 후에 더 크게 느껴지기도 했습니다. 전에는 썼지만 근래에는 활용하지 않았던 Liquibase를 통한 DB 스키마 관리도 다음 프로젝트에서는 다시 시도해봐야겠다는 생각도 듭니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;유용하다고 느낀 지침과 조언을 새겨두고 실천하기 위해 여러 번 읽어야겠다는 생각을 하면서 마지막 장을 덮었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;의견_메모&quot;&gt;의견 메모&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;책을 보다가 의견을 메모한 부분을 옮겨봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;p102&quot;&gt;p102&lt;/h3&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예외는 일찍 던지고 최대한 나중에 처리하자.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;최대한 나중에 처리하는 것보다는 적절한 추상화 레이어에서 처리하자는 것이 좋다고 생각한다. 예를 들면 사용자의 ID로 조회했을 때 DB 조회가 실패했을 때 생기는 `EmptyResultDataAccessException`을
어플리케이션 콜스택의 마지막에서 처리하기보다는 비지니스 로직을 다루는 레이어에서 `UserNotFoundException`과 같이 비지니스 맥락의 의미가 부여된 예외로 바꾸는 것이 좋을 때도 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;p204&quot;&gt;p204&lt;/h3&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;기능 브랜치 기반 개발은 트렁크의 코드가 사용자에게 릴르스하기엔 너무 불안정해서 트렁크를 안정화시키는 동안 개발자가 기능 개발을 수행할 수 없는 경우에 사용하는 방법이다. 고객이 각자 다른 버전의 소프트웨어를
사용하는 경우에 보편적으로 기능 브랜치 기반 개발 전략을 채택한다. 서비스 지향 시스템(service-oriented systems)은 일반적으로 트렁크 기반 개발 전략을 채택한다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;가장 보편적인 기능 브랜치 전략은 2010년 빈센트 드리센이 소개한 깃플로(Gitflow)라고 부르는 전략이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git-flow는 필요에 따라 팀 브랜치 정책의 종착역은 될수 있지만
시작점으로 권장하고 싶지는 않다. 자세한 의견은
&lt;a href=&quot;https://blog.benelog.net/rethink-about-git-flow&quot;&gt;Git-Flow에 대해서 다시
생각해보기&lt;/a&gt;라는 글로 따로 정리했다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;p212&quot;&gt;p212&lt;/h3&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;버전 제어 시스템을 릴리스 리포지토리처럼 사용할 수는 있지만 원래
용도라고 보기 어렵다. 버전 제어 시스템에서는 검색이나 배포 관련 기능을
별로 제공하지 않는다. 대규모 배포를 위해 만들어진 시스템이 아니므로 그런
상황에서는 문제를 일으킬 수도 있다. 버전 제어 시스템 머신과 동일한
머신이 개발자 체아크웃, 도구로부터의 요청, 배포 요청을 모두 담당하면
프로덕션 배포에 영향을 미칠 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Node.js나 Go언어의 오픈소스 생태계에서는 GitHub.com을 릴리스
리포지토리처럼 쓰는 관행이 굳어졌다. 하지만 사내에서라면 이를 분리하는
것이 좋다는 저자의 의견에 동의한다. 버전 관리 시스템은 GitHub
Enterprise를 쓰더라도 릴리스 저장소는 Nexus나 Artifactory를 쓰는 식이다.
분리하면 두 시스템을 각각 업그레이드하기에도 유리하다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;p213&quot;&gt;p213&lt;/h3&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;릴리스는 최대한 자주 수행하자. 릴리스 주기가 늘어지 거짓된 안정감을 심어줄 수 있다. 즉 릴리스 사이의 주지가 길면 변경사항을 테스트할 충분한 시간이 있는 것처럼 느껴지기 때문이다. 실제로 릴리스 주기를 짧게 가져가면 더 안정적인 소프트웨어를 구현할 수 있어 버그가 발견됐을 때 더 쉽게 처리할 수 있다. 매 주기마다 릴리스되는 변경사항의 수가 더 적으므로 각 릴리스의 위험도도 낮아진다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;공감한다. 1~2주에 한번 릴리스하는 조직과 한달에 한번 릴리스하는 조직을 비교했을 때 후자의 릴리스가 훨씬 힘겹고 위험한 일이 되는 현상을 몇 번 목격했다. 테스트와 모니터링의 자동화 수준이 높아져야 릴리스의 비용이 적어져서 더 자주 릴리스하게 될 수 있기도하다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;p212_2&quot;&gt;p212&lt;/h3&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;배포를 원자적으로 만드는 가장 쉬운 방법은 기존에 설치된 소프트웨어를 덮어쓰는 것이 아니라 다른 경로에 소프트웨어를 설치하는 것이다. 일단 패키지가 설치된 다음에는 단축 아이콘이나 심볼릭 링크를 이용해 교체하면
된다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(의견) 많이 쓰던 기법이다. 사내 배포 시스템에서 디폴트로 제공이
되기도한다. 이동욱 님이 발표한 &lt;a href=&quot;https://youtu.be/_nkJkWVH-mo?t=2452&quot;&gt;우아한스프링배치&lt;/a&gt; 발표에서도 이
기법을 응용한 무중단 배포를 소개하고 있다. 배포된 최신 버전 파일로  심볼릭 링크를 교체하고 (&lt;code&gt;ln -s -f v2.jar app.jar&lt;/code&gt;) readlink 명령어로 그 파일을 실행하는(&lt;code&gt;java -jar ${readlink ./app.jar&lt;/code&gt;}) 이용하는 방식이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;p219&quot;&gt;p219&lt;/h3&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한 번에 모조리 새 코드로 전환하는 일은 위험하다. 테스트를 아무리 많이 해도 버그 발생 가능성을 없앨 수는 없으며 한 번에 모든 사용자에게 코드를 롤아웃하면 모두가 동시에 문제를 겪을 수 있다. 따라서 변경 사항을
점진적으로 롤아웃하고 시스템 상태 지표를 모니터링 하는 편이 좋다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;규모가 있는 시스템을 변경하는 개발자라면 절실하게 읽어야할 문장이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;p311&quot;&gt;p311&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;데이터베이스 스키마 마이그레이션 도구인 Liquibase 등을 소개함.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;데이터베이스와 애플리케이션 수명주기를 결합해서는 안 된다. 애플리케이션 배포 과정에서 스키마를 마이그레이션하는 것은 위험하다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;의도한 바은 아니지만 JPA의 스키마 관리 기능이 운영환경에서 실행되어서 발생하는 장애를 종종 전해듣는다. 스프링 부트의 &lt;code&gt;spring.jpa.hibernate.ddl-auto&lt;/code&gt; 같은 옵션도 Local PC에 DB가 따로 설치되어 있거나 Embeded DB를 쓸때만 활성화해야 한다고 생각한다. 공용 개발 DB에서부터는 Liquibase와 같은 스키마 관리 도구를 활용하는 것이 좋다. 공용 개발 DB라도 여러 개가 있는 것이 다양한 테스트를 하는데 유리하고, 개발 DB가 여러개가 되면 스키마 관리 도구의 필요성이 높아진다.
Oracle 등 개발자가 여러 인스턴스를 설치하는데 제약이 있는 DB를 오랫동안 써 온 개발자는 이런 도구의 필요성을 느끼거나 적용할 수 있는 기회를  얻기가 어려울 수도 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;운영DB는 DBA가 따로 SQL파일을 실행해서 스키마를 반영하는 회사라도 Liquibase는 충분히 사용 가능하다. Liquibase는 오프라인 지원 기능이 있어서 실제 DB에 스키마 변경을 실행하지 않고 DDL을 파일로 뽑아낼수도 있다. 예를  들어 스키마 버전 1.0.0-RC1과 1.0.0-RC2 버전 사이에 변경된 컬럼을 반영하는 DDL을 담은 .sql파일을 뽑을 때는 아래 명령어로 가능하다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;rm target/release.csv
mvn liquibase:updateSQL -Dliquibase.url=&apos;offline:mysql?changeLogFile=target/release.csv&apos; -Dliquibase.toTag=1.0.0-RC1
mvn liquibase:updateSQL -Dliquibase.url=&apos;offline:mysql?changeLogFile=target/release.csv&apos; -Dliquibase.toTag=1.0.0-RC2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;추천자료&quot;&gt;추천자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책에서는 각 장의 끝무렵에 ’레벨업을 위한 읽을 거리’로 해당 장에서 다룬 주제를 더 깊이 있게 학습하는데 도움이 되는 자료를 추천했습니다.
여러 번 언급되는 구글의 SRE 관련 책들은 &lt;a href=&quot;https://sre.google/books/&quot; class=&quot;bare&quot;&gt;https://sre.google/books/&lt;/a&gt; 에서 무료로 열람도 가능합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;표기된 제목이 여러가지 이유로 달라진 책들은 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[8장] &lt;a href=&quot;https://www.yes24.com/Product/Goods/11406822&quot;&gt;Continuous Delivery: 신뢰할 수 있는 소프트웨어 출시&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;번역판 초판에는 이 책 제목이 `신뢰할 수 있는 소프트웨어 출시&apos; 였으나 중쇄를 하면서 원서와 동일하게 제목이 바뀌었다고
함.(&lt;a href=&quot;https://www.facebook.com/fupfin.geek/posts/pfbid02ksHoMdnvhkJkN9xWS4qQimt541smxX3oscttadaYVMHLxtQ6CZtkKsHEw82LjXs3l?comment_id=328411842995894&quot;&gt;박성철 님의 관련 댓글&lt;/a&gt;) ’개발자 온보딩 가이드’에서는 번역판 초판의 제목으로 표기되어 있어 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[8장] &lt;a href=&quot;https://smartstore.naver.com/yes24book/products/9565539174&quot;&gt;Release의 모든 것 - 대규모 웹 분산 시스템을 위한 운영 고려 설계&lt;/a&gt; (번역서 2판. 2023년 11월 출간)&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;번역서 1판 : &lt;a href=&quot;https://www.yes24.com/Product/Goods/2753365&quot;&gt;Release it:성공적인 출시를 위한 소프트웨어 설계와 배치&lt;/a&gt; (2007년 출판) 이 제목으로 책에서는 표기&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[10장] Elements of Style : 영어 글쓰기에 대한 유명한 책으로 번역판도 여러 번 나옴.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001729444&quot;&gt;글쓰기의 요소(삽화판)&lt;/a&gt; (2016년 출판, 장영준 역) : ’개발자 온보딩 가이드’에서 소개한 판인데 현재 품절이라 중고로만 구할 수 있음.
&lt;a href=&quot;https://www.yes24.com/Product/Goods/74241463&quot;&gt;Yes24에서 원서&lt;/a&gt;가 같은 삽화가 들어간 판으로는 배송비 고려하면 가장 싸게 구할 수 있는
경로인 듯함.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/books/details/%EC%9C%8C%EB%A6%AC%EC%97%84_%EC%8A%A4%ED%8A%B8%EB%A0%81%ED%81%AC_2%EC%84%B8_%EA%B8%80%EC%93%B0%EA%B8%B0%EC%9D%98_%EC%9A%94%EC%86%8C&quot;&gt;글쓰기의 요소&lt;/a&gt; (2016년 출판. 김영일 역, 전자책)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/2746329&quot;&gt;영어 글쓰기의 기본&lt;/a&gt;
(2007년 출판, 조서연 공역) : 번역서와 원서가 한권으로 묶여서 나온 판이다. 현재도 구매 가능하다. 역자가 쓴 저서인 &lt;a href=&quot;https://www.yes24.com/Product/Goods/4671236&quot;&gt;영어 글쓰기의 기본 2&lt;/a&gt;도 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;2장_역량을_높이는_의식적_노력&quot;&gt;2장 역량을 높이는 의식적 노력&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/4045732&quot;&gt;프로그래머의 길,멘토에게 묻다&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/95735260&quot;&gt;나는 왜 도와달라는 말을 못할까: 부담은 줄이고 성과는 높이는 부탁의 기술&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;짝 프로그래밍&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/2126201&quot;&gt;익스트림 프로그래밍&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://martinfowler.com/articles/on-pair-programming.html&quot;&gt;On Pair Programming&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;가면 증후군이나 더닝 크루거 효과에 대한 참고자료&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)자존감은 어떻게 시작되는가: 당신의 인생을 결정짓는 자세의 차이&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/36962337&quot;&gt;종이책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/books/details/%EC%97%90%EC%9D%B4%EB%AF%B8_%EC%BB%A4%EB%94%94_%EC%9E%90%EC%A1%B4%EA%B0%90%EC%9D%80_%EC%96%B4%EB%96%BB%EA%B2%8C_%EC%8B%9C%EC%9E%91%EB%90%98%EB%8A%94%EA%B0%80?id=IFcxDwAAQBAJ&quot;&gt;전자책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;3장_코드와_함께_춤을_레거시_코드에_임하는_우리의_자세&quot;&gt;3장 코드와 함께 춤을: 레거시 코드에 임하는 우리의 자세&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/64586851&quot;&gt;레거시 코드 활용 전략: 손대기 두려운 낡은 코드, 안전한 변경과 테스트 기법&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.amazon.com/Legacy-Code-Programmers-Toolbox-Professionals/dp/1691064130&quot;&gt;The Legacy Code Programmer’s Toolbox: Practical Skills for Software Professionals Working with Legacy Code&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/89649360&quot;&gt;리팩터링 2판: 코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/16928943&quot;&gt;맨먼스 미신&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;4장_운영_환경을_고려한_코드_작성_개발환경과_프로덕션_환경은_엄연히_다르다&quot;&gt;4장 운영 환경을 고려한 코드 작성: 개발환경과 프로덕션 환경은 엄연히 다르다.&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/44130507&quot;&gt;Code Complete 코드 컴플리트 2: 더 나은 소프트웨어 구현을 위한 실무 지침서&lt;/a&gt; : 8장에서 방어적 프로그래밍에 대해서 다룸.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/11681152&quot;&gt;Clean Code:소프트웨어 장인 정신&lt;/a&gt; : 7장과 8장에서 예외처리와 경계에 대해서 다룸&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://aws.amazon.com/builders-library/&quot;&gt;The Amazon Builders’ Library&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/105804670&quot;&gt;SRE를 위한 시스템 설계와 구축: 구글이 공개하는 SRE 모범 사례와 설계, 구현, 운영 노하우&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/57979286&quot;&gt;사이트 신뢰성 엔지니어링: 구글이 공개하는 서비스 개발과 운영 노하우&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;5장_피할_수_없는_코드_의존성의_관리_복잡한_프로그램을_짜봐야_비로서_깨닫는_의존성이_진실&quot;&gt;5장 피할 수 없는 코드 의존성의 관리: 복잡한 프로그램을 짜봐야 비로서 깨닫는 의존성이 진실&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://semver.org/&quot;&gt;시맨틱 버저닝 스펙&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://peps.python.org/pep-0440/&quot;&gt;PEP 440 – Version Identification and Dependency Specification&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;6장_테스트_개발자의_든든한_지원군&quot;&gt;6장 테스트! 개발자의 든든한 지원군&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/104084175&quot;&gt;단위 테스트:생산성과 품질을 위한 단위 테스트 원칙과 패턴&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/12246033&quot;&gt;테스트 주도 개발&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/107077663&quot;&gt;실용주의 프로그래머&lt;/a&gt;: 속성 기반 테스팅(Property based testing)절 살펴보기&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/14829054&quot;&gt;탐험적 테스팅: 배우고 통찰하며 개선하는 소프트웨어 테스트&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;7장_올바로_주고받는_코드_리뷰_원만한_팀_협업과_높은_코드_품질을_목표로&quot;&gt;7장 올바로 주고받는 코드 리뷰: 원만한 팀 협업과 높은 코드 품질을 목표로&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://google.github.io/eng-practices/review/&quot;&gt;구글의 Code Review Developer Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/14759898&quot;&gt;하버드 피드백의 기술: 밀어붙이는 피드백에서 끌어당기는 피드백으로&lt;/a&gt;
(&lt;a href=&quot;https://play.google.com/store/books/details/%EB%8D%94%EA%B8%80%EB%9F%AC%EC%8A%A4_%EC%8A%A4%ED%86%A4_%EC%89%B4%EB%9D%BC_%ED%9E%8C_%ED%95%98%EB%B2%84%EB%93%9C_%ED%94%BC%EB%93%9C%EB%B0%B1%EC%9D%98_%EA%B8%B0%EC%88%A0?id=6RirBAAAQBAJ&quot;&gt;Google
play 이북&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;8장_고객_앞으로_소프트웨어_전달_마침내_프로덕션_환경에_안착시킬_소프트웨어의_종착지&quot;&gt;8장 고객 앞으로! 소프트웨어 전달: 마침내 프로덕션 환경에 안착시킬 소프트웨어의 종착지&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/33057253&quot;&gt;팀을 위한 Git: Git 워크플로우를 효율적으로 만드는 사용자 중심 접근법&lt;/a&gt; (Yes24 전자책)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/11406822&quot;&gt;Continuous Delivery: 신뢰할 수 있는 소프트웨어 출시&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/57979286&quot;&gt;사이트 신뢰성 엔지니어링: 구글이 공개하는 서비스 개발과 운영 노하우&lt;/a&gt; : 8장에 릴리스 엔지니어링 참조&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://aws.amazon.com/ko/builders-library/&quot;&gt;Amazon Builders&apos; Library&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://smartstore.naver.com/yes24book/products/9565539174&quot;&gt;Release의 모든 것 - 대규모 웹 분산 시스템을 위한 운영 고려 설계&lt;/a&gt; (번역서 2판. 2023년 11월 출간)&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;번역서 1판 : &lt;a href=&quot;https://www.yes24.com/Product/Goods/2753365&quot;&gt;Release it:성공적인 출시를 위한 소프트웨어 설계와 배치&lt;/a&gt; (2007년 출판)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;원서 2판 : &lt;a href=&quot;https://www.amazon.com/Release-Design-Deploy-Production-Ready-Software/dp/1680502395/&quot;&gt;Release It!: Design and Deploy Production-Ready Software&lt;/a&gt; (2018년 출간)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;9장_긴급대응_온콜_업무&quot;&gt;9장 긴급대응 온콜 업무&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://increment.com/on-call/when-the-pager-goes-off/&quot;&gt;What happens when the pager goes off?&lt;/a&gt; : 책에서 발췌한 장애 대응 5단계&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/57979286&quot;&gt;사이트 신뢰성 엔지니어링: 구글이 공개하는 서비스 개발과 운영 노하우&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;4장: SLI, SLO를 직접 정의해야할 때 참조&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;11장, 13장, 14장, 15장 : 온콜, 비상 대기, 장애 처리, 포스트터모텀&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;10장_견고한_소프트웨어를_위한_기술_설계_절차&quot;&gt;10장 견고한 소프트웨어를 위한 기술 설계 절차&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(동영상)&lt;a href=&quot;https://www.youtube.com/watch?v=f84n5oFoZBc&quot;&gt;Hammock Driven Development - Rich Hickey&lt;/a&gt; : 복잡한 소프트웨어 설계 과정을 가장 잘 설명한 자료라고 소개됨.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)대규모 오픈소스 프로젝트의 설계 과정&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/python/peps&quot;&gt;파이썬 개선 제안(PEP)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cwiki.apache.org/confluence/display/kafka/kafka+improvement+proposals&quot;&gt;카프카
개선 제안(KIP)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rust-lang/rfcs&quot;&gt;러스트 검토 요청(RFCs)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://wecode.wepay.com/posts/effective-software-design-documents&quot;&gt;Effective Software Design Documents&lt;/a&gt; : 위페이가 설계를 수행하는 방법과 그 방법이 어떻게 개선돼왔는지를 설명&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/wepay/design_doc_template&quot;&gt;설계 문서 템플릿&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)Elements of Style&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001729444&quot;&gt;글쓰기의 요소(삽화판)&lt;/a&gt; (2016년 출판, 장영준 역)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/books/details/%EC%9C%8C%EB%A6%AC%EC%97%84_%EC%8A%A4%ED%8A%B8%EB%A0%81%ED%81%AC_2%EC%84%B8_%EA%B8%80%EC%93%B0%EA%B8%B0%EC%9D%98_%EC%9A%94%EC%86%8C&quot;&gt;글쓰기의 요소&lt;/a&gt; (2016년 출판. 김영일 역, 전자책)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/2746329&quot;&gt;영어 글쓰기의 기본&lt;/a&gt;
(2007년 출판, 조서연 공역) : 번역서와 원서가 한권으로 묶여서 나온 판&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/2774464&quot;&gt;글쓰기 생각쓰기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)폴 그레이엄(Paul Grahm)의 글쓰기에 대한 기고&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.paulgraham.com/useful.html&quot;&gt;How to Write Usefully&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.paulgraham.com/talk.html&quot;&gt;Write Like You Talk&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;11장_소프트웨어_수명주기를_고려한_진화하는_아키텍처_구현&quot;&gt;11장 소프트웨어 수명주기를 고려한 진화하는 아키텍처 구현&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)진화적 아키텍처: 피트니스 함수, 거버넌스 자동화를 활용해 생산성 높은 소프트웨어 구축하기&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/121961003&quot;&gt;종이책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/books/details/%EB%8B%90_%ED%8F%AC%EB%93%9C_%EC%A7%84%ED%99%94%EC%A0%81_%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98?id=6w_UEAAAQBAJ&quot;&gt;전자책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`필독! 개발자 온보딩 가이드&apos; 본문에서는 원서가 소개되어 있는데, 2023년 8월에 번역판이 출판되었다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/25100510&quot;&gt;도메인 주도 설계 구현: Implementing Domain-Driven Design&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.amazon.com/Philosophy-Software-Design-2nd-ebook/dp/B09B8LFKQL/&quot;&gt;A Philosophy of Software Design, 2ED&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.amazon.com/Elements-Clojure-Zachary-Tellman/dp/0359360580/&quot;&gt;Elements of Clojure&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(동영상)(&lt;a href=&quot;https://www.youtube.com/watch?v=SxdOUGdseq4)&quot;&gt;Simple Made Easy - Rich Hickey
(2011)&lt;/a&gt; : 간결성, 복잡성, 손쉬움, 좋은 소프트웨어를 구현하는 방법에 대해서 설명&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.amazon.com/Data-Mesh-Zhamak-Dehghani-ebook/dp/B09V4KWWJ8/&quot;&gt;Data Mesh&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/595ref=sr_1_1?crid=3UIMYGOHHDTLL&amp;amp;keywords=data+mesh&amp;amp;qid=1698069866&amp;amp;sprefix=data+me%2Caps%2C261&amp;amp;sr=8-166585&quot;&gt;데이터 중심 애플리케이션 설계: 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운
시스템을 지탱하는 핵심 아이디어&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;12장_효율적인_협업을_위한_애자일_문화&quot;&gt;12장 효율적인 협업을 위한 애자일 문화&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://agilemanifesto.org/principles.html&quot;&gt;Principles behind the Agile Manifesto&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(웹)&lt;a href=&quot;https://www.atlassian.com/agile&quot;&gt;Atlassian의 애자일 문서&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;13장_관리자_팀장_상사와_함께_일하기&quot;&gt;13장 관리자, 팀장, 상사와 함께 일하기&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(책)개발 7년차, 매니저 1일차: 개발만 해왔던 내가, 어느 날 갑자기 ‘팀’을 맡았다!&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/87336637&quot;&gt;종이책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/books/details/%EC%B9%B4%EB%AF%B8%EC%9C%A0_%ED%91%B8%EB%A5%B4%EB%8B%88%EC%97%90_%EA%B0%9C%EB%B0%9C_7%EB%85%84%EC%B0%A8_%EB%A7%A4%EB%8B%88%EC%A0%80_1%EC%9D%BC%EC%B0%A8?id=Qbf_DwAAQBAJ&quot;&gt;전자책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.amazon.com/Elegant-Puzzle-Systems-Engineering-Management-ebook/dp/B07QYCHJ7V/&quot;&gt;An
Elegant Puzzle: Systems of Engineering Management&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)일의 99%는 피드백이다: 하버드 협상연구소에서 알려주는 대화의 기술&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/102003851&quot;&gt;종이책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/books/details/%EB%8D%94%EA%B8%80%EB%9F%AC%EC%8A%A4_%EC%8A%A4%ED%86%A4_%EC%89%B4%EB%9D%BC_%ED%9E%8C_%EC%9D%BC%EC%9D%98_99_%EB%8A%94_%ED%94%BC%EB%93%9C%EB%B0%B1%EC%9D%B4%EB%8B%A4?id=UccyEAAAQBAJ&quot;&gt;전자책&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.amazon.com/Managing-Up-Move-Work-Succeed-ebook/dp/B07BB4QFDF/&quot;&gt;Managing
Up: How to Move up, Win at Work, and Succeed with Any Type of Boss&lt;/a&gt; :상사나 관리자의 개성을 어떻게 고려해야하는지, 엄격한 관리자에 대응하는 법, 이직하는 방법 등을 다룸.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(책)&lt;a href=&quot;https://www.yes24.com/Product/Goods/61333181&quot;&gt;하이 아웃풋 매니지먼트: 어떻게 성과를 높일 것인가&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Git-Flow에 대해서 다시 생각해보기</title>
      <link>https://blog.benelog.net//rethink-about-git-flow.html</link>
      <pubDate>Mon, 23 Oct 2023 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">rethink-about-git-flow.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git의 브랜치 전략으로 Git-flow가 가장 유명하고 자주 언급됩니다.
그런데 이 전략은 복잡도와 실행하는 난이도가 높은 편입니다.
Git-flow를 쓰고 있다고 주장하는 조직에서도 창시자 Vincent가 제안한 원래의 프로세스를 그대로 쓰는 경우는 드물다고 느껴집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git-flow가 특히 인터넷 서비스 개발에서 보편적으로 권장할만한 전략일지는 저는 의문입니다.
브랜치 전략은 복잡하게 시작해서 단순하게 줄여나가기 보다는 단순하게 시작해서 필요에 따라 복잡도를 늘여가는 편이 시행착오가 적다고 생각합니다.
여러 버전을 동시에 운영하고 백포트 패치를 해야하는 솔루션 개발에서는 Git-flow가 적합한 조직이 있을법도 합니다.
즉, Git-flow는 필요에 따라 종착역은 될수 있지만 시작점으로 권장하고 싶은 정책은 아닙니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git-flow가 그 이름 때문에 필요 이상으로 권장되고 있다고 저는 생각합니다. Git이 지금보다 대중화되지 않았을 때 제안된 모델이고 그 당시 다른 유명한 모델이 없겠기에 Git-flow라고 불리지 않았을까 추정하기도합니다. 만약 창시자의 이름을 딴 Vincent-flow정도의 이름이였다면 많은 사람들이 따르려고 생각하지 않았을지도 모릅니다. Git-flow가 유용해보인다면 그 이름이 Vincent-flow라도 마찬가지일지를 한번 돌아봐도 이름에 과도하게 끌린건 아닌지 돌아보는데 도움이 될듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;뱅크 샐러드에서는 Git-flow보다 더 단순한 전략으로 리뷰-배포 프로세스를 개선했다는 사례도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.banksalad.com/tech/become-an-organization-that-deploys-1000-times-a-day/&quot; class=&quot;bare&quot;&gt;https://blog.banksalad.com/tech/become-an-organization-that-deploys-1000-times-a-day/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;먼저 Git-flow를 활용한다면 하나의 기능을 배포하는 데 있어서 무려 5번의 branch switching이 필요하고, 6번의 Pull Request와 이에 따른 6번의 Code Review가 필요했습니다. 이 말은 다르게 말하면 코드 오너가 6번이나 코드를 리뷰하고 승인을 해줘야 한다는 것이죠. 이렇게 복잡한 프로세스는 자연스럽게 배포를 귀찮은 존재로 만듭니다. 이로 인해 간단한 수정 사항의 경우 develop branch에 merge 한 뒤 배포하지 않는 일이 종종 발생하기 시작했습니다. 이런 배포되지 않은 변경 사항이 쌓이면서 나중에 필요할 때 한 번에 너무 많은 변경사항을 포함한, 부담스러운 배포를 진행해야만 했습니다. 배포 시 변경 사항이 많을수록 장애가 발생할 수 있는 확률은 당연히 증가하기 때문에 개발자에게는 배포는 무서운 존재가 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 &lt;a href=&quot;https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/&quot;&gt;GitLab-flow&lt;/a&gt;정도면 많은 사람이 협업하는 인터넷 서비스를 개발하는 브랜치 전략으로도 괜찮다고 생각합니다. 이를 참고해서 필요한 나름의 정책을 추가로 정의할 수도 있겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git-flow의 창시자 Vincent Driessen는 그의 아티클에 10년간의 회고를 덧붙였습니다. 거기서 지속적인 배포를 하는 프로젝트에서는 GitHub-flow와 같은 더 단순한 모델을 권장한다는 언급을 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://nvie.com/posts/a-successful-git-branching-model/&quot; class=&quot;bare&quot;&gt;https://nvie.com/posts/a-successful-git-branching-model/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If your team is doing continuous delivery of software, I would suggest to adopt a much simpler workflow (like GitHub flow) instead of trying to shoehorn git-flow into your team.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;귀사의 팀이 지속적으로 소프트웨어를 제공하고 있다면, 귀사의 팀에 git-flow를 도입하는 대신 훨씬 간단한 워크플로우(GitHub flow와 같은)를 채택하는 것을 제안합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(번역은 파파고에게 맡겼습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;많은 조직에서는 특히 운영 환경에서는 지속적인 배포(continuous delivery)와는 먼 프로세스를 택하고 있을 수도 있습니다. 그럼에도 개발과 테스트 환경에서라도 자주 활발히 통합되어 테스트되고 리뷰되는걸 추구한다면 Vincent의 회고를 더 깊이 새겨둘만합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 문장으로 Vincent의 10년만의 회고는 끝이 납니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To conclude, always remember that panaceas don&amp;#8217;t exist. Consider your own context. Don&amp;#8217;t be hating. Decide for yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;결론적으로, 만병통치약은 존재하지 않는다는 것을 항상 기억하세요. 자신만의 맥락을 고려하세요. 미워하지 말고, 스스로 결정하세요.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Jackson으로 파싱한 JSON 속성값을 생성자로 전달하기</title>
      <link>https://blog.benelog.net//jackson-with-constructor.html</link>
      <pubDate>Sun, 15 Mar 2020 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">jackson-with-constructor.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#jackson에서_no_creators_like_default_construct_exist_에러_메시지&quot;&gt;1. Jackson에서 &lt;code&gt;(no Creators, like default construct, exist)&lt;/code&gt; 에러 메시지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#생성자로_json_속성값을_전달하는_방법들&quot;&gt;2. 생성자로 JSON 속성값을 전달하는 방법들&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#jsoncreator&quot;&gt;2.1. &lt;code&gt;@JsonCreator&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#constructorproperties&quot;&gt;2.2. &lt;code&gt;@ConstructorProperties&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#parameternamemodule_활용&quot;&gt;2.3. ParameterNameModule 활용&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#예제_소스_저장소&quot;&gt;3. 예제 소스 저장소&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;변경이력&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2023.06.25 : Spring Boot Gradle plugin에서 &lt;code&gt;-parameter&lt;/code&gt; 옵션을 넣어주는 소스로의 링크 변경&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2022.05.07 : 예제 프로젝트를 JDK17로 변경&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;jackson에서_no_creators_like_default_construct_exist_에러_메시지&quot;&gt;1. Jackson에서 &lt;code&gt;(no Creators, like default construct, exist)&lt;/code&gt; 에러 메시지&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;파싱하고자하는 JSON&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;{
    &quot;accessDateTime&quot;: &quot;2019-10-10T11:14:16Z&quot;,
    &quot;ip&quot;: &quot;175.242.91.54&quot;,
    &quot;username&quot;: &quot;benelog&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위와 같은 JSON을 파생해서 아래와 같이 setter가 없는 객체에 집어 넣고 싶은 경우가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;파싱한 결과를 넣을 클래스&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class AccessLog {
    private final Instant accessDateTime;
    private final String ip;
    private final String username;

    public AccessLog(Instant accessDateTime, String ip, String username) {
        this.accessDateTime = accessDateTime;
        this.ip = ip;
        this.username = username;
    }

    public Instant getAccessDateTime() {
        return accessDateTime;
    }

    public String getIp() {
        return ip;
    }

    public String getUsername() {
        return username;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jackson 라이브러리로 JSON을 파싱하는 테스트 코드를 아래처럼 작성했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;테스트 코드&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;class ConstructorPropertiesTest {
    @Test
    void parse() throws JsonProcessingException {
        var json = &quot;&quot;&quot;
            {
            &quot;accessDateTime&quot;: &quot;2019-10-10T11:14:16Z&quot;,
            &quot;ip&quot;: &quot;175.242.91.54&quot;,
            &quot;username&quot;: &quot;benelog&quot;
            }
            &quot;&quot;&quot;;

        var objectMapper = new ObjectMapper()
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .registerModule(new JavaTimeModule());

        AccessLog accessLog = objectMapper.readValue(json, AccessLog.class);

        then(accessLog.getAccessDateTime()).isEqualTo(&quot;2019-10-10T11:14:16Z&quot;);
        then(accessLog.getIp()).isEqualTo(&quot;175.242.91.54&quot;);
        then(accessLog.getUsername()).isEqualTo(&quot;benelog&quot;);;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 코드를 실행하면 다음의 Exception이 떨어집니다.
&lt;code&gt;(no Creators, like default construct, exist)&lt;/code&gt; 이 핵심적인 메시지입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `net.benelog.jackson.ConstructorPropertiesTest$AccessLog` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)&quot;{
&quot;accessDateTime&quot;: &quot;2019-10-10T11:14:16Z&quot;,
&quot;ip&quot;: &quot;175.242.91.54&quot;,
&quot;username&quot;: &quot;benelog&quot;
}
&quot;; line: 2, column: 1]

	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1592)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1058)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3214)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3182)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JSON을 파싱한 결과를 전달할 적절한 생성자를 찾지 못했을 때 발생하는 에러입니다.
이 문제를 해결하는 방법을 정리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;생성자로_json_속성값을_전달하는_방법들&quot;&gt;2. 생성자로 JSON 속성값을 전달하는 방법들&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jsoncreator&quot;&gt;2.1. &lt;code&gt;@JsonCreator&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jackson에서 제공하는 &lt;code&gt;@JsonCreator&lt;/code&gt;, &lt;code&gt;@JsonProperty&lt;/code&gt; 를 값을 전달할 생성자와 메서드 파라미터에 붙입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;AccessLog의 생성자에 &lt;code&gt;@JsonCreator&lt;/code&gt; 선언&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class AccessLog {

    // 멤버 변수 선언 생략

    @JsonCreator
    public AccessLog(
        @JsonProperty(&quot;accessDateTime&quot;) Instant accessDateTime,
        @JsonProperty(&quot;ip&quot;) String ip,
        @JsonProperty(&quot;username&quot;) String username) {

        this.accessDateTime = accessDateTime;
        this.ip = ip;
        this.username = username;
    }

    // getter 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JSON의 속성명과 객체의 멤버변수명이 다를 때도 자연스럽게 활용할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;생성자가 여러 개일 때 Jackson에서 사용할 생성자를 명시적으로 지정할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jackson에 의존적인 방법입니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jar파일로 배포하는 클래스 안에서 이 방법을 사용하려면 Jackson에 대한 의존성이 추가됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JSON 파싱 라이브러리를 교체한다면 전체 클래스를 수정해야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;constructorproperties&quot;&gt;2.2. &lt;code&gt;@ConstructorProperties&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDK 1.6부터 제공되었던 &lt;code&gt;@java.beans.ConstructorProperties&lt;/code&gt; 은 생성자의 파라미터 이름을 지정하는 표준적인 방법입니다.
이를 활용하면 생성자의 파라미터 이름을 Reflection API를 통해서 알 수 있습니다.
Jackson은 2.7.0버전부터 &lt;code&gt;@ConstructorProperties&lt;/code&gt; 를 인지합니다. ( &lt;a href=&quot;https://github.com/fasterxml/jackson-databind/issues/905&quot; class=&quot;bare&quot;&gt;https://github.com/fasterxml/jackson-databind/issues/905&lt;/a&gt; 참조)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;생성자에 &lt;code&gt;@ConstructorProperties&lt;/code&gt; 으로 파라미터의 이름을 지정하면, Jackson에서는 동일한 이름의 JSON 속성값을 생성자로 넘겨줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;AccessLog의 생성자에 `@ConstructorProperties`로 속성명 지정&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import java.beans.ConstructorProperties;

public class AccessLog {

    // 멤버 변수 선언 생략

    @ConstructorProperties({&quot;accessDateTime&quot;, &quot;ip&quot;, &quot;username&quot;})
    public AccessLog(Instant accessDateTime, String ip, String username) {
        this.accessDateTime = accessDateTime;
        this.ip = ip;
        this.username = username;
    }

    // getter 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Lombok을 활용한다면 이 과정을 더 편하게 할 수 있습니다.
&lt;code&gt;lombok.config&lt;/code&gt; 를 다음과 같은 선언을 하면 Lombok에서 만드는 생성자에서 &lt;code&gt;@ConstructorProperties&lt;/code&gt; 를 자동으로 넣어줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;lombok.config 설정&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;properties&quot;&gt;lombok.anyConstructor.addConstructorProperties=true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;@Builder&lt;/code&gt;, &lt;code&gt;@AllArgsConstructor&lt;/code&gt; 와 같은 애노테이션을 클래스에 붙이면 Lombok에서는 자동으로 생성자를 만들어줍니다.
이를 통해 JSON 파싱한 값을 넣을 클래스를 더 단순하게 만들 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Lombok을 이용한 AccessLog 클래스 선언&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Builder
@Getter
@ToString
public class AccessLog {
    private final Instant accessDateTime;
    private final String ip;
    private final String username;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 Lombok v1.16.20 전까지는 디폴트로 &lt;code&gt;@ConstructorProperties&lt;/code&gt; 을 넣어줬었다고 합니다.
이 이후 버전부터는 디폴트가 아니므로 &lt;code&gt;lombok.config&lt;/code&gt; 에 명시적인 선언이 필요합니다.
( &lt;a href=&quot;https://multifrontgarden.tistory.com/222&quot; class=&quot;bare&quot;&gt;https://multifrontgarden.tistory.com/222&lt;/a&gt; 참조 )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;@ConstructorProperties&lt;/code&gt; 를 직접 쓸 때의 장단점은 다음과 같다고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@JsonCreator&lt;/code&gt; + &lt;code&gt;@JsonProperties&lt;/code&gt; 보다는 코딩량이 조금 적습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jackson에 의존적이지 않습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JSON을 파싱한 값이 들어가는 클래스를 jar 파일로 배포할 때 Jackson의 의존관계가 딸려들어가지 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;같은 방식을 지원하는 다른 JSON 파싱 라이브러리로 교체할 때 코드 변경이 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JSON의 속성명과 생성자의 실제 파라미터 명이 다른 경우에는 사용하는 것이 부자연스럽습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;만약 아래와 같이 &lt;code&gt;@ConstructorProperties&lt;/code&gt; 에서는 &quot;ip_address&quot;로 지정한 속성이 실제 파라미터이름이 &lt;code&gt;String ip&lt;/code&gt; 경우라면, 코드로는 잘 동작하지만 애노테이션의 원래 의도와는 어긋난 것이 아닌가 하는 생각이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    @ConstructorProperties({&quot;accessDateTime&quot;, &quot;ip_address&quot;, &quot;username&quot;})
    public AccessLog(Instant accessDateTime, String ip, String username) {
        this.accessDateTime = accessDateTime;
        this.ip = ip;
        this.username = username;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;@ConstructorProperties&lt;/code&gt; + Lombok 은 코드량이 적다는 장점이 있지만 멤버 변수의 이름이 JSON 속성명과 일치해야 한다는 단점도 있습니다.
jar 파일로 배포하는 클래스라면 Lombok에 대한 의존성이 부담스러울수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;parameternamemodule_활용&quot;&gt;2.3. ParameterNameModule 활용&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞의 예제들을 보면 &lt;code&gt;@JsonProperty(&quot;ip&quot;)&lt;/code&gt; 와 같이 지정하는 속성의 이름과 생성자의 파라미터의 이름이 동일합니다.
&lt;code&gt;String ip&lt;/code&gt; 와 같이 생성자의 파라미터의 이름을 바로 가지고 올 수 있다면 일일히 속성명을 지정하지 않을 수 있겠다는 생각이 들만합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 JDK 8이 나오기 전까지는 Reflection만으로는 파라미터 이름을 가지고 올 수 없었고, ASM과 같은 바이트코드 조작 라이브러리를 이용해서 디버깅을 위한 정보를 이용해야만 가능했습니다. ( &lt;a href=&quot;https://stackoverflow.com/questions/2729580/how-to-get-the-parameter-names-of-an-objects-constructors-reflection#2729907&quot; class=&quot;bare&quot;&gt;https://stackoverflow.com/questions/2729580/how-to-get-the-parameter-names-of-an-objects-constructors-reflection#2729907&lt;/a&gt; 참조) 그래서 앞서 소개한 &lt;code&gt;@java.beans.ConstructorProperties&lt;/code&gt; 와 같은 애노테이션도 활용되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDK8 이상에서는 컴파일을 할 때 &lt;code&gt;-parameters&lt;/code&gt; 라는 옵션을 붙이면 Reflection API로 파라미터 정보를 가지고 올수 있도록 컴파일된 클래스에 정보를 덧붙여 줍니다.
Gradle을 쓰고 있다면 아래와 같이 설정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;build.gradle 안의 컴파일 옵션에 추가&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;tasks.withType(JavaCompile).each {
    it.options.compilerArgs.add(&apos;-parameters&apos;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;IDE 안에서도 컴파일 옵션을 신경써줘야합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;IntelliJ에서는 &lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Build, Execution, Development&lt;/code&gt; &amp;gt; &lt;code&gt;Build Tools&lt;/code&gt; &amp;gt; &lt;code&gt;Gradle&lt;/code&gt; 에서 &lt;code&gt;Build and Run using:&lt;/code&gt; 옵션을 확인해 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jackson/intellij-settings-gradle.png&quot; alt=&quot;intellij-settings-gradle.png&quot; title=&quot;Settings의 Gradle 설정&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 옵션값이 &lt;code&gt;Gradle(Default)`로 되어 있다면, `build.gradle&lt;/code&gt; 의 컴파일 옵션이 그대로 쓰입니다.
만약 그 값이 &lt;code&gt;IntelliJ IDEA&lt;/code&gt; 로 되어 있다면 IntelliJ 안에서의 Java 컴파일 옵션도 동일하게 맞춰 줘야합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Build, Execution, Development&lt;/code&gt; &amp;gt; &lt;code&gt;Compiler&lt;/code&gt; &amp;gt; &lt;code&gt;Java Compiler&lt;/code&gt; 메뉴에서 &lt;code&gt;Addtional command line parameters&lt;/code&gt; 옵션에 &lt;code&gt;-parameters&lt;/code&gt; 을 적어줍니다.
옵션을 바꾼 후에는 전체 프로젝트를 리빌드합니다.
( &lt;code&gt;Build&lt;/code&gt; &amp;gt; &lt;code&gt;Rebuild Project&lt;/code&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jackson/intellij-settings-java-compiler.png&quot; alt=&quot;intellij-settings-java-compiler.png&quot; title=&quot;Settings의 Java Compiler 설정&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jackson의 ParameterNameModule 을 쓰기 위해서는 다음과 같이 의존성을 추가해야합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;ParameterNameModule 의존성 추가&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;groovy&quot;&gt;    implementation &apos;com.fasterxml.jackson.module:jackson-module-parameter-names:2.10.3&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;ObjectMapper&lt;/code&gt; 선언에서는 &lt;code&gt;registerModule()&lt;/code&gt; 메서드로 &lt;code&gt;ParameterNamesModule&lt;/code&gt; 을 추가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;ObjectMapper에 ParameterNamesModule 추가&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;ObjectMapper 선언&quot;&gt;    var objectMapper = new ObjectMapper()
        .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
        .registerModule(new JavaTimeModule())
        .registerModule(new ParameterNamesModule());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 하면 생성자에 특별한 애너테이션을 붙이지 않아도 Jackson은 JSON의 속성을 생성자에게 전달됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring Boot에서는 &lt;code&gt;ParameterNamesModule&lt;/code&gt; 을 편하게 쓸 수 있도록 아래와 같은 기본 설정이 제공됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Spring Boot Gradle Plugin에서 Java 컴파일의 &lt;code&gt;-parameters&lt;/code&gt; 옵션이 자동 추가됩니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;( &lt;a href=&quot;https://github.com/spring-projects/spring-boot/blob/main/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java#L261&quot;&gt;JavaPluginAction.java#L90&lt;/a&gt; 참조 )&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;spring-boot-starter-web&lt;/code&gt; 에서 이미 &lt;code&gt;jackson-module-parameter-names&lt;/code&gt; 에 대한 의존성이 추가되어 있습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web/2.2.5.RELEASE&quot;&gt;spring-boot-starter-web&lt;/a&gt; &amp;#8594; &lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-json/2.2.5.RELEASE&quot;&gt;spring-boot-starter-json&lt;/a&gt; &amp;#8594; &lt;code&gt;jackson-module-parameter-names&lt;/code&gt; 로 의존관계가 연결됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;디폴트로 등록되는 &lt;code&gt;ObjectMapper&lt;/code&gt; bean에는 &lt;code&gt;ParameterNamesModule&lt;/code&gt; 이 이미 추가되어 있습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/blob/33e414fcb2f04bec653f799228907a577ac27a10/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java#L108&quot;&gt;JacksonAutoConfiguration.java#L108&lt;/a&gt; 참조&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RestTeamplteBuilder&lt;/code&gt; 로 &lt;code&gt;RestTemplate&lt;/code&gt; 을 생성한다면 디폴트 등록된 ObjectMapper 을 참조하는  &lt;code&gt;MappingJackson2HttpMessageConverter&lt;/code&gt; 가 &lt;code&gt;RestTemplate&lt;/code&gt; 에 주입됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;ParameterNamesModule&lt;/code&gt; 은 Lombok에서 자동으로 만든 생성자도 잘 인식합니다.
&lt;code&gt;lombok.config&lt;/code&gt; 에 추가 설정을 하지 않아도 된다는 점이 &lt;code&gt;@ConstructorProperties&lt;/code&gt; 를 쓸 때와의 차이점입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 방식의 장단점은&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;코드가 짧습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jackson에 대한 의존성이 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;생성자의 파라미터명과 JSON 속성의 이름이 반드시 일치해야 합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;생성자의 파라미터 이름이 JSON파싱에 쓰인다는것을 의식하지 않는다면, 파라미터 명을 잘 모르고 고쳐서 JSON 파싱이 안되게 하는 부작용이 쓰일수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컴파일 옵션을 의식하지 않으면 특정 개발자의 IDE에서는 의도대로 동작하지 않을수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;생성자가 여러 개 일때는 &lt;code&gt;@JsonCreator&lt;/code&gt; 와 같은 다른 방식과 병행해서 써야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;예제_소스_저장소&quot;&gt;3. 예제 소스 저장소&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예제는 &lt;a href=&quot;https://github.com/benelog/jackson-experiment&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/jackson-experiment&lt;/a&gt; 에 올려두었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@JsonCreator&lt;/code&gt; 활용 : &lt;a href=&quot;https://github.com/benelog/jackson-experiment/tree/master/src/test/java/net/benelog/jackson/JsonCreatorTest.java&quot;&gt;JsonCreatorTest.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@ConstructorProperties&lt;/code&gt; 활용 : &lt;a href=&quot;https://github.com/benelog/jackson-experiment/tree/master/src/test/java/net/benelog/jackson/ConstructorPropertiesTest.java&quot;&gt;ConstructorPropertiesTest.java&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;+ Lombok : &lt;a href=&quot;https://github.com/benelog/jackson-experiment/tree/master/src/test/java/net/benelog/jackson/LombokTest.java&quot;&gt;LombokTest.java&lt;/a&gt;, &lt;a href=&quot;lombok.config&quot;&gt;lombok.config&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ParameterNameModule&lt;/code&gt; 활용 : &lt;a href=&quot;https://github.com/benelog/jackson-experiment/tree/master/src/test/java/net/benelog/jackson/ParameterNameModuleTest.java&quot;&gt;ParameterNameModuleTest.java&lt;/a&gt;, &lt;a href=&quot;https://github.com/benelog/jackson-experiment/tree/master/build.gradle#L28&quot;&gt;build.gradle&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 예제는 JDK 17을 써서 작성했습니다.
Text blocks 문법을 썼기 때문에 JDK 15이상이 필요합니다.
이 문법이 &apos;Preview&amp;#8217;로 들어간 JDK 13,14에서는 컴파일 옵션으로 &apos;--enable-preview&apos; 을 넣어야합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>ktlint로 Kotlin 공식 코딩 컨벤션 맞추기</title>
      <link>https://blog.benelog.net//ktlint.html</link>
      <pubDate>Tue, 12 Nov 2019 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">ktlint.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#gradle_빌드_설정&quot;&gt;1. Gradle 빌드 설정&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#editorconfig_설정&quot;&gt;1.1. &lt;code&gt;.editorconfig&lt;/code&gt; 설정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#스타일_검사&quot;&gt;1.2. 스타일 검사&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#스타일_일괄_변환&quot;&gt;1.3. 스타일 일괄 변환&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#git_hook으로_설정&quot;&gt;1.4. Git hook으로 설정&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#intellij_설정&quot;&gt;2. IntelliJ 설정&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#포멧터_설정&quot;&gt;2.1. 포멧터 설정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#기존_코드_일괄_변환&quot;&gt;2.2. 기존 코드 일괄 변환&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#파일을_저장할_때마다_포멧터_적용하기&quot;&gt;2.3. 파일을 저장할 때마다 포멧터 적용하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#포멧터가_바꿔주지_않는_규칙_신경쓰기&quot;&gt;2.4. 포멧터가 바꿔주지 않는 규칙 신경쓰기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Kotlin 언어의 공식 사이트에서는 코딩 컨벤션 가이드를 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://kotlinlang.org/docs/reference/coding-conventions.html&quot; class=&quot;bare&quot;&gt;https://kotlinlang.org/docs/reference/coding-conventions.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://ktlint.github.io/&quot;&gt;ktlint&lt;/a&gt;는 Kotlin의 공식 가이드의 규칙을 포함하여 코드 스타일을 검사하고, 맞춰주는 도구입니다.
이 글에는 ktlint를 Gradle, IntelliJ, Git과 연동하여 사용하는 방법을 정리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;gradle_빌드_설정&quot;&gt;1. Gradle 빌드 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://ktlint.github.io/&quot;&gt;ktlint&lt;/a&gt;는 jar 파일로 제공되고, &lt;a href=&quot;https://jcenter.bintray.com/&quot; class=&quot;bare&quot;&gt;https://jcenter.bintray.com/&lt;/a&gt; 에도 배포되어 있습니다.
따라서 별도의 Gradle plugin이 없어도 Gradle에서 task를 정의해서도 쓸 수 있습니다.
그런데 ktlint의 공식 사이트에서는 Gradle plugin을 더 권장한다고 나와 있습니다.
이 글에서는 ktlint를 래핑한 Gradle plugin 중 Github에서 별 갯수가 가장 높은 &lt;a href=&quot;https://github.com/jlleitschuh/ktlint-gradle&quot;&gt;jlleitschuh/ktlint-gradle&lt;/a&gt;를 사용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;editorconfig_설정&quot;&gt;1.1. &lt;code&gt;.editorconfig&lt;/code&gt; 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ktlint는 &lt;code&gt;.editorconfig&lt;/code&gt; 파일에 선언된 규칙을 포함하여 코드 스타일을 검사합니다.
&lt;code&gt;.editorconfig&lt;/code&gt; 는 다양한 에디터와 IDE에서 공통적으로 지원하는 코드 스타일에 대한 설정 파일입니다.
자세한 스펙은 &lt;a href=&quot;https://editorconfig.org/&quot; class=&quot;bare&quot;&gt;https://editorconfig.org/&lt;/a&gt; 에서 파악할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같이 &lt;code&gt;.editorconfig&lt;/code&gt;  설정을 추가하면 Kotlin 코딩컨벤션과 함께 쓰기에 무난합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;.editorconfig 예시&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120
tab_width = 4

[*.{kt,kts}]
disabled_rules=import-ordering
# java.* 패키지를 의존하는 경우  IntelliJ의 Orgarnize Import 기능으로는 알파벳 순서대로 import 구문을 정렬할 수 없다.
# 이는 ktlint의 import-ordering 규칙과 맞지 않는다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ktlint는 &lt;code&gt;.editorconfig&lt;/code&gt; 파일이 없어도 디폴트 규칙으로 실행할 수 있습니다.
그렇지만 아래와 같은 이유로 명시적으로 이 파일을 선언하는 것을 권장합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;의도하지 않게 상위 디렉토리의 &lt;code&gt;.editorconfig&lt;/code&gt; 가 참조되는 경우를 막아줍니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.editorconfig&lt;/code&gt; 파일의 규약에 따르면, 이 파일을 지원하는 도구들은 &lt;code&gt;root = true&lt;/code&gt; 라는 선언이 된 &lt;code&gt;.editorconfig&lt;/code&gt; 파일을 찾기 전까지는 모든 상위 디렉토리를 탐색합니다. &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ktlint에서도 이 규약을 준수합니다. 따라서 특정 컴퓨터에만 소스를 복제한 디렉토리의 상위 디렉토리에 &lt;code&gt;.editorconfig&lt;/code&gt; 가 있다면 그 장비에서는 스타일 체크 결과가 다르게 나오게 됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ktlint가 버전이 올라가면서 규칙의 디폴트 값이 변경될 경우를 대비합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;예를 들면 ktlint 0.34.0 버전부터 &lt;code&gt;insert_final_newline = true&lt;/code&gt; 가 디폴트 값이 되었습니다.  &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;다양한 IDE와 Editor등의 도구에서 이 설정을 참조합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;IntelliJ, VSCode, GitHub등에서 뷰어,포멧터의 설정으로 자동 반영됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;코드 포멧에 대한 문서와 역할도 할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;위의 예시와 같이 &lt;code&gt;.editorconfig&lt;/code&gt; 에 선언되는 속성의 이름은 이해하기 쉽습니다. 이 파일만 봐도 파일 포멧에 대한 일부 규칙을 파악할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://kotlinlang.org/docs/reference/coding-conventions.html&quot;&gt;Kotlin 공식 코딩 컨벤션&lt;/a&gt;에 명시되지 않은 규칙까지 일치시킬 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;max_line_length&lt;/code&gt; , &lt;code&gt;insert_final_newline&lt;/code&gt; 과 같은 규칙을 Kotlin 공식 코딩 컨벤션에는 명시되어 있지는 않습니다. 그러나 같은 소스를 고치면서 협업하는 개발자들이 이를 통일하지 않을 경우 불필요한 diff 가 발생하여 코드 변경분을 파악할 때 불편해집니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;disabled_rules&lt;/code&gt; 속성으로 검사하지 않을 규칙을 지정할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;특히 현시점에서는 &lt;code&gt;import-ordering&lt;/code&gt; 규칙은 쓰지 않는 것이 좋습니다. 위의 예시 파일에도 이를 반영했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jlleitschuh/ktlint-gradle&quot;&gt;jlleitschuh/ktlint-gradle&lt;/a&gt;는 아래와 같이 설정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Gradle Groovy설정 (build.gradle)&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;groovy&quot;&gt;buildscript {
    repositories {
        maven {
            url &apos;https://plugins.gradle.org/m2/&apos;
        }
    }
    dependencies {
        classpath &apos;org.jlleitschuh.gradle:ktlint-gradle:9.1.0&apos;
    }
}

plugins {
    id &apos;org.jlleitschuh.gradle.ktlint&apos; version &apos;9.1.0&apos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Gradle Kotlin설정 (build.gradle.kts)&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;kotlin&quot;&gt;buildscript {
    repositories {
        maven(url = &quot;https://plugins.gradle.org/m2/&quot;)
    }
    dependencies {
        classpath(&quot;org.jlleitschuh.gradle:ktlint-gradle:9.1.0&quot;)
    }
}
plugins {
    id(&quot;org.jlleitschuh.gradle.ktlint&quot;) version &quot;9.1.0&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock caution&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Caution&lt;/div&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jlleitschuh/ktlint-gradle&quot;&gt;jlleitschuh/ktlint-gradle&lt;/a&gt;는 Gradle 버전 5.4.1이상에서만 사용할 수 있습니다. 그 이하 버전에서는 아래와 같은 에러가 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;* What went wrong:
Could not determine the dependencies of task &apos;:ktlintCheck&apos;.
&amp;gt; Could not create task &apos;:ktlintTestSourceSetCheck&apos;.
   &amp;gt; Could not create task of type &apos;KtlintCheckTask&apos;.
      &amp;gt; Could not generate a decorated class for class org.jlleitschuh.gradle.ktlint.KtlintCheckTask.
         &amp;gt; org/gradle/work/InputChanges&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이럴 경우에는 Gradle의 버전을 업그레이드해야합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gradle Wrapper를 쓰고 있다면
&lt;code&gt;{프로젝트홈}/gradle/wrapper/gradle-wrapper.properties&lt;/code&gt; 파일의 distributionUrl 속성에 지정된 버전을 고쳐서 버전을 올릴 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다양한 Gradle 버전을 별도로 설치하는데에는 &lt;a href=&quot;https://sdkman.io/&quot;&gt;SDKMAN&lt;/a&gt; 을 권장합니다.
Gradle이 별도로 설치되어 있을 경우 Gradle Wrapper를 업그레이드하는 방법은 &lt;a href=&quot;https://java.ihoney.pe.kr/476&quot; class=&quot;bare&quot;&gt;https://java.ihoney.pe.kr/476&lt;/a&gt; 을 참조합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gradle 버전을 업그레이드하기가 어려울 경우 &lt;a href=&quot;https://ktlint.github.io/&quot; class=&quot;bare&quot;&gt;https://ktlint.github.io/&lt;/a&gt; 에 안내된 다른 Gradle Plugin이나 Plugin없이 사용하는 방법을 참고하시기 바랍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;gradle.properites&lt;/code&gt; 파일에도 공식 코딩 컨벤션을 사용한다는 정책을 아래와 같이 명시합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;gradle.properites 설정&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;properties&quot;&gt;kotlin.code.style=official&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;IntellJ에서 프로젝트를 import할때 위의 설정을 참조할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;플러그인 설정이 완료되면 ktlint를 실행하는 Gradle 태스크는  &lt;code&gt;./gradlew tasks | grep ktlint&lt;/code&gt; 명령으로 확인합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;스타일_검사&quot;&gt;1.2. 스타일 검사&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;./gradlew ktlintCheck&lt;/code&gt; 태스크는 스타일 검사를 수행합니다.
이 태스크는 &lt;code&gt;./gradlew build&lt;/code&gt; 를 실행했을 때 연결되는 전체 프로젝트 빌드 싸이클에 포함됩니다. 따라서 디폴트 설정으로는  ktlint에서 가이드하는 코드 스타일을 지키지 않으면 빌드가 실패합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;스타일_일괄_변환&quot;&gt;1.3. 스타일 일괄 변환&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;./gradlew ktlintFormat&lt;/code&gt; 태스크로는 스타일에 맞지 않는 코드를 바꿔줍니다. 전체 프로젝트의 소스 코드를 한꺼번에 고칠 때 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, 이 기능을 사용하다가 의도하지 않게 파일이 삭제되는 경우도 있었습니다.
그래서 이 태스크는 조심해서 사용해야합니다.
이 글에서 설명한 IntelliJ로 포멧을 일괄변환하는 방법도 참고해서 병행해서 사용하는 편이 좋습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;git_hook으로_설정&quot;&gt;1.4. Git hook으로 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞에서 설명한 &lt;code&gt;ktlintCheck&lt;/code&gt; , &lt;code&gt;ktlintFormat&lt;/code&gt; 태스크를 Git의 Hook으로 등록하여 commit을 하면 자동으로 실행되게 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;./gradlew addKtlintCheckGitPreCommitHook&lt;/code&gt; : ktlintCheck 태스크를 pre-commit hook으로 등록&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;./gradlew addKtlintFormatGitPreCommitHook&lt;/code&gt; : ktlintFormat 태스크를 pre-commit hook으로 등록ktlintCheck 태스크로 검사하도록 설정합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞에서 설명한 것처럼 &lt;code&gt;ktlintFormat&lt;/code&gt; 태스크는 의도하지 않게 파일을 바꿀 위험이 있기 때문에 &lt;code&gt;addKtlintCheckGitPreCommitHook&lt;/code&gt; 를 더 권장합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;등록된 pre-commit hook은 &lt;code&gt;rm .git/hooks/pre-commit&lt;/code&gt; 명령으로 삭제할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intellij_설정&quot;&gt;2. IntelliJ 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;IntelliJ의 코드 포멧터는 &lt;a href=&quot;https://kotlinlang.org/docs/reference/coding-conventions.html&quot;&gt;Kotlin 공식 코딩 컨벤션&lt;/a&gt;이 나오기 전부터 쓰이던 디폴트 설정이 있었습니다. IntelliJ에서는 Kotlin 1.3에서는 신규로 생성되는 프로젝트에서, Kotlin 1.4에서는 모든 프로젝트에서 공식 컨벤션에 맞춘 포멧터가 디폴트로 설정될 것이라고 합니다. &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt; 오랫동안 유지보수할 Kotlin 코드라면 공식 컨벤션에 맞춰서 IntelliJ에서도 포멧터를 설정되었는지 신경을 써야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;포멧터_설정&quot;&gt;2.1. 포멧터 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음에 안내한 A, B 2가지 방법 중의 하나를 선택하셔서  공식 코딩컨벤션에 맞게 IntelliJ의 포멧터 설정을 맞출수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;a_predefined_을_이용한_설정&quot;&gt;2.1.1. A. `predefined ` 을 이용한 설정&lt;/h4&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;IntelliJ 메뉴에서 &lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Editor&lt;/code&gt; &amp;gt; &lt;code&gt;Code Style&lt;/code&gt; &amp;gt; &lt;code&gt;Kotlin&lt;/code&gt; 으로 이동합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Scheme&lt;/code&gt; 항목을 Project로 지정합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;여러 프로젝트에서 다른 설정을 쓸 경우를 대비해 가급적 글로벌 설정을 바꾸지 않기 위함입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Set from&amp;#8230;&amp;#8203;&lt;/code&gt; &amp;#8594; &lt;code&gt;Predefined Style&lt;/code&gt; &amp;#8594; &lt;code&gt;Kotlin Style Guide&lt;/code&gt; 를 선택합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/kotlin-code-style/intelli-j-kotlin-style-guide.gif&quot; alt=&quot;IntelliJ Kotlin Style&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;b_gradle_plugin을_이용한_설정&quot;&gt;2.1.2. B. Gradle plugin을 이용한 설정&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞서 설정한 &lt;a href=&quot;https://github.com/jlleitschuh/ktlint-gradle&quot;&gt;jlleitschuh/ktlint-gradle&lt;/a&gt;를 이용하여 &lt;code&gt;./gradlew ktlintApplyToIdea&lt;/code&gt; 태스크를 실행합니다.
이 태스크는 IntelliJ 설정파일의 코드 스타일 부분을 덮어써서 ktlint의 규칙과 가급적 맞는 포멧터가 설정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;포멧터가 설정되면 파일을 편집할 때 &lt;code&gt;Code&lt;/code&gt; &amp;gt; &lt;code&gt;Reformat Code&lt;/code&gt; 메뉴를 선택하거나 단축키 &lt;code&gt;Ctrl + Shift + L&lt;/code&gt; 단축키로 포멧터를 적용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;기존_코드_일괄_변환&quot;&gt;2.2. 기존 코드 일괄 변환&lt;/h3&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;IntelliJ의 프로젝트 탐색기에서 프로젝트의 최상위 디렉토리를 우클릭합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reformat Code 를 실행합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;우클릭을 하여 나오는 메뉴에서 &lt;code&gt;Reformat Code&lt;/code&gt; 를 선택하거나&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + Shift + L&lt;/code&gt; 단축키를 누릅니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;파일을_저장할_때마다_포멧터_적용하기&quot;&gt;2.3. 파일을 저장할 때마다 포멧터 적용하기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7642-save-actions&quot;&gt;Save Actions Plugin&lt;/a&gt;를 사용하면 파일을 저장할 때 자동으로 포멧터를 실행할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;File&lt;/code&gt; &amp;gt; &lt;code&gt;Settings&lt;/code&gt; ( &lt;code&gt;Ctrl + Alt + S&lt;/code&gt; ) &amp;gt; &lt;code&gt;Plugins&lt;/code&gt; 메뉴로 이동합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Marketplace&lt;/code&gt; 탭에서 &apos;Save Actions&apos; 로 검색합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Save Actions&apos; plugin의 상세 설명 화면에서  `[Install]&lt;/code&gt; 버튼을 누릅니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;IntelliJ를 재시작합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;File&lt;/code&gt; &amp;gt; &lt;code&gt;Settings&lt;/code&gt; &amp;gt;  &lt;code&gt;Other Settions&lt;/code&gt; &amp;gt; &lt;code&gt;Save Actions&lt;/code&gt; 메뉴로 이동합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;아래 항목 혹은 그외의 원하는 정책을 체크합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Activate save actions on save&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Optimize imoprts&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Refomat file&lt;/code&gt; (전체 프로젝트의 스타일이 통일된 경우)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Refomat only changed code&lt;/code&gt; (프로젝트의 스타일이 통일되어 있지 않아서 스타일이 맞지 않는 코드를 함께 고치면 변경 부분을 알아보기가 더 어려운 경우)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/kotlin-code-style/intelli-j-save-actions-config.jpg&quot; alt=&quot;IntelliJ Save Actions&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;포멧터가_바꿔주지_않는_규칙_신경쓰기&quot;&gt;2.4. 포멧터가 바꿔주지 않는 규칙 신경쓰기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;IntelliJ의 포멧터는 ktlint에서 검사하는 규칙을 다 자동을 맞추어주지는 못합니다.
즉 &lt;code&gt;Reformat Code&lt;/code&gt; ( &lt;code&gt;Ctrl + Alt + L&lt;/code&gt; ) 을 실행하는 것만으로는 ktlint 검사를 통과한다는 보장은 없습니다.
다음에서 설명하는 IntelliJ의 기능을 잘 활용하면 보다 빠른 시점에서 ktlint의 규칙을 준수하는데 도움이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;파일의_마지막에_자동으로_개행문자_추가&quot;&gt;2.4.1. 파일의 마지막에 자동으로 개행문자 추가&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;POSIX 명세에 따라서, 텍스트 파일의 마지막에 개행문자(LF)를 추가하는 것이 권장됩니다. &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, IntelliJ의 &lt;code&gt;Reformat Code&lt;/code&gt; 기능으로는 마지막 개행문자 추가가 되지 않습니다.
&lt;code&gt;File&lt;/code&gt; &amp;gt; &lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Editor&lt;/code&gt; &amp;gt; &lt;code&gt;General&lt;/code&gt; 메뉴에서 &lt;code&gt;Ensure line feed at file end on Save&lt;/code&gt; 를 선택하면, 파일이 저장될 때 자동으로 마지막에 개행문자를 추가해줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/kotlin-code-style/intelli-j-lf-eof.jpg&quot; alt=&quot;IntelliJ line feed end of file&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 설정은 &lt;code&gt;.editorconfig&lt;/code&gt; 의 선언에 따라 자동으로 활성화될 수도 있습니다.
그렇지만 의도대로 동작하지 않는다면 한번 확인해볼만합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;intellij_경고에_따라_고치기&quot;&gt;2.4.2. IntelliJ 경고에 따라 고치기&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Kotlin 공식 코딩 컨벤션에는 공백 등 단순한 파일 형식 외에도 문법적인 요소에 대한 것도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들면 String Template를 쓸 때는 꼭 필요한 경우가 아니면 중괄호를 넣지 말라는 규칙이 있습니다.
( &lt;a href=&quot;https://kotlinlang.org/docs/reference/coding-conventions.html#string-templates&quot; class=&quot;bare&quot;&gt;https://kotlinlang.org/docs/reference/coding-conventions.html#string-templates&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(O): &lt;code&gt;println(&quot;$name is my friend.&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(X): &lt;code&gt;println(&quot;${name} is my friend.&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 규칙을 어긴 코드는 &apos;Reformat Code&apos; 로는 바로 바뀌지 않습니다.
IntelliJ에서 이런 코드에 경고를 보내고 &lt;code&gt;Alt + Shift + Enter&lt;/code&gt; 단축키로 코드로 바꾸는 기능을 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/kotlin-code-style/intelli-j-string-template-warning.jpg&quot; alt=&quot;IntelliJ String template&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이처럼 IntelliJ에서는 언어 문법을 활용할때도 Kotlin 공식 코딩 컨벤션에서 권장하는 스타일대로 쓰도록 유도하고 있습니다.
이런 경고들을 무시하지 않고 반영한다면 ktlint의 검사에서도 통과할 가능성이 높아집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. &lt;a href=&quot;https://editorconfig.org/&quot; class=&quot;bare&quot;&gt;https://editorconfig.org/&lt;/a&gt; 에서 &apos;When opening a file, EditorConfig plugins look for a file named .editorconfig in the directory of the opened file and in every parent directory.&apos;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. 0.34.0 릴리즈 노트 참고 : &lt;a href=&quot;https://github.com/pinterest/ktlint/blob/master/CHANGELOG.md#0340---2019-07-15&quot; class=&quot;bare&quot;&gt;https://github.com/pinterest/ktlint/blob/master/CHANGELOG.md#0340---2019-07-15&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. &lt;a href=&quot;https://kotlinlang.org/docs/reference/code-style-migration-guide.html&quot; class=&quot;bare&quot;&gt;https://kotlinlang.org/docs/reference/code-style-migration-guide.html&lt;/a&gt; 에서 설명된 계획입니다.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnotedef_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. &lt;a href=&quot;https://blog.coderifleman.com/2015/04/04/text-files-end-with-a-newline/&quot; class=&quot;bare&quot;&gt;https://blog.coderifleman.com/2015/04/04/text-files-end-with-a-newline/&lt;/a&gt; 참조
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>여러 개의 JDK를 설치하고 선택해서 사용하기</title>
      <link>https://blog.benelog.net//installing-jdk.html</link>
      <pubDate>Mon, 1 Jul 2019 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">installing-jdk.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#특별한_도구를_안_쓸_때의_jdk_설치_버전_선택&quot;&gt;1. 특별한 도구를 안 쓸 때의 JDK 설치 &amp;amp; 버전 선택&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#jdk_설치&quot;&gt;1.1. JDK 설치&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#jdk_버전_선택&quot;&gt;1.2. JDK 버전 선택&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#다양한_jdk_설치와_사용을_편하게_하는_도구&quot;&gt;2. 다양한 JDK 설치와 사용을 편하게 하는 도구&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#aptyum&quot;&gt;2.1. APT/YUM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#update_alternatives_alternatives&quot;&gt;2.2. update-alternatives / alternatives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#homebrew&quot;&gt;2.3. Homebrew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#chocolatey&quot;&gt;2.4. Chocolatey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#direnv&quot;&gt;2.5. direnv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#jenv&quot;&gt;2.6. jEnv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sdkman&quot;&gt;2.7. SDKMAN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#jabba&quot;&gt;2.8. jabba&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#무엇을_어떻게_사용할_것인가&quot;&gt;3. 무엇을 어떻게 사용할 것인가?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다양한 배포판과 버전의 JDK를 명령어 한 줄로 설치하고 OS의 쉘에서 사용할 JDK를 쉽게 지정할 수 있게 해주는 도구들을 소개합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;div class=&quot;title&quot;&gt;주요 변경이력&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2023.03.27&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jabba 설치를 안내하는 URL을 현재 관리되는 포크된 버전으로 변경&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2019.07.03&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Homebrew에 대한 소개를 별도의 단락으로 분리&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chocolatey에 대한 설명 보강&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;특별한_도구를_안_쓸_때의_jdk_설치_버전_선택&quot;&gt;1. 특별한 도구를 안 쓸 때의 JDK 설치 &amp;amp; 버전 선택&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jdk_설치&quot;&gt;1.1. JDK 설치&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDK를 수동으로 설치하는 절차는 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;설치할 버전/배포판을 다운로드합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;예) Oracle의 OpenJDK 빌드 : &lt;a href=&quot;https://jdk.java.net/&quot; class=&quot;bare&quot;&gt;https://jdk.java.net/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예) AdoptOpenJDK : &lt;a href=&quot;https://adoptopenjdk.net/&quot; class=&quot;bare&quot;&gt;https://adoptopenjdk.net/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예) Graal VM Community Edition : &lt;a href=&quot;https://github.com/oracle/graal/releases&quot; class=&quot;bare&quot;&gt;https://github.com/oracle/graal/releases&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;다운로드한 파일의 압축을 풉니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OS의 환경변수를 지정합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;JAVA_HOME&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JDK의 압축을 푼 디렉토리를 지정합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maven이나 Tomcat 같은 솔류션에서 이 환경변수로 JDK의 위치를 참조합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;PATH&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;쓰고 있던 &lt;code&gt;PATH&lt;/code&gt; 변수에 &lt;code&gt;$JAVA_HOME/bin&lt;/code&gt; 을 더합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;java&lt;/code&gt; , &lt;code&gt;javac&lt;/code&gt; 등을 명령행에서 직접 실행할 수 있도록 하기 위해서 하는 작업입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jdk_버전_선택&quot;&gt;1.2. JDK 버전 선택&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;IDE에서는 프로젝트별로 사용할 JDK의 위치를 선택할 수 있습니다.
IntelliJ에서는 &lt;code&gt;File&lt;/code&gt; &amp;gt; &lt;code&gt;Project Structure&lt;/code&gt; &amp;gt; &lt;code&gt;Platform Settings&lt;/code&gt; (단축키 &lt;code&gt;Ctrl + Alt + Shift + S&lt;/code&gt; ) &amp;gt; &lt;code&gt;SDK&lt;/code&gt; 메뉴 에서 이를 지정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;:&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jdk/intelli-j-jdk.jpg&quot; alt=&quot;intelli-j-jdk.jpg&quot; title=&quot;IntelliJ 에서 JDK 선택&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OS의 명령행에서 Maven, Gradle로 직접 빌드를 하거나 &lt;code&gt;java -jar&lt;/code&gt; 로 직접 프로그램을 실행시킬 때를 대비해서 &lt;code&gt;JAVA_HOME&lt;/code&gt;, &lt;code&gt;PATH&lt;/code&gt;  설정이 되어 있어야합니다.
프로젝트마다 사용하는 JDK 버전이 다르면 사용할 JDK를 지정하기가 번거롭습니다.
매번 이런 &lt;code&gt;JAVA_HOME&lt;/code&gt; 같은 환경 변수를 바꾸거나 &lt;code&gt;/usr/lib/jvm/java-13-openjdk-amd64/bin/java&lt;/code&gt; 와 같이 전체 경로로 실행할 도구를 지정한다면 더욱 그렇습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;반복적인 작업을 쉘스크립트나 배치파일로 할 수도 있습니다.
그런데 이미 이런 작업을 편리하게 해주는 도구들이 몇 가지 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;다양한_jdk_설치와_사용을_편하게_하는_도구&quot;&gt;2. 다양한 JDK 설치와 사용을 편하게 하는 도구&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDK의 설치와 OS의 명령행에서 사용할 버전을 지정할 때는 아래 도구들을 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-ends grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 1. JDK 설치와 버전 지정에 사용할 수 있는 도구들&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 37.5%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 37.5%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;이름&lt;/th&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;기능&lt;/th&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;사용 가능한 OS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;YUM/APT&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;범용 패키지 관리 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Linux&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;update-alternatives/alternatives&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;범용 패키지 버전 선택 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Linux&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Homebrew&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;범용 패키지 관리 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;macOS&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;범용 패키지 관리 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Windows&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://sdkman.io/&quot;&gt;SDKMAN&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;범용 패키지 관리 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Linux&lt;br&gt;
macOS&lt;br&gt;
Windows(Cygwin, Git Bash)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://github.com/Jabba-Team/jabba&quot;&gt;jabba&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JDK 설치 특화 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Linux&lt;br&gt;
macOS&lt;br&gt;
Windows&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://www.jenv.be/&quot;&gt;jEnv&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JDK 버전 선택 특화 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Linux&lt;br&gt;
macOS&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://direnv.net/&quot;&gt;direnv&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;범용 디렉토리별 환경 변수 관리 도구&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Linux&lt;br&gt;
macOS&lt;br&gt;
Windows&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;aptyum&quot;&gt;2.1. APT/YUM&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ubuntu, CentOS 등의 Linux 배포판에서는 해당 OS에 맞도록 빌드한 OpenJDK 배포판을 APT,YUM 등으로 간단히 설치할 수 있도록 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;APT(Advanced Packaging Tool)는 Ubuntu 등 Debian 계열의 리눅스에서 사용할 수 있는 패키지 관리 프로그램입니다.
Ubuntu에서는 APT로 아래와 같이 여러 버전의 JDK를 설치할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo apt install openjdk-8-jdk
sudo apt install openjdk-11-jdk
sudo apt install openjdk-12-jdk&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;YUM(Yellow dog Updater, Modified)은 Red Hat/CentOS 리눅스 배포판에서 사용할 수 있는 패키지 관리자입니다.
아래와 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo yum install java-1.8.0-openjdk-devel.x86_64&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Adopt OpenJDK 배포판은 패키지 저장소를 추가해서 설치할 수 있습니다.
Ubuntu에서는 아래와 같이 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo add-apt-repository ppa:rpardini/adoptopenjdk
sudo apt install adoptopenjdk-11-installer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설치된 JDK의 &lt;code&gt;java&lt;/code&gt; , &lt;code&gt;javac&lt;/code&gt; 도구는  &lt;code&gt;/usr/bin/java&lt;/code&gt; , &lt;code&gt;/usr/bin/javac&lt;/code&gt; 에서 심볼릭 링크로 연결되어 어느 디렉토리에서나 실행될 수 있게 됩니다.
이 심볼릭 링크는 이어서 소개할 update-alternatives / alternatives 도구로 관리할수 있습니다.
&lt;code&gt;JAVA_HOME&lt;/code&gt; 환경 변수는 직접 &lt;code&gt;~/.bashrc&lt;/code&gt; 와 같은 쉘별 설정 파일에 넣어줘야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OS에서 기본 제공하는 도구이기에 도구를 위한 별도의 설치 과정이 필요 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JDK 외에도 Maven, Gradle의 설치에도 활용할 수 있는 범용적인 패키지 관리 도구입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SDKMAN/ Jabba에 비하면 다양한 JDK 배포판을 제공하지는 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;update_alternatives_alternatives&quot;&gt;2.2. update-alternatives / alternatives&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;update-alternatives와 alternatives는 여러 버전의 패키지를 관리할 수 있는 Linux에서 제공되는 도구입니다.
여기서는 Ubuntu에서 쓰는 &lt;code&gt;update-alternatives&lt;/code&gt; 를 기준으로 설명하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞서 나온데로 apt 로 설치한 JDK는 &lt;code&gt;/usr/bin/java&lt;/code&gt; 에서 심볼릭 링크로 연결됩니다.
이 심블릭 링크는 &lt;code&gt;/etc/alternatives/java&lt;/code&gt; 를 중간에 거쳐서 실제 설치한 디렉토리로 연결된 다는 것을 아래와 같이 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;➜  ~ ll /usr/bin/java
lrwxrwxrwx 1 root root 22  6월  9 22:20 /usr/bin/java -&amp;gt; /etc/alternatives/java
➜  ~ ll /etc/alternatives/java
lrwxrwxrwx 1 root root 43  6월  9 22:20 /etc/alternatives/java -&amp;gt; /usr/lib/jvm/java-12-openjdk-amd64/bin/java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;readlink -f /usr/bin/java&lt;/code&gt; 명령으로도 동일한 결과를 볼 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 링크는 &lt;code&gt;update-alternatives&lt;/code&gt; 로 관리됩니다. 아래와 같은 명령으로 현재 설치된 버전들과 우선 순위를 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo update-alternatives --display java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;수동으로 다운로드 압축을 풀어서 설치하거나 SDKMAN, Jabba등으로 설치한 JDK가 있다면 아래 명령으로 &lt;code&gt;update-alternatives&lt;/code&gt; 의 관리대상에 추가할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.8.0_31/bin/java 1000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;심볼릭 링크로 연결되는 버전을 바꾸고 싶다면 아래와 같이 입력합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sudo update-alternatives --config java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설치된 버전을 확인하고 번호를 선택해서 심볼릭 링크를 바꿀 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;There are 4 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                            Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-12-openjdk-amd64/bin/java      1211      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1111      manual mode
  2            /usr/lib/jvm/java-12-openjdk-amd64/bin/java      1211      manual mode
  3            /usr/lib/jvm/java-13-openjdk-amd64/bin/java      1211      manual mode
  4            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      manual mode

Press &amp;lt;enter&amp;gt; to keep the current choice[*], or type selection number:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 명령행에서 실행한 &lt;code&gt;java&lt;/code&gt; 가 어느 곳으로 연결될지는 환경변수 &lt;code&gt;PATH&lt;/code&gt; 에 영향을 받습니다.
&lt;code&gt;/usr/bin/java&lt;/code&gt; 보다 더 우선 순위가 높게 먼저 선언된 디렉토리에 &lt;code&gt;java`가 있다면 `update-alternatives&lt;/code&gt; 에서 지정한 java가 실행되지 않을 수도 있습니다.
SDKMAN, Jabba 등을 함께 사용한다면 이 점을 유의해야 합니다.
현재 쉘, 디렉토리에서 어느 &lt;code&gt;java&lt;/code&gt; 를 실행하고 있는지는 &lt;code&gt;which java&lt;/code&gt; 로 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OS에서 기본적으로 제공하는 도구라서 별도의 설치 과정이 필요하지 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;YUM/APT 과 자연스럽게 함께 쓰이는 도구입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;심블릭 링크로 쉘에서 사용할 디폴트 버전을 지정하는 기능만 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;homebrew&quot;&gt;2.3. Homebrew&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;macOS에서 많이 쓰는 범용 패키지 관리 프로그램입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Homebrew로 AdoptOpen JDK배포판은 아래와 같이 설치할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;brew tap AdoptOpenJDK/openjdk
brew cask install adoptopenjdk11&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 macOS를 써본적이 없어서 Homebrew로 설치하는 방법에 대해서는 &lt;a href=&quot;https://findstar.pe.kr/2019/01/20/install-openjdk-by-homebrew/&quot;&gt;homebrew로 opendjk 설치하기&lt;/a&gt; 글을 참조했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;chocolatey&quot;&gt;2.4. Chocolatey&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Chocolatey는 Windows OS를 위한 패키지 관리자입니다.
Linux에는 APT/YUM, macOS에는 Homebrew가 있다면 Windows에는 Chocolatey가 대표적인 패키지 관리자입니다.
&lt;a href=&quot;https://chocolatey.org/install&quot; class=&quot;bare&quot;&gt;https://chocolatey.org/install&lt;/a&gt; 을 참고해서 설치할수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Chocolatey로 설치가능한 JDK 패키지는 &lt;a href=&quot;https://chocolatey.org/packages?q=jdk&quot; class=&quot;bare&quot;&gt;https://chocolatey.org/packages?q=jdk&lt;/a&gt; 으로 확인하실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;:&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jdk/chocolatey-jdk.jpg&quot; alt=&quot;chocolatey-jdk.jpg&quot; title=&quot;Chocolatey 패키지 중에서 JDK로 검색한 결과&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oracle의 OpenJDK 빌드나 Adopt OpenJDK 배포판 등을 아래와 같이 설치할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Oracle의 OpenJDK 빌드 최신 버전 설치&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;choco install openjdk&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;AdoptOpenJDK 최신 버전 설치&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;choco install adoptopenjdk&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Corretto 11 버전 설치&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;choco install corretto11jdk&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;zulu 최신 버전 설치&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;choco install zulu&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;--version&lt;/code&gt; 옵션을 붙이면&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Oracle의 OpenJDK 빌드 11.0.2 버전 설치&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;choco install openjdk --version 11.0.2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 명령이 수행되고 나면 &lt;code&gt;\Program Files\&lt;/code&gt; 디렉토리 아래에 JDK 들이 위치하게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Oracle의 OpenJDK 빌드 : &lt;code&gt;\Program Files\OpenJDK&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adopt OpenJDK : &lt;code&gt;\Program Files\AdoptOpenJDK&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Corretto : &lt;code&gt;\Program Files\Coretto&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Zulu : &lt;code&gt;\Program Files\zulu&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런대 Chocolatey는 여러 JDK 버전을 동시에 쓰는 쓰임새가 우선적으로 고려되지는 않았습니다.
JDK 12.0.1을 설치후에 11.0.2을 뒤에 설치하려고하면, 다운그레이드가 된다는 경고 메시지가 나옵니다.
이럴 때에는 &apos;-sidebyside&apos; 혹은 &lt;code&gt;--force&lt;/code&gt; 등의 옵션을 붙여줘야합니다.
&lt;code&gt;JAVA_HOME&lt;/code&gt; 도 마지막으로 설치한 JDK의 위치로 지정됩니다.
&lt;code&gt;echo %java_home%&lt;/code&gt; 명령으로 이를 확인해 볼 수 있습니다.
여러 배포판을 설치할 경우 &lt;code&gt;PATH&lt;/code&gt; 환경 변수의 값도 새로 설치한 배포판의 &lt;code&gt;%JAVA_HOME%\bin&lt;/code&gt; 디렉토리가 뒤 쪽에 계속 추가만 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JDK 외에도 Maven, Gradle의 설치에도 활용할 수 있는 범용적인 패키지 관리 도구입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SDKMAN/ Jabba에 비하면 다양한 JDK 배포판을 제공하지는 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;여러 버전을 동시에 설치할 수 있는 동작이 디폴트가 아닙니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OS 명령행에서 여러 JDK 버전을 함께 사용하려면 direnv등 별도의 프로그램과 함께 쓰는 것이 좋습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;direnv&quot;&gt;2.5. direnv&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://direnv.net/&quot;&gt;direnv&lt;/a&gt; 는 특정 디렉토리와 그 하위 디렉토리에서만 사용할 환경 변수를  지정할 수 있는 도구입니다.
Linux와 macOS에서 사용할 수 있습니다. 설치 방법은 &lt;a href=&quot;https://direnv.net/&quot; class=&quot;bare&quot;&gt;https://direnv.net/&lt;/a&gt; 을 참조합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;direnv에서 참조하는 &lt;code&gt;.envrc&lt;/code&gt; 라는 파일에 PATH, JAVA_HOME 을 아래와 같이 지정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;export JAVA_HOME=/home/benelog/.sdkman/candidates/java/12.0.1.hs-adpt
export PATH=$JAVA_HOME/bin:$PATH&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;파일을 처음 생성하거나 변경했을 때에는 &lt;code&gt;direnv allow .&lt;/code&gt; 명령을 한번 내려줘야합니다.
이 파일이 의도하지 않게 생성/수정 되었을 때 보안을 위한 장치입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이후로 이 파일이 있는 디렉토리에 들어가면 이 환경변수가 활성화됩니다.
cd 명령으로 디렉토리에 들어가면 아래와 같은 메시지가 콘솔에 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;direnv: loading .envrc
direnv: export ~JAVA_HOME ~PATH&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보편적으로 사용할 수 있는 도구이기에 &lt;code&gt;JAVA_HOME&lt;/code&gt; 외의 다른 환경 변수도 관리할 수 있습니다.
같은 프로젝트를 하더라도 개발자의 PC마다 달라지는 값이나 테스트를 위한 변수도 &lt;code&gt;.envrc&lt;/code&gt; 에 넣어둘만합니다.
그럴 경우에는 &lt;code&gt;.envrc&lt;/code&gt; 은 &lt;code&gt;.gitignore&lt;/code&gt; 에 추가해서 Git 저장소에는 들어가지 않도록 해야 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;JAVA_HOME&lt;/code&gt; 이나 &lt;code&gt;PATH&lt;/code&gt; 외의 환경 변수도 관리할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;특정 디렉토리 내에서의 환경 변수 기능만 제공합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jenv&quot;&gt;2.6. jEnv&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jenv.be/&quot;&gt;jEnv&lt;/a&gt; 는 JDK 버전관리만을 위한 전용 도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같이 &lt;code&gt;add&lt;/code&gt; 명령으로 관리할 버전을 추가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jenv add /usr/lib/jvm/java-11-openjdk-amd64/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;add&lt;/code&gt; 로 지정한 디렉토리에서 JDK의 버전을 인식하여 아래와 같은 메시지가 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;jenv add 명령의 결과&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;openjdk64-11.0.3 added
11.0.3 added
11.0 added&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설치된 버전은 &lt;code&gt;jenv versions&lt;/code&gt; 명령으로 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;jenv versions 명령의 결과&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;  system
  1.8
  1.8.0.212
* 11.0 (set by JENV_VERSION environment variable)
  11.0.3
  openjdk64-1.8.0.212
  openjdk64-11.0.3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;디폴트로 사용할 버전은 &lt;code&gt;global&lt;/code&gt; 명령으로 지정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jenv global 11.0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;해당 쉘에서 임시로 사용할 버전은 &lt;code&gt;shell&lt;/code&gt; 명령으로 지정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jenv shell 11.0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재 디렉토리에서 사용할 버전은 &lt;code&gt;local&lt;/code&gt; 명령으로 지정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jenv local 11.0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위와 같이 디렉토리에 지정된 버전은 &lt;code&gt;.java-version&lt;/code&gt; 이라는 파일에 저장됩니다.
다음 번에 같은 디렉토리에서 java를 실행하면 이 파일에 지정된 해당 버전이 선택됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;JAVA_HOME&lt;/code&gt; 환경 변수가 제대로 지정되기 위해서는 jENV의 export plugin을 아래 명령으로 활성화해줘야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jenv enable-plugin export&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;jEnv를 다른 도구와 잘 어우러지게 사용하기 위해서는 동작 원리를 알아두는 것이 좋습니다.
jEnv로 JDK 버전을 지정한 후 &lt;code&gt;which java&lt;/code&gt; 로 어느 디렉토리에 있는 &lt;code&gt;java&lt;/code&gt; 와 연결되는지 확인을 해보면
&lt;code&gt;~/.jenv/shims/java&lt;/code&gt; 가 나옵니다. 이 파일의 내용을 보면 실제 설치한 JDK의 &lt;code&gt;java&lt;/code&gt; 가 아닌 쉘 스크립트라는 것을 알수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;&lt;code&gt;cat ~/.jenv/shims/java&lt;/code&gt; 명령의 결과&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;#!/usr/bin/env bash
set -e
[ -n &quot;$JENV_DEBUG&quot; ] &amp;amp;&amp;amp; set -x

program=&quot;${0##*/}&quot;
if [ &quot;$program&quot; = &quot;java&quot; ]; then
  for arg; do
    case &quot;$arg&quot; in
    -e* | -- ) break ;;
    */* )
      if [ -f &quot;$arg&quot; ]; then
        export JENV_DIR=&quot;${arg%/*}&quot;
        break
      fi
      ;;
    esac
  done
fi

export JENV_ROOT=&quot;/root/.jenv&quot;
exec &quot;/root/.jenv/libexec/jenv&quot; exec &quot;$program&quot; &quot;$@&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서 다른 도구와 병행해서 사용할 경우, 환경변수 &lt;code&gt;$PATH`에 `~/.jenv/shims/java`가 다른 도구에서 넣어준 JDK와 연결된 경로들보다 앞에 있어야 jEnv에서 설정한 버전대로 `java&lt;/code&gt; 가 실행됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;$JAVA_HOME`도 어떻게 지정되어 있는지 `echo $JAVA_HOME&lt;/code&gt; 로 확인을 해보면 &lt;code&gt;~/.jenv/versions/11.0&lt;/code&gt; 와 같이 지정되어 있습니다.
`~/.jenv/versions/ 디렉토리에 각 버전별로 실제로 JDK가 설처되어있는 디렉토리로의 심볼릭 링크가 들어가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;&lt;code&gt;~/.jenv/versions&lt;/code&gt; 디렉토리 안의 심볼릭 링크&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;lrwxrwxrwx  1 benelog benelog   33 Jun 30 17:05 1.8 -&amp;gt; /usr/lib/jvm/java-8-openjdk-amd64/
lrwxrwxrwx  1 benelog benelog   33 Jun 30 17:05 1.8.0.212 -&amp;gt; /usr/lib/jvm/java-8-openjdk-amd64/
lrwxrwxrwx  1 benelog benelog   34 Jun 30 17:08 11.0 -&amp;gt; /usr/lib/jvm/java-11-openjdk-amd64/
lrwxrwxrwx  1 benelog benelog   34 Jun 30 17:08 11.0.3 -&amp;gt; /usr/lib/jvm/java-11-openjdk-amd64/
lrwxrwxrwx  1 benelog benelog   33 Jun 30 17:05 openjdk64-1.8.0.212 -&amp;gt; /usr/lib/jvm/java-8-openjdk-amd64/
lrwxrwxrwx  1 benelog benelog   34 Jun 30 17:08 openjdk64-11.0.3 -&amp;gt; /usr/lib/jvm/java-11-openjdk-amd64/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 jEnv는 여러 배포판을 동시에 설치할 때는 충돌을 일으킬수 있습니다.
예를 들어 Ubuntu 패키지 저장소의 OpenJDK 11을 이미 &apos;jenv add&apos; 로 넣은 다음,
AdoptOpenJDK 11을 추가하면 아래와 같이 이미 존재하는 버전이라는 메시지가 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;&lt;code&gt;`jenv add /usr/lib/jvm/adoptopenjdk-11-jdk-hotspot&lt;/code&gt; 실행결과&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt; openjdk64-11.0.3 already present, skip installation
 11.0.3 already present, skip installation
 11.0 already present, skip installation&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;jEnv는 동일한 JDK 배포판의 여러 버전을 관리하는데 적합합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;다양한 범위(디폴트(global), 디렉토리별, 쉘 범위)의 버전 방식을 지원합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;다양한 배포판의 동일한 JDK 버전(예: 11.0.3)을 관리할 수 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;sdkman&quot;&gt;2.7. SDKMAN&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SDKMAN(The Software Development Kit Manager)은 여러 개발도구를 설치할 수 있는 도구입니다.
JDK 뿐만 아니라 Maven, Gradle, Ant, AsciidoctorJ 등 JVM 세계의 다양한 도구들을 설치할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OS별로 SDKMAN을 설치하는 방법은 &lt;a href=&quot;https://sdkman.io/install&quot; class=&quot;bare&quot;&gt;https://sdkman.io/install&lt;/a&gt; 을 참조합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SDKMAN으로 설치할 수 있는 JDK 배포판/버전은 &lt;code&gt;sdk list java&lt;/code&gt; 명령으로 확인할 수 있습니다.
아래와 같이 사용할 수 있는 배포판들과 설치된 버전 등을 표시해 줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 12.0.1.j9    | adpt    |            | 12.0.1.j9-adpt
               |     | 12.0.1.hs    | adpt    | installed  | 12.0.1.hs-adpt
               |     | 11.0.3.j9    | adpt    |            | 11.0.3.j9-adpt
               |     | 11.0.3.hs    | adpt    |            | 11.0.3.hs-adpt
               |     | 8.0.212.j9   | adpt    |            | 8.0.212.j9-adpt
               | &amp;gt;&amp;gt;&amp;gt; | 8.0.212.hs   | adpt    | installed  | 8.0.212.hs-adpt
 Amazon        |     | 11.0.3       | amzn    |            | 11.0.3-amzn
               |     | 8.0.212      | amzn    |            | 8.0.212-amzn
 Azul Zulu     |     | 12.0.1       | zulu    |            | 12.0.1-zulu
               |     | 11.0.3       | zulu    |            | 11.0.3-zulu
               |     | 10.0.2       | zulu    |            | 10.0.2-zulu
               |     | 9.0.7        | zulu    |            | 9.0.7-zulu
               |     | 8.0.212      | zulu    |            | 8.0.212-zulu
               |     | 7.0.222      | zulu    |            | 7.0.222-zulu
               |     | 6.0.119      | zulu    |            | 6.0.119-zulu
 Azul ZuluFX   |     | 11.0.2       | zulufx  |            | 11.0.2-zulufx
               |     | 8.0.202      | zulufx  |            | 8.0.202-zulufx
 BellSoft      |     | 12.0.1       | librca  |            | 12.0.1-librca
               |     | 11.0.3       | librca  |            | 11.0.3-librca
               |     | 8.0.212      | librca  |            | 8.0.212-librca
 GraalVM       |     | 19.0.2       | grl     |            | 19.0.2-grl
               |     | 19.0.0       | grl     |            | 19.0.0-grl
               |     | 1.0.0        | grl     | installed  | 1.0.0-rc-16-grl
 SAP           |     | 12.0.1       | sapmchn |            | 12.0.1-sapmchn
               |     | 11.0.3       | sapmchn |            | 11.0.3-sapmchn
 java.net      |     | 14.ea.1      | open    |            | 14.ea.1-open
               |     | 13.ea.25     | open    |            | 13.ea.25-open
               |     | 12.0.1       | open    |            | 12.0.1-open
               |     | 11.0.2       | open    |            | 11.0.2-open
               |     | 10.0.2       | open    |            | 10.0.2-open
               |     | 9.0.4        | open    |            | 9.0.4-open
================================================================================
Use the Identifier for installation:

    $ sdk install java 11.0.3.hs-adpt
================================================================================&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;AdoptOpenJDK HotSpot 배포판 12.0.1 버전을 설치하고 싶다면 아래와 같은 명령을 내립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sdk install java 12.0.1.hs-adpt&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;PATH&lt;/code&gt; , &lt;code&gt;JAVA_HOME&lt;/code&gt; 환경변수도 알아서 잘 잡아줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;명령행에서 디폴트로 사용할 JDK 버전은 &lt;code&gt;~/.sdkman/candidates/java/current&lt;/code&gt; 에서 심볼릭 링크로 관리됩니다.
이 링크가 환경변수 &lt;code&gt;$PATH`와 `$JAVA_HOME&lt;/code&gt; 에 추가 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 심볼릭 링크는 아래 명령으로 바꿀 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sdk default java 8.0.212.hs-adpt&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재 쉘에서 사용할 버전만 임시로 바꾸고 싶다면 &lt;code&gt;default&lt;/code&gt; 대신 &lt;code&gt;use&lt;/code&gt; 명령을 씁니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;sdk use java 8.0.212.hs-adpt&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;다양한 JDK 배포판을 설치할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JDK 설치와 버전 지정을 하나의 도구로 관리할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;특정 디렉토리에 들어갔을 때 사용할 버전을 자동을 지정하는 기능이 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sdk use&lt;/code&gt;  명령이 jabba의 동일한 기능에 비해 실행 속도가 느립니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jabba&quot;&gt;2.8. jabba&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;jabba는 JDK의 설치/버전 관리만을 위한 도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;각 OS별 jabba의 설치 방법은 &lt;a href=&quot;https://github.com/Jabba-Team/jabba#installation&quot; class=&quot;bare&quot;&gt;https://github.com/Jabba-Team/jabba#installation&lt;/a&gt; 을 참조합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설치할 수 있는 JDK의 배포판은 &lt;code&gt;jabba ls-remote&lt;/code&gt;  명령으로 확인할 수 있습니다.
이중 Amazon에서 제공하는 Corretto 배포판 JDK 11을 설치한다면 아래와 같은 명령을 내립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jabba install amazon-corretto@1.11.0-3.7.1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설치된 버전들은 &lt;code&gt;jabba ls&lt;/code&gt; 명령으로 확인할 수 있습니다.
현재 쉘에서 사용할 버전은 아래와 같이 지정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;jabba use adopt-openj9@1.12.33-0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;jabba use&lt;/code&gt; 를 실행하면 &lt;code&gt;PATH&lt;/code&gt; 와 &lt;code&gt;JAVA_HOME&lt;/code&gt; 환경변수를 지정한 JDK 버전을 참조할수 있도록 바꾸어줍니다.
&lt;code&gt;echo $PATH&lt;/code&gt; 로 PATH 값을 확인해보면, 가장 앞에 설치한 JDK의 bin 디렉토리를 지정할 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;같은 디렉토리에 `.jabbarc`라는 파일이 있다면, 그 파일에 지정된 버전을 참조할 수 있습니다.
즉 아래와 같이 실행해도 특정 버전을 지정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;echo &quot;adopt-openj9@1.12.33-0&quot; &amp;gt; .jabbarc
jabba use&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음 번에 같은 디렉토리에 들어왔을 떄에는 &lt;code&gt;jabba use&lt;/code&gt; 만 간단하게 실행해서 같은 효과를 낼 수 있습니다.
direnv나 jEnv를 쓸 때처럼 디렉토리에 들어가면 자동으로 환경변수를 바꾸어주는 기능은 없습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재 쉘범위의 JDK 버전만 지정한다는 점이 jabba의 장점이나 단점입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;장점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;다른 도구와 충돌없이 쓰기에 좋습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jabba use&lt;/code&gt; 명령이 SDKMAN의 &lt;code&gt;sdk use&lt;/code&gt; 에 비해 실행 속도가 빠릅니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;디폴트 버전 지정이 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;디렉토리별 버전 비전 기능이 완전 자동이 아닙니다. 해당 디렉토리에서 &lt;code&gt;jabba use&lt;/code&gt; 를 한번 입력해야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;무엇을_어떻게_사용할_것인가&quot;&gt;3. 무엇을 어떻게 사용할 것인가?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 다양한 도구 중 어떤 것을 골라 쓸지는 개발장비의 OS와 필요한 범위에 따라서 결정해야할 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우선 다양한 배포판의 JDK를 쓰는 것까지 필요가 없다면 아래 정도의 조합을 고려할만합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Windows : Chocolatey + direnv&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Linux : APT/YUM + update-alternatives + jEnv (또는 direnv)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;macOS : Homebrew + jEnv(또는 direnv)&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[Mac에 Java 여러 버전 설치] 글에서는 Homebrew로 Oracle JDK를 설치하고 jEnv와 함께 사용하는 사례가 정리되어 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Amazon Corretto, GraalVM 등 다양한 배포판의 여러버전을 설치해보고 싶다면 SDKMAN이나 jabba를 함께 쓰는 것을 추천합니다.
각 도구들이 지원하는 배포판은 아래와 같습니다. (2019년 7월1일 기준)&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-ends grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 2. JDK 설치 도구들이 지원하는 배포판&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 33.3333%;&quot;&gt;
&lt;col style=&quot;width: 66.6667%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;이름&lt;/th&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;지원하는 JDK 배포판&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;YUM/APT&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;OS 배포판별 OpenJDK &lt;strong&gt;(*1)&lt;/strong&gt;&lt;br&gt;
AdoptOpen JDK&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Homebrew&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Oracle JDK&lt;br&gt;
Adopt OpenJDK&lt;br&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Oracle JDK&lt;br&gt;
Oracle의 OpenJDK 빌드 &lt;strong&gt;(*2)&lt;/strong&gt;&lt;br&gt;
Adopt OpenJDK&lt;br&gt;
Amazon Corretto&lt;br&gt;
Zulu OpenJDK&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://sdkman.io/&quot;&gt;SDKMAN&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Oracle의 OpenJDK 빌드 &lt;strong&gt;(*2)&lt;/strong&gt;&lt;br&gt;
Adopt OpenJDK&lt;br&gt;
Amazon Corretto
GraalVM CE&lt;br&gt;
Zulu OpenJDK&lt;br&gt;
Zulu OpenJDK + OpenJFX&lt;br&gt;
SapMachine&lt;br&gt;
Liberica JDK&lt;br&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://github.com/Jabba-Team/jabba&quot;&gt;jabba&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Oracle JDK&lt;br&gt;
Oracle의 OpenJDK 빌드 &lt;strong&gt;(*2)&lt;/strong&gt;&lt;br&gt;
Adopt OpenJDK&lt;br&gt;
Amazon Corretto&lt;br&gt;
GraalVM CE&lt;br&gt;
Zulu OpenJDK&lt;br&gt;
IBM SDK&lt;br&gt;
OpenJDK 참조 구현체&lt;br&gt;
OpenJDK + Shenandoah GC&lt;br&gt;
Liberica JDK&lt;br&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(*1)&lt;/strong&gt; : 해당 OS 배포판을 위해 빌드된 OpenJDK 배포판입니다. OS의 배포판을 관리하는 업체/커뮤니티에서 관리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(*2)&lt;/strong&gt; : &lt;a href=&quot;https://jdk.java.net/&quot; class=&quot;bare&quot;&gt;https://jdk.java.net/&lt;/a&gt; 에서 다운로드 받을 수 있는 OpenJDK 배포판입니다. 출시 후 6개월까지만 최신 버전이 업데이트됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위에 정리한 것처럼 SDKMAN과 jabba가 많은 JDK 배포판을 지원합니다.
둘다 Adopt OpenJDK, Amazon Corretto, GraalVM CE, Zulu 등 주목받는 주요 배포판은 모두 포함하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SDKMAN에서는 제공하는 반면  jabba에는 없는 배포판은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Zulu OpenJDK + OpenJFX&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SapMachine&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;jabba에서는 제공하는 반면 SDKMAN에는 없는 배포판은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;IBM SDK&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK 참조 구현체&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://jdk.java.net/java-se-ri/8&quot; class=&quot;bare&quot;&gt;https://jdk.java.net/java-se-ri/8&lt;/a&gt; 등 에서 받을수 있는 배포판 입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenJDK + Shenandoah GC&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SDKMAN과 jabba는 JDK 설치와 버전 지정 기능을 동시에 제공합니다.
그런데 jenv등 다른 도구에서 제공하는 버전 지정 기능을 완정히 제공하지는 않습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-ends grid-all stretch&quot;&gt;
&lt;caption class=&quot;title&quot;&gt;Table 3. JDK 버전 지정 기능&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 40%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;col style=&quot;width: 20%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;도구&lt;/th&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;디폴트&lt;/th&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;디렉토리별&lt;/th&gt;
&lt;th class=&quot;tableblock halign-center valign-top&quot;&gt;쉘 범위&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;update-alternatives/
alternatives&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;X&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;X&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://sdkman.io/&quot;&gt;SDKMAN&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;X&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://github.com/shyiko/jabba&quot;&gt;jabba&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;X&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;△&lt;strong&gt;(*3)&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://www.jenv.be/&quot;&gt;jEnv&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;a href=&quot;https://direnv.net/&quot;&gt;direnv&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;X&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;O&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-center valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;X&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(*3)&lt;/strong&gt; : jEnv나 direnv처럼 디렉토리에 들어가면 자동으로 특정 JDK 버전이 선택되는 방식은 아니기 때문에 △로 표기했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서 SDKMAN이나 jabba는 다른 도구와 조합해서 사용하면 더욱 편리하게 쓸 수 있습니다.
그런데 앞서 언급했듯이 jEnv는 SDKMAN이나 jabba와 함께 쓰기에는 적합하지 않습니다.
&lt;code&gt;$PATH&lt;/code&gt; 환경 변수에 지정된 경로의 순서에 따라서 여러 도구의 버전 지정 결과가 의도하지 않게 덮어 써질수 있습니다.
즉 SDKMAN에 지정한 경로가 앞에 있으면 jEnv에서 지정한 JDK 버전이 인식되지 않는 것처럼 보일수도 있습니다.
그리고 jabba로는 여러 배포판의 JDK 11.0.3 을 설치할 수 있지만 jEnv에서는 &apos;jenv add&apos; 로 같은 버전(11.0.3)의 다른 배포판을 추가할 수 없습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서 다양한 배포판을 설치하고자 할때는 SDKMAN(또는 jabba) +  direnv 조합을 추천합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 이 도구들을 쓰는 환경은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;각각 다른 JDK 버전을 쓰는 여러 프로젝트의 소스를 고칩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;업무 혹은 취미로 JDK의 여러 배포판/ 버전을 설치해서 차이가 있는지 확인하고 있습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(예: 포함된 ca-cert 목록 비교, GraalVM으로 네이티브 이미지 만들기 시도)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;회사의 업무용 노트북과 집에 있는 PC에서 Ubuntu 19.04를 씁니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이에 따라 저는 아래와 같이 도구를 조합해서 쓰고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JDK 설치에는 APT, SDKMAN, jabba를 다 사용해 보고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;사용할 버전을 선택할 때는&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;디폴트 버전은 SDKMAN으로 지정합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SDKMAN을 설치하면 SDK에서 관리하는 패키지들이 &lt;code&gt;/usr/bin&lt;/code&gt; 보다 앞에 오기 때문입니다. 디폴트 버전은 자주 바꾸진 않기 때문에 굳이 이를 조정하진 않았습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;특정 디렉토리에서 사용한 버전을 지정할 때는 direnv를 씁니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;쉘에서 일시적으로 사용할 버전을 지정할 때는 SDKMAN, jabba를 씁니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>정적 사이트로 블로그 마이그레이션</title>
      <link>https://blog.benelog.net//migration-to-static-site.html</link>
      <pubDate>Mon, 6 May 2019 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">migration-to-static-site.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#블로그를_이전한_동기&quot;&gt;1. 블로그를 이전한 동기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#요구사항&quot;&gt;2. 요구사항&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#선택한_기술&quot;&gt;3. 선택한 기술&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#정적_사이트_생성기&quot;&gt;3.1. 정적 사이트 생성기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#git_저장소_빌드배포&quot;&gt;3.2. Git 저장소, 빌드/배포&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#편집기&quot;&gt;3.3. 편집기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#마이그레이션_프로그램&quot;&gt;3.4. 마이그레이션 프로그램&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#마치며&quot;&gt;4. 마치며&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;블로그를_이전한_동기&quot;&gt;1. 블로그를 이전한 동기&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 이 블로그를 &lt;a href=&quot;http://www.egloos.com&quot;&gt;Egloos&lt;/a&gt;에서 커스텀 URL(blog.benelog.net)을 설정하여 쓰고 있었습니다.
Egloos는 무난한 블로깅 플랫폼입니다. 그러나 개발과 관련된 글을 쓰면서 코드를 편집하기에는 저한테는 Egloos에서 제공하는 에디터가 잘 맞지가 않았습니다.
점점 글을 안 쓰게 되었고, 이 블로그는 갱신되지 않는 옛날 글만 가득하게 되었습니다.
공유할 글이 생길 때는 &lt;a href=&quot;https://gist.github.com/&quot; class=&quot;bare&quot;&gt;https://gist.github.com/&lt;/a&gt; 를 이용하기도 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이전처럼 가끔씩이라도 블로그에 글을 올리려면 블로그 플랫폼을 바꿔야겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;요구사항&quot;&gt;2. 요구사항&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;블로그 플랫폼을 선택하는데 염두에 둔 조건은 아래와 같았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;기존의 포스트의 주소가 동일하게 유지되어야 한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;외부에서 걸린 링크가 깨어지지 않아야한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;기존 블로그에 달린 댓글을 그대로 옮겨올 수 있어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;다른 서비스/도구로 다시 옮겨가기가 쉬워야한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AsciiDoc으로 포스트를 작성할 수 있어야한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;선택한_기술&quot;&gt;3. 선택한 기술&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞의 요구사항을 충족시키는 다음과 같은 서비스/도구들을 선택했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;정적 사이트 생성기 : &lt;a href=&quot;https://jbake.org/&quot;&gt;JBake&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컨텐츠 저장소 : GitHub&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;빌드/배포 : &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;댓글 : &lt;a href=&quot;https://help.disqus.com/&quot;&gt;Disqus&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;방문자 통계 집계 : &lt;a href=&quot;https://analytics.google.com/analytics/web/&quot;&gt;Google Analytics&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;새로운 블로그는 정적 사이트 생성기(Static Site Generator)로 Git 저장소에 있는 AsciiDoc 형식의 컨텐츠를 HTML 파일로 만들어서 배포하는 구조입니다.
덕분에 Egloos로부터의 마이그레이션 작업이 더 수월했고 더 좋은 서비스가 있을 때 쉽게 이전을 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;컨텐츠 저장소는 GitHub외에도 Gitlab, Bitbucket 등의 대안이 있고, 컨텐츠 빌드 배포 플랫폼도 다양합니다.
필요하다면 더 좋은 기능이 있는 서비스를 유료로 사용할 수도 있습니다.
컨텐츠 편집기도 여러가지를 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;사용할 기술을 선택하면서 했던 고민들은 아래에 정리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;정적_사이트_생성기&quot;&gt;3.1. 정적 사이트 생성기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정적 사이트 생성기로 JBake를 선택한 이유는 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;필수요구사항이였던 AsciiDoc 지원&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JVM 기반&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;일할 때도 쓰는 환경이라서 문제를 만났을 때 해결하는 시간이 짧고, 직접 고쳐서 쓸 수 있는 가능성도 높습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gradle 빌드 구성 등 이미 알고 있는 지식과 결합시키기에도 편했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JBake로 만들어진 템플릿인 &lt;a href=&quot;https://github.com/manikmagar/jbake-future-imperfect-template&quot;&gt;jbake-future-imperfect-template&lt;/a&gt;의 편의성&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Disqus와 Google Analytics를 설정파일을 1줄씩을 고쳐서 간단히 적용할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;비슷한 고민을 하실 분들이 참고할만한 링크들을 정리해봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;정적 사이트 생성기가 정리된 사이트&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.staticgen.com/&quot; class=&quot;bare&quot;&gt;https://www.staticgen.com/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://staticsitegenerators.net/&quot; class=&quot;bare&quot;&gt;https://staticsitegenerators.net/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/briandominick/e5754cc8438dd9503d936ef65fffbb2d&quot;&gt;AsciiDoc을 지원하는 정적 사이트 생성기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JVM 기반의 정적 사이트 생성기&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://orchid.netlify.com/&quot;&gt;Orchid&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.opoopress.com/&quot;&gt;OpooPress&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/wiztools/stagen&quot;&gt;StaGen&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sysgears.com/work/grain/&quot;&gt;Grain&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nakkaya.com/static.html&quot;&gt;nakkaya staic&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;미래에 JBake를 다른 도구로 교체한다면 아래 2개의 도구를 가장 우선적으로 고려할 예정입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://orchid.netlify.com/&quot;&gt;Orchid&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JVM 기반이면서 AsciiDoc 지원&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;현대적인 기술 스택인 &lt;a href=&quot;https://jamstack.org/&quot;&gt;JAMstack&lt;/a&gt; 활용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.netlifycms.org/&quot;&gt;Netlify CMS&lt;/a&gt;와 연동됨&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;빠른 빌드 성능&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;템플릿, 편집과 배포에 대한 생태계가 풍부함&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gohugo.io/tools/frontends/&quot; class=&quot;bare&quot;&gt;https://gohugo.io/tools/frontends/&lt;/a&gt; 참조&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.netlifycms.org/&quot;&gt;Netlify CMS&lt;/a&gt;와도 연동됨&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;git_저장소_빌드배포&quot;&gt;3.2. Git 저장소, 빌드/배포&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git 저장소는 많이 쓰이는 GitHub를 무난하게 선택했습니다.
AsciiDoc으로 작성된 포스트의 원본은 &lt;a href=&quot;https://github.com/benelog/blog/tree/master/src/content&quot;&gt;github/benelog/blog/src/content&lt;/a&gt; 에서 이력을 관리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음에는 &lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis CI&lt;/a&gt;와 Githb Page를 활용해서  빌드/배포를 구성하고 커스텀 도메인과 HTTPS를 적용하려고 했었습니다.
그래서 이 블로그는 &lt;a href=&quot;https://benelog.github.io/blog&quot; class=&quot;bare&quot;&gt;https://benelog.github.io/blog&lt;/a&gt; 로도 접근이 가능합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;빌드 스크립트는 &lt;a href=&quot;https://github.com/benelog/blog/blob/master/build.gradle&quot;&gt;build.gradle&lt;/a&gt;에서 확인이 가능합니다.
Travis CI 설정인 &lt;a href=&quot;https://github.com/benelog/blog/blob/master/.travis.yml&quot;&gt;.travis.yml&lt;/a&gt;은 아래와 같이 단순합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;yml&quot;&gt;language: java
script: ./gradlew gitPublishPush&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 작업을 실행하기 위해서는 &lt;a href=&quot;https://github.com/settings/tokens&quot; class=&quot;bare&quot;&gt;https://github.com/settings/tokens&lt;/a&gt; 에서 발급받은 Personal Access Token을
Tracis CI의 저장소별 설정에서 &lt;code&gt;GRGIT_USER&lt;/code&gt; 라는 환경변수로 지정을 해주어야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/blog-migration/travis-ci-settings.png&quot; alt=&quot;travis-ci-settings&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;최종적으로는 지원하는 기능이 더 풍부한 &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netflify&lt;/a&gt;을 이 도메인에는 사용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gradle로 만든 빌드 스크립트를 지정하고 Domain을 지정하고 HTTPS를 적용하는 작업까지 편하게 할 수 있었습니다.
Netflify에는 Name 서버 기능까지 있어서 사용하던 Name 서버도 이 서비스로 이전을 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/blog-migration/netflify.png&quot; alt=&quot;netflify&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Netflify에서는 &lt;code&gt;./gradlew bake&lt;/code&gt; 로 빌드를 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/blog-migration/netflify-deployment.png&quot; alt=&quot;netflify&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 블로그의 JBake 설정으로는 &lt;a href=&quot;https://blog.benelog.net/904735.html&quot; class=&quot;bare&quot;&gt;https://blog.benelog.net/904735.html&lt;/a&gt; 와 같이 HTML 확장자를 붙인 주소를 씁니다.
Egloos의 이전 포스트들은 &lt;a href=&quot;https://blog.benelog.net/904735&quot; class=&quot;bare&quot;&gt;https://blog.benelog.net/904735&lt;/a&gt; 와 같이 HTML 확장자가 없게 되어 있습니다.
Netflify와 Github pages에서는 디폴트로 아무런 설정이 없어도 HTML 확장자가 없는 주소를 HTML 확장자가 붙은 주소와 동일하게 취급을 해주어서
URL의 하위 호환성을 쉽게 유지할 수 있었습니다.
JBake의 설정으로 HTML 확장자를 제외하는 것도 시도해보았지만, 태그 링크 같은 블로그의 다른 요소의 링크들과 함께 잘 어우러지는 것이 쉽지 않았습니다.
호스팅 서비스 단에서 연결해 해주는 방법이 더 간편하고 부작용이 없어서 최종적으로 그에 의지하도록 했습니다.
즉, 기존 주소 연결을 위해서 아무런 설정도 안 했다는 이야기입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정적 페이지를 호스팅할수 있는 다른 서비스를 찾는다면 아래의 링크들을 참조하실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://about.gitlab.com/product/pages/&quot;&gt;Gitlab Pages&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://surge.sh/&quot;&gt;Surge&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.aerobatic.com/&quot;&gt;Aerobatic&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zeit.co/now&quot;&gt;Now&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/ko/s3/&quot;&gt;Amazon S3&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://firebase.google.com/docs/hosting/&quot;&gt;Firebase Hosting&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;편집기&quot;&gt;3.3. 편집기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;긴 글을 편집할 때는 IntelliJ Community Edition을 사용했습니다.
평소 개발할 때의 편집방식, 단축키와 Git 저장소 연동 방식을 그대로 활용할 수 있습니다.
&lt;code&gt;Find in Path (Ctrl + Shift + F)&lt;/code&gt; , &lt;code&gt;Replace in Path (Ctrl + Shift + R)&lt;/code&gt; 으로 여러 파일에 걸쳐 문자열을 검색하고 치환하는 기능은 마이그레이션 된 컨텐츠를 정리할 때 많은 도움이 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7391-asciidoc&quot;&gt;AsciiDoc plugin&lt;/a&gt;을 설치하면
HTML으로 렌더링될 모습을 동시에 보면서 편집을 할 수 있고 이미지 파일을 참조할 때는 자동완성이 되기도합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/blog-migration/intellij-asciidoc.png&quot; alt=&quot;IntelliJ로 AsciiDoc 편집&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;간단한 글을 쓰거나 수정할 때는 Github에서 바로 온라인으로 편집을 할 생각입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 정적 사이트 생성기를 기반으로 온라인 에디터를 제공하는 서비스들은 아래와 같은 것들이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://appernetic.io/&quot;&gt;Appernetic.io&lt;/a&gt; : Hugo 지원&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://forestry.io/&quot;&gt;Forestry&lt;/a&gt; : Jekyll, Hugo, Vuepress 지원&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.netlifycms.org/&quot;&gt;Netlify CMS&lt;/a&gt; : Hugo, Gatsby, Middleman 등 지원&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;마이그레이션_프로그램&quot;&gt;3.4. 마이그레이션 프로그램&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마이그레이션 프로그램의 소스와 사용법은 &lt;a href=&quot;https://github.com/benelog/egloos-migration/&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/egloos-migration/&lt;/a&gt; 을 참고하시기 바랍니다.
이것도 제가 알고 있는 기술들을 활용해서 Kotlin + Spring 으로 작성했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;컨텐츠를 담은 파일을 생성할 때 Kotlin의 문자열 문법이 유용할 것으로 예상해서 언어는 Kotlin을 선택했습니다. 아래와 같은 코드입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;kotlin&quot;&gt;    private fun convert(post: EgloosPost): String {
        val asciiDoc = converter.convert(post.content)
        return &quot;&quot;&quot;= ${post.title}
${post.nick}
${post.createdAt.format(formatter)}
:jbake-type: post
:jbake-status: published
:jbake-tags: ${post.tags}
:idprefix:

$asciiDoc
&quot;&quot;&quot;
        // String을 inputStream으로 읽어서쓰니 trimIndent가 안 먹음.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 프로그램을 부분적으로 실행하고 검증해보기 위해서 Spring Batch를 썼습니다.
원래 Spring Batch는 대용량 데이터를 메모리에 다 올리지 않고 처리할 때 적합한 프레임워크입니다.
제 블로그의 포스트는 100개 정도로 작은 데이터인데 여기에 Spring Batch를 쓰는 것은 어울리지 않을 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, Spring Batch의 ItemReader, ItemProcessor, ItemWriter 구조에 맞춰서 프로그램을 만들고
이 기능 단위로 테스트를 해보면 것이 마이그레이션 프로그램을 만드는 효율적인 방법이라고 판단했습니다.
Spring Batch를 쓰지 않았어도 비슷한 기능 단위로 프로그램을 쪼갯을 것 같았기에 이미 익숙한 프레임워크의 인터페이스를 활용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;부분적인 기능의 테스트들은 &lt;a href=&quot;https://github.com/benelog/egloos-migration/tree/master/src/test/kotlin/net/benelog/blog/migration/etl&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/egloos-migration/tree/master/src/test/kotlin/net/benelog/blog/migration/etl&lt;/a&gt; 을 참조하실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;마치며&quot;&gt;4. 마치며&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오랜 숙원 사업이였던 블로그 이전을 마쳐서 뿌듯합니다.
다른 개발자들에게 도움이 될만한 글을 가끔씩이라도 써보겠습니다.
이전에 썼던 글들도 틈틈히 최신화하려고 합니다.
할 작업들은 &lt;a href=&quot;https://github.com/benelog/blog/issues&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/blog/issues&lt;/a&gt; 에 기록해두고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Robolectric을 활용한 안드로이드 쾌속 테스팅</title>
      <link>https://blog.benelog.net//robolectric.html</link>
      <pubDate>Mon, 10 Oct 2016 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">robolectric.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2014년 8월1일 &lt;a href=&quot;http://d2.naver.com/helloworld/870369&quot;&gt;제9회 오픈 세미나 in 대구&lt;/a&gt; 행사에서 한 발표입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예제 코드들은 당시의 Rolbectric의 최신 버전을 기준으로 한 것이라 현재의 최신 버전에서는 그대로 실행되지 않을 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오늘 발표에서는 테스트 프레임워크인 Robolectric을 사용하면서 실무에서 얻었던 경험을 공유하고자합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;먼저 테스트코드란 무엇인지와 안드로이드에서 테스트 작성을 어렵게 하는 난관등을 말씀드리고 Robolectric을 활용하는 방법을 소개하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;테스트_코드란&quot;&gt;테스트 코드란?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참석하신 분 중에서 JUnit(제이유닛)에 대해서 한번이라도 들어보신 분은 손을 들어보시겠습니까? 이중에 Junit을 실제로 써보신분은 얼마나 되시나요? Android에서 JUnit으로 테스트를 시도해보신 분은 계신가요? 경험을 하신 정도가 다양하기 때문에 우선 오늘 다룰 테스트 코드란 무엇인지를 한번 정리하고 시작을 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;단순히 말해서 테스트 코드는 &apos;검증&amp;#8217;을 위한 코드입니다. 다음은 이미지로딩 라이브러리인 Universal ImageLoader의 소스 중 테스트 코드입니다. github에서 전체소스를 확인하실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Test
public void testSchemeFile() throws Exception {
    String uri = &quot;file://path/on/the/device/1.png&quot;;
    Scheme result = Scheme.ofUri(uri);
    Scheme expected = Scheme.FILE;
    assertThat(result).isEqualTo(expected);
}

@Test
public void testSchemeUnknown() throws Exception {
    String uri = &quot;other://image.com/1.png&quot;;
    Scheme result = Scheme.ofUri(uri);
    Scheme expected = Scheme.UNKNOWN;
    assertThat(result).isEqualTo(expected);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( Android Universal Image Loader의 &lt;a href=&quot;https://github.com/nostra13/Android-Universal-Image-Loader/blob/master/library/test/com/nostra13/universalimageloader/core/download/BaseImageDownloaderTest.java&quot;&gt;BaseImageDownloaderTest&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;골뱅이 Test가 붙은 애노테이션으로 실행될 코드를 지정하고 assert 구문으로 기대하는 결과를 명시합니다. 이 코드는 uri를 파싱해서 Scheme (스킴)이라는 Enum(이늄)클래스를 만든 결과가 기대대로 FILE이나 UNKNOWN인지를 확인하고 있습니다. 이 코드는 오늘 이야기할 Robolectric으로 만들어졌습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트를 도와주는 프레임워크도 굉장히 많습니다. 앞에서도 언급드렸듯이, 테스트를 실행하는데는 JUnit이 가장 많이 쓰입니다. 안드로이드 SDK에서도 이를 바탕으로 한 테스트 프레임워크를 제공하고 있습니다. 그리고 테스트에 쓰이는 가짜 객체를 흔히 목(Mock)이라고 부르는데, Mockito, JMock, PowerMOck과 같은 라이브러리들이 있습니다. 안드로이드를 위한 테스트 프레임워크에도 오늘 다룰 Robolectric을 비롯해 Robotium, Spoon, Robospock 등이 존재합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오늘 설명을 이어가는데 혼동을 줄이기 위해 유의해야할 개념을 몇가지 말씀드리겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첫째, JUnit으로 하는 테스트라고 전부 유닛테스트는 아니라는 것입니다. JUnit에 유닛(Unit)이라는 이름이 들어가서 생기는 혼동입니다. 테스트 Functional 테스트 (혹은 시스템 테스트)도 JUnit으로 작성하는 경우도 많습니다. 오늘 발표에서는 이를 엄밀히 구분하지는 않고 폭넓게 &apos;테스트 코드&amp;#8217;라는 말로만 칭하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;둘째, 테스트 코드를 작성하는 작업을 &apos;TDD를 한다&amp;#8217;라고 한마디로 말하기는 어렵다는 것입니다. TDD는 테스트를 작성하는 하나의 방식입니다. TDD 기법은 테스트를 먼저 작성하고, 테스트를 통과시키는 코드를 구현한 후 리팩토링을 하는 절차를 거칩니다. 오늘 발표에서는 TDD 같은 절차와 상관없이 테스트를 작성하는 라이브러리 활용법에 대해서 주로 말씀드리겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;왜 이런 테스트를 만드는지에 대한 의문을 가지실분도 있을 듯합니다. 간단히 설명드리면, 그 이유는 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첫째, 디버깅 편의성을 위해서입니다. 테스트 코드 작성에 능숙해지면 실제 어플리케이션을 실행하고 수동으로 반복 테스트하는것보다 훨씬 빠르고 정교하게 내가 짠 코드의 동작을 확인하고 오류를 수정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;둘째, 설계를 개선하기 위해서입니다. 테스트 하기에 쉬운 구조의 코드는 역할과 책임이 잘 나누어진 코드입니다. 그런 코드는 재활용하고 기능을 추가하거나 버그를 발견하기에도 편합니다. 테스트를 의식하면서 개발을 하면 그런 구조의 코드를 작성하는데 도움이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;셋째, 테스트 자체가 동작하는 예제이자 명세가 됩니다. 다른 사람이 어플리케이션이나 라이브러리의 전체를 실행시키지 않아도 코드가 실행된 결과를 이해할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;넷째, 반복적으로 수행할 회귀테스트를 자동화합니다. 앞으로 기능을 추가하거나 코드를 개선할 때 든든한 버팀목이 되고 시간을 아껴줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다섯째, 개발 작업에 더 집중하게 해줍니다. 테스트를 통과한다는 명확한 목표가 있고, 이를 빠른 시점에 명확하고 신호로 알려주고 작업의 난이도와 간격은 스스로 적당하게 조절할수 있습니다. 심리학에서 말하는 몰입경험의 조건과 일치합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;안드로이드_테스트의_장벽&quot;&gt;안드로이드 테스트의 장벽&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다면 이런 테스트의 장점을 안드로이드에서도 행복하게 누릴수가 있을까요? 불행히도 많은 장벽이 있다는 것을 경험했습니다. 그 이유를 몇가지 나누어서 말씀드리겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첫째, Mock을 쓰기 어려운 기본 프레임워크 구조입니다. 안드로이드에서 굉장히 많이 쓰이는 getViewById, getSystemService 같은 코드는 상위클래스에 있는 메서드를 호출하는 구조입니다. 이런 형태는 가짜 클래스인 Mock을 주입하기가 어렵습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;둘째, 빈약한 기본 Mock클래스입니다. android.test.mock 패캐지 아래에는 MockContext, MockApplication, MockResource 등의 많은 클래스들이 있지만, 이들은 UnsupportedOperationException을 던지는 껍데기일 뿐입니다. 필요한 동작은 다음과 같이 직접 override해서 구현해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;static public class MockServiceContext extends MockContext {
    @Overrride
    public getSystemService(String name){
        ……
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;셋째, 기본적으로 제공되는 Instrumentation Test를 쓰는것도 배우기가 쉽지않습니다. 예를 들면 Activity를 테스트할때 ActivityTestCase, ActivityUnitTestCase, ActivityInstrumentationTestCase2의 세 가지 클래스 중 어느것을 써야할까를 하려면 많은 것을 알고 있어야합니다. 그리고 ActivityUnitTestCase에서 Dialog생성 등에 Event가 전달되면 BadToken Exception이 발생한다거나, ActivityInstrumentationTestCase2에서 Dialog 객체를 생성 후 dismiss() 메서드를 호출하지 않으면 leak window Exception이 발생하는등 부딛히는 예외상황도 많습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;넷째, UI 테스트 본연의 어려움이 있습니다. 안드로이드 코드는 역할상 UI 생성과 이벤트를 다루는 코드의 비중이 높습니다. 이는 웹어플리케이션 등 다른 플랫폼에서도 테스트하기 어려운 분야입니다. UI 객체의 속성은 자주 바뀌고 익명 클래스 등을 통해서 처리되는 이벤트는 Mock 객체로 바꾸고 추적하기가 어렵습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다섯째, 느린 테스트 실행 속도입니다. 단 한줄을 고쳐도 패키징 &amp;#8594; 설치 &amp;#8594; 실행 싸이클을 거칩니다. 이 부분이 테스트의 장점을 다 말아먹는 가장 치명적인 단점입니다. 요즘 PC나 단말이 많이 빨라졌고 Genymotion같은 빠른 에뮬레이터도 활용할 수 있어서 많이 나아졌지만, 그래도 실행싸이클의 특성상 개선에 한계가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;robolectric_활용법&quot;&gt;Robolectric 활용법&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저도 Android의 기본 Instrutation테스트를 시도해보다 앞에서 말씀드린 이유로 많은 좌절을 느꼈습니다. 그래서 이를 개선하는 기술로 Robolecric을 시도해봤고, 어느정도 노하우를 쌓았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Robolectric은 배포, 설치가 필요없도록 PC의 JVM에서 안드로이드 코드를 실행해줍니다. 아마 한두번쯤을 만나셨을 메시지일듯한데, 이클립스 같은 IDE안에서 안드로이드 코드를 바로 돌리면 &apos;java.lang.RuntimeException: Stub!?&apos; 에러가 발생합니다. Robolectric은 Android SDK가 제공하는 클래스를 가로채어서 서 JVM에서 ANdroid 코드를 실행해도 저런 에러가 나지 않는 가짜코드를 실행합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 프로젝트는 Github에서 활발하게 개발되고 있습니다. ActionbarSherlock으로 유명한 JakeWharton도 주요 커미터입니다. 174명의 기여자가 참여했고, 저도 그 174명 중의 한명이기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;릴리즈 노트를 보시면 아시겠지만, 이 프레임워크는 꾸준히 발전하고 있습니다. 최근에는 KitKat에 추가된 API를 지원하는 작업도 진행되고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Android를 만든 구글에서도 Robolectric의 1점대의 버전을 자체 포크해서 쓴 흔적이 Android 코드저장소에 남아있습니다. 이렇게 포크로 그치지 않고 구글에서도 같이 Robolectric 2점대 버전의 개발에 참여했으면 더 좋지 않을까 하는 아쉬움이 남습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;물론 Dalvik이나 Art같은 Android 본연의 환경이 아닌 JVM에서 실행되다는 점 때문에 이 라이브러리의 한계는 있습니다. 그리고 Android SDK의 모든 영역을 SDK 출시 즉시에 제공하지도 못합니다. 그렇지만 Robolectric의 한계를 잘 인식하고 효율적으로 테스트할수 있는 부분에 집중을 한다면 앱이나 라이브러리를 개발하는데 많은 도움이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;몇가지 대표적인 사용사례를 들어보겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;로그를_system_out으로_출력하기&quot;&gt;로그를 System.out으로 출력하기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우선 LogCat으로 출력되는 로그를 Log를 System.out으로 출력하려면 아래와 같이 구현을 하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SystemUtilsTest {
    @Before
    public void setUp() {
        ShadowLog.stream = System.out;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;android.util.Log를 이용한 클래스를 JVM에서 바로 실행가능합니다. java.lang, java.util등 기본 JDK에도 동일한 이름으로 존재하는 클래스를 주로 쓰는 유틸리티 클래스를 만들어도 Log를 찍는 코드가 중간에 들어가있으면 이를 Dalvik에서만 실행해야했습니다. Robolect은 그런 코드도 JVM에서 실행되도록 하며 위와 같이 ShawdowLog클래스에 stream속성을 System.out으로 지정하면 System.out.println으로 찍는것과 유사하게 PC의 표준출력에서 로그메시지를 확인할수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;단말기의_정보_변경&quot;&gt;단말기의 정보 변경&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;종종 Build.VERSION.SDK_INT 변수의 값을 참조해서 SDK의 버전별로 분기처리를 해야하는 코드가 있습니다. Robolectric에서는 이런 상수값도 아래와 같이 조작을 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;	Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, &quot;SDK_INT&quot;, Build.VERSION_CODES.JELLY_BEAN);&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 기법은 Http호출을 하는 클라이언트에서 userAgent에 단말의 정보를 조합해서 넣어하는 경우를 테스트하는 경우에 유용하게 썼습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Activity 클래스는 ActivityController라는 클래스를 통해 생성할 수 있습니다. 아래 코드는 스크린밝기를 지정하는 유틸리티는 테스트하는 코드입니다. 이 소스코드는 github에서 전체를 확인해보실수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Test
public void shouldChangeScreenBrightness() {
    TestActivity activity = createActivity(TestActivity.class);
    float brightness = 0.5f;
    ScreenUtils.setScreenBrightness(activity, brightness);

    LayoutParams lp = activity.getWindow().getAttributes();

    assertThat(lp.screenBrightness, is(brightness));
}

private &amp;lt;T extends Activity&amp;gt; T createActivity(Class&amp;lt;T&amp;gt; activityClass) {
        ActivityController&amp;lt;T&amp;gt; controller = Robolectric.buildActivity(activityClass);
        controller.create();
        return controller.get();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( &lt;a href=&quot;https://github.com/naver/android-utilset/blob/master/UtilSet/test/com/navercorp/utilset/ui/ScreenUtilsTest.java&quot;&gt;ScreenUtilsTest&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;DisplayMetricsDensity 속성은 직접 &lt;a href=&quot;https://github.com/robolectric/robolectric/blob/master/src/main/java/org/robolectric/Robolectric.java&quot;&gt;org.robolectric.Robolectric&lt;/a&gt;의 set메서드로 지정할 수 있습니다. 아래는 DP와 Pixel을 전환하는 코드를 예제로 들어봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class PixelUtilsTest {
    private Context context;

    @Before
    public void setUp() {
        ShadowLog.stream = System.out;
        this.context = Robolectric.application;
    }

    @Test
    public void shouldGetDpFromPixel(){
        Robolectric.setDisplayMetricsDensity(1.5f);
        int dp = PixelUtils.getDpFromPixel(context, 50);
        assertThat(dp, is(33));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( &lt;a href=&quot;https://github.com/naver/android-utilset/blob/master/UtilSet/test/com/navercorp/utilset/ui/PixelUtilsTest.java&quot;&gt;PixelUtilsTest&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 클래스의 Setter 메소드를 살펴보면 그밖에도 다양하게 테스트를 위한 가짜 객체를 설정하는 기능을 찾으실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;단말의_sdk_정보를_원하는_값으로&quot;&gt;단말의 SDK 정보를 원하는 값으로&lt;/h3&gt;

&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;system_서비스의_결과를_원하는_값으로&quot;&gt;System 서비스의 결과를 원하는 값으로&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;몇가지 예를 들었는데, Robolectric을 결국 어떻게 활용할 것이 좋을까요? JVM에서 테스트해도 동일한 결과를 보장하는 문자열, 날짜 처리, 프로토콜 파싱 영역에서 이득이 많습니다. 주로java.lang, java.util , java.io 패키지가 다루는 영역에 우선 집중하기는 것이 좋습니다. 처음부터 Activity, Fragment같은 UI영역까지 포함한 통합 테스트에 너무 많은 기대를 걸면 오히려 어려울 수 있습니다. Utility 클래스부터 우선 적용해보면서 점점 영역을 넓혀가시기를 권장드립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Robolectric의 버전 2.3부터는 실제 Sqlite 구현체를 이용하기 시작했습니다. 이 버전부터는 DB관련 테스트도 JVM에서 시도해볼만합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;당연히 Robolectric으로 테스트를 포기할 영역도 많습니다. 노하우가 쌓이면 이를 의식해서 테스트의 이득이 높은 영역을 분리해서 설계할 수 있습니다. 이는 재활용/기능 추가/버그 발견에도 좋은 구조가 될것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;코드_기여&quot;&gt;코드 기여&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;계속 발전하고 있는 프레임워크이기 때문에 Robolectric에는 미비한 기능도 많습니다. 테스트 대상인 ANdroid 자체가 계속 변화하고 있어서 더욱 그렇기도 합니다. Robolectric은 Github에 올라간 오픈소스 프로젝트이기 때문에 누구나 코드 기여를 할 수 있습니다. 저도 3번 정도 Pull request를 날렸는데 그 경험을 공유해보겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음에는 Javadoc의 오타부터 수정해봤습니다. &lt;a href=&quot;https://github.com/robolectric/robolectric/pull/804&quot;&gt;Pull request 번호 804번&lt;/a&gt;에서는 ShadowCookieManager의 javadoc에서 TelephonyManager로 적힌 단어를 CookieManager 로 수정했습니다. 주석을 한번이라도 본 사람이면 할 수 있는 아주 단순한 수정이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한번 해보고나니 조금 더 어려운 기여를 해보고 싶어졌습니다. 프로젝트를 진행하다가 Robolectric의 ShawdowCookieManager가 실제 android의 CookieManager의 동작과는 많이 다르다는 것을 발견했습니다. Robolectric 2.2까지는 단순히 HashMap에 key,value를 저장하는 수준이였습니다. Expires같은 속성이 들어가면 실제 SDK와 다르게 동작함. 아래 코드는 테스트가 실패합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;	cookieManager.setCookie(httpUrl, &quot;name=value; Expires=Wed, 11 Jul 2035 08:12:26 GMT&quot;);
	assertThat(cookieManager.getCookie(httpUrl)).isEqualTo(&quot;name=value&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Pull request 번호 853번에서는 이를 실제와 비슷하게 재구현했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 과정이 흥미로웠기 때문에 잠시 설명드리면, 먼저 실제 단말에서의 동작을 AndroidTestCase로 확인했습니다. ( &lt;a href=&quot;https://gist.github.com/benelog/7655764&quot;&gt;https://gist.github.com/benelog/7655764&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들면 아래와 같이 removeExpireCookie를 호출했을 때 Expires값이 지나간 쿠키값은 삭제하는 동작을 확인해봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;CookieManager cookieManager;

public void setUp() {
    Context context = getContext();
    CookieSyncManager.createInstance(context);
    cookieManager = CookieManager.getInstance();
    cookieManager.removeAllCookie();
}

public void testRemoveExpiredCookie() {
    cookieManager.setCookie(url, &quot;name=value; Expires=Wed, 11 Jul 2035 10:18:14 GMT&quot;);
    cookieManager.setCookie(url, &quot;name2=value2; Expires=Wed, 13 Jul 2011 10:18:14 GMT&quot;);
    cookieManager.removeExpiredCookie();
    assertEquals(&quot;name=value&quot;, cookieManager.getCookie(url));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 유사한 테스트 케이스를 Robolectric으로 작성했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;CookieManager cookieManager = Robolectric.newInstanceOf(CookieManager.class);

@Test
public void shouldRemoveExpiredCookie() {
    cookieManager.setCookie(url, &quot;name=value; Expires=Wed, 11 Jul 2035 10:18:14 GMT&quot;);
    cookieManager.setCookie(url, &quot;name2=value2; Expires=Wed, 13 Jul 2011 10:18:14 GMT&quot;);
    cookieManager.removeExpiredCookie();
    assertThat(cookieManager.getCookie(url)).isEqualTo(&quot;name=value&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 테스트를 통과시키는 ShadowCookieManager를 구현하여 Pull request를 날렸습니다. Robolectric에 들어갈 코드를 Robolecric으로 검증한 셈입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마지막으로 ShawdowProcess 구현한 코드입니다. 이 클래스로 android.os.Process.myPid()에서 나오는 값을 가짜로 지정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Test
public void shouldBeTrueWhenThisContextIsForeground(){
    int pid = 3;
    ShadowProcess.setPid(pid);
    createRunningAppProcessInfo(pid);
    boolean foreground = ActivityUtils.isContextForeground(context);
    assertThat(foreground, is(true));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;구글의 Android 소스 저장소의 Robolectric fork판에도 유사한 클래스가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 클래스는 &lt;a href=&quot;https://github.com/robolectric/robolectric/pull/861/&quot;&gt;Pull request 861번&lt;/a&gt; 으로 던져서 반영되었습니다. 중간에 이 클래스가 없으면 어떻게 되냐고 물어보길래 자세히 설명하려고 노력했던 과정이 재미있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코드 기여에 유의할 점도 있습니다. Merge를 받아줄 주요 커미터들이 작업하기 편하게 Pull request를 하는 브랜치는 계속 master의 최신 commit으로 맞춰서 rebase를 해줘야합니다. 제가 한 요청들도 다른 요청에 밀려서 merge가 안 되고 있었는데, 계속적으로 rebase를 하고 있으니 그 정성을 봐서도 merge를 해준것 같기도합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그외에는 컨티리뷰션 가이드를 참조하시면 됩니다. 대표적인 내용을 소개드리면, Indent에는 탭대신 공백 2칸을 쓰는등 컨벤션을 맞춰야하고, 적절한 테스트 코드를 같이 commit을 해야합니다. 앞에서 나온 CookieManager 사례를 참고하셔도 좋습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정리하자면 다음과 같습니다. Android 테스트는 난관이 많습니다. 특히 느린 실행속도가 치명적입니다. 여기서 Robolectric이 도움이 됩니다. 우선은 문자열, API 파싱. 유틸리티등 테스트하기 쉬운 영역부터 시도해볼만하고, 궁극적으로는 설계개선을 고민하는 것이 좋습니다. 코드 기여도 어렵지 않은, 기여자에게 관대한 프로젝트입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오늘 발표와 관련해서 helloworld 블로그에 게시된 &lt;a href=&quot;http://helloworld.naver.com/helloworld/342818&quot;&gt;Android에서 @Inject, @Test&lt;/a&gt;글도 참고하실만합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;발표자료&quot;&gt;발표자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.slideshare.net/deview/5robolectric&quot;&gt;https://www.slideshare.net/deview/5robolectric&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/hzukqnu9TZiiOJ&quot; width=&quot;595&quot; height=&quot;485&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt; &lt;/iframe&gt; &lt;div style=&quot;margin-bottom:5px&quot;&gt; &lt;strong&gt; &lt;a href=&quot;//www.slideshare.net/deview/5robolectric&quot; title=&quot;[D2 오픈세미나]5.robolectric 안드로이드 테스팅&quot; target=&quot;_blank&quot;&gt;[D2 오픈세미나]5.robolectric 안드로이드 테스팅&lt;/a&gt; &lt;/strong&gt; from &lt;strong&gt;&lt;a href=&quot;https://www.slideshare.net/deview&quot; target=&quot;_blank&quot;&gt;NAVER D2&lt;/a&gt;&lt;/strong&gt; &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>&apos;네이버를 만든 기술, 읽으면서 배운다 - 자바편&apos; 출간</title>
      <link>https://blog.benelog.net//3134907.html</link>
      <pubDate>Wed, 25 Feb 2015 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">3134907.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;img/book-cover/naver-java.jpg&quot; alt=&quot;cover&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( 이미지 출처 : &lt;a href=&quot;http://wikibook.co.kr/how-naver-works-java/&quot; class=&quot;bare&quot;&gt;http://wikibook.co.kr/how-naver-works-java/&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;네이버의 개발자 블로그인 헬로월드 ( &lt;a href=&quot;http://helloworld.naver.com&quot; class=&quot;bare&quot;&gt;http://helloworld.naver.com&lt;/a&gt; )에 공개되었던 글을 중심으로 책이 출판됩니다. 자바의 핵심영역을 다룬 17개의 글을 묶었습니다. 출판을 위해 새로 쓰여진 글도 있고, 사내에서만 공유되었던 글들도 재발굴했고, 이미 공개된 글도 최신 내용을 반영해 다듬었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자바개발의 A부터 Z까지 다 다루는 책은 아니지만, API를 설계할 때 고민해야할 요소, 반복해서 문제를 겪을 만한 부분, 장애 해결/분석 경험의 액기스를 담았습니다. 네이버의 주요 서비스를 개발한 담당자가 문제를 해결한 사례를 정리한 글도 있고, 플랫폼개발, 기술 지원조직인 웹플랫폼개발랩, 성능엔지니어링랩, 생산성혁신랩의 개발자들이 반복해서 전파해야할 지식을 효율적으로 공유하기 위해 쓴 글도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음에는 &lt;a href=&quot;http://www.yes24.com/24/goods/1941571?scode=032&amp;amp;OzSrank=5&quot;&gt;조엘이 엄선한 소프트웨어 블로그 베스트 29선&lt;/a&gt;처럼, 온라인의 글을 오프라인으로 옮기는 성격의 책이 될것이라고 예상했었습니다. 그런데 생각보다 출판에 이르기까지는 많은 노력이 들어갔습니다. 글이 쓰여진 시점이 제 각각 이였기 때문에 현시점에 맞춰서 고칠 내용도 많았고, 사내에서만 공유되던 글은 서비스명, 담당자명, 부서명등이 들어간 문장은 바꿔야했습니다. 다양한 저자가 쓴 글이였지만 용어를 일관되게 맞추려고 노력했습니다. 기술문서팀의 담당자분께서 많은 수고를 해주셨습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오류가 있어도 바로 고치면 되는 인터넷 페이지가 아닌, 종이로 찍혀나오는 책에 들어갈 글을 쓰는 부담은 생각보다 컸습니다. 이미 공개된 글도 여러 번 더 신중하게 검토를 했습니다. 블로그에 올릴 때는 &apos;아직 정확한 답은 찾지 못했다.&apos; 정도로 대충 쓰고 넘어갔었던, 타임존데이터베이스에서 우리나라 시간대 정보가 역사적 사실과 맞지 않았던 문제를 더 깊이 파악한 것이 그 예입니다. 책을 쓰면서 IANA ( Internet Assigned Numbers Authority)에 이 오류를 수정한 패치를 전달해서 JDK에도 반영되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;결국 종이로 찍혀나오게 되었으니, Helloworld의 글들을 좋아하셨던 많은 분들에게 소장할 가치가 있는 책으로 남았으면 좋겠습니다. 그 바램을 담아서 서문와 아래와 같이 적었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞으로 자바 10, 자바 11이 나오고 시간이 흐르면 더 최신 정보를 담은 책이 나올 것이다. 그래도 이 시대의 자바 기술과 네이버에서 일한 개발자의 노력을 담은 타입캡슐이 돼 오랜 시간이 지난 후에도 누군가의 책장에 이 책이 꽂혀 있으면 좋겠다. 그것이 인터넷에 있던 글을 종이로 옮긴 가장 큰 의미가 아닐까 생각한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책이 호응이 얻는다면, 바쁜 시간을 쪼개어서 글을 썼던 저자에게 응원이 되고, 예비 저자에게도 용기를 주어 앞으로 더 좋은 글을 외부로 공개하는데 힘을 보탤 것이라 믿습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;목차&quot;&gt;목차&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;글 하나하나가 독립적이기 때문에 관심있는 주제부터 읽으셔도 되지만, 가장 겉으로 들어난 영역인 API부터 시작해서 JVM내부, 분석도구, Garbarge Collection, DB연결까지 이어지는, 더 안쪽과 JVM 뒤쪽의 영역으로 흐름이 이어지도록 목차를 잡았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;1부_자바의_api_이해하기&quot;&gt;1부 : 자바의 API 이해하기&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;01장: 자바의 날짜와 시간 API - 정상혁&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;02장: 자바의 HashMap은 어떻게 작동하는가? - 송기선&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;03장: 자바에서 외부 프로세스를 실행할 때 - 정상혁&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;04장: 람다가 이끌어 갈 모던 자바 - 정상혁&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;2부_문제_분석과_사례&quot;&gt;2부 : 문제 분석과 사례&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;05장: JVM 이해하기 - 박세훈&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;06장: 스레드 덤프 분석하기 - 구태진&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;07장: 자바 애플리케이션 분석을 위한 BTrace - 이상민, 정상혁&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;08장: 하나의 메모리 누수를 잡기까지 - 김민수, 김택수&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;09장: 고맙다 JVM, 사과해라 JVM 크래시 - 강경태&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;3부_가비지_컬렉션&quot;&gt;3부 : 가비지 컬렉션&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;10장: 자바 가비지 컬렉션의 작동 과정 - 이상민&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;11장: 가비지 컬렉션 모니터링 방법 - 이상민, 송기선&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;12장: 가비지 컬렉션 튜닝 - 이상민&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;13장: 자바의 Reference 클래스와 가비지 컬렉션 - 박세훈&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;14장: 가비지 컬렉션과 Statement Pool - 최동순&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;15장: 아파치 MaxClients와 톰캣의 Major GC - 최동순&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;4부_데이터베이스_연결_설정&quot;&gt;4부 : 데이터베이스 연결 설정&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;16장: JDBC의 타임아웃 이해하기 - 강운덕&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;17장: Commons DBCP 이해하기 - 최동순, 강운덕, 정상혁&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>시간대 DB에서 우리나라 시간의 오류</title>
      <link>https://blog.benelog.net//3120317.html</link>
      <pubDate>Mon, 3 Nov 2014 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">3120317.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;변경이력&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2015/02/13&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;tzdata2014j에 반영 사실 갱신&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;북한의 시간대에 대한 진행 상황 설명&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2014년 2월, 회사의 기술블로그인 &lt;a href=&quot;http://helloworld.naver.com에&quot; class=&quot;bare&quot;&gt;http://helloworld.naver.com에&lt;/a&gt; &lt;a href=&quot;http://helloworld.naver.com/helloworld/64560&quot;&gt;Java의 날짜와 시간 API&lt;/a&gt;이라는 글을 기고한 적이 있습니다. 그 글을 쓰던 도중에 우리나라의 타임존 데이터에 대한 몇가지 의문을 가지게 되었지만 완벽히 해결하지는 못했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;얼마 전에 이 문제를 좀 더 깊이 파악을 해보았고, 원천 데이터인 IANA Timezone 데이터베이스에 패치를 전달해서 반영되었습니다. 조사과정에서 1920년대부터 1999년까지의 과거 뉴스를 조회하는 네이버의 뉴스라이브러리 서비스 ( &lt;a href=&quot;http://newslibrary.naver.com/&quot; class=&quot;bare&quot;&gt;http://newslibrary.naver.com/&lt;/a&gt;) 가 큰 도움이 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 오류는 tzdata2014j에 반영되고, 이를 참조하는 Java, Android, FreeBSD에서도 2014년 11월경에 반영되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://permalink.gmane.org/gmane.comp.time.tz/9111&quot;&gt;tzdata2014j의 릴리즈노트&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://bugs.openjdk.java.net/browse/JDK-8064560&quot;&gt;JDK-8064560&lt;/a&gt; : Open JDK의 반영. TZUpdater 1.4.10가 오류가 수정된 이 데이터를 참조하고, JDK 8u40, JDK
7u80, JDK 7u75등 여러 버전에 백포트 패치&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://android.googlesource.com/platform/bionic.git/+/b11d8e057c86c3926128af9d07180d9328e144c6&quot;&gt;Android에 반영 Commit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://lists.freebsd.org/pipermail/svn-src-stable-10/2014-November/003831.html&quot;&gt;FreeBSD에 반영 Commit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;플랫폼별로 반영시점은 다르겠지만, 다른 OS에서 이를 반영할 것으로 예상합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현시점에서는 드물것 같지만, 혹시 우리나라의 1912 ~ 1980년대의 섬머타임과 시간대 변경에 영향받은 프로그램을 만드셨던 분이 있다면 참고로 알아둘만 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;섬머타임의_오류_발견&quot;&gt;섬머타임의 오류 발견&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음 발견한 오류는 1988년의 섬머타임이 시작된 시간이였습니다. 아래 자료에 따르면 이 해의 섬머타임은 5월 8일 새벽 2시부터 시작되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&apos;한국 표준시&apos;, 위키백과, &lt;a href=&quot;http://ko.wikipedia.org/wiki/한국_표준시&quot; class=&quot;bare&quot;&gt;http://ko.wikipedia.org/wiki/한국_표준시&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1988050700099215012&amp;amp;editNo=1&amp;amp;printCount=1&amp;amp;publishDate=1988-05-07&amp;amp;officeId=00009&amp;amp;pageNo=15&amp;amp;printNo=6822&amp;amp;publishType=00020&quot;&gt;&apos;내일부터 서머타임&amp;#8230;&amp;#8203; 새벽2시를 3시로&apos;&lt;/a&gt;, 매일경제, 1988. 5. 7.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 Java프로그램으로는 1988년 5월 7일 23시의 1시간 후가 5월8일 1시인것으로 나와서 00시를 기점으로 섬머타임이 적용되어 있습니다. 아래 테스트는 아직 오류수정이 정식 릴리즈되지 않은 지금 시점에서는 통과합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Test
public void shouldGetAfterOneHour() {
    TimeZone seoul = TimeZone.getTimeZone(&quot;Asia/Seoul&quot;);
    Calendar calendar = Calendar.getInstance(seoul);
    calendar.set(1988, Calendar.MAY , 7, 23, 0);
    String pattern = &quot;yyyy.MM.dd HH:mm&quot;;
    String theTime = toString(calendar, pattern, seoul);
    assertThat(theTime).isEqualTo(&quot;1988.05.07 23:00&quot;);
    calendar.add(Calendar.HOUR_OF_DAY, 1);
    String after1Hour = toString(calendar, pattern, seoul);
    assertThat(after1Hour).isEqualTo(&quot;1988.05.08 01:00&quot;);}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(자세한 설명은 &lt;a href=&quot;http://helloworld.naver.com/helloworld/645609&quot;&gt;Java의 날짜와 시간 API&lt;/a&gt;,전체 소스는 &lt;a href=&quot;https://github.com/benelog/java-date-time/blob/master/jdk7-date-time/src/test/java/com/nbp/weblab/date/OldJdkDateTest.java&quot;&gt;OldJdkDateTest.java)&lt;/a&gt; 참조)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;시간대 변경에 대한 정보는 윈도우즈, 안드로이드, OSX, 리눅스, Java, 오라클 등 거의 모든 플랫폼에서 Internet Assigned Numbers Authority (IANA)라는 조직에서 관리하는 시간대 데이터베이스를 원천으로 참조합니다. 처음에는 이 타임존 데이터베이스의 오류일지 아니면 다른 이유가 있을지 확신을 하지 못했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;오류의_역사&quot;&gt;오류의 역사&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Helloworld에 글이 나간 후에 이응준님께서 알려주셔서 우리나라의 섬머타임을 기록한 사람이 누구인지 알게 되었습니다. Github에 올라간 &lt;a href=&quot;https://github.com/eggert/tz의&quot; class=&quot;bare&quot;&gt;https://github.com/eggert/tz의&lt;/a&gt; 커밋로그를 바탕으로 이를 자세히 분석해봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우리나라의 섬머타임에 대한 기록은 &apos;Arthur David Olson&amp;#8217;의 &lt;a href=&quot;https://github.com/eggert/tz/commit/79373b10463c3b59a1b2af49491dc308efbad09d&quot;&gt;1988년 1월 3일의 커밋&lt;/a&gt;에 아래와 같이 처음으로 등장합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;# Republic of Korea. According to someone at the Korean Times in San Francisco,# Daylight Savings Time was not observed until 1987. He did not know# at what time of day DST starts or ends.# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/SRule ROK 1987 max - May Sun&amp;lt;=14 2:00 1:00 DRule ROK 1987 max - Oct Sun&amp;lt;=14 3:00 0 S&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;주석을 봐서는 섬머타임의 시작시기도 정확히 몰랐던 사람의 증언을 참고로 한 듯합니다. 그리고 1987년 이전에는 우리나라에 섬머타임이 없었다는 이야기도 사실과 다릅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 코드로는 1987년부터 섬머타임이 계속되고 있다고 정의되었습니다. 1987,1988년에 우리나라에서 섬머타임이 실행되었으니 commit시점에서는 적어도 이 년도에 대해서는 맞는 데이터였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 1988년 이후로도 우리나라에서는 섬머타임이 계속되어 있는것처럼 한동안 유지가 됩니다. 1993년에 이르러서야 이 데이터는 정정됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/eggert/tz/commit/61315cadc3d0e8b4ef559539502a606cf58fc0f7&quot;&gt;1993년 11월23일의 커밋&lt;/a&gt;으로 다음의 날짜가 다시 반영됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Rule ROK 1960 only - May 15 0:00 1:00 DRule ROK 1960 only - Sep 13 0:00 0 SRule ROK 1987 1988 - May Sun&amp;lt;=14 0:00 1:00 DRule ROK 1987 1988 - Oct Sun&amp;lt;=14 0:00 0 S&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 commit은 아래 2가지 오류를 담고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1987~1988년도의 섬머타임은 시작시간 2시부터인데 0시부터로 표기되었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;새로 추가한 1960년의 섬머타임은 실제로는 5월1일부터 9월18일까지였습니다. 위키페디아와 옛날신문의 자료가 일치합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1960050100209103019&amp;amp;editNo=2&amp;amp;printCount=1&amp;amp;publishDate=1960-05-01&amp;amp;officeId=00020&amp;amp;pageNo=3&amp;amp;printNo=11713&amp;amp;publishType=00010&quot;&gt;썸머타임 1일부터&lt;/a&gt;, 동아일보, 1960.05.01&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1960091800209103010&amp;amp;editNo=2&amp;amp;printCount=1&amp;amp;publishDate=1960-09-18&amp;amp;officeId=00020&amp;amp;pageNo=3&amp;amp;printNo=11853&amp;amp;publishType=00010&quot;&gt;없어지는 섬머타임&lt;/a&gt;, 동아일보, 1960.09.18&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이외에도 이 Commit은 우리나라 시간대 변경에 대한 많은 오류를 포함하고 있습니다. 섬머타임 외의 오류는 나중에 다시 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;주석으로 볼때 위의 &lt;a href=&quot;https://github.com/eggert/tz/commit/61315cadc3d0e8b4ef559539502a606cf58fc0f7&quot;&gt;1993년 11월23일의 커밋&lt;/a&gt;은 &lt;a href=&quot;http://en.wikipedia.org/wiki/Thomas_G._Shanks&quot;&gt;Thomas G. Shanks&lt;/a&gt;의 The International Atlas의 제3판에 있는 내용을 반영한것을 보입니다. 주석에도 아래와 같이 미국이외의 타임존 정보는 별다른 명시가 없다면 이 책을 참고로 했다고 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;# A good source for time zone historical data outside the U.S. is# Thomas G. Shanks, The International Atlas (3rd edition),# San Diego: ACS Publications, Inc. (1991).# Except where otherwise noted, it is the source for the data below.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지금 이 책은 &lt;a href=&quot;http://www.amazon.com/International-Atlas-6th-Thomas-Shanks/dp/0935127887/&quot;&gt;6판&lt;/a&gt;까지 나와있고, 이후의 commit에서도 5판,6판을 따라서 수정한 내용이 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 이후 &lt;a href=&quot;https://github.com/eggert/tz/commit/da23b4d3897fd8a9e302ca0e85b25ce921f95ec7&quot;&gt;2012년 7월 18일의 커밋&lt;/a&gt;이 한번더 섬머타임 데이터를 수정했습니다. 1987년, 1988년의 표현규칙을 바꾼것으로 근본적인 오류가 수정되지는 않았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Rule ROK 1987 1988 - May Sun&amp;gt;=8    0:00 1:00 DRule ROK 1987 1988 - Oct Sun&amp;gt;=8    0:00 0 S&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;1960년 이전의 데이터까지 포함한다면, IANA 데이터베이스에서 우리나라의 섬머타임이 제대로 반영된 적은 한번도 없었던 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;패치_전달와_반영&quot;&gt;패치 전달와 반영&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;조사결과 섬머타임의 오류를 확신하고, 이를 수정하는 패치파일을 직접 만들어서 시간대데이터를 관리하는 IANA에 메일(&lt;a href=&quot;mailto:tz@iana.org&quot;&gt;tz@iana.org&lt;/a&gt; )로 보냈습니다. 여러 옛날 신문들을 많이 찾아본결과 &lt;a href=&quot;http://ko.wikipedia.org/wiki/한국_표준시&quot;&gt;위키페이디아의 &apos;한국표준시&amp;#8217;페이지&lt;/a&gt;의 정보가 신뢰할만하다고 판단했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1948.06.01. 00:00 ~ 1948.09.13. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1949.04.03. 00:00 ~ 1949.09.11. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1950.04.01. 00:00 ~ 1950.09.10. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1951.05.06. 00:00 ~ 1951.09.09. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1955.05.05. 00:00 ~ 1955.09.09. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1956.05.20. 00:00 ~ 1956.09.30. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1957.05.05. 00:00 ~ 1957.09.22. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1958.05.04. 00:00 ~ 1958.09.21. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1959.05.03. 00:00 ~ 1959.09.20. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1960.05.01. 00:00 ~ 1960.09.18. 00:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1987.05.10. 02:00 ~ 1987.10.11. 03:00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1988.05.08. 02:00 ~ 1988.10.09. 03:00&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들면 1948년의 정보는 &lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1948060100209202008&amp;amp;editNo=1&amp;amp;printCount=1&amp;amp;publishDate=1948-06-01&amp;amp;officeId=00020&amp;amp;pageNo=2&amp;amp;printNo=7607&amp;amp;publishType=00020&quot;&gt;1948년 6월1일자 동아일보 기사&lt;/a&gt;에서 확인할수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;패치절차는 시간대데이터베이스의 소스에 있는 &lt;a href=&quot;https://github.com/eggert/tz/blob/master/CONTRIBUTING&quot;&gt;CONTRIBUTING&lt;/a&gt;파일에 잘 설명되어 있습니다. 정식절차와는 별도로 github에도 올려봤습니다. ( &lt;a href=&quot;https://github.com/eggert/tz/pull/9&quot; class=&quot;bare&quot;&gt;https://github.com/eggert/tz/pull/9&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;얼마 후 제가 보낸 패치를 포함하는 &lt;a href=&quot;https://github.com/eggert/tz/commit/929c14a3d8ce505a9b8fb7099bee3a03ccf7e8e4&quot;&gt;2014년 10월30일의 Commit&lt;/a&gt;이 올라왔습니다. &apos;Unreleased, experimental changes&amp;#8217;라는 문구가 포함되었지만, 이를 뒤집는 증거가 발견되지 않는한 정식릴리즈에 포함될 것으로 예상합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;IANA쪽에서 이 수정을 받아준 Paul Eggert은 제가 섬머타임 변경의 근거로 보낸 &lt;a href=&quot;http://ko.wikipedia.org/wiki/한국_표준시&quot;&gt;위키페이디아의 &apos;한국표준시&amp;#8217;페이지&lt;/a&gt;를 보고 우리나라의 시간대 변경시점에 대한 오류도 추가로 수정을 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;시간대_변경시점의_오류&quot;&gt;시간대 변경시점의 오류&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음에 보낸 패치에는 포함되지 못했지만 섬머타임 외에도 우리나라 시간대 변경에 대한 의문도 있었습니다. &quot;yyyy.MM.dd HH:mm (Z)&quot;을 포멧으로 해서, 1954년, 1961년, 1968년의 특정시간과 그 때와 UTC와의 차이를 출력해보면, 아래와 같이 나옵니다. (소스는 &lt;a href=&quot;https://github.com/benelog/java-date-time/blob/master/jdk7-date-time/src/main/java/com/nbp/weblab/date/TimeZoneChangePoint.java&quot;&gt;TimeZoneChangePoint.java&lt;/a&gt; 참조 )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;1954.03.20 22:59 (+0900)1954.03.20 23:00 (+0800)1961.08.09 23:59 (+0800)1961.08.10 00:30 (+0830)1968.09.30 23:59 (+0830)1968.10.01 00:30 (+0900)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 소스의 결과는 &lt;a href=&quot;https://github.com/eggert/tz/commit/61315cadc3d0e8b4ef559539502a606cf58fc0f7&quot;&gt;1993년 11월23일의 수정&lt;/a&gt; 때 반영된 타임존DB의 정보에 의지합니다. 위의 결과라면 우리나라의 시간대 변경시점은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1954년 : UTC+0900 &amp;#8594; UTC+0800&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1961년 : UTC+0800 &amp;#8594; UTC+0830&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1968년 : UTC+0830 &amp;#8594; UTC+090&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 과거 신문에서 확인한 역사적 사실은 아래와 같습니다. 위키페디아의 내용과도 일치합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1954년 : UTC+0900 &amp;#8594; UTC+0830&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1954032100209203002&amp;amp;editNo=1&amp;amp;printCount=1&amp;amp;publishDate=1954-03-21&amp;amp;officeId=00020&amp;amp;pageNo=3&amp;amp;printNo=9522&amp;amp;publishType=00020&quot;&gt;&apos;오늘은 춘분 시간도 광복&apos;&lt;/a&gt;, 동아일보, 1954.03.21&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1954032100329203006&amp;amp;editNo=1&amp;amp;printCount=1&amp;amp;publishDate=1954-03-21&amp;amp;officeId=00032&amp;amp;pageNo=3&amp;amp;printNo=2462&amp;amp;publishType=00020&quot;&gt;&apos;입일일은 춘분, 표준시간광복의 날&apos;&lt;/a&gt;. 경향신문, 1954.03.21&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1961년 : UTC+0830 &amp;#8594; UTC+0900&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1961080900329203009&amp;amp;editNo=2&amp;amp;printCount=1&amp;amp;publishDate=1961-08-09&amp;amp;officeId=00032&amp;amp;pageNo=3&amp;amp;printNo=4796&amp;amp;publishType=00020&quot;&gt;&apos;구일밤 통금 싸이렌 불면 시계 바늘을 12시반으로&lt;/a&gt;, 경향신문, 1961.8.09&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://newslibrary.naver.com/viewer/index.nhn?articleId=1961081000209204005&amp;amp;editNo=2&amp;amp;printCount=1&amp;amp;publishDate=1961-08-10&amp;amp;officeId=00020&amp;amp;pageNo=4&amp;amp;printNo=12179&amp;amp;publishType=00020&quot;&gt;&apos;새 표준자오선이란?&apos;&lt;/a&gt;, 동아일보, 1961.08.10&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;즉 현재의 시간대데이터로는 1961~1968년사이는 아예 우리나라의 시간대가 잘못 계산되어 나온다는 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 부분은 섬머타임이 반영되는 것을 보고 조금 더 조사를 한 후에 추가 패치를 하려고 생각했었습니다. 기존 데이터가 그렇게까지 다 틀렸다는 것이 믿기가 어려웠고, 우리나라의 시간대 정보에 대한 거의 모든것을 한번에 고치기가 조심스러웠기 때문입니다. 그런데 Paul Eggert가 먼저 적극적으로 반영해주었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Paul Eggert는 이와 더불어 &lt;a href=&quot;http://ko.wikipedia.org/wiki/한국_표준시&quot;&gt;위키페이디아의 &apos;한국표준시&amp;#8217;페이지&lt;/a&gt;에 따르면 1912년에 UTC+0900로 변경이 있었는데, 1910년에도 같은 변경이 있었던것으로 기록된 부분이 혼동된다며 이를 명확히 확인해주었다면 좋겠다고 했습니다. 위키페디아에서 1910년도 변경의 근거로 든 &apos;여적 표준시 변경, 경향신문, 2000-08-14.&apos;라는 자료는 현재 인터넷으로 찾을 수 없어서 대신 여러 기록을 확인해보았습니다. 많은 자료가 1912년에 변경되었다는것으로 일치했고, 1910년도의 변경기록은 누군가가 한일합방 연도와 혼동한것이 아닐까하는 의견을 답장으로 보냈습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;북한의_타임존_데이터&quot;&gt;북한의 타임존 데이터&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;또하나 의문이였던 점은 &lt;a href=&quot;https://github.com/eggert/tz/commit/61315cadc3d0e8b4ef559539502a606cf58fc0f7&quot;&gt;1993년 11월23일의 커밋&lt;/a&gt;으로 북한의 시간대가 1961년에 UTC+0900으로 변경되었다는 내용입니다. 그때 남한 쪽에서 시간대 변경이 있었는데, 당시 신문을 다 찾아봐도 남북한이 동시에 추진을 했다는 내용은 없었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Paul Eggert도 이를 이상하게 여겨 일단은 북한쪽은 1940년대 이후로 변화가 없는것으로 가정했다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While we&amp;#8217;re in the neighborhood, it&amp;#8217;s completely implausible that Pyongyang faithfully mimicked Seoul time during and after the Korean war (which is what Shanks says), so let&amp;#8217;s remove that obviously-bogus guess.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저도 답장으로 북한쪽의 변경에 대한 의미있는 기록을 찾지 못했고, Paul Eggert의 가정에 동의한다는 내용을 보냈습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;tzdata2014j버전대로라면 1954년과 1961년 사이 서울과 평양사이에는 30분의 시차가 존재합니다. 이 것이 역사적 사실과 부합하는지 알아내려고 계속 알아보고 있는 중입니다. 현재 한국표준과학연구원과 통일부, 국정원에 문의를 했지만, 의미있는 답변은 받지 못했습니다. 특히 친절히 전화까지 해주신 통일부 직원분께 감사드립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;1954년과 1961년 사이에 남파/북파 간첩활동을 한 분이 있다면, 그 사실을 정확히 알고 있을 것 같기도 합니다. 그런데 과거 간첩사건을 조사해보니 생각보다 간첩들의 나이가 많아서 지금까지 생존한 분이 계실 가능성은 별로 없어보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;재미있게도 위의 오류를 신고한지 얼마뒤인 2014년 11월 1일에 &apos;&lt;a href=&quot;https://github.com/eggert/tz/commit/bee436b03e28bd5ea984534fcc5e4f3a4d18b47a&quot;&gt;Be less enthusiastic about Shanks and clarify UT vs UTC.&lt;/a&gt; &apos;라는 제목으로 commit이 올라왔습니다. 우리나라 시간대에 대한 잘못된 정보의 출처였던 Shanks의 저서에 많은 오류가 있음을 지적하는 주석이 들어갔습니다. 아시아, 아프리카, 오스트랄라시아, 유럽 등 지역별 정보를 기록하는 모든 파일에 &apos;A good source for time zone historical data outside the U.S. is..&apos;라는 내용이 삭제되고, &apos;unfortunately this book contains many errors and cites no sources.&apos;라는 문장이 추가되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저의 신고가 영향을 준것인지는 알 수 없지만, 이 주석을 기점으로 기존의 데이터를 조금 더 의심하는 계기가 될 것으로 기대합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;비록 오래전 과거데이터라서 지금 시점의 영향성은 적지만, 믿음직한 표준데이터라고 생각했던 IANA Timezone DB에 이렇게 오류가 많았다는 점, 특히 우리나라 관련한 데이터에는 제대로 된 것이 거의 없었다는 사실은 놀랍습니다. 우리나라의 과거 자료와는 별도로, 국제화관련 개발을 하는 사람이라면 내 컴퓨터/내 담당서버에 들어와있는 타임존데이터베이스가 언제 시점인지, 업데이트는 잘 되어 있는지도 잘 확인해봐야겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지금까지의 내용과 관련된 메일스레드는 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2014년 10월 30일 00:07:24 (UTC) From 정상혁, To &lt;a href=&quot;mailto:tz@iana.org&quot;&gt;tz@iana.org&lt;/a&gt; : 최초 패치를 보낸 메일 &lt;a href=&quot;http://mm.icann.org/pipermail/tz/2014-October/021830.html&quot; class=&quot;bare&quot;&gt;http://mm.icann.org/pipermail/tz/2014-October/021830.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2014년 10월 30일 06:53:09 (UTC) From Paul Eggert, To 정상혁 : 답장과 1910년 시간대 변경에 대한 문의 &lt;a href=&quot;http://mm.icann.org/pipermail/tz/2014-October/021831.html&quot; class=&quot;bare&quot;&gt;http://mm.icann.org/pipermail/tz/2014-October/021831.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2014년 10월 30일 09:18:57 (UTC) From 정상혁, To Paul Eggert : 1910년의 시간대 변경과 북한시간대에 대한 의견 : &lt;a href=&quot;http://mm.icann.org/pipermail/tz/2014-October/021833.html&quot; class=&quot;bare&quot;&gt;http://mm.icann.org/pipermail/tz/2014-October/021833.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Android에서 @Inject, @Test</title>
      <link>https://blog.benelog.net//3017438.html</link>
      <pubDate>Wed, 17 Apr 2013 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">3017438.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 지난달에 썼던 글이 네이버의 기술블로그인 &lt;a href=&quot;http://helloworld.naver.com/&quot;&gt;http://helloworld.naver.com&lt;/a&gt;에 공개되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://helloworld.naver.com/helloworld/342818&quot;&gt;Android에서 @Inject, @Test&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Android에서 DI + Test 스타일로 개발을 하는데 겪는 어려움과 이를 극복하는데 도움을 주는 Android annotations와 Robolectric 등의 오픈소스 프레임워크를 비교하고 소개했습니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>10 things you need to know about Android and Google Play - Tony Chan (Google), GDG Korea Android 컨퍼런</title>
      <link>https://blog.benelog.net//3017439.html</link>
      <pubDate>Wed, 17 Apr 2013 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">3017439.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지난 2013/04/13일, 종로 페럼타워에서 열렸던 &apos;구글 개발자와 함께하는 GDG Korea Android 컨퍼런스 &apos;에서 공유된 내용입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참석해서 메모했던 내용을 정리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;구글 Play와 API, 권장 UI스타일, 정책 등 다양한 주제를 이야기한 발표였습니다.  UX설계를 할 때 많은 고민이 필요하겠다고 느꼈습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;1_think_global&quot;&gt;1. Think Global&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;한 지역만 염두에 둔다면 기회를 놓치고 있는것이다. 구글 Play 수익의 67%가 미국외에서 나오고 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Localization을 고려해라. 구글 Play의 그래픽스와 비디오도 이제 로컬라이즈되었다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;웹애플리케이션은 국제화에 대한 고려를 하는 비율이 높지 않습니다. 앱개발은 반대로 국제화를 고려하지 않는 것이 더 합당한 이유가 있어야하는 듯합니다. 기준이 차이가 생겼다고 느껴집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;2_pure_android&quot;&gt;2. Pure Android&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;다른 플랫폼의 UI형태를 모방하지 마라. 각각의 플랫폼은 고유한 스타일이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;플랫폼 특화된 아이콘을 밖에서 쓰지 말라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;바닥에 붙은 탭바를 쓰지 마라. (아이폰 스타일)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;별도의 라벨이 붙은 백버튼, &apos;right point caret&apos;(&amp;gt;) 버튼을 쓰지 마라. 백버튼을 플랫폼에서 제공하는 것을 쓰도록 유도해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UI에 대한 지침을 지키지 않으면 사용자가 안 좋은 Rating을 할 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이전의 안드로이드 버전에 지원하던 스타일의 레가시 버튼을 쓰지 마라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Action bar를 써라. 곧 support 라이브러리를 통해 하위 버전에서도 ActionBar를 쓸 수 있게 될 것이다. 지금도 오픈소스로 &lt;a href=&quot;http://actionbarsherlock.com/&quot;&gt;Actionbar-sherlock&lt;/a&gt;이 있지만, 기본 SDK에서 이제 지원된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;가장 최신의 SDK에 targeting하라. 최신의 SDK에서만 돌아가야한다는 의미는 아니다. 하위버전에서도 돌아가도록 만들면서도 상위버전에서는 최신의 UI 요소들을 활용해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개별 앱의 UI는 같은 플랫폼 내에서의 일관성을 해치지 않아야한다는 지침을 강조했습니다. 같은 사용자가 2개의 다른 플랫폼을 오가면서 같은 앱을 사용하는 경우보다는, 하나의 플랫폼에서 여러 앱을 사용하므로 플랫폼 내에서의 일관성이 중요한 것은 당연한듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;기획자나 디자이너 입장에서는 하나의 앱을 출시하면 여러 플랫폼이 동일한 UI로 나오기를 바랄 수도 있을 듯한데, 이러한 지침을 초기부터 염두에 두고 엔지니어가 피드백을 줄 수 있어야겠습니다. &lt;a href=&quot;http://developer.android.com/design/index.html&quot;&gt;안드로이드 디자인가이드&lt;/a&gt;도 정독해봐야겠구나 싶었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 &lt;a href=&quot;http://actionbarsherlock.com/&quot;&gt;Actionbar-sherlock&lt;/a&gt;에서 해주던 ActionBar의 하위 호환성 지원이 support 라이브러리에 들어간다는 소식도 반가웠습니다. Fragment가 support 라이브러리에서 지원되는 것을 생각하면 자연스러운 개선이기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;3_design_for_all_form_factors&quot;&gt;3. Design for all form-factors&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fragment를 써라&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;타블릿과 폰을 위해서 별도의 프로젝트를 프로젝트를 만드는것은 바람직하지 않다. Fragment를 이용해서 타블릿에서는 하나의 Activity에 여러개의 Fragment가, 폰에서는 Activity당 하나의 Fragment를 보여주는 식으로 구성하면 얼마든지 하나의 프로젝트로 구성할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UI요소의 크기는 기기의 해상도에 독립적이도록 DP를 쓰고, 텍스트에는 SP를 써라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;적어도 3개의 레이아웃을 고려해라. 폰, 7인치 타블릿, 10인치 타블릿. DP는 해상도 문제만 해결할 뿐, 다른 사이즈에 대한 문제는 별도로 고려해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;지원하려는 화면 밀도(Density), 해당도에 맞는 Asset, 이미지를 각각 준비해라. Aliasing은 보기에도 좋지 않고 CPU를 많이 사용해서 배터리 사용시간을 짧아지게 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;DP에 대해서는 아래 그림 1장이 직관적인 이해에 많은 도움이 되었습니다.
&lt;span class=&quot;image-wrap&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;http://cfile8.uf.tistory.com/image/11355F45516932300FB576&quot; alt=&quot;11355F45516932300FB576&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(이미지 출처 : &lt;a href=&quot;http://www.kmshack.kr/317#.UWy9VIpdUbj&quot; class=&quot;bare&quot;&gt;http://www.kmshack.kr/317#.UWy9VIpdUbj&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;웹에서는 &apos;방탄형 웹&amp;#8217;이나 &apos;반응형 웹디자인&amp;#8217;에 대한 많은 노하우가 공유되고 있는데, 안드로이드에서도 점점 더 섬세한 노하우들이 공유될 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;4_correct_back_button_behaviors&quot;&gt;4. Correct back button behaviors&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;앱의 메인 화면에서 사용자는 홈스크린으로 바로 돌아갈 수 있어야 한다. 종료 할때 다이얼로그로 종료할지 물어보는 방식으로 Back에 대한 이벤트를 강제적으로 막지마라.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;많은 앱이 이렇게 되어 있지 않기에 다소 의외의 가이드라고 느껴졌습니다. 이것도 일관성을 생각하면 의미 있는 지침입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;5_proper_notification&quot;&gt;5. Proper notification&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;핵심 기능에 대해서 통지해라. 광고에 Notification을 쓰지 마라.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;6_app_quality_improvement&quot;&gt;6. App quality improvement&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://bit.ly/google-play-core-checklist&quot; class=&quot;bare&quot;&gt;http://bit.ly/google-play-core-checklist&lt;/a&gt; 참고&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://bit.ly/google-play-tablet-checklist&quot; class=&quot;bare&quot;&gt;http://bit.ly/google-play-tablet-checklist&lt;/a&gt; 참고&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한글 번역도 &lt;a href=&quot;http://googledevkr.blogspot.kr/2013/01/app-quality-guidelines.html&quot; class=&quot;bare&quot;&gt;http://googledevkr.blogspot.kr/2013/01/app-quality-guidelines.html&lt;/a&gt; 에서 보실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(이 포스트를 쓰고 난 후에 권순선님께서 알려주셨습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;7_permission&quot;&gt;7. Permission&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;핵심기능에 필요한 최소한의 권한만 부여하도록 해라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;민감한 권한을 너무 많이 가지고 있으면 당신에게 해가 된다. 앱의 다운로드 숫자나 주목 받을 기회가 줄어들 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;8_google_play_service&quot;&gt;8. Google play service&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;구글에서 제공하는 서비스 API를 나열하면서 소개.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maps API 2, Google+, Google Authorization, Google Play services APK 등&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위 내용은 &lt;a href=&quot;http://developer.android.com/google/play-services/index.html에&quot; class=&quot;bare&quot;&gt;http://developer.android.com/google/play-services/index.html에&lt;/a&gt; 자세히 나와 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;9_new_platform_api&quot;&gt;9. New platform API&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;새로 추가된 API를 간단히 나열.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lock screen widget&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Seconary display&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Day dreams (interactive screensaver mode)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Natvie RTL(right-to-left) support&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tablet sharing&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;최근 업데이트된 API 소개&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GCM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Analystic SDK&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In-app Billing V3 (V2는 더이상 쓰지마라)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Youtube android player&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자세한 내용은 &lt;a href=&quot;http://developer.android.com/about/versions/jelly-bean.html에&quot; class=&quot;bare&quot;&gt;http://developer.android.com/about/versions/jelly-bean.html에&lt;/a&gt; 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;발표된지는 한참된 기능이지만, 이렇게 모아서 보니 앱개발자가 화면을 다양하게 활용할 수 있는 여지가 더 늘어나고 있다고 느껴졌습니다. 이에 따라 새로운 사업모델도 나올 수 있었는데, 이런 아이디어들이 기획부서에서만 나오기를 기다리는 것보다 개발자들도 적극 제안을 해 보면 어떨까하는 생각이 들었습니다. 개발자들은 새로운 API가 추가되는것을 늘 관심있게 보고 있기 때문에 자연스럽게 아이디어가 나올만도 합니다. Lock screen widget을 이용한 광고 제품이 나왔듯이, 앞으로도 그런 기회가 많이 남아 있을 것이라고 기대가 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;10_publishing_on_google_play&quot;&gt;10. Publishing on google play&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Google play는 풍부하게 미디어를 활용한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;특징을 멋지고 깔끔한 이미지로&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;스크린샷&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;짧은 유튜브 동영상&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;로컬라이제이션&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;대표적인 정책 위반 사례들.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;3rd party 지불&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;다운로드를 위해 3rd party 사이트로의 연결&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;스팸 키워드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;별점에 인센티브 부과&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;정책에 대한 자세한 내용은 &lt;a href=&quot;http://bit.ly/google-play-policy-edu&quot; class=&quot;bare&quot;&gt;http://bit.ly/google-play-policy-edu&lt;/a&gt; 참조&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정책에 따라 구현이 달라져야 할 부분을 미리 파악하고 있으려면 꾸준한 관심을 가져야겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Android App과 TDD - 임유진 (Kakao), GDG Korea Android 컨퍼런스 중에서</title>
      <link>https://blog.benelog.net//3017442.html</link>
      <pubDate>Wed, 17 Apr 2013 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">3017442.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지난 2013/04/13일, 종로 페럼타워에서 열렸던 &apos;구글 개발자와 함께하는 GDG Korea Android 컨퍼런스 &apos;에서 공유된 내용입니다&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;발표_자료&quot;&gt;발표 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;발표 자료 : &lt;a href=&quot;http://goo.gl/1WyAz&quot; class=&quot;bare&quot;&gt;http://goo.gl/1WyAz&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예제코드 : &lt;a href=&quot;https://github.com/eugenelgm/AndroidForTest&quot; class=&quot;bare&quot;&gt;https://github.com/eugenelgm/AndroidForTest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;요약&quot;&gt;요약&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 핵심으로 기억하는 내용은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;앱이 기능이 점점 추가되고 복잡해져 가면서 코드가 점점 누더기가 되어갔다. 수정에 대한 부작용을 파악하기 어려워져 갔다. 그래서 테스트의 필요성이 느껴졌다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Android의 기본 Test Framework는 너무 느려서 포기했다. 한번 실행에 30~40초가 걸렸다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Robolectric도 검토했으나, 없는 기능이 많고 라이브러리 충돌 때문에 포기했다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;테스트가 실패할 때 Robolectric의 버그인지 앱의 버그인지 확인하기 어려워서 디버깅에 시간이 걸렸다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;암호화 관련 라이브러리에서 충돌이 일어남&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;결국 UI쪽의 테스트보다는 UI를 벗어난 layer에서 핵심로직을 테스트하는데 집중했다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;테스트 기법&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;android.util.Log를 호출하는 부분은 별도의 Wrapping 클래스로 작성. 테스트 프로젝트에서는 같은 패키지에 같은 클래스 이름으로 System.out으로 로그를 출력하는 클래스를 작성. class loader의 순서를 조정해서 테스트 코드에서는 테스트용 Logger클래스를 호출.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;실환경용 Log 랩퍼 클래스 : &lt;a href=&quot;https://github.com/eugenelgm/AndroidForTest/blob/master/src/com/example/test/util/Logger.java&quot;&gt;Logger.java
&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;테스트용 Log 클래스  : &lt;a href=&quot;https://github.com/eugenelgm/AndroidForTest/blob/master/tests/src/com/example/test/util/Logger.java&quot;&gt;Logger.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Background Thread에 대한 테스트도 래핑 클래스를 이용. 테스트 환경에서는 테스트 코드와 같은 쓰레드에서 동기적으로 실행되도록 Runnable.run을 호출하는 래퍼클래스를 호출함.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;실환경용 쓰레드 실행 클래스 : &lt;a href=&quot;https://github.com/eugenelgm/AndroidForTest/blob/master/src/com/example/test/util/DatabaseJobQueue.java&quot;&gt;DatabaseJobQueue.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;테스트용 Log 클래스  : &lt;a href=&quot;https://github.com/eugenelgm/AndroidForTest/blob/master/tests/src/com/example/test/util/DatabaseJobQueue.java&quot;&gt;DatabaseJobQueue.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;같은 interface를 구현한 테스트용 Mock객체를 작성하고, 주입은 별도의 setter 메소드를 사용.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API에 대한 테스트는 정적파일을 통해서 함. 후임자가 API의 명세를 예시로 금방 확인할 수 있는 장점도 생김&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Context에 대한 참조 등 Android에 대한 의존성을 제거하기 어려운 부분은 PowerMock + Mockito로 해결&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PowerMock + Mockto로 Context.getString(int)의 동작을 가로채는 예제 : &lt;a href=&quot;https://github.com/eugenelgm/AndroidForTest/blob/master/tests/test/com/example/test/MainActivityTest.java&quot;&gt;MainActivityTest.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;테스트할 수 있는 Layer를 구분하다 보니 설계 개선을 이끔&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TDD에 대한 오해&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;꼭 Dalvik에서 테스트해야 의미가 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TDD로 모든 에러를 잡을 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;개발 후에 만들어도 된다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;질문&quot;&gt;질문&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;좀 더 자세히 알고 싶은 점이 있어서 발표가 끝난 후에 아래 2가지를 질문했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;1_robolectric의_라이브러리_충돌의_구체적인_사례&quot;&gt;1. Robolectric의 라이브러리 충돌의 구체적인 사례&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞에서 정리한 암호화 라이브러리와의 충돌사례를 알려주셨고, Robolectric의 버그 때문에 디버깅이 어려웠다는 이야기도 구체적으로 해주셨습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;2_depenency_injection_프레임워크를_고려&quot;&gt;2. Depenency Injection 프레임워크를 고려&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;검토는 했지만 쓰지는 않았고, 발표한 내용은 사례일 뿐이기 때문에 각자 생각하는 좋은 방법이 있으면 계속 시도해봤으면 좋겠다고 말씀해주셨습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;의견&quot;&gt;의견&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;추가로 제 의견을 덧붙이면, Roblectric에 빈틈이 많다는 단점은 저도 공감은 가지만 꼭 UI를 테스트하지 않더라도 Robolectric을 부분적으로 유용하게 쓸 수는 있다고 생각합니다. 예를 들면 android.util.Log 클래스를 쓰는 코드도 Robolectric을 쓰면 별도의 랩퍼 클래스가 없어도 편하게 테스트할 수 있습니다. ShadowLog라는 클래스를 사용하면  Console이나 특정 파일등 로그를 쓰는 위치도 좀 더 편하게 조정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;ShadowLog.stream = System.out;
Robolectric.bindShadowClass(ShadowLog.class);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 멀티쓰레드에 대한 테스트를 할 때도 Robolectric의 RobolectricBackgroundExecutorService를 쓰면 편할 때가 있습니다. 이 클래스도 다른 쓰레드를 생성하지 않고 호출한 쪽과 같은 쓰레드에서 Runnable 클래스를 실행해 줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Android-annotations를 쓰면 @Background가 붙은 메소드는 BackgroundExecutor라는 클래스를 통해서 실행되는데, 이 클래스에 있는 executor라는 멤버변수를 교체하면 쓰레드의 생성 정책을 조절 할 수 있습니다. 따라서 테스트를 할 때는 아래와 같이 테스트용 Executor를 넣으면 자연스럽게 같은 쓰레드에서 동기적으로 Runnable 클래스를 실행할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;BackgroundExecutor.setExecutor(new RobolectricBackgroundExecutorService());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 구조적으로 Thread의 Executor를 바꿔치기 하기 힘든 경우에는 &lt;a href=&quot;https://code.google.com/p/awaitility/&quot;&gt;Awaitility&lt;/a&gt;라는 라이브러리도 사용해볼만도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 SDK버전에 따라서 다르게 돌아가는 코드가 있다면 Robolectric에서 아래와 같이 조작을 할 수 있습니다. (물론 PowerMock을 써도 같은 일을 할 수 있기는 합니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, &quot;SDK_INT&quot;, Build.VERSION_CODES.JELLY_BEAN);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;암튼 Robolectric에 빈틈이 많기에 큰 기대를 하지 말고 UI 레이어의 테스트에는 많은 욕심을 부리지 말자고 생각하지만 그래도 몇가지 매력적인 기능이 있어서 Robolectric을 아예 외면을 할 수는 없었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mockito + Powermock의 조합은 강력하지만, 구조를 고치기 어려운 레가시 코드에만 한정해서 썼으면 한다는 의견입니다. 가능하다면 Powermock이 없어도 테스트할 수 있도록 구조를 개선하는 것이 더 클래스의 역할이 명확해지고, 앞으로 기능을 추가하거나 읽기에도 좋은 코드가 되기 때문입니다. 그렇게 구조개선을 하는데는 DI 프레임워크가 많은 도움이 되기도 합니다.  DI 프레임워크를 쓰면 Context에 대한 직접 의존이나 안드로이드 기본 프레임워크의 final 메소드의 동작을 가로채야할 일이 적어져서  훨씬 테스트하기 편해집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Helloworld에 올라온  &lt;a href=&quot;http://helloworld.naver.com/helloworld/342818&quot;&gt;Android에서 @Inject, @Test&lt;/a&gt; 에서 이에 대해 자세히 적었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;소감&quot;&gt;소감&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개인적으로 많은 고민을 했던 주제였고, 발표자께서 내리신 결론이 저와 거의 비슷했기에 무척 반가웠습니다. 저도 Android의 기본 테스트 클래스를 쓰면서 느꼈던 좌절감에 결국 JVM에서 테스트를 해야 TDD로서 의미가 있다고 느꼈습니다. UI에 대한 테스트보다는 안드로이드와 독립적인 Layer를 테스트하는 것이 ROI가 높고, 좋은 설계를 이끈다는 점도 공감이 갔습니다. 로그호출 부분이나 멀티쓰레드에 대한 테스트 등 제가 했던 고민도 보편적인 문제였다는 것도 확인했습니다. API의 호출결과를 정적 파일로 저장해두고 테스트 코드에서 파싱부터 검증하는 기법은 저도 Server to Server API클라이언트 모듈 테스트 때 많이 썼던 방법이였습니다&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 편향된 생각을 가졌을지 늘 걱정이 되었는데, 같은 의견을 가지신 분이 구체적인 사례까지 공유해주셔서 많은 도움이 되었습니다.  앞으로  다른 분께도 안드로이드에서 TDD를 자신있게 권장해드릴 용기를 얻었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring tag library +  Tomcat7.x 또는 Glassfish 2.2.x의 EL Injection 위험</title>
      <link>https://blog.benelog.net//3007743.html</link>
      <pubDate>Tue, 5 Mar 2013 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">3007743.html</guid>
      	<description>
	&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;취약점의_조건&quot;&gt;취약점의 조건&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 조건을 모두 충족시킨다면 코드는 치명적인 Remote code execution 취약성이 존재할 여지가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jcp.org/aboutJava/communityprocess/mrel/jsr245/index.html&quot;&gt;EL 2.2&lt;/a&gt;를 지원하는 서블릿 컨테이너를 쓰거나 EL 2.2 라이브러리를 직접 jar파일로 참조해서 쓰고 있다. (대표적으로 Tomcat 7.x혹은 Glassfish 2.2.x)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring 3.1.x 미만 버전을 쓰고 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring의 JSP Tag( &amp;lt;spring:message.. 등)을 쓰고 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring의 JSP Tag에서 EL을 지원하는 속성에 사용자가 입력한 값이 들어갈 수 있다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;각 태그의 속성의 EL지원 여부는 &lt;a href=&quot;https://docs.google.com/document/d/1dc1xxO8UMFaGLOwgkykYdghGWm_2Gn0iCrxFsympqcE/edit&quot;&gt;Stefano Di Paola 등의 Expression Language Injection 보고서&lt;/a&gt; 참조&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위에서 첫번째 조건이 해당하지 않더라도 다른 취약점을 조심해야 합니다. EL 2.2을 사용하지 않더라도 JSP 2.0이상을 지원하는 서블릿 컨테이너를 쓰면서 2,3,4에 해당하는 코드라면 어플리케이션의 중요한 정보를 노출시킬 수 있는 취약점이 존재할 위험이 있습니다. Remote code execution만큼 심각하지는 않지만, 역시나 조치가 필요합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 취약 지점은 의도하지 않은 정보를 노출할 수 있는 위험성으로 이미 2011년 9월 지적되어 보안되었으나, 이번달인 2013년 1월에는 같은 취약 지점을 통해서 원격 코드를 실행하는 기법이 발견되어 위험성 등급이 올라갔습니다. 즉, 해당 라이브러리가 취약성을 유발한다는 점 자체는 새로운 것이 아니고, 이미 이를 보강하는 버전이 나와 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 취약점의 원인과 조치방법은 아래 링크에 정리해놓았습니다. (코드가 들어간 글은 GIST를 쓰는 것이 훨씬 편하네요.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/benelog/4582041&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/4582041&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Markdown + Dropbox</title>
      <link>https://blog.benelog.net//3007746.html</link>
      <pubDate>Tue, 5 Mar 2013 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">3007746.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;업데이트&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2019.04.10 : 현시점에서는 Calepin 서비스도 종료되었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2013.03.05 : GIST에 썼던 글을 옮기어 봅니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Springnote 서비스 종료 전에 쓴 글이라서  Springnote의 마이그레이션 코드는 이제는 쓸수가 없네요)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;자료를_정리하는_형식과_도구에_대한_고민&quot;&gt;자료를 정리하는 형식과 도구에 대한 고민&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;회사에서 만든 자료들은 MOSS(Microsoft Office SharePoint Server)나 위키, 메일함 등이 있으니 큰 아쉬움은 없는데, 개인적인 자료들은 어떻게 정리하고 보관, 공유할지가 늘 고민이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 공개하고 싶지 않은 것들은 &lt;a href=&quot;http://www.evernote.com&quot;&gt;에버노트&lt;/a&gt;와 &lt;a href=&quot;http://www.dropbox.com&quot;&gt;드롭박스&lt;/a&gt;를 쓰고, 공유할 자료들은 스프링노트와 &lt;a href=&quot;http://blog.benelog.net&quot;&gt;이글루스 블로그&lt;/a&gt;을 활용하고 있습니다.코드들은 주로 &lt;a href=&quot;https://github.com/benelog&quot;&gt;Github&lt;/a&gt;와 &lt;a href=&quot;https://gist.github.com/benelog&quot;&gt;Gist&lt;/a&gt;에 올립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 다양한 서비스를 쓰다보니 자료를 서비스 간에 이동을 할 일이 생기면 많이 불편합니다. 예를 들면 에버노트에 있는 자료를 블로그에 올린다거나 스프링노트에 있는 글을 구글사이트로 옮길 때가 그렇습니다.그냥 한 두 페이지면 복사해서 붙여넣기를 하겠지만, 여러 페이지를 옮길 때 그렇게 하면 단순 반복 작업에 들어가는 시간이 아깝습니다.웹 브라이저나 웹 에디터의 차이 때문에 칸 맞추기 같은 사소한 편집에 시간을 많이 허비하기도 합니다.웹이나 클라이언트 환경 등 다양한 환경에서 모두 편집이 간편했으면 하는 바램도 있었습니다. 우분투를 쓰는 시간이 많다보니 MS워드처럼 특정 OS가 종속적인 편집기는 별로 좋아하지 않기도 합니다. 그리고 정리한 내용을 자주 찾아보니 검색과 탐색 기능을 쓰기 쉬웠으면도 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이번에 스프링노트가 종료될 예정이라 어떤 형식으로 자료를 정리할 것인지에 대한 고민이 더 커졌습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;요구사항을 정리하면&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;자료를 이동하는 비용이 적어야하고 (이식성)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;자료의 모양 때문에 편집을 하는 시간이 되도록 없었으면 하고&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;특정 환경에 종속적이지 않은 다양한 문서 편집기를 사용할 수 있어야 하고&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;검색 기능을 간편하게 쓸 수 있어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 점들을 고민하다보니 우선 정리하는 자료의 형식은 마크다운을 선택하게 되었습니다. Github와 Gist를 쓰면서 그 단순함에 만족을 했고, &lt;a href=&quot;http://tumblr.com&quot;&gt;텀블러&lt;/a&gt;, &lt;a href=&quot;https://trello.com/&quot;&gt;트렐로&lt;/a&gt;등 지원하고 있는 서비스도 늘어가고 있어서 이식성도 높다고 생각했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 원본 데이터를 파일단위로 관리하고 싶었습니다. 쓰던 서비스가 망하더라도 파일을 단순히 복사해서 다른 서비스로 옮길 수 있다면 이전 비용이 적을 것이기 때문이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Github를 이용한 &lt;a href=&quot;http://octopress.org/&quot;&gt;Octopress&lt;/a&gt;도 검토했으나, 버전관리까지는 필요없이 최종 파일만 관리하면 되었기 때문에 Dropbox를 썼으면 좋겠다고 생각했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;사람 생각은 다 비슷한지, 이미 Dropbox + Markdown을 이용한 블로그와 위키 서비스가 나와 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;블로그 서비스 : &lt;a href=&quot;http://calepin.co&quot; class=&quot;bare&quot;&gt;http://calepin.co&lt;/a&gt;, &lt;a href=&quot;http://scriptogr.am/&quot; class=&quot;bare&quot;&gt;http://scriptogr.am/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;위키 서비스 : &lt;a href=&quot;http://wikipackit.com&quot; class=&quot;bare&quot;&gt;http://wikipackit.com&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;calepin_markdown_dropbox로_블로그_만들기&quot;&gt;Calepin( =Markdown + Dropbox)로 블로그 만들기&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Calepin을 이용해서 &lt;a href=&quot;http://book.benelog.net이라는&quot; class=&quot;bare&quot;&gt;http://book.benelog.net이라는&lt;/a&gt; 블로그를 만들어봤습니다.그동안 여러 곳에서 정리하던 읽은 책에 대한 메모를 Markdown으로 정리한 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같이 .md 파일의 처음 시작에 날짜와 제목, URL, 태그 등의 정보를 달아주고, 파일을 [Dropbox 공유폴더]/Apps/Calepin이라는 폴더로 복사합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Date: 2012-07-19
Title: 어제를 버려라
Slug: 어제를_버려라
Tags: IT업계&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Calepin 사이트에 가서 &apos;Publish&amp;#8217;버튼을 누르면 약속된 폴더에서 파일정보를 읽어와서 블로그를 생성해 줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/my-site/calepin.png&quot; alt=&quot;calepin&quot; title=&quot;calepin&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 정보를 바탕으로 블로그를 만들어줍니다. 아래와 같이 굉장히 단순한 디자인인데, 저는 이 정도로 충분하다고 느꼈습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/my-site/book-benelog-net.png&quot; alt=&quot;book benelog net&quot; title=&quot;book-benelog-net&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://scriptogr.am/&quot;&gt;scriptogr.am&lt;/a&gt;라는 서비스도 거의 비슷한 기능을 제공하는데, 블로그 테마 등 약간의 커스터마이징이 가능합니다.
&lt;a href=&quot;http://wikipackit.com&quot;&gt;wikipackit&lt;/a&gt;도 사용법은 유사하고, 위키 페이지간의 링크 기능을 제공하지만, 현재까지는 페이지를 비공개로만 관리할 수 있습니다.
앞으로 유사한 서비스가 더 많이 나올수도 있고, 이전 비용도 크지 않으니 언제든지 좋은 서비스가 나오면 갈아탈 생각입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;스프링노트_마이그레이션&quot;&gt;스프링노트 마이그레이션&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 스프링노트로 관리하던 페이지들 중 일부는 &lt;a href=&quot;http://wikipackit.com&quot; class=&quot;bare&quot;&gt;http://wikipackit.com&lt;/a&gt; 으로 옮겼습니다. 스프링노트의 API를 이용해서 &lt;a href=&quot;https://gist.github.com/3194442#file_springnote2markdown.py&quot;&gt;Markdown형식으로 스프링노트 페이지를 다운로드하는 스크립트&lt;/a&gt;를 작성했습니다.Python과 &lt;a href=&quot;http://johnmacfarlane.net/pandoc/&quot;&gt;Pandoc&lt;/a&gt;을 활용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;eclipse_markdown_text_editor&quot;&gt;Eclipse Markdown Text Editor&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;편집기로는 주로 Eclipse에서 &lt;a href=&quot;http://marketplace.eclipse.org/content/markdown-text-editor&quot;&gt;Markdown Text Editor&lt;/a&gt;을 사용하고 있습니다.Eclipse가 무거운 편이지만, Platform Runtime Binary를 찾아서 최소한 도로 설치하면 50MB가 넘지 않게 비교적 가볍게 쓸 수도 있습니다. 예를 들어 Eclipse 3.8의 최소설치를 위한 파일은 아래 링크에서 찾으실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://download.eclipse.org/eclipse/downloads/drops/R-3.8-201206081200/&quot; class=&quot;bare&quot;&gt;http://download.eclipse.org/eclipse/downloads/drops/R-3.8-201206081200/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;편집을 하면서 HTML렌더링을 할 수도 있고, Eclipse의 단축키를 그대로 활용할 수도 있습니다.
예를 들어 파일을 찾을 때 Ctrl + Shift + R키를 눌러서 파일명의 일부를 입력해서 바로 찾아가는 것들이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/eclipse/eclipse-markdown-plugin.png&quot; alt=&quot;eclipse markdown plugin&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 구축한 글쓰기와 자료정리 환경을 굉장히 만족하면서 쓰고 있습니다.
Markdown을 지원하는 서비스나 도구들은 앞으로 계속 나올 듯하고, 스스로 만들기도 쉽기 때문에 더 편하게 쓸 수있는 가능성도 크다고 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Java에서 여러줄에 걸친 문자열 선언을 편하게 하는 @Multiline</title>
      <link>https://blog.benelog.net//2999108.html</link>
      <pubDate>Tue, 29 Jan 2013 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2999108.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Andrian Walker라는 영국 개발자가 만든 라이브러리를 제가 수정해서 Github에 올렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 프로젝트에 대한 자세한 설명은 아래에 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/multiline/wiki/Java%EC%97%90%EC%84%9C-%EC%97%AC%EB%9F%AC%EC%A4%84%EC%97%90-%EA%B1%B8%EC%B9%9C-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%84%A0%EC%96%B8%EC%9D%84-%ED%8E%B8%ED%95%98%EA%B2%8C-%ED%95%98%EB%8A%94-%40Multiline&quot;&gt;Java에서 여러줄에 걸친 문자열 선언을 편하게 하는 @Multiline&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(github wiki에 적고 블로그에 붙여넣기를 하니 코드 블럭이 깨지는 등 편집이 쉽지가 않네요. 그래서 링크로 대신 합니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위 글의 핵심 내용은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java에는 여러 줄에 걸친 문자열을 선언하는 문법이 없어서 긴 문자열을 편집하는 작업이 불편합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이를 보완하는 방법을 찾던 중 Adrian Walker라는 개발자가 만든 Multiline-string 이라는 라이브러리를 발견했고, Eclipse에서도 쓸 수 있도록 코드를 수정해서 원저자의 허락을 받고 Github에 올렸습니다. (&lt;a href=&quot;https://github.com/benelog/multiline&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/multiline&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이 과정 중에 Annotation Processing과 ECJ(Eclipse compiler for Java)에 대해서 알게 된 것들을 정리했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이 라이브러리와 비슷한 기능을 Lombok에 추가하거나, 안드로이드에서 annotation Processing을 활용할만한 방안을 더 연구해볼만 합니다&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>코딩 인터뷰 완전 분석 210쪽 17.3 변형 문제 풀이</title>
      <link>https://blog.benelog.net//2962391.html</link>
      <pubDate>Sat, 8 Sep 2012 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2962391.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;계속 이어지는 인사이트 문제입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.insightbook.co.kr/post/3876&quot; class=&quot;bare&quot;&gt;http://www.insightbook.co.kr/post/3876&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.insightbook.co.kr/wp-content/uploads/2012/08/%EC%BD%94%EB%94%A9%EC%9D%B8%ED%84%B0%EB%B7%B0-%ED%91%9C%EC%A7%801.jpg&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;http://www.insightbook.co.kr/wp-content/uploads/2012/08/%EC%BD%94%EB%94%A9%EC%9D%B8%ED%84%B0%EB%B7%B0-%ED%91%9C%EC%A7%801-204x300.jpg&quot; alt=&quot;image&quot; width=&quot;132&quot; height=&quot;194&quot; title=&quot;코딩인터뷰-표지&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;문제&quot;&gt;문제&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코딩 인터뷰 완전 분석』210쪽 17.3 변형 문제&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자연수 n을 입력받고, n!의 계산 결과 중 마지막에 붙은 연속된 0의 개수와 연속된 0 바로 앞에 나오는 숫자를 구하라.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;실행_예&quot;&gt;실행 예&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;input n: 15
output: 3 8&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;설명&quot;&gt;설명&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;15!은 1307674368000이므로, 마지막에 연속된 0은 3개이고, 바로 앞의 숫자는 8이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;조건&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;n의 범위는 1 이상, 10000 이하입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;테스트 입력은 다음과 같습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;20! = 2432902008176640000
30! = 265252859812191058636308480000000
40! = 815915283247897734345611269596115894272000000000
50! = 30414093201713378043612608166064768844377641568960512000000000000
100! = 93326215443944152681699238856266700490715968264381621468592963
8952175999932299156089414639761565182862536979208272237582511852
10916864000000000000000000000000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;프로그래밍 언어에서 제공하는 자릿수 제한 없는 곱셈을 이용하거나, 이런 형태의 곱셈 함수를 직접 구현해도 답을 얻을 수 있지만, 문제의 의도와는 다릅니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;정답 검토의 편의를 위해 블로그 포스팅에 2012!와 10000!의 결과를 남겨주세요.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(심화 문제) 연속된 0 앞에 나오는 여러 숫자를 구하는 것도 가능하니, 심심하신 분은 도전해보세요. ^^&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;풀이&quot;&gt;풀이&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;먼저 입력값이 2012와 10000일 때의 결과는 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;input : 2012

output: 501 8


input : 10000

output: 2499 8&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 푼 방식은 10은 5와 2의 배수라는 점을 이용했습니다.
곱해지는 숫자를 2와 5로만 소인수 분해하고  그 갯수를 각각 누적해서 구합니다.
즉 최종 팩토리안 값의 약수 중에서 2와 5가 몇 번 들어가 있는지를 계산하는 것이죠.
그리고 2,5가 아닌 나머지 약수는 구하고자하는 자릿수만큼만 잘라서 보관합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들면 5!에서 0의 갯수가 0이 아닌 마지막 숫자를 구한다면&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;5! = 1 * 2 * 3 * 4 *5 = 2^3 * 5 * 3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이이므로 2는 3번, 5는 1번 들어갑니다. 0의 갯수는 2,5가 짝이 맞는 갯수이므로 둘 중에 작은 숫자인 5의 약수의 갯수, 즉 1개입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 마지막 0이 아닌 숫자는 나머지 약수의 마지막 숫자인 3과 5와 짝이 맺어지지 못한 2의 배수를 곱해서 구합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여기서는 약수인 2중 1개는 5와 짝이 맺어졌고, 나머지 2개가 남았으므로 2^2*3 = 12, 마지막 숫자만 남기면 2입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트 코드에서는 이미 알려진 숫자를 검증했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class FactorianAnalyzerTest {
    FactorianAnalyzer analyzer = new FactorianAnalyzer();

    @Test

    public void test5(){
        assertFactorianResult(5, 1, 2);
    }

    @Test
    public void test15(){
        assertFactorianResult(15, 3, 8);
    }

    @Test
    public void test20(){
        assertFactorianResult(20, 4, 4);
    }

    @Test
    public void test30(){
        assertFactorianResult(30, 7, 8);
    }

    @Test
    public void test40(){
        assertFactorianResult(40, 9, 2);
    }

    @Test
    public void test50(){
        assertFactorianResult(50, 12, 2);
    }

    @Test
    public void test100(){
        assertFactorianResult(100, 24, 4);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 꼭 마지막 1개의 숫자가 아닌 여러 숫자로 구할 수 있도록 확장했기 때문에 그것도 검증해보았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class FactorianAnalyzerNonZeroTwoDigitsTest {

    FactorianAnalyzer analyzer = new FactorianAnalyzer();

    @Test
    public void test5(){
        assertFactorianResult(5, 2, 1, 12);
    }

    @Test
    public void test6(){
        assertFactorianResult(6, 2, 1, 72);
    }

    @Test
    public void test7(){
        assertFactorianResult(7, 3, 1, 504);
    }

    @Test
    public void test8(){
        assertFactorianResult(8, 2, 1, 32);
    }

    private void assertFactorianResult(int n,  int numOfLastNonZeroDigits, int expectedZeroCount, int expectedNonzeroDigit) {
        FactorianResult result = analyzer.countZero(n, numOfLastNonZeroDigits);
        assertThat(result.getZeroCount(), is(expectedZeroCount));
        assertThat(result.getNonZeroDigits(), is(expectedNonzeroDigit));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전체 코드는 아래 주소에 올렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/quiz/blob/master/src/test/java/net/benelog/quiz/insight/FactorianAnalyzerTest.java&quot;&gt;FactorianAnalyzerTest.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/quiz/blob/master/src/main/java/net/benelog/quiz/insight/FactorianResult.java&quot;&gt;FactorianResult.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/quiz/blob/master/src/main/java/net/benelog/quiz/insight/FactorianAnalyzer.java&quot;&gt;FactorianAnalyzer.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/quiz/blob/master/src/test/java/net/benelog/quiz/insight/FactorianAnalyzerNonZeroTwoDigitsTest.java&quot;&gt;FactorianAnalyzerNonZeroTwoDigitsTest.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>문제로 풀어보는 알고리즘 0.3 생각해보기 풀이</title>
      <link>https://blog.benelog.net//2960128.html</link>
      <pubDate>Fri, 31 Aug 2012 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2960128.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;출판사 인사이트 블로그에 아래와 같은 퀴즈가 올라왔네요
( 원문: &lt;a href=&quot;http://www.insightbook.co.kr/post/3814&quot; class=&quot;bare&quot;&gt;http://www.insightbook.co.kr/post/3814&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;문제&quot;&gt;문제&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;배열 arr[]과 위치 s, t가 있을 때,arr[s], arr[s+1], … , arr[t-1]을 오른쪽으로 한 칸씩 이동하고,arr[t]는 arr[s]로 복사하는 것을 ’1만큼 오른쪽으로 회전시켰다’고 한다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들어 길이가 8인 배열에서 s=2, t=6이면 다음 그림처럼 바뀐다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.insightbook.co.kr/wp-content/uploads/2012/08/%EA%B7%B8%EB%A6%BC-0-1.jpg&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;http://www.insightbook.co.kr/wp-content/uploads/2012/08/%EA%B7%B8%EB%A6%BC-0-1.jpg&quot; alt=&quot;image&quot; width=&quot;404&quot; height=&quot;194&quot; title=&quot;그림 0-1&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;길이가 n인 배열의 위치는 0, 1, 2, … , n-1이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;문제 :k를 인자로 받아서 k만큼 오른쪽으로 회전시키는 함수를 작성하라.단, 1만큼 오른쪽으로 이동시키는 과정을 k번 반복해서는 안 된다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;조건 1 : 작성하는 언어에는 제한이 없습니다.
조건 2 : 답안으로 작성하신 글 제목에는 ‘문제로 풀어보는 알고리즘 0.3 생각해보기 풀이’라는 문장이 들어가야 합니다. (저희 블로그에도 트랙백을 걸어주세요.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(주의: 이 코딩 인터뷰는 인사이트 입사와는 무관합니다. ㅡㅁㅡ /)&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;풀이&quot;&gt;풀이&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;밀린 일이 많아서 정신적인 여유가 없지만,  경품에 눈이 어두워서 급하게 풀어봤습니다&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우선 테스트부터 먼저 작성하고&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;

public class ArrayHandlerTest {
    ArrayHandler handler = new ArrayHandler();

    @Test
    public void testShift1() {
        int[] array = {1,2,3,4,5};
        handler.shift(array,0,3,1);
        assertThat(array, is(new int[]{4,1,2,3,5}));
    }

    @Test
    public void testShift2() {
        int[] array = {1,2,3,4,5};
        handler.shift(array,1,3,1);
        assertThat(array, is(new int[]{1,4,2,3,5}));
    }

    @Test
    public void testShift3() {
        int[] array = {1,2,3,4,5};
        handler.shift(array,1,2,1);
        assertThat(array, is(new int[]{1,3,2,4,5}));
    }
....&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 이를 통과시키는 실행코드입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import java.util.Arrays;public class ArrayHandler {
    public void shift(int[] array, int s, int t, int k) {
        if (s &amp;gt; t) return;
        if (t &amp;gt;= array.length) return;

        int[] rangeToShift = Arrays.copyOfRange(array, s, t + 1);
        int rangeLength = t-s + 1; for(int i=0; i&amp;lt; rangeLength; i++) {
            int offset = (i+k) % rangeLength;
            array[s + offset] = rangeToShift[i];
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대용량 배열일 때 메모리를 더 적게 쓰는 방법들도 고민해볼만도 하지만, 우선은 쉽게 문제를 푸는데 집중했습니다.
t,s, k 같은 한글자 변수명은 별로 좋아하지 않지만, 문제에 있는 변수명이라서 그대로 살렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전체 소스는 Github에 올렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;테스트 코드 : &lt;a href=&quot;https://github.com/benelog/quiz/blob/master/src/test/java/net/benelog/quiz/insight/ArrayHandlerTest.java&quot;&gt;ArrayHandlerTest.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;문제 풀이 코드 : &lt;a href=&quot;https://github.com/benelog/quiz/blob/master/src/main/java/net/benelog/quiz/insight/ArrayHandler.java&quot;&gt;ArrayHandler.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 java.util.Collections.roate()메소드를 쓰면 위 문제는 아래 두 줄로 풀 수 있기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;[source,java&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;List&amp;lt;Integer&amp;gt; range = list.subList(s, t+1);
Collections.rotate(range, k);&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 이렇게 하는게 이 문제의 의도는 아닐듯해서 참고구현 정도로 남겨 두었습니다. 처음 풀이에서도 Arrays.copyOfRange 메소드를 쓰기는 했지만 이 부분은 특별한 알고리즘이 없는 단순한 배열복사라서 활용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Collections.rotate를 이용한 풀이 코드 : &lt;a href=&quot;https://github.com/benelog/quiz/blob/master/src/main/java/net/benelog/quiz/insight/ListHandler.java&quot;&gt;ListHandler.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>버전 관리 시스템 유랑기, 그리고 Git 적응기</title>
      <link>https://blog.benelog.net//2937766.html</link>
      <pubDate>Tue, 12 Jun 2012 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2937766.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;httpsgist_github_combenelog2922437ebb284eca084_eab480eba6ac_ec8b9cec8aa4ed859c_ec9ca0eb9e91eab8b0_eab7b8eba6aceab3a0_git_eca081ec9d91eab8b0버전_관리_시스템_유랑기_그리고_git_적응기&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%9C%A0%EB%9E%91%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-git-%EC%A0%81%EC%9D%91%EA%B8%B0&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%9C%A0%EB%9E%91%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-git-%EC%A0%81%EC%9D%91%EA%B8%B0&lt;/a&gt;버전 관리 시스템 유랑기, 그리고 Git 적응기&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://onoffmix.com/event/3101&quot;&gt;2011년 6월 9일&lt;/a&gt;과 &lt;a href=&quot;http://onoffmix.com/event/4190&quot;&gt;11월 10일&lt;/a&gt;에 열린 세미나에서 두 번 발표했던 내용을 글로 정리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지금까지 다양한 버전관리 시스템을 쓴 경험과 Git의 장점이라고 느낀 점에 대해서 발표했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;httpsgist_github_combenelog2922437ebb284eca084eab480eba6aceba5bc_eab1b0ec9d98_ec9588_ed9598eb8d98_ec8b9ceca088버전관리를_거의_안_하던_시절&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC%EB%A5%BC-%EA%B1%B0%EC%9D%98-%EC%95%88-%ED%95%98%EB%8D%98-%EC%8B%9C%EC%A0%88&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC%EB%A5%BC-%EA%B1%B0%EC%9D%98-%EC%95%88-%ED%95%98%EB%8D%98-%EC%8B%9C%EC%A0%88&lt;/a&gt;버전관리를 거의 안 하던 시절&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437ebb284eca084eab480eba6aceb8a94_eba8bc_eb8298eb9dbcec9d98_ec9db4ec95bceab8b0버전관리는_먼_나라의_이야기&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC%EB%8A%94-%EB%A8%BC-%EB%82%98%EB%9D%BC%EC%9D%98-%EC%9D%B4%EC%95%BC%EA%B8%B0&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC%EB%8A%94-%EB%A8%BC-%EB%82%98%EB%9D%BC%EC%9D%98-%EC%9D%B4%EC%95%BC%EA%B8%B0&lt;/a&gt;버전관리는 먼 나라의 이야기?&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git에 대해 이야기를 하기 전에 지금까지 어떻게 버전관리를 해왔는지 되돌아보겠습니다. 아마 비슷한 경험을 하시고 공감하실 분들이 많으실 듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;버전관리 시스템을 실무에서 쓰기 전에는 책에서 간단한 소개를 접한 정도였습니다. 김익환님이 지은 &lt;a href=&quot;http://www.yes24.com/24/goods/416619&quot;&gt;&apos;대한민국에는 소프트웨어가 없다&apos;&lt;/a&gt; 라는 책에는 아래와 같은 내용이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;내가 아무리 소스코드관리 프로그램과 버그 관리 프로그램의 찬양자가 되어 조언을 해도 평생 사용하지 않을 회사들이 대부분일 것이다. 이건 진짜 좋은 건데. 안타깝다(153쪽)&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책이 나온 때가 2003년 말이였는데, 그 당시에 국내에서 버전관리를 제대로 사용하는 개발 조직은 많지 않았나봅니다. 제 주변에서도 그랬습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그때는 버전관리가 실무에서 필요하다기 보다 &apos;CMMI&apos; 같은 프로세스처럼 이론으로만 강조되는 분야라고 느꼈습니다. 아니면 엄격한 프로세스를 준수하고 그만큼 개발자에게 시간을 충분히 주는 소프트웨어 선진국에서나 가능한 기법이라고 생각했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;김익환님이 미국에서 근무했던 &apos;GTE&amp;#8217;라는 조직에서는 개발자들마다 코딩하는 줄 수를 측정했더니 하루 평균 8줄이 나왔다는 내용이 있습니다.(171쪽). 그정도로 엄격한 리뷰와 관리를 거친다는데, 하루에 화면 4~5개씩 찍어내야하는 제 주변의 현실과는 동떨어진 이야기였습니다. 그래서 버전관리도 우리와는 다른 방식으로 개발을 하는 먼 나라의 일로, 우리와는 관계가 없는 절차로만 여겼습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437eab09cebb09cec849cebb284ec9790_ed8c8cec9dbcec9db4_ec9e88eb8a94eb8db0_ec999c_eb9890_ed8c8cec9dbcec9d84_eb94b0eba19c_eab480eba6aced95a0eab98c개발서버에_파일이_있는데_왜_또_파일을_따로_관리할까&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EA%B0%9C%EB%B0%9C%EC%84%9C%EB%B2%84%EC%97%90-%ED%8C%8C%EC%9D%BC%EC%9D%B4-%EC%9E%88%EB%8A%94%EB%8D%B0-%EC%99%9C-%EB%98%90-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EB%94%B0%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%A0%EA%B9%8C&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EA%B0%9C%EB%B0%9C%EC%84%9C%EB%B2%84%EC%97%90-%ED%8C%8C%EC%9D%BC%EC%9D%B4-%EC%9E%88%EB%8A%94%EB%8D%B0-%EC%99%9C-%EB%98%90-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EB%94%B0%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%A0%EA%B9%8C&lt;/a&gt;개발서버에 파일이 있는데 왜 또 파일을 따로 관리할까?&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;신입 때 처음 투입되었던 프로젝트에서는 버전관리를 하기는 했지만, 공유폴더보다 더 불편하다는 느낌 뿐이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 프로젝트에서는 Local PC에 개발환경을 설치하지 않고, 개발서버에 FTP로 jsp 파일를 올려서 개발을 진행했었습니다. JSP로만 주로 개발을 하던 초기에는 흔한 방식이였습니다. 그래서 개발서버에 이미 최신 파일이 올라가 있는데, 따로 왜 버전관리 시스템이라는 것을 써야하는지 이해가 되지 않았습니다. 가끔 실수로 파일을 덮어쓸 때에는 백업 폴더에 가서 복사해서 복구했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 조직에도 Merant Version Manager라는 버전관리 시스템이 설치되어 있기는 했습니다.그 도구를 개발자들이 필요해서 쓴다기보다는 본사에서 시키니까 쓰기는하는데, 뭔가 부가적인 작업을 한다고 느꼈습니다. 개발서버에 파일 올려서 테스트를 다 해본다음에, 버전관리 시스템에 로그인하고, PC에 있는 파일 찾아서 올리고, Local에서도 여러 군대의 파일을 같이 신경써줘야 했습니다. 귀찮고 그냥 빼먹고 싶은 작업이였습니다. 그때는 지금의 SVN처럼 Eclipse plugin이 있는 것도 아니어서 따로 클라이언트 프로그램을 띄워야했기에 더욱 불편했었습니다. 그래서인지 두번 째 투입되었던 조직에서는 개발서버에만 파일을 올려놓고 아예 버전관리를 안 했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437eab3b5ec9ca0ed8fb4eb8d94ec9790_ec98aceba0a4eca3bcec8b9ceba9b4_eca080eb8581_6ec8b9cec9790_ebb0b0ed8faced95b4_eb939ceba6bdeb8b88eb8ba4공유폴더에_올려주시면_저녁_6시에_배포해_드립니다&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EA%B3%B5%EC%9C%A0%ED%8F%B4%EB%8D%94%EC%97%90-%EC%98%AC%EB%A0%A4%EC%A3%BC%EC%8B%9C%EB%A9%B4-%EC%A0%80%EB%85%81-6%EC%8B%9C%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4-%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EA%B3%B5%EC%9C%A0%ED%8F%B4%EB%8D%94%EC%97%90-%EC%98%AC%EB%A0%A4%EC%A3%BC%EC%8B%9C%EB%A9%B4-%EC%A0%80%EB%85%81-6%EC%8B%9C%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4-%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4&lt;/a&gt;공유폴더에 올려주시면 저녁 6시에 배포해 드립니다~&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;세번째 프로젝트에서는 요즘처럼 Local PC에 WAS를 띄우고 Eclipse를 써서 개발을 하기는 했지만, 윈도우즈의 공유 폴더로 소스 관리를 했었습니다. 그때 완성된 프로그램을 서버에 배포하는 절차는 다음과 같았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;개발이 끝난 파일은 공유폴더의 약속한 위치에 올린다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;담당자 한명이 매일 그 폴더의 파일을 자기 PC로 복사하고, Eclipse에서 컴파일한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컴파일 에러가 나는 것이 있으면 그 파일을 올렸을 것으로 추측되는 사람에게 이야기해서 고쳐달라고 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컴파일 에러 안 나면 Eclipse에서 컴파일된 class파일과 JSP등을 FTP로 개발서버에 올리고 서버를 재시작한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;서버 재시작이 끝나면 개발자들이 각자 자기가 만든 모듈을 확인해본다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;개발서버에서 잘 안 되는 기능 중에 급한 것은 다시 고쳐서 공유폴더에 넣고, 미안하지만 한번더 올려달라고 담당자에게 부탁한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;안 급한 것은 그냥 내일 반영하기로 하고, 일단 Local PC에서만 고쳐둔다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;파일서버의 backup 폴더 아래에 날짜별로 오늘 배포한 것은 복사해 둔다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;매일매일 위의 절차를 반복하는데 30분을 넘게 썼었습니다. 프로젝트 구성원들 모두 개선을 해보려는 생각은 있었지만 당장의 업무가 급하다보니 투자할 시간이 없었습니다. 돌이켜 보면 프로젝트 개발기간이 6개월이 넘었는데, 6개월동안 매일 30분이면 엄청난 시간이 들어가는 작업이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 프로젝트에서도 버전관리시스템인 Merant Version Manager를 본사의 담당자가 와서 설치해주고,고객와 개발자들을 다 모아서 따로 교육까지 해주었습니다.그러나 역시나 버전관리 시스템에 파일을 올리는 건 파일서버에 복사를 하는 것보다 번거로운 일이라고 생각했었습니다. 그래서 버전관리 서버에는 1차 프로젝트 종료 후에 몰아서 한번 파일을 올리기로 합의했고, 그 방식이 효율적이라고 모든 프로젝트 구성원들이 공감을 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그나마 개발 영역별로 업무분담이 명확해서 파일 충돌은 잘 일어나지 않았습니다. 충돌보다는 작업한 내용을 실수로 날릴까봐 걱정하는 사람은 많았는데, 어떤 개발자는 매일 퇴근 전에 개인 PC에 날짜별로 복사해 놓기도 했었습니다. 다들 그 개발자를 보고 부지런하고 좋은 습관을 가지고 있다고 칭찬을 했었죠. 이 기간 동안에도 공유폴더에 만족을 하고 버전관리는 업무를 더 느리게 하는 작업이라고 인식했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;httpsgist_github_combenelog2922437merant_version_managerec9d84_ec93b0eb8d98_ec8b9ceca088merant_version_manager을_쓰던_시절&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#merant-version-manager%EC%9D%84-%EC%93%B0%EB%8D%98-%EC%8B%9C%EC%A0%88&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#merant-version-manager%EC%9D%84-%EC%93%B0%EB%8D%98-%EC%8B%9C%EC%A0%88&lt;/a&gt;Merant Version Manager을 쓰던 시절&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437ebb98ceb939c_ec9e90eb8f99ed9994_eab7b8eba6aceab3a0_merant_version_managereba5bc_eca09ceb8c80eba19c_ec82acec9aa9ed9598eab8b0_ec8b9cec9e91ed9598eb8ba4빌드_자동화_그리고_merant_version_manager를_제대로_사용하기_시작하다&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%B9%8C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-%EA%B7%B8%EB%A6%AC%EA%B3%A0-merant-version-manager%EB%A5%BC-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8B%A4&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%B9%8C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-%EA%B7%B8%EB%A6%AC%EA%B3%A0-merant-version-manager%EB%A5%BC-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8B%A4&lt;/a&gt;빌드 자동화, 그리고 Merant Version Manager를 제대로 사용하기 시작하다.&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇게 공유폴더를 쓰던 개발팀에서 고도화 성격의 2차프로젝트가 시작되었습니다.운영 중인 시스템에서 몇가지 업무가 추가되는 사업이였는데, 운영 중인 시스템에 당장 배포할 소스와 2차 프로젝트 오픈 때 배포할 소스를 따로 관리해야 했습니다. 유지보수성 요청 때문에 수정된 소스는 바로 운영 시스템에 배포를 하지만, 2차 사업에 추가요구사항으로 개발된 소스는 오픈 시점에 맞춰서 운영서버에 배포해야 했습니다. 그래서 유지보수와 신규개발 버전을 따로 브랜치를 분리할 필요성이 생겼습니다. 그리고 전에는 담당자별로 업무영역이 명확하게 나뉘어져 있었는데, 새로운 프로젝트에서는 그런 경계가 명확하지 않은 부분이 많이 생겼습니다.그런 필요성 때문에 버전관리를 본격적으로 시작하게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 시점에서 빌드자동화 스크립트도 구성했습니다. 수동으로 해서 매일 30분이 걸리던 빌드절차를 개선하는 시도였었습니다. 파일서버에 따로 복사한 파일이 아닌, 버전관리에 올라온 최신 소스 파일을 읽어서 컴파일하고 서버에 FTP로 올리고, WAS를 재시작하는 스크립트를 ANT로 구성했습니다. 제가 한 3일정도 걸려서 그 작업을 했었는데, 처음 맨바닥에서 하는 일이라서 지금의 기준으로 보면 정말 많은 시간이 걸렸습니다. 지금은 Maven + Hudson으로 하면 몇십분 안에 가능한 작업이지만, 그래도 3일 투자해서 매일매일 30분의 작업을 없애서 정말 만족스러웠습니다.그때부터 다른 개발자들도 이제 공유폴더에 파일을 복사해 넣는 대신 매일매일 버전관리시스템인 Merant Version Manager에 파일을 올리기 시작했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437_ec9db4_eb8c80ebb680ebb684ec9db8_commit_eba19ceab7b8_이_대부분인_commit_로그&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#-%EC%9D%B4-%EB%8C%80%EB%B6%80%EB%B6%84%EC%9D%B8-commit-%EB%A1%9C%EA%B7%B8&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#-%EC%9D%B4-%EB%8C%80%EB%B6%80%EB%B6%84%EC%9D%B8-commit-%EB%A1%9C%EA%B7%B8&lt;/a&gt;&apos;.&apos; 이 대부분인 commit 로그&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 단계부터는 단순하게 파일시스템으로 백업을 하는 시대는 지났지만, 버전관리를 깊이 있게 사용하지는 못했습니다.그때 대부분의 커밋로그를 보면 점하나 (.)만 찍은 것이 많았습니다. 아무런 내용을 안 쓰면 입력하라고 메시지가 나오니까 그 걸 피하려고 넣은 문자가 &apos;.&apos;점이였습니다. 시스템을 운영하고, 담당자가 바뀌고, 업무 규칙이 바뀌어갈수록 버전관리 시스템에 있는 커밋로그가 정말 소중한데, 그때는 그런 걸 알지 못했습니다. SI프로젝트이다 보니 빨리 개발 다 끝내고 도망갈 생각이 먼저이지, 운영할 사람에게 도움이 되는 정보를 남겨야한다는 생각은 잘 나지 않았습니다.한참 지난 다음의 이야기지만, 불행스럽게도 제가 그 프로젝트에서 마지막까지 남아서 유지보수를 했습니다. 물론 그 덕분에 중간에 나갔으면 몰랐을 것들을 많이 느끼긴했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437lock_eab1b8eab3a0_ed87b4eab7bclock_걸고_퇴근&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#lock-%EA%B1%B8%EA%B3%A0-%ED%87%B4%EA%B7%BC&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#lock-%EA%B1%B8%EA%B3%A0-%ED%87%B4%EA%B7%BC&lt;/a&gt;Lock 걸고 퇴근&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 처음 버전관리 시스템으로 썼던 Merant Version Manager는 CVS처럼 배타적인 방식으로 파일의 소유권을 관리했었습니다. 즉, 고칠파일이 있으면 먼저 그 파일들을 &apos;Checkout&amp;#8217;해야 하는데, 그 상태에서는 다른 사람은 그 파일을 건드리지 못하게 됩니다. 수정이 끝난 후에 &apos;Check in&amp;#8217;을 하면 고친 파일이 올라가고, 다른 사람이 &apos;Check out&amp;#8217;을 받을 수 있는 상태로 변합니다. 그 제품의 최신 버전은 어떤지는 몰라도 당시에는 그렇게 썼었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;몇번은 다른 개발자가 파일을 Checkout 해놓은 상태로 퇴근을 했는데, 급하게 파일을 고쳐야 할 때가 있었습니다. 전화해서 비밀번호를 물어보고 해결을 했는데, 나중에는 업무가 밀접하게 관련있는 사람들끼리는 비밀번호를 거의 공유하다시피 했었습니다. 그리고 Lock 걸린 파일을 수정해야 할 때도 급한 일이 아니라면 먼저 &apos;Check out&amp;#8217;한 사람이 다 고칠 때까지 기다렸었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그때는 그게 당연한 방식이였고, 그렇게 해야 질서가 있고 안전하다고 생각했습니다. 얼핏 SVN이 lock없이 파일을 고쳐도 된다고 듣기는 했지만, 그렇게 하면 소스가 엉켰을 때 해결이 되지 않을 위험한 방식이라고 추측했습니다. 당시에는 그 버전관리 방식에 역시나 만족을 했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;httpsgist_github_combenelog2922437svnec9d84_ec93b0eab8b0_ec8b9cec9e91ed9598eb8ba4svn을_쓰기_시작하다&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#svn%EC%9D%84-%EC%93%B0%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8B%A4&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#svn%EC%9D%84-%EC%93%B0%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8B%A4&lt;/a&gt;SVN을 쓰기 시작하다&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437svn_ec9e85ebacb8svn_입문&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#svn-%EC%9E%85%EB%AC%B8&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#svn-%EC%9E%85%EB%AC%B8&lt;/a&gt;SVN 입문&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러다가 직장이 바뀌어서 SVN을 쓰는 개발조직에서 일하게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SVN을 특별히 공부한 적이 없었는데도 &lt;a href=&quot;http://subclipse.tigris.org/&quot;&gt;Subclipse&lt;/a&gt;나 &lt;a href=&quot;http://www.eclipse.org/subversive/&quot;&gt;Subversive&lt;/a&gt;같은 Eclipse plugin 덕분에 금방 적응을 했습니다. 그런 Plugin 덕분에 버전관리가 부가적인 작업이 아니고, 개발과정에 자연스럽게 녹아든다는 느낌이 들었습니다.배타적이지 않은 lock관리 방식과 &apos;transactional&amp;#8217;한 특성을 가진 commit 방식도 직접 써보니 더 편리한 개념이라는 것을 깨달았습니다. 가끔 Eclipse plugin의 버그나 뭔가 상태가 꼬였을 때 헤매기도 했지만, 얼마 지나지 않아 비슷한 상황에서의 문제해결에 익숙해지고, Plugin도 점점 안정화되어서 큰 불편없이 쓰게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SVN을 쓰기 전까지는 이전의 버전관리 방식에 만족을 하고 있었는데, 막상 써보니 이전에는 그 불편함을 어떻게 견디었나하는 생각이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437eba8b8eca780eb8a94_ec96b8eca09ceb8298_eb9190eba0a4ec9ab4_ec9dbc머지는_언제나_두려운_일&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%A8%B8%EC%A7%80%EB%8A%94-%EC%96%B8%EC%A0%9C%EB%82%98-%EB%91%90%EB%A0%A4%EC%9A%B4-%EC%9D%BC&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%A8%B8%EC%A7%80%EB%8A%94-%EC%96%B8%EC%A0%9C%EB%82%98-%EB%91%90%EB%A0%A4%EC%9A%B4-%EC%9D%BC&lt;/a&gt;머지는 언제나 두려운 일&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 SVN에서는 브랜치 관리나 머지가 여전히 번거롭거나 두려운 일이였습니다.특히나 브랜치를 생성하고 오랫동안 유지하는 일은 정말 손이 많이 가는 일이였습니다.장기 프로젝트를 했을 때는 브랜치가 분기된 이후로 트렁크(trunk)에 반영된 사안들을 다시 브랜치로 옮기는 작업을 주기적으로 했는데, 자주 안하면 나중에 최종 머지가 두려워지고, 그렇다고 자주하기에는 시간이 많이 걸리는 작업이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;브랜치 관리와 머지를 잘하기 위해서 여러 개발팀에서 다양한 방법을 쓰고 있는 것을 보았습니다. 어떤 프로젝트에서는 일주일에 한번씩 트렁크 쪽의 이력을 가장 많이 알고 있는 사람과 브랜치를 가장 많이 수정한 사람이 같이 앉아서 작업을 했는데, 오래 걸리면 한번에 30~40분이 넘게 걸리기도 했습니다.팀에서 그런 머지작업을 많이 한 사람이 &apos;머지 전문가&amp;#8217;로 불리기도 했습니다. 당시 썼던 SVN버전에서는 머지한 후에 그동안의 이력이 남지 않기 때문에, 머지를 수정한 커밋 로그에는 &quot;from revision 10244 to 10532&quot; 와 같은 형식으로, 어디서부터 어디까지를 합쳤는지 로그를 적었습니다. 머지에 쓰는 도구도 사람마다 팀마다 다양했습니다. &lt;a href=&quot;http://subclipse.tigris.org/&quot;&gt;Subclipse&lt;/a&gt;나 &lt;a href=&quot;http://www.eclipse.org/subversive/&quot;&gt;Subversive&lt;/a&gt;의 메뉴를 활용하는 팀이 있었는데, 각자 개인마다 나름대로의 노하우가 있는 듯했습니다.어떤 팀에서는 그런 도구를 이용하면 오히려 실수가 많다면서 2개의 브랜치를 모두 Eclipse에서 띄운 후에 Diff로 하나하나 비교해가면서 파일도 하나하나 복사해서 가는 방식을 썼었습니다.시간이 상당히 많이걸리는 방식이였지만, 그 팀에서는 머지는 그렇게 하는게 최고라면서 상당히 만족하고 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오랜 노하우가 쌓인다고 해도 어떤 방식이든 여전히 실수할 여지는 많았습니다. 머지되어야할 이력이 빠져서 오류가 발생했던 경험도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇게 브랜치를 관리하는 부담이 크다보니, 규모가 큰 변경이 아니라면 왠만해서는 브랜치를 안 따고 Trunk만 사용하고 싶었습니다.그런데 개발서버에라도 언제 배포될지도 모르는 Trunk에 마음대로 커밋을 하는 것은 부담이 되는 일이였습니다. 부분적으로 잘 안 돌아가는 소스를 커밋할 수는 없으니,어느 정도 기능별로 완결된 것만 커밋을 하게 되었고, 그러다보면 커밋을 자주하지 않게 되기도 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;박재성님이 쓰신 &lt;a href=&quot;http://www.yes24.com/24/Goods/3301419&quot;&gt;Java 프로젝트 필수유틸리티&lt;/a&gt;라는 책에서도 아래와 같이 SVN merge의 어려움에 대해서 나와있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SVN에서 소스 코드 머지가 어려워 브랜치를 사용하지 않고 신규 메소드를 추가하는 방식으로 변경할 수 밖에 없는 상황도발생한다. 그렇지 않아도 VCS의 사용에 반대하는 개발자들이 있는 상황에서 머지의 어려움은 개발자들을 설득하기 어렵게 한다.(307쪽)&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 문단을 포함해서 이 책의 306~307쪽에는 머지를 한후 커밋 로그에 리비전 번호를 기록하는 팁이라던지, SVN에서 merge를 하다가 문제를 겪으신 분의 이야기가 나와 있습니다.( &lt;a href=&quot;http://ahnyounghoe.tistory.com/5#comment670941&quot;&gt;안영회님의 블로그 글에 달린 강수형님의 댓글&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SVN의 최신버전에서는 머지를 하면 SVN에서 알아서 따로 추가적인 정보를 남기도록 개선이 되었다고는 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;httpsgist_github_combenelog2922437giteba5bc_eba79bebb3b4eb8ba4git를_맛보다&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#git%EB%A5%BC-%EB%A7%9B%EB%B3%B4%EB%8B%A4&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#git%EB%A5%BC-%EB%A7%9B%EB%B3%B4%EB%8B%A4&lt;/a&gt;Git를 맛보다&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437dvcsec9790_eb8c80ed959c_ec868cebacb8eba78c_eb93a3eb8ba4eab080dvcs에_대한_소문만_듣다가&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#dvcs%EC%97%90-%EB%8C%80%ED%95%9C-%EC%86%8C%EB%AC%B8%EB%A7%8C-%EB%93%A3%EB%8B%A4%EA%B0%80&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#dvcs%EC%97%90-%EB%8C%80%ED%95%9C-%EC%86%8C%EB%AC%B8%EB%A7%8C-%EB%93%A3%EB%8B%A4%EA%B0%80&lt;/a&gt;DVCS에 대한 소문만 듣다가..&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그럭저럭 SVN에 만족했지만 브랜치 관리에는 다소 아쉬움이 있었던 차에, 여러 오픈소스 프로젝트들이 Git이나 Mercurial같은 분산버전관리시스템(DVCS)로 옮기어 간다는 이야기를 들었습니다.인터넷에도 분산버전관리 시스템에 대한 글들이 많이 올라왔는데, 그 중에서 &lt;a href=&quot;http://surpreem.com/archives/51&quot;&gt;조엘 스폴스키가 쓴 Mercurial 튜터리얼&lt;/a&gt;이 인상 깊었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래도 직접 써본적이 없으니, 어떤 점이 좋을지 막연하게 상상한 하는 정도였습니다.그러던 중 팀내부에서 진행하는 프로젝트에서 Git으로 버전관리를 하게 되면서, Git의 좋은 점들을 직접 체험하게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437ecb2b4eab090ed959c_ec9ea5eca090체감한_장점&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EC%B2%B4%EA%B0%90%ED%95%9C-%EC%9E%A5%EC%A0%90&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EC%B2%B4%EA%B0%90%ED%95%9C-%EC%9E%A5%EC%A0%90&lt;/a&gt;체감한 장점&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래에 정리한 Git의 장점들은 다른 누군가가 그렇다더라.. 해서 적은 것이 아니고, 실제로 프로젝트를 하면서 솔직하게 느껴졌던 것들입니다.오랫동안 쓰다보면 이것보다 더 많은 것을 느끼겠지만, 처음 써보는 프로젝트에서는 이정도가 개발하는데 도움이 된다고 생각되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;httpsgist_github_combenelog2922437ctrlz_ed8c8cec9dbc_ebb3b5ec82acebb3b4eb8ba4_ec9588ec8bacec9db4_eb9098eb8a94_local_ebb284eca084_eab480eba6acctrlz_파일_복사보다_안심이_되는_local_버전_관리&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#ctrlz-%ED%8C%8C%EC%9D%BC-%EB%B3%B5%EC%82%AC%EB%B3%B4%EB%8B%A4-%EC%95%88%EC%8B%AC%EC%9D%B4-%EB%90%98%EB%8A%94-local-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#ctrlz-%ED%8C%8C%EC%9D%BC-%EB%B3%B5%EC%82%AC%EB%B3%B4%EB%8B%A4-%EC%95%88%EC%8B%AC%EC%9D%B4-%EB%90%98%EB%8A%94-local-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC&lt;/a&gt;Ctrl+Z, 파일 복사보다 안심이 되는 Local 버전 관리&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개발자의 Local PC에서도 버전관리를 할 수 있다는 점은 DVCS의 대표적인 장점으로 꼽히고 있습니다.그런데 과연 그것이 꼭 필요한지, 오히려 버전관리 과정을 더 복잡하게 하지 않는지 우려하시는 분이 많고, 저도 그랬었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;실제로 프로젝트를 해보니 지금까지 &apos;Ctrl + Z&amp;#8217;나 주석문으로 가리기, 파일복사로 해왔던 작업 중에 어떤 부분은 Local에서의 버전관리로 해결할 부분이 아니었나하는 생각이 듭니다.코드를 작성하다보면 성공의 확신이 없는 시도를 해볼 때도 있고, 시도한 것이 여의치 않았을 때 다시 돌아올 수 있는 지점을 기록하고 싶어지기도 합니다.물론 간단한 1~2줄의 수정 같은 정도는 Ctrl+Z, 주석문 등을 이용하는 것이 더 편리할 수도 있습니다. 그러나 파일의 여러 부분이 동시에 바뀌어야 하는데 나중에 되돌릴 가능성이 있다던지, 두가지 방식의 구현을 시험삼아서 같이 진행할 때는 Local에서 중간중간 commit을 하거나 브랜치를 생성하는 편이 훨씬 편리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SVN에서는 따로 브랜치를 따고, 나중에 머지하는 일이 번거롭고 두렵기 때문에 통합할 가능성이 확실하지 않은 수정은 Local PC에서 원래의 파일을 복사본을 만들어서 수정하기도 했었습니다. 그러나 Git에서는 Local branch 생성과 이동이 빠르고 결과를 머지하는 것도 더 안정감이 있다는 느낌이 들기 때문에, Ctrl + Z,주석문 처리(//), 파일 복사로 때우던 소규모의 변경 지점 관리가 더 편해집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git을 썼던 프로젝트에서 XML파싱을 하는 모듈을 개발했었는데, JDOM과 DOM4J중에서 어떤 것이 해당 용도에 적합할지 확신이 없는 상태에서 2개의 Local branch를 만들어서 작업을 했었습니다.처음에 JDOM으로 갔다가, 마음에 안 드는 부분이 있어서 DOM4J로 구현했다가 다시 JDOM으로 돌아왔는데, 이렇게 결정을 번복하는 과정에서 Local의 버전관리가 없었다면 훨씬 번거롭고 다른 작업자들에게도 방해가 되었을 듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;httpsgist_github_combenelog2922437ebb284eca084eab8b0eba19deab3bc_ed86b5ed95a9ec9d98_eab09ceb8590_ebb684eba6ac버전기록과_통합의_개념_분리&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B8%B0%EB%A1%9D%EA%B3%BC-%ED%86%B5%ED%95%A9%EC%9D%98-%EA%B0%9C%EB%85%90-%EB%B6%84%EB%A6%AC&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B8%B0%EB%A1%9D%EA%B3%BC-%ED%86%B5%ED%95%A9%EC%9D%98-%EA%B0%9C%EB%85%90-%EB%B6%84%EB%A6%AC&lt;/a&gt;&apos;버전기록&amp;#8217;과 &apos;통합&amp;#8217;의 개념 분리&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞에서 말했듯이, Local에서 버전관리가 가능하기 때문에 &apos;버전기록&amp;#8217;과 &apos;통합&amp;#8217;이 다른 개념이 됩니다.SVN에서 &apos;커밋&amp;#8217;은 &apos;버전기록&amp;#8217;이자 &apos;소스 통합&amp;#8217;을 의미합니다.. 즉, 작업한 내용을 기록하는 일과 다른 사람이 일한 소스와 합치는 일이 같은 일입니다.저도 지금까지 그것이 당연하다고 생각해왔기 때문에 두가지를 같이 취급하는 것이 전혀 어색하지 않았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 엄밀히 따지면 2가지는 구분이 됩니다. 버전기록은 중간중간 의미 있는 단위의 작업이 끝날 때마다 할만한 일입니다. 그에 반해서 통합은 내가 작업한 소스를 다른 작업자들이 받아가도 괜찮은 시점에 하는 것이 바람직합니다. 예를 들어 컴파일도 되지 않는 소스를 커밋을 하고 그 에러를 고치는 커밋을 하기 전에 다른 동료가 소스를 받아간다면, 동료는 원래 하는 일에 방해를 받게 됩니다.SVN에서는 &apos;커밋=통합&amp;#8217;이라서 개인이 작업한 것을 전체 소스에 통합을 해도 괜찮은 시점에만 커밋을 해야 그런 부작용이 없습니다.만약 작업하는 브랜치가 언제라도 배포될 수 있는 브랜치라면 극단적으로는 개발작업에 완료된 시점에만 커밋을 해야 안전합니다. 이렇게 &apos;커밋=통합&amp;#8217;일 때는 버전관리의 모든 욕구를 다 충족시키지 못할 수도 있습니다. 오류가 있거나, 아직 확정되지 않은 인터페이스를 포함하고 있어서 다른 사람에게 전파되지 않았으면 하는 프로그램의 상태라도 버전기록은 하고 싶은 상황도 생길 수 있습니다.많은 경우, 통합보다 버전기록이 더 자주 필요한 일입니다.그리고 통합은 버전기록보다 더 신중하고 노력이 많이 들어가는 일입니다.예를 들면 안전한 통합을 위해서는 svn update &amp;#8594; mvn test &amp;#8594; commit의 절차를 거쳐야하는데, 버전기록이 필요할 때마다 위와 같이 안전한 통합을 위한 절차를 할지말지 고민해야 한다면 버전기록은 훨씬 무거운 일이됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;물론 SVN으로도 별도의 브랜치를 딴다면 다른 작업자에게 영향없이 독립적으로 버전기록을 할 수 있지만, 굉장히 번거롭습니다.Git에서는 버전기록이 필요한 시점에서는 언제든지 commit을 하고, 통합할만한 상태가 되었을 때만 push를 하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;어떤 분은 통합 없는 버전기록을 할 수 있다면 통합을 자주 하지 않게 된다는 우려를 하시기도 합니다.그런데 SVN으로 작업할 때도 통합 가능한 상태를 염두에 두지 않고 버전기록이 필요할 때마다 자주 commit을 하다보면 CI서버의 빌드가 자주 깨지게 됩니다.빌드 실패는 빠른 피드백으로서 의미가 있지만, 너무 자주 실패하면 잡음이 되어서 다른 사람에게 방해가 되거나, 그 신호를 무시하게 됩니다.그런 부작용을 우려해서 오히려 commit을 자주 하지 않게 되는 경우를 보기도 했었습니다. 그래서 어떤 프로젝트에서는 &apos;적어도 하루에 한번씩은 commit을 하자&amp;#8217;라고 규칙을 정한 곳도 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;버전기록과 통합을 강제로 묶어서 통합을 자주 하기를 바라기보다는 두 가지 개념을 구분하고 어떻게 활용할지는 상황에 따라서 선택하는 편이 좋다고 생각합니다.버전기록과 통합이 강제로 묶여있다면 개발자들이 오히려 둘 다 자주 하지 않게 될 수도 있습니다.통합을 자주하고 싶다면 필수적인 통합주기를 개발팀에서 약속을 하는 편이 더 실용적입니다.
기술 도메인의 특성이나 작업을 어떻게 분담하느냐에 따라서 통합과 버전관리의 주기는 다양할 것입니다.보편적인 버전관리 도구라면 상황에 따라서 다양한 정책을 적용할 수 있도록 개념이 섬세하고 유연한 편이 더 좋다고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;httpsgist_github_combenelog2922437ebb9a0eba5b4eab3a0_ed8eb8ed959c_ebb88ceb9e9cecb998_ec9db4eb8f99빠르고_편한_브랜치_이동&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%B9%A0%EB%A5%B4%EA%B3%A0-%ED%8E%B8%ED%95%9C-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%9D%B4%EB%8F%99&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%B9%A0%EB%A5%B4%EA%B3%A0-%ED%8E%B8%ED%95%9C-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%9D%B4%EB%8F%99&lt;/a&gt;빠르고 편한 브랜치 이동&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;어느 정도 규모가 있는 프로젝트에 참여하다보면 여러 브랜치를 왔다갔다 하면서 작업할 일이 많습니다. SVN에서도 Local에 받은 소스 폴더를 다른 브랜치로 이동을 할 수 있고, Eclipse plugin으로 편하게 지원됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Switch to branchhttps://camo.githubusercontent.com/14111eced471ead7d87404b86e3d337f404dce42/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f73766e5f7377746963685f6272616e63682e706e67[&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://camo.githubusercontent.com/14111eced471ead7d87404b86e3d337f404dce42/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f73766e5f7377746963685f6272616e63682e706e67&quot; alt=&quot;SVN switch branch&quot;&gt;&lt;/span&gt;]&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, 저는 프로젝트를 하면서 저 기능을 거의 쓴 적이 없습니다. 네트워크로 다른 브랜치의 파일을 받아오기 때문에 이동하는 속도가 굉장히 느리기 때문입니다.그리고 원래 있던 브랜치에서 커밋을 아직 안 한 파일이 남아 있어도 이동이 안 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런 불편함 때문에 보통 여러 branch를 Eclipse에서 별도의 프로젝트로 받아서 한꺼번에 열어두는 일이 많습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://camo.githubusercontent.com/8bf54e103af68913b7d606dea0af60e45048f57a/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f73766e5f6272616e636865732e706e67&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://camo.githubusercontent.com/8bf54e103af68913b7d606dea0af60e45048f57a/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f73766e5f6272616e636865732e706e67&quot; alt=&quot;SVN branches&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러다보면 파일을 수정할때 어느 브랜치의 파일을 수정하는 건지 헷갈릴 때가 많습니다. 보통 저는 Ctrl + Shift + R로 파일을 찾는데, 여러 브랜치를 동시에 열고 있을 때는 같은 파일이름이 동시에 떠서 불편합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://camo.githubusercontent.com/63777e9ff0cc1771b81a86b4e3e0766a2b4e77cd/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f6f70656e5f7265736f757263652e706e67&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://camo.githubusercontent.com/63777e9ff0cc1771b81a86b4e3e0766a2b4e77cd/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f6f70656e5f7265736f757263652e706e67&quot; alt=&quot;Open Resources&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 여러 브랜치가 열려있는 상황에서 의도하지 않은 브랜치의 파일을 고치는 실수는 주변에서 굉장히 흔하게 보였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git에서는 이런 브랜치 전환이 굉장히 빠릅니다. 네트워크를 타지 않고 Local에서 branch를 전환을 하니 당연한 일입니다.Git을 처음 써본 프로젝트에서도 몇번 브랜치를 전환하면서 작업을 했었는데, 하나의 프로젝트만 열고만 있으면 되니 훨씬 편리하고 빨랐습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;httpsgist_github_combenelog2922437eb8d94_eca095eab590ed959c_ecbba4ebb08b_eba19ceab7b8더_정교한_커밋_로그&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%8D%94-%EC%A0%95%EA%B5%90%ED%95%9C-%EC%BB%A4%EB%B0%8B-%EB%A1%9C%EA%B7%B8&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%8D%94-%EC%A0%95%EA%B5%90%ED%95%9C-%EC%BB%A4%EB%B0%8B-%EB%A1%9C%EA%B7%B8&lt;/a&gt;더 정교한 커밋 로그&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SVN에서는 브랜치에 있던 파일을 트렁크에 머지하면 그동안 브랜치에 커밋한 이력은 옮겨지지 않았습니다. 트렁크와 브랜치 사용 정책을 정하느냐에 따라서 다르지만, 어떤 프로젝트에서는 트렁크에는 아래와 같이 머지를 했다는 기록 밖에 없고, 머지를 담당한 사람의 이름밖에는 남아 있지 않았었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://camo.githubusercontent.com/abca6698c82e72666dac32691aa356562e04452c/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f73766e5f686973746f72792e706e67&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://camo.githubusercontent.com/abca6698c82e72666dac32691aa356562e04452c/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f73766e5f686973746f72792e706e67&quot; alt=&quot;SVN history&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git에서는 원한다면 모든 이력을 남길 수 있습니다. Eclipse plugin으로 보니 머지한 이력을 그래프로 이쁘게 보여줬습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://camo.githubusercontent.com/2726e7ce963eff68da29490e80bb00880ec774cc/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f6769745f686973746f72792e706e67&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://camo.githubusercontent.com/2726e7ce963eff68da29490e80bb00880ec774cc/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f6769745f686973746f72792e706e67&quot; alt=&quot;Git history&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git에서는 커밋 로그를 정교하게 수정할 수도 있습니다. 몇개의 커밋을 하나로 합칠 수도 있고, 앞에 커밋한 이력을 수정할 수도 있습니다.파일을 하나 빠뜨리고 커밋을 해서 불필요하게 커밋이 나누어지거나, 설명을 대충 적은 것 같아서 아쉬울 때가 많았는데 Git에서는 commit을 한 이후에도 그걸 보완하는 작업이 가능합니다. 어쩌면 한 번에 완벽한 코드를 짜는 것이 불가능한 것처럼, 한번에 깔끔한 커밋 이력을 남기고 논리적으로 균일한 커밋 단위를 관리하기는 어렵습니다.오래 가는 코드를 위해서는 커밋 로그는 깔끔하고 친절하게 정리되어야 합니다. Git을 쓰면 commit 로그도 리팩토링의 대상이 되어서 더 정교한 commit 관리를 원하는 사람에게 도움이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437eab7b8eba6aceab3a0_gitflow_gerrit그리고_gitflow_gerrit&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EA%B7%B8%EB%A6%AC%EA%B3%A0-gitflow-gerrit&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EA%B7%B8%EB%A6%AC%EA%B3%A0-gitflow-gerrit&lt;/a&gt;그리고 Gitflow, Gerrit&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지금까지는 직접 경험한 장점을 이야기했는데, &lt;a href=&quot;https://github.com/nvie/gitflow&quot;&gt;Gitflow&lt;/a&gt;와 &lt;a href=&quot;http://code.google.com/p/gerrit/&quot;&gt;Gerrit&lt;/a&gt;은 직접 써본 도구는 아닙니다. 그래도 Git의 매력중의 중요한 부분이라고 생각되어서 간단하게 언급하고 넘어갑니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nvie/gitflow&quot;&gt;Gitflow&lt;/a&gt;는 Best Practice라고 할만한 버전관리 절차를 Git으로 편하게 쓸 수 있게 도와주는 도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://camo.githubusercontent.com/f862a6cc2cf265c31f354a666120ce26c224db8c/687474703a2f2f6e7669652e636f6d2f696d672f323030392f31322f53637265656e2d73686f742d323030392d31322d32342d61742d31312e33322e30332e706e67&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://camo.githubusercontent.com/f862a6cc2cf265c31f354a666120ce26c224db8c/687474703a2f2f6e7669652e636f6d2f696d672f323030392f31322f53637265656e2d73686f742d323030392d31322d32342d61742d31312e33322e30332e706e67&quot; alt=&quot;Gitflow model&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git이 워낙 기능이 많기 때문에 어떤 버전관리 정책을 써야할지 처음에는 막막하게 느껴질 수도 있을 것 같습니다. GitFlow는 &apos;Feature&apos; ,&apos;Hotfix&apos; ,&apos;Release&apos; 등과 같이 전형적인 역할의 브랜치들을 기본적으로 녹여내고 있습니다. 즉 좀 더 정형화되고 추상화된 개념들을 더 짧은 명령어로 쓸 수 있는 것입니다. &apos;git flow hotfix start&amp;#8217;와 같이 명령어를 치면, hotfix를 위한 branch 생성을 해주는 식입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://code.google.com/p/gerrit/&quot;&gt;Gerrit&lt;/a&gt;은 Git바탕의 코드 리뷰도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;어떤 블로거는 &lt;a href=&quot;http://alblue.bandlem.com/2011/02/someday.html&quot;&gt;Getrrit을 소개하면서 &apos;Someday, all software will be built this way.&apos;&lt;/a&gt;라고 주장하기도 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래에 Gerrit과 Jenkins를 연동한 인상적인 Demo도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://alblue.bandlem.com/2011/02/gerrit-git-review-with-jenkins-ci.html&quot; class=&quot;bare&quot;&gt;http://alblue.bandlem.com/2011/02/gerrit-git-review-with-jenkins-ci.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;9분 40초부터 1분동안의 데모가 핵심적인 장면입니다. 코드를 수정하고 commit, push를 하고 Gerrit에 들어가보면 CI서버지인 Jenkins에서는 &apos;verified&amp;#8217;되었다는 표시가 나옵니다.그리고 그 코드를 리뷰해서 &apos;Great&amp;#8217;라는 메시지를 달아주고 +2점으로 점수를 부여해주는 장면이 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;httpsgist_github_combenelog2922437eb85bcec9f81eab1b0eba6aceba5bc논쟁거리를&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%85%BC%EC%9F%81%EA%B1%B0%EB%A6%AC%EB%A5%BC&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%85%BC%EC%9F%81%EA%B1%B0%EB%A6%AC%EB%A5%BC&lt;/a&gt;논쟁거리를&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞서서 Git을 쓰면 통합을 자주 안 하게 된다는 주장에 대해서 말씀드렸지만, 그외에도 Git에 대한 이런저런 불평들은 많이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;httpsgist_github_combenelog2922437ec96b4eba0a4ec9b8cec9a94_ebb3b5ec9ea1ed95b4ec9a94어려워요_복잡해요&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EC%96%B4%EB%A0%A4%EC%9B%8C%EC%9A%94-%EB%B3%B5%EC%9E%A1%ED%95%B4%EC%9A%94&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EC%96%B4%EB%A0%A4%EC%9B%8C%EC%9A%94-%EB%B3%B5%EC%9E%A1%ED%95%B4%EC%9A%94&lt;/a&gt;어려워요~ 복잡해요~&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우선 기능이 많고 어렵다는 것입니다. 이 부분은 저도 어느 정도 동감을 합니다.저도 프로젝트를 끝낸 후에 오랜 만에 다시 Git를 쓰려니 명령어가 잘 기억이 나지 않아서 헤메었던 기억이 있습니다.그리고 분명히 SVN보다 더 많은 개념과 기능을 제공하니 제대로 쓰려면 배워야할 것이 많기도 합니다. 중간에 많은 시행착오를 거치거나 실수로 소스를 날려먹은 사람도 몇번 봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇지만 처음부터 그 많은 기능을 모두 알 필요는 없다고 생각합니다.간단히 SVN으로 하던 주요 사용법만 Git으로 배우는 것은 얼마 걸리지 않습니다.그리고 차근차근 기능을 익혀나가면 Git으로 더 편리하게 할 수 있는 일들이 늘어가고 앞에서 언급한 Git의 장점로 개발이 더 편해질 것이라 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Git의 많은 기능들은 Git 자체의 오버엔지니어링이라기보다는, 버전관리 업무 자체의 복잡함을 보여준다고 생각합니다.수많은 작업자가 같이 작업하고, 여러 배포 버전을 동시에 살려나가야 하는 대규모 소프트웨어는 버전관리 자체가 워낙 어려운 과제입니다.그런 문제 상황을 정교하게 분류하다보니, 점점 새로운 개념들이 생겨나서 처음 접하는 사람에게는 논리적으로 어렵다고 느껴질 듯합니다.그런데 그런 개념을 어떻게 응용할지 고민하면서 쓰다보면, 지금까지의 버전관리가 충분하지 않았음을 느끼게 됩니다.저도 파일 공유 서버로 버전관리가 충분했다고 생각하는 시절에는 SVN에서 제공하는 transactional한 commit 같은 기능이 필요하다고 생각하지 못했었습니다.마찬가지로 DVCS가 없이도 큰 아쉬움은 없었는데, Git등을 써보고 나니 이전에는 SVN에서 그냥 넘어갔던 부분이 더 불편하게 느껴졌습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;httpsgist_github_combenelog2922437eclipse_pluginec9db4_ebb688ed8eb8ed95b4ec9a94eclipse_plugin이_불편해요&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#eclipse-plugin%EC%9D%B4-%EB%B6%88%ED%8E%B8%ED%95%B4%EC%9A%94&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#eclipse-plugin%EC%9D%B4-%EB%B6%88%ED%8E%B8%ED%95%B4%EC%9A%94&lt;/a&gt;Eclipse Plugin이 불편해요&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;또 하나는 Eclipse plugin이 아직도 불안정하고 불편하다는 점입니다. 저는 Eclipse plugin은 history를 보는 용도로만 쓰고 모든 작업은 명령행으로 해서 특별히 불편을 겪은 적은 없습니다.다른 써보신 분들에 의견에 따르면 Eclipse plugin만으로는 Git의 모든 기능을 원활히 쓸 수 없다고 합니다.그래서 Eclipse plugin은 덤정도로 여기면서 큰 기대를 안 하고, 명령행 방식을 주로 쓴다고 생각하면 오히려 시행착오가 적을 것 같기도 합니다.명령어를 외우는 것이 처음에는 부담될지 몰라도, 나중에는 메뉴를 찾아해메는 것보다 작업속도가 더 빨리질수도 있습니다. 그리고 정보공유나 인수인계에서는 GUI보다 명령행이 더 유리하기도 하고, 더 재미있어할 개발자들도 많을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;httpsgist_github_combenelog2922437eba788ecb998eba9b0마치며&quot;&gt;&lt;a href=&quot;https://gist.github.com/benelog/2922437#%EB%A7%88%EC%B9%98%EB%A9%B0&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/2922437#%EB%A7%88%EC%B9%98%EB%A9%B0&lt;/a&gt;마치며&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;버전관리는 새로운 시도를 하기에는 워낙 두려운 분야이기는 합니다. 현재 방식의 버전관리로 충분히 만족하는 조직이 많고, 조직의 관습이나 변화에 필요한 비용등을 생각하면 그것이 어떤 조직에서는 정답일수도 있습니다.저도 돌이켜보면 버전관리의 필요성을 못 느끼던 시절부터 시작해서, Merant Version manager, SVN, Git을 거쳤고, 그 사이에 생각도 많이 바뀌었습니다.원래 하던 방식이 마음이 편했기에 제가 처한 환경의 특수성을 과대평가해서 새로운 방식이 필요하지 않다고 생각했던 시절이 많았었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래도 발전한 도구들 덕분에 개발은 훨씬 편해진 것 같기도 하지만, 더 어려워지기도 했습니다. 단순히 소프트웨어 개발만 할 줄 알던 시절에서 빌드도구, 버전관리 도구에 대해서도 전에보다 더 많은 지식을 알아야 코드 한 줄을 쓸 수 있게 되었습니다.어떤 분들은 이런 흐름이 필요이상의 복잡함을 더했다고 느끼겠고, 새로 업계에 들어오는 개발자들에게는 점점 숙제거리가 늘어만 가는듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 이런 변화는 발전이고, 여러 조직에서 비슷한 고민들이 있어서 공유하다가 약간씩 더 나은 해결방식이 나오고 있다고 봅니다. 저는 Git을 쓰는 프로젝트를 처음 해 보면서 다른 개발자들이 어떤 고민을 했을지 좀 더 많이 이해했고, 그런 고민들을 저는 지나쳤음을 깨달았습니다.그 점이 Git으로 얻은 가장 큰 선물이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>스레드 안정성의 문서화와 JCIP 애노테이션</title>
      <link>https://blog.benelog.net//2933965.html</link>
      <pubDate>Wed, 30 May 2012 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2933965.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;스레드_안전성을_어떻게_문서에_표기할까&quot;&gt;스레드 안전성을 어떻게 문서에 표기할까?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Javadoc에서는 메소드 선언에 synchronized가 있는지 표시하지 않습니다. 그 이유는 동기화 여부는 세부 구현 방식이기 때문에 문서에까지 그것을 노출하지 않는 것은 적절하지 않기 때문이라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;선언부에 synchronized가 있는 메소드도 메소드 안의 모든 블럭이 synchronized(this)와 감싸진 코드와 의미입니다. 즉 아래의 코드는&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;synchronized void run() {
//do something
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음과 똑같은 역할을 하는 코드입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;void run(){
  synchronized(this) {
  // do something
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;구현방식을 개선하면서 동기화 구간을 메소드의 일부 구간으로 좁힐 수도 있고, this 객체 말고 다른 객체로 lock을 바꿀 수도 있습니다. 이런 세부구현 상황은 외부 인터페이스보다 더 쉽게 바뀔 여지가 있습니다. 그리고 lock으로 보호되는 모든 구간을 javadoc의 메소드 선언부에 표시하기도 어렵고, this로 전체 메소드를 lock으로 잡는 메소드인 synchronized 메소드 여부를 표시하는 것만으로 충분히 문서화가 되었다고 생각하기도 어렵습니다.내부적으로 더 효율적인 방식으로 동기화가 되어 있을 수도 있으니,Javadoc에서 synchronized가 있느냐 없느냐가 스레드 안정성을 판단할 수 있는 일관적인 기준도 되지 못합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서 현재 javadoc의 정책은 적절하다고 생각됩니다. Effective Java 2nd Edition의 Item 70에서도 그렇게 언급되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;결국 현재 javadoc의 규약으로는 어떤 메소드가 멀티스레드에서의 안전한지 여부를 표시해야 하는 강제성은 없다는 이야기입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Effective Java 2nd Edition에서는 스레드 안정성을 다음 4가지 분류로 구분해서 문서화하라고 권장합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Immutable&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;상태가 없습니다. 당연히 Thread-safe합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예) String, Long, Integer, BigDecimal&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;무조건 Thread-safe&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;상태가 있으나 내부적으로 알아서 동기화되어 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예) ConcurrentHashMap&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;조건부 Thread-safe&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;어떤 메소드는 외부에서 sync가 필요한 클래스입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예) Collections.synchronizedList로 받은 List의 iterator는 ConcurrentModificationException을 발생시킬수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not Thread-safe&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;외부에서 synchronized를 해야 함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예) HashMap, ArrayList&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Thread-hostile&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;외부에서 synchronized를 해도 멀티쓰레드에서는 못 쓰는 클래스입니다. 다행히 Java에는 거의 없습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예) System.runFinalizersOnExit&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저렇게 잘 구분해서 Javadoc 문서의 제일 앞에 설명을 해준다면 좋겠지만, 강제적인 표준이 없다보니 기존의 문서들은 클래스 설명의 중간에서 스레드 안정성을 설명합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;몇가지_클래스의_사례&quot;&gt;몇가지 클래스의 사례&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다면 현재 Java 클래스가 스레드 안정성을 문서화하고 있는지 몇가지 예를 살펴보겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;java_util_linkedlist&quot;&gt;java.util.LinkedList&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://download.oracle.com/javase/1.5.0/docs/api/java/util/LinkedList.html&quot;&gt;LinkedList&lt;/a&gt;의 javadoc에서는 클래스 설명의 중간 쯤에 볼드체로 강조해서 이 구현은 &apos;not synchronized&amp;#8217;되었다고 적혀 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jcip/LinkedList-doc.png&quot; alt=&quot;LinkedList&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;java_text_simpledateformat&quot;&gt;java.text.SimpleDateFormat&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://download.oracle.com/javase/1.5.0/docs/api/java/text/SimpleDateFormat.html&quot;&gt;SimpleDateFormat&lt;/a&gt;의 javadoc에서는클래스 설명의 마지막 즈음에 synchronization이라는 제목의 절에서 &apos;not synchronized&amp;#8217;라고 설명합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jcip/SimpleDateFormat-doc.png&quot; alt=&quot;SimpleDateFormat&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;org_springframework_batch_item_file_flatfileitemwriter&quot;&gt;org.springframework.batch.item.file.FlatFileItemWriter&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링배치의 클래스인 &lt;a href=&quot;http://static.springsource.org/spring-batch/apidocs/org/springframework/batch/item/file/FlatFileItemWriter.html&quot;&gt;FlatFileItemWriter&lt;/a&gt;는 not의 앞 뒤로 별표를 붙여서 &apos;&lt;strong&gt;not&lt;/strong&gt; thread-safe&amp;#8217;라고 적어놨습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jcip/FlatFileItemWriter-doc.png&quot; alt=&quot;FlatFileItemWriter&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 클래스가 스레드 안정성을 표시하는 방법은 제 각각이고, 나름대로는 강조하고 있지만 주의깊게 API 문서를 주의 깊게 본 사람이 아니라면 지나치기 쉽습니다. 차라리 &apos;스레드 안정성은 클래스별 설명 제일 윗줄에 넣도록 하고, 반드시 빨간색 Bold로 표시한다&apos; 같은 규칙이 있었으면 얼마나 좋을까하는 생각까지도 듭니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;jcip_annotation&quot;&gt;JCIP annotation&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://jcip.net/annotations/doc/net/jcip/annotations/package-summary.html&quot;&gt;JCIP annotation&lt;/a&gt;은 &apos;Java concurrent in practice&apos; 책에서 제안된 스레드 안정성을 표시하는 기법입니다.아래 4개의 종류의 애노테이션을 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@GuardedBy&lt;/code&gt; : 해당 객체가 어떤 Lock으로 보호되고 있는지 표시. 필드에 메소드에 사용 가능&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@Immutable&lt;/code&gt; : 불변객체&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@NotThreadSafe&lt;/code&gt; : 스레드 안전하지 않음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@ThreadSafe&lt;/code&gt; : 스레드 안전함&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;apache_httpclient에서_활용_사례&quot;&gt;Apache Httpclient에서 활용 사례&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;실제 이 JCIP 애노테이션이 활용된 사례는 &lt;a href=&quot;http://hc.apache.org/httpcomponents-client-ga/&quot;&gt;Apache Httpclient&lt;/a&gt;가 있습니다. 이 라이브러리의 클래스인 &lt;a href=&quot;http://www.docjar.org/html/api/org/apache/http/message/HttpGet.java.html&quot;&gt;HttpGet&lt;/a&gt;, &lt;a href=&quot;http://www.docjar.com/html/api/org/apache/http/impl/client/DefaultHttpClient.java.html&quot;&gt;DefaultHttpClient&lt;/a&gt;, &lt;a href=&quot;http://www.docjar.com/html/api/org/apache/http/impl/conn/SingleClientConnManager.java.html&quot;&gt;SingleClientConnManager&lt;/a&gt;는 아래와 같이 @NotThreadSafe, @ThreadSafe를 클래스 선언에 붙었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@NotThreadSafe
public class HttpGet extends HttpRequestBase {

@ThreadSafe
public class DefaultHttpClient extends AbstractHttpClient {

@ThreadSafe
public class SingleClientConnManager implements ClientConnectionManager {&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 애노테이션들은 원래의 JCIP 애노테이션의 패키지인 net.jcip.annotations 대신에 org.apache.http.annotation 패키지에 들어가 있기는 합니다.그런데 이 패키지에 있는 &lt;a href=&quot;http://www.docjar.com/docs/api/org/apache/http/annotation/ThreadSafe.html&quot;&gt;@ThreadSafe&lt;/a&gt;등의 javadoc를 봐도이 애노테이션들은 JCIP책에서 유래하였다고 설명합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/DefaultHttpClient.html&quot;&gt;이 애노테이션이 붙어 있는 클래스의 avadoc&lt;/a&gt;에서는 클래스 설명 단락의 위 쪽에 이 애노테이션을 보여줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jcip/DefaultHttpClient-doc.png&quot; alt=&quot;DefaultHttpClient&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;일관성 있는 위치에 표시되기 때문에 한 눈에 스레드 안정성 여부를 인식할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;findbugs에서의_지원&quot;&gt;FindBugs에서의 지원&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오픈소스 정적분석 도구인 &lt;a href=&quot;http://findbugs.sourceforge.net/&quot;&gt;Findbugs&lt;/a&gt;에서는 JCIP 애노테이션 중 @Immutable을 인식합니다. 버전 2.0부터 JCIP라는 버그 패턴으로 등록이 되어 있습니다. 아래 페이지에서 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://findbugs.sourceforge.net/bugDescriptions.html#JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS&quot; class=&quot;bare&quot;&gt;http://findbugs.sourceforge.net/bugDescriptions.html#JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://findbugs.sourceforge.net/manual/eclipse.html&quot;&gt;Findbugs Eclipse plugin&lt;/a&gt;를 설치하면 보다 편하게 FindBugs가 주는 경고를 확인할 수 있습니다..&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;만약 아래와 같이 @Immutable로 선언된 클래스에 final이 아닌 필드가 있다면&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import net.jcip.annotations.Immutable;

@Immutable
public class Memo {
  private String content;
  public void setContent(String content) {
   this.content = content;
  }
  public String getContent() {
   return content;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eclipse에서 경고를 보여줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jcip/immutable-findbugs.png&quot; alt=&quot;findbugs&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 FindBugs에서는 @Immutable외의 애너테이션은 특별히 확인하지는 않습니다.즉 &lt;code&gt;@ThreadSafe&lt;/code&gt; 로 선언된 객체에서 &lt;code&gt;@NotThreadSafe&lt;/code&gt; 로 선언된 멤버변수를 접근하더라구도 아무런 경고를 보내지는 않습니다. JCIP 애노테이션과 Findbugs를 동시에 쓰면서 많은 기대를 하지는 말아야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Findbugs의 자세한 설명방법은 아래 포스트를 참고하시기 바랍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/2079841&quot;&gt;FindBugs + Eclipse + Maven2 + Hudson&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java에서 어떤 클래스가 멀티스레드에서 의도하지 않게 사용될 때 그 부작용은 심각하지만, 문제가 되는 지점을 추적하기는 어렵습니다. 그렇게 때문에 스레드 안정성은 반드시 엄격하게 문서에 언급 되어야 합니다.그러나 기존의 클래스를 보면 제 각각의 표현 방식으로, 때로는 눈에 잘 띄지 않게 문서에 적혀 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JCIP 애노테이션은 일관된 방식으로 스레드 안정성을 문서화하는데 도움이 됩니다. 이 애노테이션은 Apache httpclient 프로젝트에 적용되었고, 강력하지는 않지만 findbugs에서 JCIP annotation이 걸린 클래스를 정적 분석을 해주기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대부분의 웹프로젝트에서는 비지니스 레이어를 상태가 없는 클래스로 멀티스레드에서도 안전하게 만드는 방식이 권장됩니다.
그러나 때로는 스레드 안전하지 않은 클래스를 만들게 될 수도 있습니다.
예를 들면 멀티스레드에서 공유되면 안 되는 외부 라이브러리의 클래스를 사용하는데, 그 클래스를 생성자로 받아서 멤버변수에 할당해야 하는 경우입니다.
또, 배치나 데몬서버 등을 만드는 프로젝트에서는 스레드 안전한 클래스와 그렇지 않은 클래스가 혼재하게 될 때도 많습니다.
그런 클래스들을 명확하게 구분하고 싶다면 JCIP 애노테이션으로 표시하는 것을 고려해볼만 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료&quot;&gt;참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[EJ2] : Effective Java 2nd Edition - Item 70&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[JCIP] : Java concurrency in practice&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>초간단한 FTP server, One-FTPServer</title>
      <link>https://blog.benelog.net//2930401.html</link>
      <pubDate>Thu, 17 May 2012 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2930401.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;배경&quot;&gt;배경&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;서버 작업을 하다보면, PC에 있는 파일을 서버에 올리거나, 서버에 있는 로그파일을 PC로 받고 싶을 때가 있습니다.그런 용도로 저는 다운로드를 할 때는 Winstone을, 업로드를 할 때는 간단한 웹애플리케이션으로 만든 &lt;a href=&quot;https://github.com/benelog/uploader/&quot;&gt;Uploader&lt;/a&gt; 를 사용합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제 블로그의 아래 포스트를 통해서 그 방법을 설명했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/2875999&quot;&gt;Winstone, 경량 Servlet container&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/2864739&quot;&gt;간단하게 서버에 파일을 올리기&amp;#8230;&amp;#8203;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;또는 파이썬이 설치되어 있다면 &apos;python -m SimpleHTTPServer&amp;#8217;를 실행하면 간단한 웹서버를 띄울 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, 여러 개의 파일을 올리거나 받을 때는 아무래도 FTP가 더 편하기도 합니다.그럴 때는 저는 &lt;a href=&quot;http://mina.apache.org/ftpserver/&quot;&gt;Apache FtpServer&lt;/a&gt;를 사용합니다.Apache FtpServer가 설정이 간단한 편이지만, 기본 패키지를 wget으로 다운로드 받고, 압축을 풀고, 설정 파일을 편집하는 과정을 여러 번 하다보면 좀 번거롭기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래서 Apache FtpServe의 모듈을 이용한 간단한 프로그램을 직접 만들어서 Github에 올려 보았습니다.&apos;&lt;a href=&quot;https://github.com/benelog/one-ftpserver&quot;&gt;One-FTPServer&lt;/a&gt;&apos;라고 이름을 붙여봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;httpsgist_github_com2711965ec82acec9aa9ebb295사용법&quot;&gt;&lt;a href=&quot;https://gist.github.com/2711965#%EC%82%AC%EC%9A%A9%EB%B2%95&quot; class=&quot;bare&quot;&gt;https://gist.github.com/2711965#%EC%82%AC%EC%9A%A9%EB%B2%95&lt;/a&gt;사용법&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;간단하게 아래와 같이 다운로드를 받고&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;wget benelog.net/one-ftpserver.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jar파일을 직접 실행하면 끝입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar one-ftpserver.jar
[source]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아무런 옵션이 없으면 익명 사용자(anonymous) 로그인을 통해 2121번 포트로, FTP서버가 실행 중인 디렉토리를 홈디렉토리로 접속할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;포트, 아이디, 비밀번호 등을 명령행에서 바로 지정할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar one-ftpserver.jar port=10021 id=benelog password=1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그외 passive port, SSL적용여부 등 총 6가지 설정변수를 지원합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar one-ftpserver.jar port=10021 passivePorts=10125-10199 ssl=true id=benelog password=1234 home=/home/benelog/programs&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자세한 파라미터의 설명은 &lt;a href=&quot;https://github.com/benelog/one-ftpserver에&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/one-ftpserver에&lt;/a&gt; 나와 있습니다. 혹시나 외국사람들도 쓸 일이 생길가봐 잘 되지 않은 어색한 영어로 적는다고 힘들었습니다;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다양한 FTP클라언트를 쓸 수 있지만,저는 wget이나 curl을 즐겨 사용합니다. 만약 benelog.net이라는 서버에 10021포트로 id가 benelog, 비밀번호 1234로 서버를 띄었다면, 아래와 같이 접근합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;wget&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;download : wget &lt;a href=&quot;ftp://benelog:1234@benelog.net:10021/&quot;&gt;filename&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;curl&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;upload : curl -T [filename] &lt;a href=&quot;ftp://benelog.net:10021&quot; class=&quot;bare&quot;&gt;ftp://benelog.net:10021&lt;/a&gt; -u benelog:1234&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;download : curl &lt;a href=&quot;ftp://benelog.net:10021/&quot;&gt;filename&lt;/a&gt; -u benelog:1234&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;재미있었던_코드&quot;&gt;재미있었던 코드&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;사용자_인증_정책_구현_클래스&quot;&gt;사용자 인증 정책 구현 클래스&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Apache FtpServer는 UserManager라는 인터페이스로 사용자 인증 정책을 구현하도록 설계되어 있습니다. 기본 구현클래스로 Property파일이나 데이터베이스에서 사용자 정보를 읽어오는 클래스가 제공됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 프로그램에서는 UserManager를 구현한 클래스에서 단순히 사용자 한명의 아이디, 비밀번호 값만을 가지고 인증을 하는 SingleUserManager라는 클래스를 만들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/one-ftpserver/blob/master/src/main/java/net/benelog/oneftpserver/user/SingleUserManager.java&quot;&gt;net.benelog.oneftpserver.user.SingleUserManager&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;혹시나 다른 인증정책이 필요한 FTP서버를 만들게되더라도, 이 클래스만 간단하게 구현해주면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;apache_commons_net의_ftpclient모듈을_이용한_통합_테스트&quot;&gt;Apache Commons Net의 FTPClient모듈을 이용한 통합 테스트&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;특별히 어렵지 않는 프로그램이지만 여러 조합의 옵션이 다 의도대로 동작하는지 확인하는데에는 반복적인 테스트가 필요할 것 같습니다.그래서 &lt;a href=&quot;http://commons.apache.org/net/&quot;&gt;Apache Commons Net&lt;/a&gt;에서 제공하는 FTP클라언트 모듈로 통합 테스트를 만들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/one-ftpserver/blob/master/src/test/java/net/benelog/oneftpserver/CommanderFtpSslTest.java&quot;&gt;net.benelog.oneftpserver.CommanderFtpSslTest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같이 Console의 명령행에서 넘어가는 것과 똑같은 옵션을 넣어서 서버를 실행시킨 후, FTPClient로 접속했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Test
public void loginFail() throws Exception {
  startServer(new String[]{&quot;port=3131&quot;,&quot;ssl=true&quot;,&quot;id=benelog&quot;,&quot;password=1234&quot;, &quot;home=&quot; + home});
  client.connect(&quot;127.0.0.1&quot;, 3131);    // login
  boolean authorized = client.login(&quot;benelog&quot;, &quot;13234&quot;);
  assertFalse(authorized);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 통합테스트 덕분에 처음에 만들 때 디버깅 시간도 줄였고, 나중에 발견된 버그도, 버그를 드러내는 통합테스트를 먼저 추가해서 실패한 테스트를 만든 후에 그 것을 통과시키도록 수정해서 해결했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;FTPClient모듈의 사용법도 덤으로 익혔습니다. &lt;a href=&quot;http://commons.apache.org/net/&quot;&gt;Apache Commons Net&lt;/a&gt;에서는 FTP와 FTPS클라이언트가 동일한 인터페이스가 제공되어서 ssl옵션을 테스트 할 때 편리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://commons.apache.org/net/api-3.1/org/apache/commons/net/ftp/FTPClient.html&quot;&gt;org.apache.commons.net.ftp.FTPClient&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://commons.apache.org/net/api-3.1/org/apache/commons/net/ftp/FTPSClient.html&quot;&gt;org.apache.commons.net.ftp.FTPSClient&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>최상위권 n등의 점수 구하기</title>
      <link>https://blog.benelog.net//2907090.html</link>
      <pubDate>Wed, 29 Feb 2012 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2907090.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/2173103&quot;&gt;Effective &amp;amp; Agile Java Generics&lt;/a&gt; 글에 이은, Generics 활용 사례입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;배치 프로그램을 짜다가 수십만개의 점수 중에 상위 0.1%, 0.01%에 해당하는 값이 무엇인지 구해야하는 일이 생겼습니다. 기존의 프로그램은 Linux shell로 되어 있었는데, Linux의 sort를 이용해서 전체 정렬을 한 후에 찾고자하는 등수가 있는 줄을 찾아가는 방식이였습니다. 전체 건수에 비해서 구하고자 하는 등수가 상위 0.1%, 0.01% 등 아주 작은 비율임에도 불구하고, 전체 Sort를 하는 것은 비효율적이라는 생각이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래서 이 프로그램의 일부를 Java로 바꾸는 작업을 하다가 이 순위를 구하는 부분도 필요한만큼만 정렬을 하도록 바꾸고 싶었습니다.  저는 OS를 우분투를 쓰기 떄문에 상관없지만, Java로 만든 프로그램에서 중간에 Shell을 호출하는 부분이 남아있으면 다른 개발자들은 Local PC에서는 확인할 수 없는 부분이 생겨서 불편해질까봐 걱정되었던 이유도 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;등수를 구하는 대상 데이터형은 Integer와 Float이였지만, Integer를 위한 클래스 따로 만들고 Float를 위한 것을 따로 만들기 싫어서 java.lang.Comparable를 구현한 클래스명을 다 사용할 수 있게 했습니다. 이정도 유연성을 두는게 추가 개발은 거의 없이 그냥 Generics 선언을 잘하면 되는 일이였으므로 그다지 과도한 확장은 아니였다고 생각했습니다. 덕분에 String형의 문자열도 비교할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;즉, 문제를 다시 정리하면 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;데이터는 점수값만이 1차원으로 나열된다. (예: 1,5,3,10 &amp;#8230;&amp;#8203;. )&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;java.lang.Comparable형의 클래스를 모두 계산 대상으로 받을수 있고, 명시적인 캐스팅이 필요없게 사용할 수 있어야한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;전체 데이터 중에 지정한 등수를 구한다. 전체 데이터는 파일에 있고, 수십만건이라서 메모리에 모두 올릴 수 없으나, 지정한 등수까지는 메모리에 올릴 수 있을 정도로 최상위권의 값이 지정된다. 즉 예를 들면 80만건중 100등의 점수가 몇점인지를 구하는 상황이다&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Integer, Float, String형을 대상으로 작성한 테스트 코드는 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public void get3rdRankOf5() {
    //given
    List&amp;lt;Integer&amp;gt; rawData = asList(400,400,100,500,200);
    int rankToFind = 3;

    //when
    RankFinder&amp;lt;Integer&amp;gt; rankFinder = new RankFinder&amp;lt;Integer&amp;gt;(rankToFind);
    addAll(rawData, rankFinder);
    Integer rankedValue = rankFinder.getRankedValue();

    //then
    assertThat(rankedValue, is(400));
    assertThat(rankFinder.getTopValues(), is(asList(400,400,500)));
}

@Test
public void get2ndRankOf5WithFloat() {
    //given
    List&amp;lt;Float&amp;gt; rawData = asList(10.9F, 10.2F, 10.3F, 10.4F, 10.5F);
    int rankToFind = 2;

    //when
    RankFinder&amp;lt;Float&amp;gt; rankFinder = new RankFinder&amp;lt;Float&amp;gt;(rankToFind);
    addAll(rawData, rankFinder);
    Float rankedValue = rankFinder.getRankedValue();

    //then
    assertThat(rankedValue, is(10.5F));
    assertThat(rankFinder.getTopValues(), is(asList(10.5F,10.9F)));
}

@Test
public void get4thOfWord() {
    //given
    List&amp;lt;String&amp;gt; rawData = asList(&quot;e&quot;,&quot;fc&quot;,&quot;a&quot;,&quot;b&quot;,&quot;k&quot;);
    int rankToFind = 2;

    //when
    RankFinder&amp;lt;String&amp;gt; rankFinder = new RankFinder&amp;lt;String&amp;gt;(rankToFind);
    addAll(rawData, rankFinder);
    String rankedValue = rankFinder.getRankedValue();

    //then
    assertThat(rankedValue, is(&quot;fc&quot;));
}

private &amp;lt;T&amp;gt; void addAll(List&amp;lt;T&amp;gt; rawData, RankFinder&amp;lt;? super T&amp;gt; rankFinder) {
    for (T num : rawData) {
        rankFinder.addTargetValue(num);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이를 통과시키는 실행 코드와 테스트 코드 전체는 &lt;a href=&quot;https://gist.github.com/1938477&quot;&gt;GIST&lt;/a&gt;에 올렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;더 효율적으로 개선할 여지가 많은 방식이지만, 1시간이 걸리는 전체 배치 중 1초 미만의 구간이였기에 속도향상이 큰 의미가 없어서 더 이상 리팩토링을 진행하지는 않았습니다.  알려진 selection alorithm을 참고하면 이 상황에서도 더 좋은 방식이 있을 듯합니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring 3.1을 활용한 AspectJ 표현식  테스트</title>
      <link>https://blog.benelog.net//2893630.html</link>
      <pubDate>Wed, 18 Jan 2012 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2893630.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;AOP에서 AspectJ표현식은 다양하고 강력한 기능을 제공합니다.
예를 들면 Aspect가 결합될 JoinPoint를 아래와 같이 표현을 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;execution(public void set*(..))&lt;/code&gt; : 리턴 타입이 void이고 메소드 이름이 set으로 시작되고 파라미터가 0개 이상인 메소드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;execution(* com.benelog.ftpserver.&lt;strong&gt;.&lt;/strong&gt;()&lt;/code&gt; : com.benelog.ftpserver패키지의 파라미터가 없는 모든 메소드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;execution(String com.benelog.ftpserver..&lt;strong&gt;.&lt;/strong&gt;(..)&lt;/code&gt; : 리턴타입이 String이면서 com.benelog.ftpserver패키지 및 하위 패키지에 있는 파라미터가 0개 이상인 메소드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;execution(String com.benelog.ftpserver.Server.insert(..)&lt;/code&gt; : 리턴타입이 String인 com.benelog.ftpserver.Server의 파라미터가 0개 이상인 메소드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;execution(* get*(*))&lt;/code&gt; : get으로 이름이 시작하고 1개의 파라미터를 갖는 메소드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;execution(* get*(&lt;strong&gt;,&lt;/strong&gt;))&lt;/code&gt; : get으로 이름이 시작하고 2개의 파라미터를 갖는 메소드 exceution(* read*(Interger,&amp;#8230;&amp;#8203;)) : read로 이름이 시작하고, 첫번째 파라미터 타입이 Integer이며, 1개 이상의 파라미터를 갖는 메소드&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 다양하고 강력한 대신에 모든 문법을 다 익숙하게 쓰기는 힘들고, 컴파일 타임에 체크도 안 되기 때문에 실수할 여지도 큽니다. 조금이라도 표현식을 바꾸었을 때 유지하고 싶은 기존의 동작이 변함없이 작동되는지도 파악하기 어렵습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래서 AOP는 강력한만큼  통제되지 못한 부작용이 생기기도 쉽습니다. 표현식 한글자를 바꾸어도 어떤 동작을 하는지 테스트를 촘촘히 해봐야하는데, 그럴 때마다 WAS를 띄워서 테스트를 해본다면 시간이 많이 들어갈 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring 3.1부터는 테스트 코드 안에서 &lt;code&gt;@Configuration&lt;/code&gt; , &lt;code&gt;@Bean&lt;/code&gt; 과 같은 JavaConfig 설정들이 지원됩니다.
그 기능을 이용하면 XML없이 보다 짧은 코드로 AOP에 대한 다양한 테스트를 편하게 해 볼 수 있습니다.
단순한 예제로 Spring 3.1의 기능으로 AOP 테스트하는 방식을 정리해보았습니다.
아래 설명한 예제들은 gist에 올라가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/1625374&quot; class=&quot;bare&quot;&gt;https://gist.github.com/1625374&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;logaspect_java&quot;&gt;LogAspect.java&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;LogAspect.java는 간단하게 로그를 저장소에 입력하는 Aspect입니다.
&lt;code&gt;@Before(&quot;execution(void *.run())&quot;)&lt;/code&gt; 라는 표현식으로 run 메소드가 실행되기 전에 로그메시지를 저장하도록 taget 객체와 결합됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Aspect
public class LogAspect {
    private LogRepository repository;

    public LogAspect(LogRepository repository){
        this.repository = repository;
    }

    @Before(&quot;execution(void *.run())&quot;)
    public void log(JoinPoint jp){
        String message = jp.getKind() + &quot;:&quot; + jp.getSignature().getName();
        repository.insertLog(message);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;logrepository_java&quot;&gt;LogRepository.java&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로그저장소 역할을 하는 클래스입니다. 실무에서라면 DB나 파일 등을 사용하겠지만, 여기서는 단순한 예제를 만들고 싶어서 그냥 String을 화면에 찍었습니다. 정교하게 만든다면 Log정보를 담는 Domain Object를 정의해서 String대신 사용해야 할 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class LogRepository  {
    public void insertLog(String message) {
        System.out.println(message);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;taget_클래스&quot;&gt;Taget 클래스&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;LogAspect와 결합될 Target 클래스입니다. 역시나 단순하게 화면에 메시지를 뿌려줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class Printer implements Runnable {
    @Override
    public void run() {
        System.out.println(&quot;hello!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;실제_코드에서의_applicationcontext_설정&quot;&gt;실제 코드에서의 applicationContext 설정&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;applicationContext를 설정하는 xml파일에서   aop: 네임스페이스를 이용해서 aspect J 표현식을 쓸 수 있게 하고, Aspect가 정의된 LogAspect를 bean으로 등록합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&amp;lt;aop:aspectj-autoproxy/&amp;gt;
&amp;lt;bean id=&quot;printer&quot; class=&quot;edu.tdd.aop.Printer&quot;/&amp;gt;
&amp;lt;bean id=&quot;logAspect&quot; class=&quot;edu.tdd.aop.LogAspect&quot;&amp;gt;
    &amp;lt;constructor-arg&amp;gt;
        &amp;lt;bean class=&quot;edu.tdd.aop.LogRepository&quot;/&amp;gt;
    &amp;lt;/constructor-arg&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;logaspecttest_java&quot;&gt;LogAspectTest.java&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;드디어 LogAspect의 표현식을 테스트하는 코드입니다. @Bean으로 Aspect와 tagetObject를 모두 XML설정 대신 java로 한 파일 안에서 표현했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;각 테스트 메소드별로  2가지를 검증했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(1) proxyCreated() 메소드 : Proxy클래스가 생성되었는지 확인
(2) logInserted 메소드: LogAspect에서 호출하는 LogRepository에 원하는 메시지가 저장되었는지 확인&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class LogAspectTest {
    @Autowired Runnable targetProxy;
    @Autowired LogRepository repository;

    @Test
    public void proxyCreated() {
        assertTrue(AopUtils.isAopProxy(targetProxy));
    }

    @Test
    public void logInserted() {
        targetProxy.run();
        verify(repository).insertLog(&quot;method-execution:run&quot;);
    }

    @Configuration
    @EnableAspectJAutoProxy
    static public class TestContext {
        @Bean LogAspect aspect() {
            return new LogAspect(repository());
        }

        @Bean LogRepository repository(){
            return mock(LogRepository.class); // 검증용 객체를 application에다 등록
        }

        @Bean Runnable target(){
            return new Printer();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;클래스 선언부분에서 @ContextConfiguration(loader=AnnotationConfigContextLoader.class) 를 붙이면 외부의 XML 대신 테스트 클래스 안에 포함된 JavaConfig로된 설정을 읽어올 수 있습니다. TestContext라는 내부 클래스에다 @Configuration을 붙여서 필요한 Bean을 등록했습니다.
&lt;code&gt;@EnableAspectJAutoProxy&lt;/code&gt; 는 &amp;lt;aop:aspectj-autoproxy/&amp;gt;와 같은 역할을 하는 애노테이션입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같이 ApplicationContext에 LogRepository는 mock()으로 생성한 가짜 객체를 등록했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    @Bean LogRepository repository(){
        return mock(LogRepository.class); // 검증용 객체를 application에다 등록
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 객체를 다시 Autowired로 받아온 후에 verify()를 했습니다.   XML로 이와 비슷한 일을 하려면 코드가 더 길어지고, 별도의 파일로 분리가 됩니다. Mock을 Application에 등록하는 것이 바람직할지는 고민의 여지가 있지만 Aspect와 결합된 Proxy 클래스는 ApplicationContext를 통해야만 얻을 수 있고, 그 동작을  검증하고자 한다면 이 방식이 편하다고 느껴집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여기서는 Printer객체를 target클래스로 사용했는데, 이 부분도 실제로 원하는 AOP 적용을 원하는 클래스나 제외 되어야할 클래스, 또는  직접 만든 테스트 전용 객체 등을 바꿔끼워가면서 다양한 조건을 검증할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;덧붙여 &lt;code&gt;verify(repository).insertLog(&quot;method-execution:run&quot;);&lt;/code&gt; 부분에서 문자열을 직접 검증하는 것도 바람직하지 못한 방식일지도 모릅니다.  문자열 대신에 Log정보를 담는 도메인 객체를 따로 정의했다면, 그 객체에 메시지에 핵심키를 담고, equals를 override했을 것 같습니다.
문자열을 직접 검증하면 메시지의 형식이 바뀔 때마다 테스트가 깨어져서 유지보수가 번거로운 테스트 코드가 됩니다. 이 예제에서는 코드를 짧게 하고 테스트 코드 안에서 검증하고자 하는 동작을 단순하게 나타내려고 이 방식을 택했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Local 개발환경에서 WAS를 띄우는 여러가지 방법</title>
      <link>https://blog.benelog.net//2879657.html</link>
      <pubDate>Tue, 6 Dec 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2879657.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Local 개발환경에서 WAS를 띄우는 여러가지 방식과 장단점에 대해서 정리해 봤습니다.  저는 상황에 따라서 아래 3가지 방법들을 그때 그때 골라서 씁니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Eclipse WTP (+Agent based reloading) : WAS를 올린 상태에서 클래스파일을 고칠 때, class파일 1개만 리로딩하는 것이 좋을 때&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maven Jetty plugin : JSP만 고칠 때나 클래스 파일을 고치면 전체 어플리케이션을 reloading을 하는 것이 유리할 때.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maven Tomcat plugin : JSP만 고칠때. WTP가 뭔가 꼬인 것 같은데 같은 Tomcat에서 같은 에러가 나는지 확인해보고 싶을 때&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;1_eclipse_wtpweb_tools_platform_agent_based_reloading&quot;&gt;1. Eclipse WTP(Web Tools Platform) + Agent Based Reloading&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Maven 프로젝트가 &lt;code&gt;&amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;`로 선언된 경우 `mvn eclipse:eclipse&lt;/code&gt; 명령을 치면 Eclipse의 dynamic web project로 설정을 만들어 줍니다.  Run as Server혹은 server 설정에 drag &amp;amp; drop으로 넣고 Eclipse안에서 서버를 실행합니다.
M2Eclipse를 잘 활용하면 local에서는 수동으로 maven 빌드를 돌릴 일이 거의 없어지기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;항상 쓰는 것은 아니지만, SpringSource  Tool Suite의 Agent based reloading을 이용해서 class파일을 수정하고 빠르게 리로딩을 하기도 합니다. 모든 상황에서 잘 통하지는 않아서, 그냥 WTP만 쓰는 것이 나을 때도 있습니다. 예를 들면 XML파일을 고쳤을 때에는 Agent based reloading에서는 잘 반영되지 않았습니다. JRebel에서는 보다 다양한 경우를 지원하는 것 같지만, 저는 테스트 코드에서 View를 빼놓고는 왠만한 건 실행해보고 WAS를 올리기 때문에 보다 강력한 JRebel이 크게 절실하지는 않습니다&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SpringSource Tool Suite의 Java Agent based reloading 사용해 보기 : &lt;a href=&quot;http://blog.benelog.net/2796964&quot; class=&quot;bare&quot;&gt;http://blog.benelog.net/2796964&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, Eclipse의 Dynamic web project + WTP 환경에서는 JSP를 고칠때의 반영속도가 미묘하게 느린 것 때문에  web context 폴더를 소스폴더 외에도 별도로 잡고, /WEB-INF/classes나 lib까지 다 그 폴더로 복사해서 &apos;add external web module&amp;#8217;로 등록해서 쓰시는 분들도 많습니다. 어디가 소스폴더이고 어디가 목적폴더인지  정리되어 있지 않아서 WEB-INF/lib나 WEB-INF/classes같이 꼭 버전관리가 필요없는 파일까지 같이 SVN에 commit하기도 하고, 복잡하게 svn:ignore를 시키는 수고를 하기도 합니다.
WTP에서 &apos;Serve modules without publishing &apos; 옵션을 써서 이를 해결하기도 합니다.
이에 대한 자세한 내용은 아래 자료에 정리되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.google.com/document/d/1fYoWD_0-3sGxHjHNHKgGNLmONVTQ9DmaLwO-MSXVCHU/edit&quot; class=&quot;bare&quot;&gt;https://docs.google.com/document/d/1fYoWD_0-3sGxHjHNHKgGNLmONVTQ9DmaLwO-MSXVCHU/edit&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음에 소개하는 maven jetty plugin과 Jetty나 Maven plugin을 써도 JSP가 더 빠르게 반영되기 때문에, WTP의 JSP반영 속도 때문에 일부러 &apos;add external modules&amp;#8217;로 WAS를 띄울 필요성은 적어집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;2_maven_jetty_plugin&quot;&gt;2. Maven jetty plugin&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;pom.xml에 Maven jetty plugin을 설정하면 따로 별도의 WAS설치과정이 필요없이 mvn jetty:run만 치면 바로 WAS가 뜹니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;plugin&amp;gt;
    &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;maven-jetty-plugin&amp;lt;/artifactId&amp;gt;
     &amp;lt;configuration&amp;gt;
         &amp;lt;scanIntervalSeconds&amp;gt;3&amp;lt;/scanIntervalSeconds&amp;gt;
         &amp;lt;contextPath&amp;gt;/&amp;lt;/contextPath&amp;gt;
      &amp;lt;/configuration&amp;gt;
       &amp;lt;version&amp;gt;6.1.11&amp;lt;/version&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;디폴트로 8080포트를 쓰려면 위에서 &quot;connector&apos; 부분의 설정이 없어도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래의 Tomcat plugin도 마찬가지지만, 이런 plugin이 설정되어 있으면, Eclipse가 없어도 SVN에서 checkout한후바로 명령어 하나로 WAS를 띄워볼 수 있습니다. 그 프로젝트의 개발자가 아닌 사람이 프로젝트를 띄워보거나 가끔 들어가는 프로젝트를 실행할 때도 편합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대부분의 웹어플리케이션은 Servlet Spec만 따라서 개발하므로 Jetty에서 돌아가면 Tomcat에서 거의 돌아간다고 봐도 됩니다. 물론 성능테스트나 프로파일링 같은건 당연히 Tomat을 띄워서 해야겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음에 소개할 Maven Tomcat plugin보다 WAS가 뜨는 속도나 리로딩 속도도 빠르다는 느낌입니다.
&quot;scanIntervalSeconds&quot; 설정에 따라서 클래스 파일을 고쳐도 리로딩이 잘 됩니다.
&apos;WTP + Agent based reloading&amp;#8217;을 쓴다고 해도 뭔가 꼬이는 상황이 발생할 수 있고, 그럴 때는 전체 web context를 로딩하는게 좋습니다.
그런 상황이 많은 개발환경이라면 Jetty plugin이 유리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;단점은 Tomcat이 아닌 Jetty이니 WAS level의 클래스가 던지는 에러메시지가 약간 다르기도 하고, Apache와 연결할때 AJP connector등을 설정할 수 없어서 Apache httpd의 모듈을 사용하는 페이지에는 쓸 수 없다는 점입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;3_maven_tomcat_plugin&quot;&gt;3. Maven Tomcat plugin&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;jetty plugin과 비슷하게 pom.xml에 Tomcat plugin을 설정하고, &lt;code&gt;mvn tomcat:run&lt;/code&gt; 을 치면 WAS 설치가 필요없이 Tomcat이 뜹니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설정은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;plugin&amp;gt;
    &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;tomcat-maven-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.1&amp;lt;/version&amp;gt;
    &amp;lt;configuration&amp;gt;
        &amp;lt;path&amp;gt;/&amp;lt;/path&amp;gt;
        &amp;lt;serverXml&amp;gt;$\{basedir}/src/main/webapp/server.xml&amp;lt;/serverXml&amp;gt;
    &amp;lt;/configuration&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;만약 AJP설정 같은게 필요없어서 server.xml을 지정할 것까지도 없고 포트만 지정하면 된다면 아래처럼 하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;configuration&amp;gt;
    &amp;lt;path&amp;gt;/&amp;lt;/path&amp;gt;
    &amp;lt;port&amp;gt;8080&amp;lt;/port&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 port도 지정할 필요가 없이 디폴트인 8080으로 띄우고 싶다면 &quot;&amp;lt;port/&amp;gt;&quot; 부분도 생략해도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;장점은 Tomcat이니까 다른 개발서버, 운영서버와 똑같은 환경이고, 같은 에러메시지가 뜬다는 점입니다. 그리고 server.xml을 지정할 수 있으니 AJP연결등으로 Apache와 연동하는 개발에도 쓸 수 있습니다.  WTP가 꼬인 것 같을 때 mvn tomcat:run으로 확인해보고 다시 Eclipse의 WTP로 돌아가기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위 Plugin은 org.codehaus.mojo의 아래에 존재했지만, 최신 버전은 아래 링크의 Apache Tomcat 프로젝트 산하로 들어갔습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://tomcat.apache.org/maven-plugin-2.0/tomcat6-maven-plugin/&quot; class=&quot;bare&quot;&gt;http://tomcat.apache.org/maven-plugin-2.0/tomcat6-maven-plugin/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://tomcat.apache.org/maven-plugin-2.0/tomcat7-maven-plugin/&quot; class=&quot;bare&quot;&gt;http://tomcat.apache.org/maven-plugin-2.0/tomcat7-maven-plugin/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Tomcat7은 아래와 같이 지정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;    &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.tomcat.maven&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;tomcat7-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.2&amp;lt;/version&amp;gt;
        &amp;lt;configuration&amp;gt;
            &amp;lt;path&amp;gt;/&amp;lt;/path&amp;gt;
        &amp;lt;/configuration&amp;gt;
    &amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;mvn tomcat7:run&lt;/code&gt; 으로 실행할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마찬가지로 Tomcat6 plugin도 활용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;    &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.tomcat.maven&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;tomcat6-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.2&amp;lt;/version&amp;gt;
        &amp;lt;configuration&amp;gt;
            &amp;lt;path&amp;gt;/&amp;lt;/path&amp;gt;
        &amp;lt;/configuration&amp;gt;
    &amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;mvn tomcat6:run&lt;/code&gt; 으로 실행합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 spring-loaded를 함께 쓰면 Tomcat재시작 횟수를 줄일수 있습니다.
( &lt;a href=&quot;https://gist.github.com/benelog/aee89ac5b6ff896b2e0f&quot; class=&quot;bare&quot;&gt;https://gist.github.com/benelog/aee89ac5b6ff896b2e0f&lt;/a&gt; 참조 )&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Winstone, 경량 Servlet container</title>
      <link>https://blog.benelog.net//2875999.html</link>
      <pubDate>Fri, 25 Nov 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2875999.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Winstone(&lt;a href=&quot;http://winstone.sourceforge.net/&quot; class=&quot;bare&quot;&gt;http://winstone.sourceforge.net/&lt;/a&gt; )은 jar파일 하나로 실행되는 간단한 Servlet Container입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;FTP처럼 서버에 있는 파일을 다운로드 할 때나, 웹애플리케이션을 jar파일로 패키징해서 WAS 설치없이 독립적으로 실행되도록 만드는 용도로 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로, Hudson에서도 winstone을 활용하고 있습니다.  hudson.war를 WAS에 설치하지 않아도  java -jar 로 바로 실행시킬 수도 있는데, 이 기능이 Hudson 패키징 파일 안에 내장된 winstone을 이용하는 방식입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 winstone을 간단하게 활용하는 방법을 정리해봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;ftp대용으로_쓰기&quot;&gt;FTP대용으로 쓰기&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 URL에서 winstone의 jar파일이 제공됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://sourceforge.net/projects/winstone/files/&quot; class=&quot;bare&quot;&gt;http://sourceforge.net/projects/winstone/files/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 접근하기 편한 URL에 올려놓고 wget 으로 다운로드하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;wget benelog.net/winstone.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;받은 파일을 &lt;code&gt;java -jar&lt;/code&gt; 로 간단하게 실행하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재 디렉토리를 최상위 폴더로 실행하려면 아래와 같이 하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar winstone.jar --webroot=.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;HTTP 포트를 지정하려면 --httpPort 옵션을 붙이면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar winstone.jar --webroot=. --httpPort=18080&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런 다음 해당서버에 지정된 포트로 접근하면 디렉토리의 파일 목록이 뜹니다. FTP와는 다르게 비밀번호가 없이 접근이 되므로 보안문제가 염려된다면 외부에 공개되지 않은 포트로 띄어야 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/web-server/winstone.jpg&quot; alt=&quot;winstone&quot; title=&quot;winstone.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그외의 다양한 옵션은 Winstone의 사이트(&lt;a href=&quot;http://winstone.sourceforge.net/&quot;&gt;http://winstone.sourceforge.net/&lt;/a&gt; )에 자세히 나와 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;웹애플리케이션을_jar파일_하나로_실행되도록_패키징&quot;&gt;웹애플리케이션을 jar파일 하나로 실행되도록 패키징&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞선 hudson의 사례처럼, 별도의 WAS설치 없이 웹어플리케이션 배포 파일 자체에 WAS를 내장하는 방식입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jetty를  애플리케이션 안에서 직접 심어서(Embedding) 실행시켜도 같은 효과가 있습니다. 제가 간단하게 만들었던, &lt;a href=&quot;http://blog.benelog.net/2874354&quot;&gt;dumper&lt;/a&gt;라는 도구에서도 그 방식을 활용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/dumper/blob/master/src/main/java/Start.java&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/dumper/blob/master/src/main/java/Start.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jetty를 war파일 안에 패키징해서 독립적으로 실행가능한 방식도 있습니다. 아래 자료에 설명되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://eclipsesource.com/blogs/2009/10/02/executable-wars-with-jetty/&quot;&gt;http://eclipsesource.com/blogs/2009/10/02/executable-wars-with-jetty/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.jowisoftware.de/blog/archives/26-Creating-runnable-wars-with-Maven-and-Jetty.html&quot;&gt;http://www.jowisoftware.de/blog/archives/26-Creating-runnable-wars-with-Maven-and-Jetty.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 위의 방식은 따로 실행 시작점이 되는 클래스를 만들어줘야하고, maven설정을 다소 많이 고쳐야하는 단점이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이에 반해서 winstone은 소스파일은 하나도 건드릴 필요없이, maven 설정도 건드리지 않거나 약간만 추가해서 혼자서 실행되는 jar파일을 만들어줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아무런 설정이 없어도 아래와 같이 실행하면 taget 폴더 아래에 .jar파일을 만들어 줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;mvn net.sf.alchim:winstone-maven-plugin:embed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 pom.xml에 아래 설정을 추가하면 &apos;mvn package&amp;#8217;로 패키징을 할 때마다 war파일과 함께 독립실행 가능한 jar파일도 만들어줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;net.sf.alchim&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;winstone-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;1.2&amp;lt;/version&amp;gt;
        &amp;lt;executions&amp;gt;
          &amp;lt;execution&amp;gt;
            &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;
            &amp;lt;goals&amp;gt;
              &amp;lt;goal&amp;gt;embed&amp;lt;/goal&amp;gt;
            &amp;lt;/goals&amp;gt;
          &amp;lt;/execution&amp;gt;
        &amp;lt;/executions&amp;gt;
      &amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 만든 예제인  &lt;a href=&quot;http://blog.benelog.net/2864739&quot;&gt;간단하게 서버에 파일을 올리는 프로그램&lt;/a&gt;에서도 이 방식을 썼습니다. 아래의 pom.xml에 이 설정에 포함되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/uploader/blob/master/pom.xml&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/uploader/blob/master/pom.xml&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위 프로젝트를 받고서 mvn package를 하면 target 폴더 아래에 uploader.jar가 생기는데 &lt;code&gt;java -jar uploader.jar&lt;/code&gt; 만 하면 웹애플리케이션이 실행되는 것이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;어디에_쓸까&quot;&gt;어디에 쓸까?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;직접 서비스 운영과 서버관리를 담당하는 일반적인 웹애플리케이션에서는 Winstone을 쓸 일이 그다지 많아보이지는 않습니다. 다만 Huson처럼 패키징해서 배포하고, 여러 가지 옵션을 제공하는 패키지성 웹애플리케이션에서는 고려해볼만도 합니다. 유사하게 Lucene바탕의 API서버인 Solr(&lt;a href=&quot;http://lucene.apache.org/solr/)에서는&quot; class=&quot;bare&quot;&gt;http://lucene.apache.org/solr/)에서는&lt;/a&gt; Jetty를 이용한 stand-alone 실행모드를 제공합니다. 그리고 간단히 소수의 인원이 쓰는 관리도구나, Desktop application 성격의 프로그램이라도 Swing, AWT대신 Web의 UI 기술을 이용하고 싶을 때도 Winstone을 포함한 패키징을 고려해볼만도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;하지만 비슷한 용도의 Jetty와 비교해볼 때  winstone이  유망하다고 생각하지는 않습니다.  2008년도 이후에는 프로젝트 업데이트가 안 되고 있어서 앞으로의 전망이 어두워보입니다. 계속 이런 상태라면 Hudson에서도 언젠가는 Jetty로 갈아타지 않을까하는 생각도 듭니다. 별도로 클래스를 만들지 않아도 되는 장점등도 언젠가는 Jetty에서도 제공될수 있어 보입니다. 그리고 jsp를 쓰는 프로젝트에서는 따로 라이브러리를 지정해야 하는 것도 다소 번거롭습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래도 Wistone은 알아두면 서버에서 파일 주고 받을 때라도 유용하게 쓸 수도 있고, 교육 용도의 실습 프로젝트, 취미용 프로젝트에도 편하게 활용해볼만한 도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Java Stack dump 다운로드를 편하게</title>
      <link>https://blog.benelog.net//2874354.html</link>
      <pubDate>Sun, 20 Nov 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2874354.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;실행 중인 Java 프로세스의 Stack dump를 뜨는 작업은 간단합니다.  Jstack 이나 kill -3 뒤에 process id를 지정하기만 하면 됩니다. Stack dump를 서버에 있는 stack dump파일을 PC로 옮길때면 Secure CRT나 putty에서 console에 찍히는 내용을 파일로 저장하는 기능을 주로 쓰고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;언젠가 테스트를 한다고 자주 Stack dump를 뜨다보니 이런 작업마저도 번거롭게 느껴졌습니다. 그리고  파일이름에 timestamp가 들어가 있으면 나중에 찾아보기가 편한데, 그런 파일명 지정도 수동으로 하려니까 귀찮았습니다. 그래서 간단한 프로그램을 만들어 봤었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보통은 서버에서 vi로 덤프를 보는 때가 많아서 저도 잘 쓰지는 않는데, 그래도 코딩하면서는 재밌었던 기억이 나네요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;다운로드&quot;&gt;다운로드&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;wget file.benelog.net/dumper.jar&lt;/code&gt; 혹은 웹브라우저로 benelog.net/dumper.jar 접근해서 저장&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;실행&quot;&gt;실행&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Windows : &lt;code&gt;java -cp &quot;dumper.jar;%JAVA_HOME%/lib/tools.jar&quot; Start [port number]&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Linux : &lt;code&gt;java -cp &quot;dumper.jar:$JAVA_HOME/lib/tools.jar&quot; Start [port number]&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Console 창에는 아래 메시지가 찍힙니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Usage:

 (Windows)

   Prompt&amp;gt;java -cp &quot;dumper.jar;%JAVA_HOME%/lib/tools.jar&quot; Start [port]

 (Linux)

   Prompt&amp;gt;java -cp dumper.jar:$JAVA_HOME/lib/tools.jar Start [port]

-----------------------------

The port is selected by ramdom.

Web address: http://192.168.0.11:17456

2011-11-20 04:04:32.901:INFO::jetty-7.x.y-SNAPSHOT

2011-11-20 04:04:33.248:INFO::started o.e.j.s.ServletContextHandler\{/,null}

2011-11-20 04:04:34.526:INFO::Started SelectChannelConnector@0.0.0.0:17456&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;파라미터로 port번호를 넘겨서 원하는 포트로 웹페이지를 띄울 수가 있는데, 지정된 포트가 없다면 10000번에서 20000번 사이의 포트를 임의로 찍어서 띄어줍니다. 위에 &quot;Web address&quot; 라고 적힌 칸 옆의 주소가 접근할 수 있는 URL입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;사용법&quot;&gt;사용법&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Console창에서 알려주는 주소를 웹브라우저 입력하면 아래처럼 jps의 정보를 보여주는 화면이 뜹니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/my-program/dumper-jps.jpg&quot; alt=&quot;dumper jps&quot; title=&quot;JPS&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;pid를 찍으면 지정한 JVM의 jstack dump를 바로 다운로드할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;파일명은 {pid}_{연도}-{일월}-{시분초}.log의 형식으로 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;혹시 덤프파일을 생성하는데 아래 에러 메시지가 뜬다면, 장비에 여러개의 JVM이 설치되어 있으면서 덤프를 뜨려는 JVM 프로세스와 tools.jar를 classpath로 지정한 경로의 JVM이 다르지 않는지 확인해보셔야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java.lang.UnsatisfiedLinkError: no attach in java.library.path
com.sun.tools.attach.AttachNotSupportedException: no providers installed
        at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:190)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;구현방식&quot;&gt;구현방식&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jps나 Jstack과 유사하게 sun.jvmstat.monitor 패키지의 클래스를 이용한 프로그램입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Modules-sun/jvmstat/sun.jvmstat.monitor.htm&quot; class=&quot;bare&quot;&gt;http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Modules-sun/jvmstat/sun.jvmstat.monitor.htm&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 jdk의 tools.jar는 따로 배포하면 license에 어긋난다고 알고 있어서 묶어서 패키징하지 않고 classpath로 지정하도록 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전체 소스는 github에 올렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/dumper&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/dumper&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>간단하게 서버에  파일을 올리기 + MockMultipartFile을 이용한 테스트</title>
      <link>https://blog.benelog.net//2864739.html</link>
      <pubDate>Sun, 23 Oct 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2864739.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;서버에 1,2개 파일만을 올려야할 때는  FTP를  따로 설치하는 일이 번거롭다고 느껴집니다. 그럴 때  간단하게 다운받아서 실행할 수 있는 웹어플리케이션을 만들어봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;jar파일 하나만 다운로드 받아서 바로 실행시키면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;다운로드&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;wget file.benelog.net/uploader.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;실행&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java -jar uploader.jar (디폴트로 8080포트)
java -jar uploader.jar --httpPort=2010 (포트지정)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;div class=&quot;title&quot;&gt;서버를 브라우저로 접속해서 파일을 올리기&lt;/div&gt;
&lt;p&gt;예) &lt;a href=&quot;http://localhost:8080/&quot; class=&quot;bare&quot;&gt;http://localhost:8080/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/my-program/uploader.png&quot; alt=&quot;uploader.png&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따로 Tomcat과 같은 WAS를 설치할 필요가 없도록 경량 WAS인 Winstone(&lt;a href=&quot;http://winstone.sourceforge.net/&quot; class=&quot;bare&quot;&gt;http://winstone.sourceforge.net/&lt;/a&gt;) 과 함께 패키징했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;소스코드는 github에 올려놨습니다. ( &lt;a href=&quot;https://github.com/benelog/uploader/&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/uploader/&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;특별한 코드는 없지만, 아래 클래스에서 &lt;a href=&quot;http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/mock/web/MockMultipartFile.html&quot;&gt;MockMultipartFile&lt;/a&gt;를 이용해서 파일업로드에 대한 테스트코드를  만들면서 나름 재미있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/uploader/blob/master/src/test/java/net/benelog/uploader/UploadControllerTest.java&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/uploader/blob/master/src/test/java/net/benelog/uploader/UploadControllerTest.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Btrace로 DBCP의 connection정보를 모니터링 하기</title>
      <link>https://blog.benelog.net//2855201.html</link>
      <pubDate>Wed, 28 Sep 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2855201.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Connection pool을 library로 DBCP를 많이 사용하고 있습니다. DataSouce가 선언되어 있는 설정 파일을 보면 min, max Connection 숫자는 쉽게 알 있지만, 현재 Active 한 것이 몇개 인지등은 실행시간에 쉽게 알아보기가 쉽지가 않죠. 그래서 JMX로 현재 Active한 Connection 수 등을 노출하는, DBCP를 한번 감싼 DataSource를 사용하기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Btrace를 이용하면 비교적 간단하게 DBCP의 속성들을 값들을 확인할 수 있습니다. 이미 실행되고 있는 JVM에도 붙일 수 있으니 설정을 바꾸고 WAS를 재시작하지 않아도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같이 간단한 소스로도 가능합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import static com.sun.btrace.BTraceUtils.*;
import java.lang.reflect.Field;
import com.sun.btrace.BTraceUtils.Sys;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Self;

@BTrace
public class DbcpMonitorSimple {

  private static final String DS_CLASS = &quot;org.apache.commons.dbcp.BasicDataSource&quot;;

  @OnMethod(clazz = DS_CLASS, method = &quot;getConnection&quot;)
  public static void onGetConnection(@Self Object basicDataSource) {
    Field urlField = field(DS_CLASS, &quot;url&quot;);
    Object url = get(urlField, basicDataSource);
    print(&quot;=====DBCP BasicDataSource info (&quot;);
    print(url);
    println(&quot; ) ==========&quot;);
    printFields(basicDataSource);
    Field poolField = field(DS_CLASS, &quot;connectionPool&quot;);

    Object pool = get(poolField, basicDataSource);
    println(&quot;=====connectionPool (GenericObjectPool) info====&quot;);
    printFields(pool);
    println(&quot;==========&quot;);
    Sys.exit(0);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;DataSource.getConnection 요청이 있을 때 해당 객체를 얻어와서 속성값들을 찍어줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 필요한 필드만 찍는다던지, 보기좋게 정렬한다던지 하는 작업은 필요에 따라 하면 되겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;jps로 모니터링하고자 하는 JVM의 pid를 확인하고,
&lt;code&gt;btrace [pid] DbcpSimpleMonitor.java&lt;/code&gt; 로 실행하면 아래와 같이 속성값들을 쭉 찍어줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;&amp;gt;btrace 4288 DbcpMonitor.java


=====DBCP BasicDataSource info (jdbc:hsqldb:file:store;shutdown=true ) ========

{defaultAutoCommit=true, defaultReadOnly=null, defaultTransactionIsolation=-1, d

efaultCatalog=null, driverClassName=org.hsqldb.jdbcDriver, driverClassLoader=nul

l, maxActive=8, maxIdle=8, minIdle=0, initialSize=0, maxWait=-1, poolPreparedSta

tements=false, maxOpenPreparedStatements=-1, testOnBorrow=false, testOnReturn=fa

lse, timeBetweenEvictionRunsMillis=-1, numTestsPerEvictionRun=3, minEvictableIdl

eTimeMillis=1800000, testWhileIdle=false, password=, url=jdbc:hsqldb:file:store;

shutdown=true, username=sa, validationQuery=null, validationQueryTimeout=-1, con

nectionInitSqls=null, accessToUnderlyingConnectionAllowed=false, restartNeeded=t

rue, connectionPool=org.apache.commons.pool.impl.GenericObjectPool@1f31079, conn

ectionProperties={user=sa, password=}, dataSource=org.apache.commons.dbcp.Poolin

gDataSource@be8958, logWriter=java.io.PrintWriter@12b1ff9, abandonedConfig=null,

 closed=false, }

===== number of Active : 0

==========&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;FIeld 객체를 얻어올 때 field(String,String)을 쓰면 static 필드초기화나 @OnMethod 메소드가 붙지 않은 메소드에서는 객체가 얻어지지 않는  에러가 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;인터넷을 찾아보니 아래와 같이 비슷한 현상을 겪은 사람이 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://kenai.com/projects/btrace/forums/forum/topics/1366-Drilling-into-a-method-argument-s-properties&quot;&gt;http://kenai.com/projects/btrace/forums/forum/topics/1366-Drilling-into-a-method-argument-s-properties&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;결국 &lt;code&gt;@OnMethod&lt;/code&gt; 가 붙지 않는 메소드에서 호출을 할 때는 &lt;code&gt;field(classof(obj), &quot;fieldName&quot;)&lt;/code&gt; 과 같이 피해가는 방법을 썼습니다. 되도록 메소드에서 매번 호출할 필요가 없는 부분은 static 초기화를 시킬 수 있었으면 하는데, static 초기화로는 하는 것도 잘 되지 않았습니다. 직접 field(BasicDataSource.class, &quot;fieldName)으로 를 참조하기는 것도 잘 안 되었고, field(String,String)으로는 참조가 안 되었습니다.
이것만 아니면 좀 더 최적화된 호출을 할 수 있었을 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첨부한 파일들은 아래와 같은 다소 다른 방식이나 출력형식으로 택했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/1246239#file_dbcp_monitor_simple.java&quot;&gt;DbcpMonitorSimple.java&lt;/a&gt; : BasicDataSource.getConnection이 한번 호출될 때 BasicDataSource의 모든 정보와 BasicDataSource.connetionPool의 정보를 출력&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/1246239#file_dbcp_monitor.java&quot;&gt;DbcpMonitor.java&lt;/a&gt; :  이벤트 방식은 1과 같고, BasicDataSource의 모든 필드와 BasicDataSource.connetionPool에서 _numActive 값만을 보여줌&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/1246239#file_dbcp_active_connection_monitor.java&quot;&gt;DbcpActiveConnectionMonitor.java&lt;/a&gt; : 여러 개의 Datasource의 Active connection 갯수를 구할 때 사용. getConnection에서 URL별로 active connection 갯수를 저장해 두었다가, Btrace에서 이벤트를 날리면 출력을 해주고 종료&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첨부한 스크립트 중 1,2번은 getConnection이 한번 호출되고 나면 스크립트를 종료하는 구조라서 큰 부하는 없겠지만, 증가 추이 등의 통계 정보를 쌓는기능 등이 필요하다면 Field poolField = field(DS_CLASS, &quot;connectionPool&quot;); 같은 부분도 중복호출되지 않게 하는 처리가 필요합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;소스는 gist에 올렸습니다. ( &lt;a href=&quot;https://gist.github.com/1246239&quot; class=&quot;bare&quot;&gt;https://gist.github.com/1246239&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Kent Beck과의 인터뷰 중에서..</title>
      <link>https://blog.benelog.net//2815291.html</link>
      <pubDate>Tue, 21 Jun 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2815291.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2010년 9월에 Kent Beck과의 인터뷰가 SE-Radio 사이트에 올라왔었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.se-radio.net/2010/09/episode-167-the-history-of-junit-and-the-future-of-testing-with-kent-beck/&quot; class=&quot;bare&quot;&gt;http://www.se-radio.net/2010/09/episode-167-the-history-of-junit-and-the-future-of-testing-with-kent-beck/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 인터뷰 중에 아래 내용이 가장 인상 깊었고, 몇달이 지나서도 기억에 남아있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;19분 경에 나오는 내용입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;받아쓴_내용&quot;&gt;받아쓴 내용&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Martin(사회자):&lt;/div&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Do you think you&amp;#8217;ve convinced most of people you talk to?&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Kent Beck :&lt;/div&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I don&amp;#8217;t try to convince people any more.I think I spent probably 10 years really trying to convince people TDD, pair programming, you should do this, you should do that and I don&amp;#8217;t do that any more.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I am always working to improve my own practice, I am eager to share what I learn and I try and listen and understand what other people learned and their experiences as well. So I don&amp;#8217;t like &amp;#8230;&amp;#8203; I don&amp;#8217;t really keep score by how many people do TDD.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If the practice of software development is improving overall, then I think that&amp;#8217;s a great thing, If I have some small influence on that, then I think that&amp;#8217;s a great thing too.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;번역&quot;&gt;번역&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;마틴(사회자)&lt;/div&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그동안 이야기한 사람들을 대부분 설득했다고 생각합니까?&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;켄트벡 :&lt;/div&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 더 이상 사람들을 설득하려 하지 않습니다. 저는 거의 10년동안 사람들에게 TDD를 꼭해라, 짝프로그래밍을 꼭 해라고 설득하려고 애써왔지만, 더 이상 그렇게 하지 않습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전 항상 제가 수행하는 방식을 개선하려고 일하고, 제가 시도하고 배운 것을 공유하려고 노력합니다.  다른 사람이 배운 것과 경험도 듣고 이해하려 하구요. 그래서 전 얼마나 많은 사람들이 TDD를 하는지를 가지고 점수를 매기지는 않습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;소프트웨어를 개발하는 방식이 전반적으로 나아진다면 전 그걸 대단한 일이라고 생각하고, 만약 제가 거기에 약간의 영향이라도 끼친다면 역시나 대단한 일이라고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring AOP의 Aspect에서 order 값에 따른 적용순서 확인해보기</title>
      <link>https://blog.benelog.net//2805903.html</link>
      <pubDate>Mon, 30 May 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2805903.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring AOP에서 동일한 target클래스에 결합되는 Aspect의 우선순위는  &lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/core/Ordered.html&quot;&gt;Ordered&lt;/a&gt; 인터페이스를 구현하거나 @Order 애노테이션으로 지정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같은 방식입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;

@Aspect
@Order(1)
public class OrderOneAspect {
    @Before(&quot;execution(void *.run())&quot;)
    public void printOrder(JoinPoint jp){
        System.out.println(&quot;order 1&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;



@Aspect
public class OrderTwoAspect implements Ordered{
    @Before(&quot;execution(void *.run())&quot;)
    public void printOrder(JoinPoint jp){
        System.out.println(&quot;order 2&quot;);
    }
    public int getOrder() {
        return 2;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OrderOneAspect 은 애노테이션으로, OrderTwoAspect은 인터페이스로 order 값을 선언했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/core/Ordered.html&quot;&gt;Ordered&lt;/a&gt; 인터페이스의  javadoc에 보면 order의 숫자 값이 작을수록 우선 순위가 높다고 적혀 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The actual order can be interpreted as prioritization, with the first object (with the lowest order value) having the highest priority.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다면 AOP에서는 여러개의 Aspect를 엮을 때 order값을 낮게 선언한 Aspect가 먼저 실행될까요?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한번 테스트코드를 짜봤습니다. 위의 OrderOneAspect , OrderTwoAspect와 함께, 각각 0과 -1의 order를 가지는 Aspect를 더 추가했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Aspect
@Order(0)
public class OrderZeroAspect {
    @Before(&quot;execution(void *.run())&quot;)
    public void printOrder(JoinPoint jp){
        System.out.println(&quot;order 0&quot;);
    }
}

@Aspect
@Order(-1)
public class OrderMinusOneAspect {
    @Before(&quot;execution(void *.run())&quot;)
    public void printOrder(JoinPoint jp){
        System.out.println(&quot;order -1&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Target클래스와 Proxy를 만들어줄 설정을 javaConfig를 이용해서 추가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class MainBeans {
    @Bean
    public ProxyConfig proxyCreator(){
        return new AspectJAwareAdvisorAutoProxyCreator();
    }
    @Bean
    public Runnable main(){
        return new Runnable(){
            public void run() {
                System.out.println(&quot;target executed&quot;);
            }
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aspect는 모두 &quot;before&quot; Advice로 그 Aspect의 order 값을 찍도록했고, 적용대상인 클래스는 단순히 &quot;target executed&quot;를 출력합니다. 출력창에 찍힌 내용을 보면 어떤 순서로 Aspect가 적용되는지 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;모두 ApplicationContext에 등록해서 Target클래스를 출력하는 테스트 코드를 만들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class AspectOrderTest {
    @Test
    public void checkOrder() throws Exception {
        GenericApplicationContext  context =
            new AnnotationConfigApplicationContext(
                OrderZeroAspect.class,
                OrderMinusOneAspect.class,
                OrderTwoAspect.class,
                OrderOneAspect.class,
                MainBeans.class);
        Runnable printer = context.getBean(Runnable.class);
        printer.run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;출력 결과는 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;order 2
order 1
order 0
order -1
target executed&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Order의 숫자가 큰 값이 먼저 나옵니다. 큰 Order 값인, 낮은 우선 순위인 Aspect가 먼저 실행되었다는 결과입니다. 제가 처음에 매뉴얼만 보고 이해한 의미와는 반대의 결과였습니다. 제가 설정을 잘못한 부분이 있는지도 모르겠지만, 이 결과만을 해석을 해보겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aspect에서 우선순위가 높다는 의미는 &apos;안쪽의 proxy가 된다.&apos;는 의미입니다. 즉, Proxy를 만들 때 Order값이 작은, 우선 순위가 높은 Aspect부터 먼저 Proxy를 만들고, 그 뒤에 다음 우선순위의 proxy들을 차례로 입혀갑니다. 그러니 &apos;before&apos; 나 &apos;Around&amp;#8217;Advice라면 가장 바깥 쪽에 Proxy로 입혀진 , order값이 큰, 우선순위가 낮은 Aspect의 코드가 먼저 실행됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정리하면, Order값 숫자가 작을수록 우선 순위가 높아지고, 우선 적용되어서 더 안 쪽에서 감싸지는 proxy가 되어서  타겟 Object의 코드와 붙어있는 Apspect가 된다는 의미입니다.  즉, Aspect 설정에서 order값의 숫자가 클 수록 target객체에서 더 멀어지는 바깥 쪽의 Proxy로 결합됩니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring의 @Scheduled로 지정된 크론 표현식을 테스트하기</title>
      <link>https://blog.benelog.net//2802946.html</link>
      <pubDate>Mon, 23 May 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2802946.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;변경 이력&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2024.06.05 스프링 프레임워크 버전 5.3 이상에 맞는 코드로 수정&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링에서는 &lt;code&gt;@Schedule&lt;/code&gt; 애너테이션을 통해 크론 표현식으로 특정 메서드를 예약 실행할 수 있습니다.
그런데 실제로 잘 실행될지는 해당 시간이 되어야 알 수 있으므로 실수가 있어도 늦게 발견될 가능성이 높습니다.
예를 들면 매일 5시 20분, 40분, 60분에 실행될 일정을 지정하고자 했는데 &quot;* 0/20 5 * * * ?&quot;로 써야 할 표현식을 &quot;* 0,20 5 * * * ?&quot;으로 써놓고는 실운영 서버에 배포 한 후 하루가 지난 다음에 실수를 발견하는 경우입니다.
크론 표현식이 기대대로 해석되는지도 이 글을 참고하여 테스트 코드로 검증을 한다면 이런 사고를 막을 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예제는 스프링 프레임워크 6.1.8 버전에 의존하여 작성되었으나 버전 5.3 이상에서는 동일한 방식으로 사용할 수 있을 것으로 예상합니다.
5.3버전을 기준으로 날짜 관련 클래스가 JDK 8의 API를 활용하는 방식으로 바뀌었습니다.
스프링 3.0 에서는 &lt;code&gt;java.util.Date&lt;/code&gt; 등의 과거 API를 활용하는 방식으로 응용하실 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;크론_표현식으로_예약_실행할_메서드_지정&quot;&gt;크론 표현식으로 예약 실행할 메서드 지정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대상이 되는 메소드에 &lt;code&gt;@Schedule&lt;/code&gt; 애너테이션의 &lt;code&gt;cron&lt;/code&gt;, &lt;code&gt;zone&lt;/code&gt; 속성을 지정합니다.
&lt;code&gt;zone&lt;/code&gt; 속성은 필수는 아니지만 지정하면 명확성이 높아지고 시스템 기본값에 영향받지 않는다는 장점이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;JobSchedule.java&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import java.time.Instant;
import java.util.Properties;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class JobSchedule {

  @Scheduled(cron = &quot;0 0,10 0 * * ?&quot;, zone = &quot;Asia/Seoul&quot;)
  public void startHelloJob() {
    // 실행할 코드
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;테스트_코드&quot;&gt;테스트 코드&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;크론 표현식을 추출하고 검사하는 코드는 ScheduleTestUtils 클래스로 분리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import org.junit.jupiter.api.Test;

class JobScheduleTest {
  @Test
  void scheduleHelloJob() {
    var initialTime = LocalDateTime.of(2024, 6, 10, 0, 5);
    List&amp;lt;LocalDateTime&amp;gt; expectedTimes = List.of(
        LocalDateTime.of(2024, 6, 10, 0, 10),
        LocalDateTime.of(2024, 6, 11, 0, 0),
        LocalDateTime.of(2024, 6, 11, 0, 10)
    );
    ScheduleTestUtils.assertCronExpression(
        JobSchedule.class, &quot;startHelloJob&quot;,
        toInstant(initialTime),
        expectedTimes.stream().map(this::toInstant).toList()
    );
  }

  private Instant toInstant(LocalDateTime time) {
    return time.atZone(ZoneId.of(&quot;Asia/Seoul&quot;)).toInstant();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;ScheduleTestUtils.java&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import static org.assertj.core.api.Assertions.assertThat;

import java.lang.reflect.Method;
import java.time.Instant;
import java.util.List;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

public class ScheduleTestUtils {
  public static void assertCronExpression(
      Class&amp;lt;?&amp;gt; targetClass, String methodName,
      Instant initialTime, List&amp;lt;Instant&amp;gt; expectedTimes
  ) {
    Method method = ReflectionUtils.findMethod(targetClass, methodName);
    assertThat(method).isNotNull();

    Scheduled scheduled = method.getAnnotation(Scheduled.class);
    CronTrigger trigger = getTrigger(scheduled);
    var context = new SimpleTriggerContext(initialTime, initialTime, initialTime);

    for (Instant expected : expectedTimes) {
      Instant actual = trigger.nextExecution(context);
      assertThat(actual).isEqualTo(expected);
      context.update(actual, actual, actual);
    }
  }

  private static CronTrigger getTrigger(Scheduled scheduled) {
    // 스프링의 ScheduledAnnotationBeanPostProcessor 코드를 참고함
    if (StringUtils.hasText(scheduled.zone())) {
      return new CronTrigger(scheduled.cron(), StringUtils.parseTimeZoneString(scheduled.zone()));
    } else {
      return new CronTrigger(scheduled.cron());
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Quartz 설정을 읽어서 CronExpression 테스트</title>
      <link>https://blog.benelog.net//2802943.html</link>
      <pubDate>Mon, 23 May 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2802943.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Cron expression은 실수하기가 쉽고, 오류가 뒤늦게서야 발견됩니다. 매일 5시 20분, 40분,60분에 실행될 일정을 지정하고자 했는데 &quot;* 0/20 5 * * * ?&quot;로 써야 할 표현식을  &quot;* 0,20 5 * * * ?&quot;으로 써놓고는 실운영 서버에 배포해서 하루가 지난 다음에 실행결과를 보고서야 실수를 발견한 경험을 해보신 분들이 많으실 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Cron expression도 테스트 코드를 짜서 검증을 해본다면 치명적인 실수를 막을 기회가 더 많아집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 예제는  ApplicationContext에 설정되어 있는 Quartz의 CronTrigger의 일정을 테스트하는 코드입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;applicationcontext의_스케쥴링_설정_파일&quot;&gt;ApplicationContext의 스케쥴링 설정 파일&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;bean id=&quot;schedulerFactoryBean&quot; class=&quot;org.springframework.scheduling.quartz.SchedulerFactoryBean&quot;&amp;gt;
     &amp;lt;property name=&quot;triggers&quot; ref=&quot;triggers&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&amp;lt;util:list id=&quot;triggers&quot;&amp;gt;
     &amp;lt;bean p:jobName=&quot;baseballJob&quot; p:cronE-pression=&quot;0 * * * * ?&quot; parent=&quot;jobTrigger&quot;/&amp;gt;
     &amp;lt;bean p:jobName=&quot;baseballExportJob&quot; p:cronE-pression=&quot;30 5-7 * * * ?&quot; parent=&quot;jobTrigger&quot;/&amp;gt;
&amp;lt;/util:list&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 설정 예제에서는 반복되는 설정을 간편하게 해주는 FactoryBean을 썼습니다. parent=&quot;jobTrigger&quot;라고 지정된 부분이 FactoryBean 클래스와 연결되는 bean id를 지정한 속성입니다.
여기에 쓰인 FactoryBean은 이&quot;jobName&quot;과 &quot;cronExpression&quot; 속성을 받아서 org.quartz.CronTrigger 타입의 bean을 생성해주는 역할을 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;JobTriggerFactoryBean&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package edu.batch.support.launch;

import java.util.HashMap;
import java.util.Map;

import org.quartz.Trigger;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.sample.quartz.JobLauncherDetails;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.JobDetailBean;
import org.springframework.util.Assert;

/**
 * @author sanghyuk.jung
 */
public class JobTriggerFactoryBean implements FactoryBean&amp;lt;Trigger&amp;gt;, InitializingBean {
    private JobLocator jobLocator;
    private JobLauncher jobLauncher;
    private String jobName;
    private String cronExpression;
    private String triggerName;

    /**
     * @return
     * @throws Exception
     * @see org.springframework.beans.factory.FactoryBean#getObject()
     */
    public Trigger getObject() throws Exception {
        CronTriggerBean trigger = new CronTriggerBean();
        trigger.setCronExpression(cronExpression);
        JobDetailBean jobDetail = createJobDetail();
        trigger.setJobDetail(jobDetail);
        if(triggerName == null ){
        trigger.setName(jobName+&quot;Trigger&quot;);
        } else {
            trigger.setName(triggerName);
        }
        trigger.afterPropertiesSet();
        return trigger;
    }

    private JobDetailBean createJobDetail() {
        JobDetailBean jobDetail = new JobDetailBean();
        jobDetail.setName(jobName);
        Map&amp;lt;String, Object&amp;gt; jobData = new HashMap&amp;lt;String, Object&amp;gt;();
        jobData.put(&quot;jobName&quot;, jobName);
        jobData.put(&quot;jobLocator&quot;, jobLocator);
        jobData.put(&quot;jobLauncher&quot;, jobLauncher);
        jobDetail.setJobDataAsMap(jobData);
        jobDetail.setJobClass(JobLauncherDetails.class);
        jobDetail.afterPropertiesSet();
        return jobDetail;
    }

    /**
     * @return
     * @see org.springframework.beans.factory.FactoryBean#getObjectType()
     */
    public Class&amp;lt;Trigger&amp;gt; getObjectType() {
        return Trigger.class;
    }

    /**
     * @return
     * @see org.springframework.beans.factory.FactoryBean#isSingleton()
     */
    public boolean isSingleton() {
        return false;
    }

    public void setJobLocator(JobLocator jobLocator) {
        this.jobLocator = jobLocator;
    }

    public void setJobLauncher(JobLauncher jobLauncher) {
        this.jobLauncher = jobLauncher;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    /**
     * @throws Exception
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(jobName, &quot;jobName must be provided&quot;);
        Assert.notNull(jobLocator, &quot;jobLocator name must be provided&quot;);
        Assert.notNull(jobLauncher, &quot;jobLauncher name must be provided&quot;);
    }

    public void setTriggerName(String triggerName) {
        this.triggerName = triggerName;

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;테스트_코드&quot;&gt;테스트 코드&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;triggers라는 bean 이름으로 List&amp;lt;CronTrigger&amp;gt; type의 객체를 가지고 와서, List안에서 지정된 trigger의 이름을 탐색한다음에 그 안에서 그 trigger의 cron e-pression을 검사했습니다.  테스트 코드를 간결하게 유지하기 위해서 Cron e-pression을 검사하는 코드는  QuartzCronExpressionTestUtils라는 클래스로 분리해서 static import로 처리했습니다.
QuartzCronExpressionTestUtils.findTriggerByName 메소드는 List&amp;lt;CronTrigger&amp;gt; 타입의 객체가 담고 있는 여러개의 CronTrigger에서 지정된 이름의 CronTrigger를 반환해줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package edu.batch.baseball.schedule;

import static edu.batch.support.launch.QuartzCronE-pressionTestUtils.*;

import java.text.ParseException;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.CronTrigger;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( { &quot;classpath:/launch-context.xml&quot; })
public class BaseballQuartzScheduleTest {

    private static final String DATE_PATTERN = &quot;yyyy/MM/dd hh:mm:ss&quot;;

    @Resource(name = &quot;triggers&quot;)
    List&amp;lt;CronTrigger&amp;gt; triggers;

    @Test
    public void testbaseballJobTriggerSchedule() throws ParseException {
        CronTrigger trigger = findTriggerByName(triggers, &quot;baseballJobTrigger&quot;);
        String initialTime = &quot;2010/09/01 09:00:00&quot;;
        List&amp;lt;String&amp;gt; expectedTimeList = Arrays.asList(
                &quot;2010/09/01 09:01:00&quot;,
                &quot;2010/09/01 09:02:00&quot;,
                &quot;2010/09/01 09:03:00&quot;,
                &quot;2010/09/01 09:04:00&quot;);
        assertSchedule(trigger, initialTime, expectedTimeList, DATE_PATTERN);
    }

    @Test
    public void testbaseballExportJobTriggerSchedule() throws ParseException {
        CronTrigger trigger = findTriggerByName(triggers, &quot;baseballExportJobTrigger&quot;);
        String initialTime = &quot;2010/09/01 09:00:00&quot;;
        List&amp;lt;String&amp;gt; expectedTimeList = Arrays.asList(
                &quot;2010/09/01 09:05:30&quot;,
                &quot;2010/09/01 09:06:30&quot;,
                &quot;2010/09/01 09:07:30&quot;,
                &quot;2010/09/01 10:05:30&quot;,
                &quot;2010/09/01 10:06:30&quot;,
                &quot;2010/09/01 10:07:30&quot;);
        assertSchedule(trigger, initialTime, expectedTimeList, DATE_PATTERN);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;QuartzCronExpressionTestUtils&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package edu.batch.support.launch;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.text.ParseException;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.quartz.CronTrigger;

public class QuartzCronExpressionTestUtils {

    public static void assertSchedule(CronTrigger trigger, String initialTime,
            List&amp;lt;String&amp;gt; expectedTimeList, String datePattern) throws ParseException {
            Date previousStartTime = DateUtils.parseDate(initialTime,   new String[]{datePattern});

            for(String expectedTime : expectedTimeList){
                trigger.setStartTime(previousStartTime);
                Date nextExecutionTime =  trigger.getFireTimeAfter(previousStartTime);
                String actualTime = DateFormatUtils.format(nextExecutionTime, datePattern);
                assertThat(&quot;executed on expected time&quot;, actualTime, is(expectedTime));
                previousStartTime = nextExecutionTime;
            }
        }

    public static CronTrigger findTriggerByName(List&amp;lt;CronTrigger&amp;gt; triggers, String triggerName) {
        for (CronTrigger trigger : triggers) {
            if (triggerName.equals(trigger.getName())) {
                return trigger;
            }
        }
        throw new IllegalArgumentException(&quot;cannot find trigger : &quot;
                + triggerName);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>SpringSource Tool Suite의 Java Agent based reloading 사용해 보기</title>
      <link>https://blog.benelog.net//2796964.html</link>
      <pubDate>Mon, 9 May 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2796964.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SpringSource Tool Suite의 Java Agent based reloading은 개발할 때  .class파일을 고치면 그 파일만 리로딩을 시켜주는 기능입니다. JRebel과도 유사한데 아직 &quot;Experimental&apos; 표시가 붙어있고, 생긴지가 얼마안되는 기능이라서 JRebel만큼 성숙한 기술인지는 잘 모르겠습니다. 그래도 모든 경우에 다 reloading이 되지 않더라도 한번이라도 서버내렸다 올리는 시간을 절약할 수 있으면 그만큼 이득이기 때문에 없는 것보다는 도움이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 도구들이 있으면 좋기는 하지만  WAS올리기 전에 테스트 코드로 왠만한 건 다 검증을 하고, WAS 올려서는 JSP같은 View만 고치는 개발 방식이 바람직합니다.  테스트 코드를 잘 짜고 있다면 &apos;개발 중에 WAS 리로딩한다고 시간이 많이 들어요..&apos; 와 같은 이야기가 별로 안 나올 것입니다. 테스트 코드 작성은 익숙해지면 시간이 별로 안 걸리는 일입니다. 그리고 테스트 코드를 만들면 로직이 있는 모듈의 가까운 위치에서 다양한 케이스를 반복해서 테스트해 볼 수 있고, 에러 추적과 디버깅이 훨씬 편해집니다. 아무리 리로딩이 잘 지원된다고 해도, 화면을 띄어서 손으로 데이터를 매번 입력하는 시간을 없애줄 수는 없고, 리로딩이 시간보다 훨씬 긴  에러 추적과 디버깅 시간을 줄여줄 수는 없습니다. 그래서 결국에는 테스트코드 작성하는 것이 개발시간을 더 빠르게 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;STS를 설치할 때 SpringSource Tool Suite를 한번에 받아서 설치했다면 Tc server도 같이 설치되지만, 이미 깔려진 Eclipse 위에 update를 했다면 tc Server는 아래 URL에서 별도로 받아야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://s9.springsource.com/products/tc-server-developer-edition-preview&quot; class=&quot;bare&quot;&gt;http://s9.springsource.com/products/tc-server-developer-edition-preview&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;java_agent_based_reloading_설정&quot;&gt;Java agent based Reloading 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;&apos;Servers&apos; Tab에서 New메뉴로 새 서버를 추가한다.
&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/1_new-server.jpg&quot; alt=&quot;1_new-server.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tc Server를 선택한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SpringSource tc Server v2.0 혹은 v2.1 또는 v2.5를 선택한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;참고로 SpringSource Tool Suite 2.6.1부터 tc server v2.5가 포함되어 있고, &quot;VMware vFabric tc Server v2.5&quot;라는 이름으로 &quot;VM Ware&quot; 분류 폴더 아래에 포함되어 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/20_tc-server-select.jpg&quot; alt=&quot;20_tc-server-select.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;처음 설정하는 것이라면, Tc sever가 설치된 위치를 지정합니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/21_tc-server-select.jpg&quot; alt=&quot;21_tc-server-select.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;처음 설정하는 것이라면 &quot;Create new instance&quot;를 선택한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/4_new-server.jpg&quot; alt=&quot;4_new-server.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;같이 설치할 모듈을 지정합니다. 간단하게 &apos;base&amp;#8217;와 &apos;nio&amp;#8217;만 선택해 된다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/5_new-server.jpg&quot; alt=&quot;5_new-server.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;설정된 Server의 &quot;Overview&quot; 탭에서 &quot;Enable Java Agent-based reloading&quot;을 선택한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/6_servers.jpg&quot; alt=&quot;6_servers.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;시험 삼아서 서버를 시작해 본다..&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/7_servers-start.jpg&quot; alt=&quot;7_servers-start&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;서버가 시작되면서 &quot;Agent based reloading is active&quot;라는 메시지가 처음에 뜨면 제대로 설정이 된 것이다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/8_start-log.jpg&quot; alt=&quot;8_start-log.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;java_agent_based_reloading_테스트_해보기&quot;&gt;Java agent based reloading 테스트 해보기&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트용 프로젝트를 생성해서 reloading이 되는지 확인하는 과정이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Ctrl +N을 누르고 &quot;Spring Template Project&quot;를 선택한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/9_new-spring-template.jpg&quot; alt=&quot;9_new-spring-template.jpg&quot;&gt;&lt;/span&gt;
2.&quot;Spring MVC project&quot;를 선택한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/10_new-spring-mvc-project.jpg&quot; alt=&quot;10_new-spring-mvc-project.jpg&quot;&gt;&lt;/span&gt;
3.프로젝트명과 상위 패키지이름을 적는다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/11_new-spring-mvc-project.jpg&quot; alt=&quot;11_new-spring-mvc-project.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;생성된 프로젝트를 Run AS&amp;#8594; Run On Server로 실행한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/12_run-on-the-server.jpg&quot; alt=&quot;12_run-on-the-server.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;실행할 서버는 Java Agent Based Reloading을 설정한 tc Server로 지정한다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/13_run-on-the-server.jpg&quot; alt=&quot;13_run-on-the-server.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;서버가 올라간 다음에 템플릿의 HomeController 클래스를 수정해보고, 메소드의 내용을 수정한 다음에  아래와 같이 전체 &quot;Realoding&amp;#8230;&amp;#8203;&quot; 메시지가 보이며 applicationContext loading 없이 해당 클래스만 리로딩 되는 것을 확인한다.
&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sts-reload/14_modify-controller.jpg&quot; alt=&quot;14_modify-controller&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;테스트_결과&quot;&gt;테스트 결과&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Dynamic web project가 아니고 external web module로 추가한 경우 - 잘 됨&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Servlet만으로 된 프로젝트 - 잘됨&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Application Context 파일 수정 - 되기도 하고 안 되기도 함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring MVC의 controller에 @RequestMapping이 달린 새로운 메소드를 추가한 것을 인식 - 잘 안 됨&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>java.net.InetAddress + SpEL로 서버이름을 Bean 설정에서 바로 얻어오기</title>
      <link>https://blog.benelog.net//2795888.html</link>
      <pubDate>Fri, 6 May 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2795888.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring 3.0부터 추가된 SpEL(Spring E-pression Language)를 응용한 사례입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;application context 파일 선언만으로 특정 빈의 속성에 서버 이름을 넣으려면 어떻게 해야하냐는 질문을 받았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;질문자는 아래의 bean설정에 &quot;serverName&quot;이라고 지정된 곳에 각각 실행되는 서버마다 다른 이름을 넣고 싶어했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;persistentMessageStore 선언&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;bean id=&quot;persistentMessageStore&quot; class=&quot;org.springframework.integration.jdbc.JdbcMessageStore&quot;&amp;gt;
   &amp;lt;property name=&quot;region&quot; value=&quot;serverName&quot; /&amp;gt;
....

&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;나름대로의 FactoryBean을 따로 만들거나 JavaConfig을 써도 쉽게 풀리는 문제이지만, SpEL을 활용하는 것이 기존 설정을 가장 적게 바꾸는 방식입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 같이 java.net.InetAddress.getLocalHost를 bean으로 등록합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;bean id=&quot;localHost&quot; class=&quot;java.net.InetAddress&quot; factory-method=&quot;getLocalHost&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SPel이 잘 먹는지는 다음과 같이 테스트 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class HostNameTest {
    @Value(&quot;#{localHost.hostName}&quot;) String hostName;
    @Test
    public void hostNameShouldBePrinted() throws Exception {
        String expectedHostName = InetAddress.getLocalHost().getHostName();
        assertThat(hostName,is(expectedHostName));
        System.out.println(hostName);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 테스트 코드에서는 @ContextConfiuguration선언 뒤에 Application context 파일 위치를 지정하지 않았으므로,  default로 참조되는 같은 패키지 디렉토리의 HostNameTest-context.xml에 &apos;localHost&apos; bean이 선언되어 있어야 겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제일 처음의 코드1의 bean선언에서도 마찬가지로 &lt;code&gt;#{localHost.hostName}&lt;/code&gt; 을 참조할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;bean id=&quot;persistentMessageStore&quot; class=&quot;org.springframework.integration.jdbc.JdbcMessageStore&quot;&amp;gt;
   &amp;lt;property name=&quot;region&quot; value=&quot;#{localHost.hostName}&quot; /&amp;gt;
....

&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SpEL을 활용하면 아래와 같이 bean사이의 연결을 더 유연하게 할 수도 있지만, 컴파일타임에 검증되지 않는다는 단점이 있습니다. 이를 보완하려면 오타를 검증할 수 있는 테스트 코드가 있어야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다른 방법으로는,  JavaConfig를 활용해서 직접 명시적으로 메소드를 호출해서 hostName을 주입해도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Bean JdbcMessageStore persistentMessageStore(){
    JdbcMessageStore store = new JdbcMessageStore ();
    store.setRegion(localHost.getHostName());
    return store;
}

@Bean public InetAddress localHost() throws UnknownHostException {
    return  InetAddress.getLocalHost();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>파일 업로드를 하는 Servlet을 MockHttpServletRequest로 테스트하기</title>
      <link>https://blog.benelog.net//2790336.html</link>
      <pubDate>Sun, 24 Apr 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2790336.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;파일 update를 처리하는 Servlet도 Spring의 MockHttpSerlvetRequest와 MockHttpServletResponse로 테스트 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음 링크에 있는 소스를 참고했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://milandinic.blogspot.com/2009/05/testing-upload-servlet-under-spring.html&quot; class=&quot;bare&quot;&gt;http://milandinic.blogspot.com/2009/05/testing-upload-servlet-under-spring.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;request의 content 속성에 파일내용 등을 포함시켜 주면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 예제에서 Assert부분은 화면에 출력되는 문자열을 검사하는 방식입니다.
테스트 하고자하는 목적에 따라 파일업로드가 되었을 때의 특정 위치에 파일이 생성된 것을 확인한다거나, 그 뒤에 호출되는 클래스를 행위검증하는 방식등을 다양하게 응용하실 수 있을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;publicclassUploadServletTest {
    UploadServlet servlet =newUploadServlet() ;
    MockHttpServletRequest request =newMockHttpServletRequest();
    MockHttpServletResponse response =newMockHttpServletResponse();

    private static final String ENDLINE =&quot;\r\n&quot;;

    private static final String BOUNDARY =&quot;qWeRtY&quot;;

   @Test
    public void testUploadNormalFile() throws Exception {
        // given
        String fileContent =&quot;for upload test&quot;;
        String fileName =&quot;message.txt&quot;;
        String reqContent = createContentWithFile(fileName, fileContent);
        request.setContent(reqContent.getBytes());
        request.setContentType(&quot;multipart/form-data; boundary=&quot;+ BOUNDARY);
        request.setMethod(&quot;POST&quot;);

        // when
        servlet.service(request, response);

       // then
       String output = response.getContentAsString();
      assertTrue(&quot;정상적으로 업로드 되었을 때에는&quot;, output.contains(&quot;File size&quot;));
    }

    @Test
   public void testUploadEmptyFile() throws Exception {
       // given
       String fileContent =&quot;&quot;;
       String fileName =&quot;message.txt&quot;;
       String reqContent = createContentWithFile(fileName, fileContent);
       request.setContentType(&quot;multipart/form-data; boundary=&quot;+ BOUNDARY);
       request.setMethod(&quot;POST&quot;);
       request.setContent(reqContent.getBytes());

       // when
       servlet.service(request, response);

      // then
      String output = response.getContentAsString();
      assertThat(&quot;빈 파일이 올라갔을 때에는&quot;, output, is(&quot;No binary data contains&quot;));
    }

    private String createContentWithFile(String fileName, String fileContent) {
        StringBuilder reqContent =newStringBuilder();
        reqContent.append(&quot;--&quot;+ BOUNDARY + ENDLINE);
        reqContent.append(&quot;Content-Disposition: form-data; name=\&quot;myfile\&quot;;&quot;
        +&quot; filename=\&quot;&quot;+ fileName +&quot;\&quot;&quot;+ ENDLINE);
        reqContent.append(ENDLINE);
        reqContent.append(fileContent);
        reqContent.append(ENDLINE);
        reqContent.append(&quot;--&quot;+ BOUNDARY +&quot;--&quot;+ ENDLINE);
        return reqContent.toString();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 Spring에서는 MockMultipartHttpServletRequest와 MockMultipartFile 같은 첨부파일에 특화된 테스트 전용 클래스를 제공하기는 하지만, Spring MVC를 사용하지 않는 그냥 Servlet에서는 위의 방식처럼 MockHttpServletRequest을 사용해야 합니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>내가 생각하는 TDD</title>
      <link>https://blog.benelog.net//2766714.html</link>
      <pubDate>Sun, 6 Mar 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2766714.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://cdn.mystorypie.com/entities/image/invention/694c7f84b711aa600a8b3cc4/hero_2025-12-25_00-06-56-013.webp&quot; alt=&quot;image&quot; width=&quot;332&quot; height=&quot;500&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(이미지 출처: &lt;a href=&quot;https://mystorypie.com/learn/ko/invention/traffic-light/&quot; class=&quot;bare&quot;&gt;https://mystorypie.com/learn/ko/invention/traffic-light/&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;컴퓨터가 내 의도를 이해했는지 표시하는 신호등을 설치하고, 그 신호에 의지해서 사람이 더 읽기 편하고 고치기 쉬운 코드를 개발하는 기법입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음에는 내가 만드는 프로그램 조각이 어떤 상황에서 어떤 결과를 내는지 설명하는 글을 씁니다. 이것을 명세나 테스트라고 부르고 컴퓨터가 해석하고 실행가능한 형식으로 만듭니다. 그 설명을 컴퓨터가 실행해서 의도한 결과인지를 신호등으로 알려주게 합니다. 다음에는 그 설명과 맞아떨어지는 코드를 작성합니다. 신호등은 파란색이 되었다면 코드에 실린 사람의 의도를 모두 컴퓨터가 잘 받아들였다는 의미입니다. 파란색은 다음 발걸음을 내딛을 수 있다는 신호이기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;컴퓨터를 이해시킨 다음에는 사람이 더 편안하게 읽을 수 있는 코드로 다듬어야 합니다. 다시 코드를 보는 사람이 헷갈리지 않도록 중복을 없애고, 의도를 더 확실하게 표현하도록 이름을 바꾸거나 프로그램 조각의 일부를 빼내서 정리합니다. 자신이 있어서 처음부터 컴퓨터와 사람에게 모두 충분한 코드를 만들었다면, 발걸음을 크게 해서 바로 그 다음 설명과 신호등을 만듭니다. 교통 신호등처럼 시간이 고정되어 있지 않고, 신호 사이의 간격, 신호등이 확인하는 설명의 크기와 다음 신호등을 만드는 시기를 마음대로 조절해도 됩니다. 그런데 하다보면 자주 파란불을 보고 싶어서 간격을 짧게 만들고 싶어집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 과정을 거치다보면 여러 가지를 얻습니다. 신호등이 연결된 설명서는 컴퓨터와 사람이 모두 이해할 수 있는 문서입니다. 잘 설명되고, 검증될 수 있는 코드들은 역할과 책임이 명확해서 다음에 기능을 추가하거나, 수정을 할 때 더 적은 노력이 들어갑니다. 중간 중간 내부에 설치된 신호등을 한꺼번에 켜보면 엑스레이 사진처럼 프로그램 속이 들여다 보입니다. 깊숙이 박혀 있는 오류를 찾아내는 시간도 줄여줍니다. 무엇보다 빨간불, 파란불을 왔다갔다 하다보면 프로그래밍이 더 역동적인 일이 됩니다. 컴퓨터와 반응을 주고 받는 것이 마치 게임과 비슷해집니다. 파란불을 볼 때마다 뭔가 끝을 내고 성취했다고 느껴지고 칭찬을 받는 기분으로 그 다음 작업을 할 수 있는 힘을 얻습니다. 그래서 프로그래밍이 더 재미있어지고, 집중도 잘 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;신입사원 교육을 하면서 주로 유명인들이 이야기한 TDD의 여러 측면들을 인용해서 설명을 했습니다. 그런데 정말 저만의 단어로 TDD를 설명한다면 어떻게 할까. 스스로 궁금해졌습니다. 적고 나니 계속 다듬어야 할 것 같네요.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>스프링과 클라우드가 무슨 상관이 있을까?</title>
      <link>https://blog.benelog.net//2765024.html</link>
      <pubDate>Thu, 3 Mar 2011 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2765024.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(덧붙임: 이글을 쓰던 시점의 CloudFoundry는 지금의 CloudFoundry와는 다른 기술이였고,  이전 CloudFoundry는 &lt;a href=&quot;http://classic.cloudfoundry.com/&quot; class=&quot;bare&quot;&gt;http://classic.cloudfoundry.com/&lt;/a&gt; 으로 옮기어 갔습니다. )&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;스프링_클라우드를_내세우다&quot;&gt;스프링, 클라우드를 내세우다.&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링 프레임워크의 개발사인 스프링소스(SpringSource)와 VMWare는 최근 스프링이 클라우드 환경에 적합한 기술이라고 홍보를 하고 있다.
현재 스프링과 관련이 있는 클라우드 플랫폼은 아래와 같다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;vfabric&quot;&gt;vFabric&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;VMWare에서 제공하는 프라이빗 클라우드를 위한 솔루션이다. VMWare의 가상화 솔루션과 스프링소스의 미들웨어, 프레임웍, 개발도구 등을 합한 기술 스택을 제공한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;coludfoundry_httpwww_cloudfoundry_com&quot;&gt;Coludfoundry (&lt;a href=&quot;http://www.cloudfoundry.com/&quot; class=&quot;bare&quot;&gt;http://www.cloudfoundry.com/&lt;/a&gt; )&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링소스가 인수해서 운영하는 클라우드 서비스이다.
아마존의 AWS 인프라를 이용하고, Tomcat 등의 미들웨어를 제공한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;vmforce_httpwww_vmforce_com&quot;&gt;VMForce (&lt;a href=&quot;http://www.vmforce.com/&quot; class=&quot;bare&quot;&gt;http://www.vmforce.com/&lt;/a&gt; )&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;VMWare와 세일즈포스닷컴이 제휴한 클라우드이다. force.com 데이터베이스를 저장소를 이용하고, 스프링,Tomcat, vSphere 같은 VMWare의 솔루션이 들어간다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/spring-cloud/force-dot-com.png&quot; alt=&quot;image&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;VMFoce 클라우드 (출처 vmforce.com)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;google_app_engine&quot;&gt;Google App Engine&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;구글과 VMWare의 제휴로 Google App Engine에 대한 지원이 스프링소스의 개발도구에 들어가게 되었다.
Spring-roo에서 Google App Engine의 저장소를 쓸 수 있도록 JPA의 Provider로  Datanucleus를 선택할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2010년 10월에 열린 스프링원 컨퍼런스에서도 클라우드에 대한 홍보는 두드러졌다.
자사의 클라우드 솔루션인 vFabric과 CloudFoundry는 물론, VMForce와 Google App Engine 과도 스프링을 묶어서 홍보했다.
스프링원 2010의 플레티엄 스폰서 세 곳 중 두 곳이 구글과 세일즈포스닷컴이였다는 것을 봐도 그 컨퍼런스의 초점은 알 수 있었다&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링은 애플리케이션 프레임워크일 뿐인데, 과연 클라우드와 무슨 관련이 있다는 것일까?
클라우드의 핵심기술은 가상화나 분산 저장소 같은 기술이 아닐까?
클라우드 환경에서 JVM만 제대로 갖추어진다면 스프링이 아닌 스트럿츠나 구글쥬스라고 해도 안 돌아갈 이유는 없지 않은가?
요즘은 서버 여러 대만 쓰는 기술이면 다 클라우드라는 이름을 붙이고 싶어하던데, 스프링도 이런 유행에 묻어 가고 싶은 마음이 아닐까?
여러 가지 의구심이 들만도 하다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나, 이를 억지 홍보 전략이라고만 생각하면서 그냥 넘어가는 것보다는 왜 VMWare ,구글, 세일즈포스닷컴이 스프링을 클라우드에 끼워 맞추면서 제휴를 하고 있는지를 분석해 본다면 기술의 흐름을 이해하는데 도움이 될 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;지난_10년_그리고_수익_모델&quot;&gt;지난 10년, 그리고 수익 모델&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;널리 알려 진 것처럼, 스프링은 EJB의 초기 시대에 Java EE 표준에 대한 대안 기술로 시작되었다.
스프링의 아버지 로드존슨은 초기 EJB의 어려움을 해결한 자신만의 프레임워크를 만들었고, 그 코드를 저서 “Expert one to one J2EE development”에 공개했다.
그 코드가 많은 개발자들의 호응을 얻어서 오픈소스 프로젝트가 되었다.
스프링 프레임워크는 세계 10대 은행 중 9개가 쓰고 있다고 할 정도로 인기를 끌었고[주1], 핵심 개발자들은 사업체를 차리고, 벤처 캐피탈의 투자를 받기에 이른다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링소스는 창업 이후에 계속 수익 모델을 고심 했을 것이다.
과연 어플리케이션 프레임워크만으로 어떻게 수익을 창출할 것인가?
한 때 스프링소스에서는 엔터프라이즈 서비스라는 것을 만들어서 라이선스를 산 고객에게 좀 더 편하게 마이너패치 버전을 제공하는 정책을 고려하기도 했었다[주2].&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 인수 합병을 통해 프레임워크 이외의 제품군도 확보를 한다.
스프링소스가 처음으로 인수한 업체는 Tomcat과 Apache Httpd의 핵심개발자들이 있는 Covalent라는 업체였다[주3].
프레임워크와 미들웨어를 통합한 서비스를 제공한다는 명분이였지만, 프레임워크만으로는 수익 창출에 한계가 있었기에 그런 결정을 했을 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;더욱 놀랍게도 스프링소스 자체가 2009년 4억 2천만 불(당시 환율로 5천3백억원 정도[주4]) 의 가격에 VMWare에 인수된다.
 00만원짜리 서버 53만대의 가격이니 그 가격도 놀랍지만 무엇보다 인수자가 의외였다.
 오라클이나 IBM처럼 자바 개발자가 친숙한 미들웨어를 많이 파는 업체가 아니였고, 자바와는 멀리 떨어진 듯한 가상화 솔루션으로 유명한 업체인 VMWare였기 때문이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로드존슨은 확장성 있는 사업모델을 고민했다[주5]. 컨설팅과 교육만으로는 인건비를 바탕으로 하는 수익 구조가 된다.
그래서 제품과 서비스를 판매하는 사업 모델도 필요했다.
그런데 제품도 상용 버전과 오픈소스 버전의 차이를 지나치게 크게 둔다면 오픈소스 커뮤니티를 홀대한다는 비판을 받을 수도 있다.
스프링소스에서는 Tc server라는 Tomcat에 기능을 강화한 제품을 판매하고 있다.
그러나 Tc Server에는 없는 버그가 Tomcat에는 있다면 많은 비판이 쏟아질 것이다.
제품 판매에도 어느 정도 한계가 있다 보니 더 확장성 있는 사업 모델인 클라우드 서비스와 스프링을 연결시키려고 했을 것이다.
그래서 클라우드 서비스 업체인 CloudFoundry를 인수했고, 결국 VMWare와 손을 잡았다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이러한 사업 배경과는 별도로 스프링과 클라우드를 연관시키는 것이 기술적으로 무리 수는 아닌지, 그 연결에 어떤 노력을 하고 있는지 살펴보아야 할 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;스프링_포트폴리오의_확장과_클라우드&quot;&gt;스프링 포트폴리오의 확장과 클라우드&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;VMWare의 인수 이후로, 스프링 포트폴리오의 발전 속도는 더욱 빨라 지고 있다. 최근 &lt;a href=&quot;http://www.springsource.org/spring-android&quot;&gt;Spring-Android&lt;/a&gt;, &lt;a href=&quot;https://github.com/springsource/spring-hadoop&quot;&gt;Spring-Hadoop&lt;/a&gt;같은 프로젝트를 보면 자바 기술 중 스프링이 건드리지 않은 영역이 바로 생각나지 않을 정도이다.
이 중 클라우드와 관련이 있는 기술들은 아래와 같이 나누어 정리해 볼 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첫째, 대용량 처리 미들웨어를 직접 스프링에서 인수한 것들이다.
AMQP(Advanced Message Queuing Protocol) 바탕의 메시징큐 솔루션인 &lt;a href=&quot;http://www.rabbitmq.com/&quot;&gt;RabbitMQ&lt;/a&gt;와 데이터그리드 기술인 &lt;a href=&quot;http://www.springsource.com/products/data-management&quot;&gt;GemFire&lt;/a&gt;가 여기에 해당한다.
비동기 처리와 데이터 캐쉬는 대용량 처리가 많은 클라우드 환경에서 더욱 많이 쓰이는 기술이 될 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;둘째, 분산 저장소에 대한 API의 래핑(Wrapping)을 제공하는 것이다.
&lt;a href=&quot;http://www.springsource.org/spring-data&quot;&gt;Spring-data&lt;/a&gt;라는 프로젝트 아래에서 많은 저장소를 위한 프로젝트들이 진행되고 있다.
이미 Redis, Riak, CouchDB , MongoD, Neo4j등을 지원한다. 물론 꼭 스프링에서 지원해 주지 않아도 그런 저장소를 쓰는 데에는 문제가 없다.
그런 프로젝트의 의도는 JDBC를 바로 사용하는 것보다 Spring-jdbc를 사용하는 것처럼, 저장소 API도 스프링의 설정과 프로그래밍 방식으로 API를 편리하고 일관성 있게 사용하도록 도와 주는 것이다.
물론 저장소 API들이 JDBC만큼 불편하지는 않겠지만, Template-callback 패턴으로 반복된 Try-catch를 줄일 수 있는 부분 등 개선점이 없지는 않을 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 스프링 3.1에 추가되는 캐쉬 추상화도 분산 캐쉬 솔루션을 편하게 활용하도록 해 준다.
트랜잭션 처리 방식과 유사하게 애노테이션으로 캐쉬할 대상과 키를 지정하는 기능이다.
EhCache에 대한 구현체는 기본 제공되고 SPI(Service Provider Interface)에 맞추면 다양한 캐쉬가 이 스펙에 맞추어서 활용될 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;셋째, 모니터링 기술이다.
클라우드 환경에서는 물리적인 서버에 대해서 사용자가 신경을 쓰지 않더라도 실제로 많은 서버와 미들웨어 인스턴스를 쓰게 된다.
그리고 사용량으로 요금이 결정되므로, 리소스 사용량을 지속적으로 확인할 필요도 생길 수 있다.
사용자가 쓰는 패턴을 빨리 파악을 하는 것도 비용 대비 효과를 판단하는데 도움이 된다.
그래서 모니터링이 전통적인 시스템보다 더욱 중요하다. 스프링소스에서 모니터링 도구인 Hyperic을 인수하고 Spring-insight 같은 도구들을 만들고 있는 것이 이에 대한 대응으로 보여 진다.
Spring-insight에서는 Spring MVC의 Controller 정보 등 스프링에만 특화된 정보도 제공하는데, 모니터링이 독립적인 영역이지만 스프링을 썼을 때에 이득을 더 부각시키려는 의도일 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;메시징큐, 데이터 저장소와 캐쉬, 모니터링 기술들은 기존 환경에 설치해서 사용할 수도 있지만,클라우드 서비스에서 제공하는 기술 스택에 포함되기도 한다.
클라우드 서비스에서도 기본으로 제공되는 솔루션들은 중요한 경쟁력이다.
아마존 클라우드에는 메시징 큐로 SQS(Simple Queue Servcie), 저장소로 SimpleDB가 있고, Google App Engine에서는 캐쉬로 Memcache, 저장소로 빅테이블에 바탕을 둔 DataStore를 쓸 수 있다.
VMWare가 제공하는 클라우드 솔루션인 VFabric에는 TcServer의 세션 클러스터링을 Gemfire로 쓰고, Hyperic으로 모니터링을 하도록 구성된다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;최근 스프링 포트폴리오의 확장 중 많은 부분은 클라우드 시대에 맞춘 기술 투자라고 볼 수 있다&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;스프링과_클라우드_이식성cloud_portability&quot;&gt;스프링과 클라우드 이식성(Cloud Portability)&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링원 2010 컨퍼런스에서는 VMWare의 클라우드에서 돌아가는 애플리케이션이 Google App Engine에서 똑같이 돌아가는 데모를 보여 줬다.
JVM의 이식성을 생각한다면 어떻게 보면 당연한 결과일 수도 있는데, 이것이 어떤 의미가 있을까?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;클라우드를 도입하려는 쪽에서는 미래에 일어날 수 있는 다양한 상황들을 감안해야 한다.
애플리케이션을 레거시 서버에서 클라우드로, 클라우드에서 다른 클라우드로, 클라우드에서 다시 기존 방식의 서버로의 이전하는 모든 경우가 충분히 일어날 수 있다.
그렇다면 애플리케이션이 특정 실행 환경에 종속적인 부분이 많아 지는 것은 애플리케이션의 소유자에게는 큰 짐이 된다.
그래서 특정 클라우드에 애플리케이션이 묶여 버린다는 것은 얼핏 생각하면 클라우드 사업자에게 유리한 것 같지만, 클라우드의 잠재 사용자에게는 초기의 클라우드 도입을 망설이게 해서, 사용자층이 넓어지는데 부정적인 요인이 된다.
클라우드로 이사하는데 드는 비용이 많다면 기존 애플리케이션을 올리지도 않을 것이다.
그리고 이사 한 후에도 빠져 나오기 힘들다면 사용자가 가격 정책 협상에 불리한 위치가 된다.
그래서 클라우드 사업자는 클라우드 사용자가 기존 애플리케이션을 작은 수정으로 클라우드에 올릴 수 있고, 이사 나갈 때도 쉽게 옮길 수 있다는 것은 강조하는 편이 초기에 시장을 넓히는 데에는 유리할 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 클라우드 환경이 정말 기존 애플리케이션을 똑같이 받아줄 수 있을까? 우선 웹어플리케이션이 올라갈 WAS부터 그러기가 쉽지 않다.
클라우드 서비스에서는 한정된 종류의 WAS가 제공된다.
Google App Engine은 Jetty를 수정해서 쓰고 있고, VMWare가 관여하는 클라우드인 CloudFoundry, VMForce, VFabric은 당연히 Tomcat의 상용판인 Tc Server를 제공하고 있다. 거기다 클라우드에 올라가는 JVM이나 WAS는 지원하는 스펙이 제약된다. Google App Engine에서는 파일을 직접 쓰지 못하고, 쓰레드나 소켓을 생성할 수 없다.
서블릿 스펙에서도 ServletContext.getNamedDispatcher 을 호출해서 디폴트 서블릿의 이름을 알아내는 메소드가 제대로 동작하지 않는다.
보안 문제나 남용의 여지가 있는 부분은 지원하지 않는 것이다.
레가시 시스템을 클라우드 환경으로 옮기는 상황이라면 기존에 레거시 시스템에서 쓰던 WAS와 클라우드 위의 WAS의 종류가 다를 가능성이 높고, 기존의 WAS가 Java EE Server라면 더욱 그렇다.
더욱이 WAS 자체 혹은 애플리케이션에서 호출하는 기능까지도 제약된다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여기서 스프링이 클라우드 사업자들에게도 도움이 될 수 있다.
클라우드 소비자에게 지정된 WAS, 그것도 서블릿 스펙만 지원하는 WAS를 제공한다는 클라우드 서비스의 약점을 스프링이 상쇄해 줄 수 있다.
즉, JavaEE 스펙을 지원하는 서버가 없어도 Tomcat만으로도 객체의 라이프 싸이클 관리와 관계 주입, 선언적 트랜잭션 등을 쓸 수 있다.
그리고 JavaEE server를 쓰는 경우라도 서버가 제공하는 데이터 소스, 트랜잭션 서비스들을 스프링을 거쳐서 사용할 수 있다. 스프링을 통해 간접적으로 Java EE 스펙을 쓴다면, 나중에 Tomcat이나 Jetty로 WAS를 바꿀 때에도 어플리케이션의 적은 부분만 수정하면 된다.
예를 들면 JNDI로 데이터 소스를 찾아오는 부분은 DBCP로 바꾸고, JtaTransationManager를 쓰도록 선언된 Bean선언을 DataSourceTransactionManager으로 바꾸는 정도이다.
그렇게 때문에 스프링을 사용한 어플리케이션은 WAS간의 이식성이 높아지고, WAS선택의 폭이 넒어 진다.
WAS의 완충 지대 역할이라 할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한편으로는 스프링을 활용했을 때 WAS 같은 미들웨어에 대한 종속성은 적어지지만 프레임워크 자체에 종속성이 다시 생기기 때문에, 그것 또한 이식성을 줄이는 일이 아니냐는 생각을 할 수 있다.
하지만 굳이 둘 중에 종속성을 가져야 한다면, WAS보다는 프레임워크에 종속되는 편이 더 낫다고 생각한다.
프레임워크는 하나의 WAS 위에서 여러 개가 공존할 수도 있어서 점진적으로 바꿔나가기도 쉽다.
그리고 애플리케이션이 의존하는 부분을 WAS보다는 프레임워크에 두는 것이 유연성 측면에서는 유리하다.
프레임워크의 버전 업그레이드나 특정 라이브러리 변경은 WAS에 대한 업그레이드보다 간편하기 때문이다.
jar파일을 바꾸는 일만 생각한다면 프레임워크의 업그레이드는 Maven의 pom.xml에서 버전 선언 몇 줄만 바꿔 주면 된다.
반면 WAS는 설치 자동화가 되어 있다면 간편하게 모든 서버에 한꺼번에 복사할 수도 있겠지만, 아무래도 프레임워크 업그레이드 보다는 부담되는 일이다.
그리고 스프링은 스프링에 종속적이기 않게 코드를 작성하는 방법들을 많이 제공하고 있다.
&lt;code&gt;@Inject&lt;/code&gt; 같은 표준 애노테이션이 그 예이다.
이를 적절히 활용한다면 프레임워크에 대한 종속성도 다소 덜어낼 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;세일즈포스닷컴, 구글은 스프링소스를 소유한 VMWare와 함께 스프링을 통한 클라우드 이식성(Cloud Portability)을 강조하고 있다.
어떻게 보면 경쟁 관계에 있는 이들이 한 목소리를 내고 있는 것은 초기 시장 확대가 무엇보다 중요하기 때문일 것이다.
그리고 사용자들을 자신들의 플랫폼 만으로 가두는 전략보다는 원한다면 오갈 수도 있는 길을 열어 두는 것이 장기적으로는 이득이라는 믿음을 공유하고 있는 것 같다.
그렇게 해도 될 만큼 핵심 경쟁력인 인프라 기술 등에서는 자신이 있다는 해석도 할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;기술_포털로서의_스프링&quot;&gt;기술 포털로서의 스프링&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링과 클라우드가 연관되고 있는 또 하나의 측면은, 스프링이 자바 기술 생태계에서 일종의 ‘포털’(Portal) 역할을 하고 있어서라고 분석된다.
인터넷 포털은 많은 사용자들이 방문하고 여러 정보들을 모아서 사용자에게 일관된 UX를 제공한다.
CP(Contents provider)사가 컨텐츠를 포털과 제휴하는 것은, CP사 입장에서는 자사의 컨텐츠를 알릴 수 있고, 포털 입장에서는 방문자에게 풍부한 컨텐츠를 제공해서 트래픽을 더 늘릴 수도 있다는 점에서 양측 모두에 이득이 된다.
그리고 사용자는 포털의 일관된 접근 경로와 UX로 다양한 컨텐츠를 접할 수 있다.
예를 들면 네이버의 휘발류 가격 정보는 석유공사에서 운영하는 Opinet에서 데이터를 가지고 오는 것이지만, 사용자는 네이버 검색창을 통해서 다른 컨텐츠를 볼 때와 같은 화면 스타일과 사용 방법으로 컨텐츠에 쉽게 접근할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;div class=&quot;title&quot;&gt;포털의 UI 일관화&lt;/div&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/spring-cloud/portal.png&quot; alt=&quot;image&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마찬가지로 스프링은 많은 개발자들이 사용하는 기술이고, 다양한 기술들을 조율해서 일관된 설정 방식과 명명 규칙, API 스타일을 제공한다.
API는 Application Programming Interface이니 포털의 User interface가 최종 사용자가 보는 화면이라면 개발자들이 보는 interface는 프레임워크와 라이브러리의 API라 할 수 있겠다.
그리고 스프링과 제휴하는 요소 기술의 보유 업체들은 CP사와 같이 자사의 기술을 알려서 사용자를 늘릴 수 있는 기회를 얻는다.
그리고 스프링소스는 스프링과 연결되는 기술 생태계를 더 풍요롭게 만든다는 이득을 얻는다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링은 다양한 기술을 같은 API로 추상화 시켜서 유연성을 주거나, 비슷한 스타일로 정리해서 개발자들에게 초기 학습 비용을 줄여 준다.
예를 들면 데이터그리드인 Gemfire에서 기존의 스프링의 DB 트랜잭션 관리 인터페이스에 맞춘 GemfireTransactionManager를 제공하는 것이 있다.
그리고 Spring-amqp 같이 최근 추가된 프로젝트도 클래스명, 인터페이스명, 메소드명은 스프링에 익숙한 사람이면 처음 보아도 친숙한 스타일을 느낄 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;VMWare가 스프링소스를 인수한 일이라던지, 세일즈포스닷컴과의 제휴나 Neo4j, Terracotta같은 저장소, 캐쉬 기술과 스프링이 연결되는 것은 기술적인 시너지를 기대한 측면도 있다.
그러나 그것도 스프링이 많은 사람들에게 익숙한 기술이고, 사람들을 모을 수 있기에 성사된 일들이다.
어느 새 VMWare가 자바 개발자들에게 이전보다 친숙한 기업이 되었으니 스프링소스 인수로 홍보 효과는 충분히 성공을 한 듯하다.
물론 그리고 단순한 홍보 수단 이상의 가치가 있으려면, 소비자들에게  시너지를 체감하게 하는 과제가 남아 있긴 하다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;비침범적인_설계와_클라우드_시대&quot;&gt;비침범적인 설계와 클라우드 시대&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링이 처음 만들어 졌을 때 클라우드 시대를 염두에 두었을 리는 없다.
급작스런 시대의 변화에 스프링도 적응을 해야 하는 상황인데, 현재까지는 클라우드 업체 사이에 활발한 제휴 대상이 되고 있다.
인기 있는 기술이라서 홍보를 위해서 제휴를 하는 측면도 있겠지만, 그래도 스프링이 주는 유연성과 이식성이 클라우드에서도 의미가 있다는 평가를 받아서 투자와 협업 대상이 되었을 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 현상의 가장 근본적인 이유는 인프라성 코드와 업무 로직 코드를 침범적이지 않게 한다는 스프링의 설계 철학에서 비롯된다고 생각한다.
스프링에서 강조하는 POJO(Plain Old Java Object) 방식의 개발은 특별한 규약에 의존이 없는 자바 코드가 핵심 로직을 담당해서 실행 환경에 덜 의존적인 코드를 만든다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예를 들면 트랜잭션 처리 같은 인프라성 코드는 WAS가 제공하는 JTA(Java Transaction API) 같은 규약을 쓸 수도 있다.
그렇게 특정 미들웨어에 의존적인 코드는 최대한 한 곳으로 모으는 것이 향후 그 미들웨어가 바뀌었을 때 더 적은 수정을 유발한다.
스프링에서는 JTA에 중립적인 트랜잭션 API(PlatformTransactionManager)를 만들고, 이를 AOP를 통해서 사용해서 결국 JTA를 쓰는 코드를 한 곳에 모으고, 한 두줄 수정으로 다른 방식으로도 바꿀 수 있게 되었다.
다른 예로 Spring security가 Google App Engine의 로그인 인증에도 쓰일 수 있는 이유도  사용자 정보 저장소 인프라에 의존하는 부분이 잘 추상화되어 있기 때문일 것이다[주6].&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;거창하게 &apos;클라우드 이식성 &apos;을 위해서가 아니고, 시스템의 변화가 있을 때 적은 수정으로 대응할 수 있도록 고려를 하는 것은 설계의 기본이다.
역할과 책임이 잘 구분된 설계는 시대와 환경을 초월해서 의미가 있다.
결국 유연성, 이식성이라는 것은 잘된 모듈화의 일반적인 결과이지 꼭 특별한 기술을 적용해야 얻어지는 것은 아니다.
스프링의 AOP니 Dependency Injection이니 하는 기술들도 결국 그런 일을 돕기 위해서 존재하는 것일 뿐이다.
스프링 이전에도, 스프링 이후에도 이런 모듈화는 중요하고, 스프링이 클라우드 시대에서도 유행하고 있는 것은 이런 보편적인 설계 원리를 잘 지켰기 때문이라고 할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;클라우드 시대에 애플리케이션 개발은 얼마나 달라질까?
사용하는 저장소나 미들웨어 같은 인프라는 많이 달라진다.
클라우드에 들어오는 새로운 구성요소들에 잘 적응을 하는 것이 처음에는 생산성을 결정하는 요인이 될 것이다.
그러나 그렇다고 애플리케이션을 개발하는 방식이 크게 달라진다고는 생각하지 않는다.
잘 모듈화되어서 역할과 책임이 잘 구분된 코드는 클라우드 시대까지 살아남을 것이고, 그런 코드는 클라우드 시대에도 수정, 추가 비용을 적게 하고, 클라우드 이후 시대까지도 남겨 질 것이다.
프레임워크의 코드이든, 응용 개발자가 짜는 애플리케이션의 코드이든 마찬가지로 말이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;덧붙여서, 그렇게 관심과 역할이 잘 정리된 좋은 코드를 만들었다는 것을 무엇으로 증명할 것인가?
바로 테스트 코드를 짜 보는 것이다. 인프라를 관리하는 코드와 업무 규칙의 코드가 섞여있다면, 테스트 코드를 짜기가 힘들 것이고, 그런 코드는 앞으로의 변경에도 더 많은 비용이 드는 코드가 되고 살아남기 힘든 코드가 된다.
특정 프레임워크를 썼다고 좋은 설계가 당연히 바로 나오지는 않는다.
스프링을 쓰면서 테스트하기 쉬운 코드를 짜고 있는지를 돌아보는 것이 스프링을 잘 쓰고 있는지를 가장 잘 확인하는 방법이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링소스가 클라우드와 관련한 활발한 활동들을 하는 것은 확장성 있는 사업 모델을 찾기 위한 돌파구였고, 포트폴리오와 강화와 각종 제휴로 현재까지는 클라우드를 지원하는 기술 쪽으로도 빠른 발전을 보이고 있다. 그리고 스프링이 제공하는 이식성 덕분에 제한된 WAS를 제공하는 클라우드에 유리한 점이 있고, 넓은 사용자층을 가진 기술 생태계의 포털 역할로 그 가치를 인정받은 것으로 분석된다. 그런데 스프링이 클라우드 시대에도 흥행하고 적응하고 있는 가장 근본적인 이유는 애플리케이션의 핵심 로직과 인프라를 담당하는 부분을 구분한 설계를 하는데 도움이 되는 기술이기 때문이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;10년전 한 개인이 만들었던 코드조각이였을 뿐인 스프링이 이제는 업계 흐름을 좌우하는 기술이 되었다. 스프링의 예처럼, 오픈소스는 더 이상 잉여시간이 넘치는 개인들의 습작이 아니고, 기업이 전략적 협업을 하는 매개체이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;올해에도 스프링의 소식들이 마구 쏟아질 것이고 그 중 상당 부분은 클라우드와 관련이 있을 것이다. 그들의 전략이 자바 생태계의 참여자 모두에게 혜택이 될 수 있는 방향으로 이어지기를 바란다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;주&quot;&gt;주&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;[주1] 관련 내용이 인용된 기사 &lt;a href=&quot;http://www.bloter.net/archives/15878&quot; class=&quot;bare&quot;&gt;http://www.bloter.net/archives/15878&lt;/a&gt; . 물론 스프링 기술 중 작은부분만 활용하고 있어도 스프링을 쓰는 것으로 집계되었을 것이라고 생각된다.
[주2] 당시 논의 되었던 라이선스 정책에 대해서는  &lt;a href=&quot;http://toby.epril.com/?p=440&quot; class=&quot;bare&quot;&gt;http://toby.epril.com/?p=440&lt;/a&gt; 에 자세히 설명되어 있다.
[주3]로드존슨은 Covalent 인수 배경에 대해서 아래와 같이 밝히고 있다.
&lt;a href=&quot;http://blog.springsource.com/2008/01/29/some-decisions-are-easy-%E2%80%93-like-springsource-acquiring-covalent/&quot; class=&quot;bare&quot;&gt;http://blog.springsource.com/2008/01/29/some-decisions-are-easy-%E2%80%93-like-springsource-acquiring-covalent/&lt;/a&gt;
[주4] 인수 가격과 원화 환산 금액은 아래 링크를 참조했다
&lt;a href=&quot;http://younghoe.info/1192&quot; class=&quot;bare&quot;&gt;http://younghoe.info/1192&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;[주5]로드존슨의 사업 모델에 대한 고민은 아래에 발표자료에 언급되어 있다.
&lt;a href=&quot;http://gotocon.com/dl/jaoo-brisbane-2010/slides/RodJohnson_bKeynotebThingsIWishIdKnown.pdf&quot; class=&quot;bare&quot;&gt;http://gotocon.com/dl/jaoo-brisbane-2010/slides/RodJohnson_bKeynotebThingsIWishIdKnown.pdf&lt;/a&gt;
[주6] &lt;a href=&quot;http://blog.springsource.com/2010/08/02/spring-security-in-google-app-engine/&quot; class=&quot;bare&quot;&gt;http://blog.springsource.com/2010/08/02/spring-security-in-google-app-engine/&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>SpringOne2GX 2010 (4) Spring Roo 관련 발표들</title>
      <link>https://blog.benelog.net//2710123.html</link>
      <pubDate>Thu, 11 Nov 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2710123.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;작년 SpringOne에서도 Spring Roo에 대한 발표가 있었고, Google IO 등 다른 컨퍼런스에서도 꾸준히 Spring Roo는 홍보되었지만, 이번 SpringOne에서는 부쩍 그 비중이 높게 느껴졌습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;키노트가 있었던 주행사장에는 Spring Roo, Spring, SpringSource, Groovy, Grails의 5개의 로고가 조명으로 비추어져 있었습니다. Groovy-Grais의 관계처럼 Spring-SpringRoo의 관계를 연상시켜서, Spring의 대표 기술로  홍보하려는 전략으로 보였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://lh3.ggpht.com/_oJrmz3UkGJk/TMi7gUjZNII/AAAAAAAAC0A/hSU3s8E2XCk/s640/PA220055.JPG&quot; alt=&quot;_oJrmz3UkGJk/TMi7gUjZNII/AAAAAAAAC0A/hSU3s8E2XCk/s640/PA220055.JPG&quot; width=&quot;640&quot; height=&quot;480&quot; title=&quot;_oJrmz3UkGJk/TMi7gUjZNII/AAAAAAAAC0A/hSU3s8E2XCk/s640/PA220055.JPG&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://lh6.ggpht.com/_oJrmz3UkGJk/TMi7hDFNR_I/AAAAAAAAC0A/mWjWPk7bMPE/s912/PA220058.JPG&quot; alt=&quot;_oJrmz3UkGJk/TMi7hDFNR_I/AAAAAAAAC0A/mWjWPk7bMPE/s912/PA220058.JPG&quot; width=&quot;465&quot; height=&quot;348&quot; title=&quot;_oJrmz3UkGJk/TMi7hDFNR_I/AAAAAAAAC0A/mWjWPk7bMPE/s912/PA220058.JPG&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 행사 기념품으로 나온 배찌에서도, Spring,SpringSource, Goorvy, Grails, Tomcat 등과 함께 Spring Roo의 로고가 박힌 것이 포함되었습니다. Security나 Batch같이 이미 현장에서 더 많이 쓰이고 있는 하위 프로젝트들도 있는데, Roo만 특별대우 한다는 느낌까지 들 정도였습니다.  다른 프레임웍 기술만과는 차별된 Spring만의 강점을 강조하기 위해서 Spring Roo가 전면에 나왔다고 생각됩니다.  스프링에서 지원하는 기술이 많아질 수록, API들을 전파하는 것도 쉽지 않을 것인데, Spring Roo를 통해서 사용할 수 있는 방법을 제공하면 코드가 자동생성 되므로 사용법이 더 간편해 보인다는 장점이 있을 것입니다. 그리고 Roo가 그렇게 새로운 API 전파 창구의 역할을 수행한다면 Roo를 직접 사용하지 않는 사람도 Roo가 생성해주는 코드를 샘플로 활용할 수도 있을 것입니다. Spring 3.0.4에 포함된 &amp;lt;mvc:default-servlet-handler/&amp;gt;가 Roo에 바로 반영된 것이나, Neo4j의 Roo addon등이 그 예입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;컨퍼런스가 끝나고 몇일 뒤에 바로 Spring Roo 1.1.0 버전이 발표되었는데,  이번 컨퍼런스에서 1.1.0에 포함된 기능을 소개하는 발표가 많았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.springsource.com/2010/10/27/spring-roo-1-1-0-is-released/&quot; class=&quot;bare&quot;&gt;http://blog.springsource.com/2010/10/27/spring-roo-1-1-0-is-released/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OSGi, GWT, GAE-J 지원,  검색서버인 Apache Solr  지원, Database reverse engineering 등 많은 발전을 보여줍니다. 아래 포스트에 있는 지난 1년간의 Roo의 commit 내력을 시각화한 그림에서도 그런 변화가 표현되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://stsmedia.net/spring-roo-1-1-ga-released/&quot; class=&quot;bare&quot;&gt;http://stsmedia.net/spring-roo-1-1-ga-released/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;벤알렉스 아저씨가 열심히 개발을 하는 모습이 보이는군요.  이번 컨퍼런스에  벤알렉스는 참석하지 않았고, 벤알렉스가 진행하기로 한 발표의 일부는 로드존슨이 직접 진행했습니다. 아파서 못 왔다고는 말했는데, 1.1 출시를 얼마 안 앞두고 마무리 작업 때문에 못 온 것이 아닌가 하는 생각도 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 Roo 관련 세션 중에 Add-On 개발 관련 세션에 들어갔었습니다.
아래 URL에 있는 toString addon을 샘플소스로 보라고 했는데,  toString을 Addon도 크게 쉬워보이지는 않았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://git.springsource.org/roo/roo/trees/master/addon-tostring/src/main/java/org/springframework/roo/addon/tostring&quot; class=&quot;bare&quot;&gt;http://git.springsource.org/roo/roo/trees/master/addon-tostring/src/main/java/org/springframework/roo/addon/tostring&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;가장  흥미로운 이야기는 Roo에서 앞으로 iBatis, Spring jdbc 같은  JPA 이외의 Persistence 기술도 지원하겠다는 것이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Maven 멀티 프로젝트를 언제 지원할 수 있으냐는 질문이 세션 중에 나왔는데, 명확한 일정을 확답을 하지 못한 것으로 봐서는 가까운 시일 내에 가능해지지는 않을 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Roo 관련 발표 자료들은 아래에 공개되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.slideshare.net/schmidtstefan/new-persistence-features-in-spring-roo-11&quot;&gt;New Persistence Features in Spring Roo 1.1&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.slideshare.net/schmidtstefan/next-generation-spring-mvc-with-spring-roo&quot;&gt;Next Generation Spring MVC with Spring Roo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.slideshare.net/schmidtstefan/spring-one2010addondev&quot;&gt;Spring Roo Add-On Development &amp;amp; Distribution&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Java에서 XML없이 SQL개발하기</title>
      <link>https://blog.benelog.net//2708621.html</link>
      <pubDate>Mon, 8 Nov 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2708621.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;div class=&quot;title&quot;&gt;변경 이력&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2015/01/21 :  Eclipse에서 Groovy를 쓰기위한  plugin과 Maven 선언 부분을 현행화&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2013/01/25 : &lt;a href=&quot;http://www.dpriver.com/pp/sqlformat.htm&quot; class=&quot;bare&quot;&gt;http://www.dpriver.com/pp/sqlformat.htm&lt;/a&gt; 의 캡쳐화면 추가 등&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2012/11/06 :  Eclipse에서 Groovy를 쓰기위한  plugin과 Maven 선언 부분을 더 편한 방식으로 수정&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;요약하면, Java의 여러 프레임웍은  XML안에 SQL을 넣는 방식을 지원하는데, 줄바꿈이 있는 문자열을 편하게 쓰게 해주는 따옴표 세 개문법 (&quot;&quot;&quot;)만 Java에 추가된다면 XML을 사용하는 목적을 충족시키면서도 XML로 인한 여러 단점들을 겪지 않아도 된다는 것입니다. 따옴표 세개는 Java에서 추가될 예정이지만, Groovy등에서는 이미 지원합니다. 지금이라도 SQL관리에만 Groovy를 쓰면 쿼리편집이 조금 더 편리해질만도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;xml로_sql을_관리할_수_있는_java_framework&quot;&gt;XML로 SQL을 관리할 수 있는 Java framework&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;많은 Java 프레임웍들이 SQL구문들을 XML파일 안에서 코딩하게 되어 있습니다.
가장 대표적인 것이 iBatis입니다. 아래와 같이 SQL 구문, 파라미터를 운반하는 클래스,  쿼리의 결과가 담길 클래스를 XML안에 선언합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;    &amp;lt;select id=&quot;findByIsbn13&quot; parameterClass=&quot;string&quot; resultClass=&quot;book&quot;&amp;gt;
    SELECT  title,    author,     isbn13,     isbn10,     pages, content, imageUrl
    FROM book
    WHERE isbn13 = #isbn13#
    &amp;lt;/select&amp;gt;
    &amp;lt;select id=&quot;findByTitle&quot; parameterClass=&quot;string&quot; resultClass=&quot;book&quot;&amp;gt;
    SELECT  title,    author,     isbn13,     isbn10,     pages, content, imageUrl
    FROM book
    WHERE title = #title#
    &amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Hibernate와 JPA에서도 &quot;named query&quot;라는 개념으로, SQL을 따로 XML파일로도 뺄 수 있습니다. 아래는 Hibernate에서 SQL을 XML 파일안에 설정한 예입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;sql-query name=&quot;findBookByIsbn13&quot;&amp;gt;
    &amp;lt;return alias=&quot;book&quot; class=&quot;tdd.edu.domain.Book&quot;/&amp;gt;
   SELECT  title,    author,     isbn13,     isbn10,     pages, content,  imageUrl
   FROM book
    WHERE isbn13 = :isbn13
 &amp;lt;/sql-query&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Navie SQL과 Hibernate가 쓰는 HQL을 모두 .xml파일 안에 선언하는 것이 가능합니다. JPA를 사용해도 마찬가지로 JPA-QL, Native SQL을 Java에서 String으로 선언할 수도 있지만, XML 파일 안에 넣어도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring JDBC에서는 Jdbctemplate.execute 등의 메소드에서 SQL내용을 직접 문자열로 넘기게 되어있지만, Applicaton context 안에 쿼리를 저장해두고, 이를 사용하는 쪽에서 &lt;code&gt;java.util.Properties&lt;/code&gt; 같은 객체를 Dependency Injection 받아서 사용하면 iBatis처럼 XML로 쿼리가 관리됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;util:properties id=&quot;bookSqls&quot;&amp;gt;
    &amp;lt;prop key=&quot;findByIsbn13&quot;&amp;gt;
   SELECT  title,    author,     isbn13,     isbn10,     pages, content,  imageUrl
   FROM book
    WHERE isbn13 = :isbn13
    &amp;lt;/prop&amp;gt;
&amp;lt;/util:properties&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, Spring-jdbc나 Hibernate, JPA에서는 XML에 SQL을 저장하는 방식이 선택일 뿐이지만, iBatis 2.x에서는 반드시 XML안에 쿼리를 넣어야합니다.  myBatis라고 이제 이름이 갈라진 iBatis 3.x에서는 Annotation으로 쿼리를 지정할 수 있어서, .java파일 안에 문자열로 SQL에 넣어도 되기는 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;final String PERSIST_INFO =
“INSERT INTO simple_information(info_id, info_content) VALUES (#\{infoId}, #\{infoContent})”;

@Insert(PERSIST_INFO)
public int persistInformation(SimpleInformationEntity simpleInfo) throws Exception;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(예제는 &lt;a href=&quot;http://java.dzone.com/articles/mybatis-formerly-called-ibatis&quot; class=&quot;bare&quot;&gt;http://java.dzone.com/articles/mybatis-formerly-called-ibatis&lt;/a&gt;http://java.dzone.com/articles/mybatis-formerly-called-ibatis 에서)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, 이런식으로 쿼리까지 Annotation으로 지정하는 것에 대해서는 의견이 분분할 것 같고, 개인적인 생각으로는 Spring jdbc나 Hibernate처럼 필요하면 직접 메소드 시그니처에 직접 SQL을 문자열로 넘기는 방식이 훨씬 더 자연스럽다고 보여집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;문제점은&quot;&gt;문제점은?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;iBatis에서는 파라미터에 따라서 SQL이 다르게 구성되는 다이나믹 쿼리를 아래와 같이 선언합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;isEqual property=&quot;writerSelected&quot; compareValue=&quot;false&quot;&amp;gt;
  &amp;lt;isNotNull property=&quot;writerList&quot;&amp;gt;
    &amp;lt;iterate prepend=&quot; AND writer in&quot; property=&quot;writerList&quot;
       open=&quot;(&quot; close=&quot;)&quot; conjunction=&quot;,&quot;&amp;gt;#writerList[]#
    &amp;lt;/iterate&amp;gt;
  &amp;lt;/isNotNull&amp;gt;
&amp;lt;/isEqual&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;if, for문 처럼 조건,반복문들이 XML로 표현되어 있습니다. 이는 절차적 프로그래밍을 SQL로 하게 되어서 아래와 같은 단점이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;조건, 반복문에 해당하는 태그 문법을 별도로 배워야함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;괄호&quot;\{}&quot;대신 열고 닫는 태그가 단락을 구분하기 때문에, 같은 조건,반복문을 코딩해도 Java 같은 범용언어에서보다 긴 코드가 나오게됨&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compile time의 validation범위가 더 줄어들게 됨.  getter, setter로 참조하게 될 속성명에 오타가 있어도 직접 실행해봐야지 오타를 알 수 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java파일 밖이므로, Emma와 같은 Coverage 확인 툴로 실제 해당 절이 실행되었는지 확인할 수도 없음.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;왜_sql이_xml에_들어가게_되었을까&quot;&gt;왜 SQL이 XML에 들어가게 되었을까?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;직접 JDBC를 쓰면 Connection 관리와 Exception처리 등이 불편합니다.
그리고 JDBC의 Prepared Statement에서는 파라미터를 &quot;?&quot;를 표시하기 때문에 거기에 넘어가는 변수를 위치의 순서로 파악을 해야 합니다.
&quot;:id&quot;와 같이 named parameter를 넣을 수 있다면 훨씬 쿼리의 가독성이 높아집니다.
그래서 그러한 Jdbc의 미흡한 점들을 보완해주는 프레임웍들이 각광을 받았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데, Connection이나 Excpetion처리의 편의성, named parameter의 활용하고 싶다고 해서 반드시 XML로 SQL를 관리해야 하는 것은 아닙니다.
XML을 안 써도 되는 Spring의 JdbcTemplate에서도 그런 기능은 다 제공을 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SQL이 한 파일에 모여있지 않으면  DBA한테 쿼리 검수를 맡기거나, 여러 SQL을 한번에 수정할 일이 있을 때 불편해 지기도 합니다.
그러나 그런 점도 SQL 내용을 상수로 선언하는  .Java 파일을 따로 분리하면 해결할 수 있습니다.
SQL을 보관하는 .java파일에 *SqlMap.java와 같은 명명규칙을 부여하고,  SQL 검수를 맡길 때 그 파일만 넘기면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;또, 과거에는  .java파일 밖에 SQL이 있으면 SQL만을 수정을 했을 때는 다시 컴파일을 안 해도 된다는 장점이 강조되었습니다.
그러나, 요즘은 개발 PC에서는 Eclipse로, 서버에 배포할 때는 Ant나 Maven으로 빌드과정이 간편해졌고, 설정파일을 수정해도 파일의 복사를 위해 그런 배포과정을 똑같이 거쳐야 하므로, 컴파일이 필요없다는 것도 더이상 장점이 되지 못합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;XML에 SQL을 썼던 가장 핵심적인 이유는 .java파일에서는 줄 바꿈이 들어간 문자열을 편집을 하는 것이 불편했기 때문입니다.
Java 파일에서는 문자열이 한 줄이 넘어가면 아래와 같이 &lt;code&gt;+&lt;/code&gt; 기호를 이용해서 이를 연결해주는 방법 밖에 없습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public static final String SELECT_BY_ISBN13 =
    &quot;SELECT name , id &quot;
    + &quot;FROM user &quot;
    + &quot;WHERE isbn13 = :isbn13 &quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보통 Toad와 같은 DB client 도구에서 SQL을 작성해서 프로그램에 붙여넣기도 하고, 디버깅 중에는 프로그램 내에 있는 SQL을 반대로 DB client 툴에 붙여넣어서 실행해보기도 하는데, 그 때마다 저렇게 줄바꿈마다 &quot;+&quot;가 있다면 쿼리 편집이 많이 번거로워집니다. 그래서 XML파일 안에 SQL이 있으면 줄바꿈이 있는 긴 문자열도 똑같이 붙여넣을 수 있기 때문에, SQL을 개발하는 작업이 훨씬 편해집니다.  이렇게 SQL이 XML안에 들어가다보니 동적쿼리를 만들기 위한 조건,반복문과 각종 파라미터 매핑 클래스등까지 다 XML에 포함되어 버렸고, 앞에서 말한 부작용들이 점점 드러나기시작했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;물론 Eclipse의 설정으로 .java 파일에 붙여넣기를 할 때는 &quot;+&quot;를 넣는 것과 같이 줄을 바꿀 때 필요한 작업들을 자동으로 할 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;Windows-Preference-Java-Editor-Typing란의 &quot;Escape text When pasting into a string literal&quot;을 선택하고, 큰 따옴표 하나를 연 채 여러줄을 붙여넣으면, 알아서 줄이 바뀔 때는 &quot; + &quot; 기호를 넣어줍니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sql-without-xml/typing.png&quot; alt=&quot;typing.png&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 반대로 이런 여러줄의 String을 DB 접속 툴에 붙여 넣을 때도 &lt;a href=&quot;http://squirrel-sql.sourceforge.net/&quot;&gt;Sql-Squirrel&lt;/a&gt;이나 Toad 같은 툴에서는 그런 &quot;+&quot;와 같은 기호를 제거해 주는 기능이 있기도 합니다.  그리고 웹으로 이런 변환을 해주는 사이트도 있습니다.  &lt;a href=&quot;http://www.dpriver.com/pp/sqlformat.htm는&quot; class=&quot;bare&quot;&gt;http://www.dpriver.com/pp/sqlformat.htm는&lt;/a&gt;  여러줄로 된 SQL문장을 Java, C#, Delphi, PHP등 다양한 언어의 문자열 선언으로 변환해줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sql-without-xml/sql-formatter.png&quot; alt=&quot;sql-formatter.png&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 편집을 도와주는 설정이나 도구들을 쓰더라도  중간 변환과정에서 몇번의 키 입력과 클릭이 필요해서 아예 그런 과정이 없는 것보다는 번거롭게 느껴집니다. 그래서 문자열 전체를 중간 변환과정없이 편집할 수 있는 XML에 SQL을 선언하기 시작했다고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;대안으로_groovy로_따옴표_3개_문법을_이용해서_sql_관리하기&quot;&gt;대안으로 Groovy로 따옴표 3개 문법을 이용해서 SQL 관리하기&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Python, Groovy, Scala, Ruby에서 이미 지원하고 있는 &apos;따옴표  3개짜리 문자열 선언&amp;#8217;이 Java에도 포함된다면 여러줄의 문자열을 따로 편집하는 불편함을 겪지 않아도 됩니다. 아래와 같이 중간에 줄바꿈이 있어도 전체 SQL 내용이 끊어지지 않고 들어갑니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt; public static final String SELECT_BY_ISBN13 =    &quot;&quot;&quot;

  SELECT  title,    author,     isbn13,     isbn10,     pages, content,  imageUrl
   FROM book
   WHERE isbn13 = :isbn13

&quot;&quot;&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 따옴표 3개는 이미 JDK7에 포함되는 것이 제안된 상태인데, JDK에 포함될 실험적인 내용을 구현해보는 &quot;Kijaro&quot;라는 프로젝트에서는 &lt;a href=&quot;https://docs.google.com/View?docid=d36kv8n_32g9zj7pdd&quot;&gt;Enhanced String Handling for Java&lt;/a&gt;라는 이름으로 이 명세를 다루고 있습니다. 그러나, 내년 중반기 쯤에 JDK7에 포함되어 발표될 예정인, java의 문법 개선내용을 주로 담고 있는 &lt;a href=&quot;http://openjdk.java.net/projects/coin/&quot;&gt;project coin&lt;/a&gt;에서는 아직 이를 찾아볼 수 없어서, 언제 Java에 반영될지는 아직 미지수입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다면 Java에서 따옴표 3개를 지원해주기 전까지는 계속 XML의 불편함을 감수해야 할까요? 저는 이미 이 문법을 지원하는 Groovy를 SQL관리 용도로 사용해볼만 하다고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Groovy를 사용하기 위해서는 Eclipse와 Maven에 아래 설정만 해주면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;1.Eclipse에서 Groovy plugin 설치&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Update site:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;4.2 (Juno) : &lt;a href=&quot;http://dist.springsource.org/release/GRECLIPSE/e4.2/&quot; class=&quot;bare&quot;&gt;http://dist.springsource.org/release/GRECLIPSE/e4.2/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.7 (Indigo) : &lt;a href=&quot;http://dist.springsource.org/release/GRECLIPSE/e3.7/&quot; class=&quot;bare&quot;&gt;http://dist.springsource.org/release/GRECLIPSE/e3.7/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.6 (Helios) : &lt;a href=&quot;http://dist.springsource.org/release/GRECLIPSE/e3.6/&quot; class=&quot;bare&quot;&gt;http://dist.springsource.org/release/GRECLIPSE/e3.6/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Groovy-Eclipse Configurator for M2Eclipse도  설치가 필요한데, 2번 과정에서 pom.xml에 빨간 줄이 뜨면 Ctrl +1 을 눌러서도 설치할수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2.pom.xml에 Groovy를 compile할 수 있는 plugin과 runtime dependency 추가&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Groovy를 Compile하는 Maven plugin은  &lt;a href=&quot;http://docs.codehaus.org/display/GMAVEN/Home&quot;&gt;Gmaven&lt;/a&gt; 과 &lt;a href=&quot;http://groovy.codehaus.org/Groovy-Eclipse+compiler+plugin+for+Maven&quot;&gt;Groovy-Eclipse Compiler Plugin For Maven&lt;/a&gt;이 있습니다. 후자가 Eclipse 최신버전의 me2와 더 나은 궁합을 보여줘서 컴파일을 할 때는 후자를 선택했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(1)Dependencies에 선언 추가&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.codehaus.groovy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;groovy-all&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.4.5&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(2)build-plugins 에 아래 내용 추가&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;               &amp;lt;plugin&amp;gt;
                &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
                &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;
                &amp;lt;version&amp;gt;2.3.2&amp;lt;/version&amp;gt;
                &amp;lt;configuration&amp;gt;
                    &amp;lt;compilerId&amp;gt;groovy-eclipse-compiler&amp;lt;/compilerId&amp;gt;
                    &amp;lt;meminitial&amp;gt;128m&amp;lt;/meminitial&amp;gt;
                    &amp;lt;maxmem&amp;gt;512m&amp;lt;/maxmem&amp;gt;
                    &amp;lt;source&amp;gt;1.8&amp;lt;/source&amp;gt;
                    &amp;lt;target&amp;gt;1.8&amp;lt;/target&amp;gt;
                    &amp;lt;encoding&amp;gt;utf-8&amp;lt;/encoding&amp;gt;
                &amp;lt;/configuration&amp;gt;
                &amp;lt;dependencies&amp;gt;
                    &amp;lt;dependency&amp;gt;
                        &amp;lt;groupId&amp;gt;org.codehaus.groovy&amp;lt;/groupId&amp;gt;
                        &amp;lt;artifactId&amp;gt;groovy-eclipse-compiler&amp;lt;/artifactId&amp;gt;
                        &amp;lt;version&amp;gt;2.7.0-01&amp;lt;/version&amp;gt;
                    &amp;lt;/dependency&amp;gt;
                &amp;lt;/dependencies&amp;gt;
            &amp;lt;/plugin&amp;gt;

&amp;lt;plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3.Groovy 사용&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 New&amp;#8594; groovy class를 선택하여서 java 파일 작성하듯이 클래스를 만듭니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sql-without-xml/new-groovy-class.png&quot; alt=&quot;new-groovy-class.png&quot; title=&quot;new_groovy_class.png&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java 문법을 그대로 쓸 수 있으니 따옴표 3개를 쓸 수 있다는 점만 다르다고 생각해도 됩니다. 아래와 같이 .groovy 파일 안에 들어간 SQL이 색깔도 다르게 표시되어 비교적 가독성이 높게 표시되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sql-without-xml/groovy-sqls.png&quot; alt=&quot;roovy-sqls.png&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런 다음 DAO 등 SQL을 호출하는 쪽에서는 이 상수 문자열을 바로 참조합니다. 상수 선언이 되어 있으니 아래와 같이 오타를 쳐도 미리 알려주고, Ctrl + Space를 치면 자동완성도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sql-without-xml/typing-error.png&quot; alt=&quot;typing-error.png&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dynamic SQL의 경우에도 직접 Java안에서 if문으로 써서 적어주면 됩니다. 아래와 같이 EclEmma 같은 도구로 coverage를 측정하면, 실제 실행되지 않은 조건분기도 눈으로 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/sql-without-xml/coverage.png&quot; alt=&quot;image&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 코드를 Spring-JDBC를 사용했는데, 필요하다면 Hibernate나 &lt;a href=&quot;http://commons.apache.org/dbutils/apidocs/index.html&quot;&gt;apache commons DBUtils&lt;/a&gt;에서도 적용 가능한 방법입니다. 다만 Hibernate에서는 Criteria 같은 것을 이용하면 문자열로 길게 쓰는 쿼리가 많이 나오지는 않을 것으로 예상합니다. 그리고 myBatis(iBatis 3.0)의 Annotation으로 지정하는 쿼리 문자열에서도 똑같이 참조할  수 있습니다. static final String으로 선언된 문자열 상수만 쓰는 것이기 때문에 Groovy의 성능문제도 걱정할 필요가 없습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;단점과 꺼림직함도 물론 있습니다. 별도의 Eclipse plugin을 설치해야 하기 때문에, 이미 많은 수의 Plugin을 설치해서 Eclipse가 무겁다고 느껴지는 개발환경에서는 다소 부담이 될지 모른다는 점입니다. Groovy가 거의 java와 같은 문법을 지원하기는하지만, 이 문법 하나 때문에 새로운 언어를 도입하는 것이 과하다고 느끼시는 분들도 계실 것입니다. 이 문법을 위해서 Groovy의 다른 부분은 쓰지 않는데도 Runtime에 Groovy의 라이브러리를 올리는 것이 부담스러울 수도 있습니다. (따옴표 3개와 같은 Groovy만의 문법을 바이트코드로 변환하는 작업은 Compile time에 이루어지지만, Groovy로 선언한 객체는 groovy.lang.GroovyObject를 상속해야 하기 때문에, 다른 기능을 쓰지 않더라도 Runtime에서 추가 라이브러리가 필요합니다.) 그런 분들은 언제가 될지는 몰라도 Java에서 따옴표 3개를 지원하는 때까지 기다리는 것이 좋으실듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SQL을 XML에 쓰는 것이 전혀 불편하지 않다고 느끼신다면 계속 하던대로 개발을 하면 되겠지만, 저는 위의 시도가 조금이라도 더 편한 개발환경을 만든다고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>SpringOne2GX 2010 (3) Spring and Java EE 6,Synergy or Competition?</title>
      <link>https://blog.benelog.net//2703581.html</link>
      <pubDate>Fri, 29 Oct 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2703581.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;논쟁에_응하다&quot;&gt;논쟁에 응하다&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2010년 10월 4일, &lt;a href=&quot;http://www.theserverside.com/&quot;&gt;http://www.theserverside.com&lt;/a&gt; 에는 이제 프레임웍의 시대는 가고, Spring에서 Java EE6로 옮겨야 한다는 주장을 담은 글이 올라왔고, 뜨거운 논쟁거리가 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.theserverside.com/news/thread.tss?track=NL-461&amp;amp;ad=790558&amp;amp;thread_id=61023&amp;amp;asrc=EM_NLN_12619056&amp;amp;uid=2873925&quot;&gt;Moving from Spring to Java EE 6: The Age of Frameworks is Over&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;과거 Java EE 스펙의 부족함 때문에 Spring이 떠올랐지만, 이제 Java EE 6에서는 그런 것들을 다 극복을 했으니 Java EE6로 갈아타자는 내용이였습니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;이 글의 댓글에서부터 많은 반론이 올라왔고, 별도의 포스트로 쓰여진 아래와 같은 글들도 있었습니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/re_moving_from_spring_to&quot; class=&quot;bare&quot;&gt;http://raibledesigns.com/rd/entry/re_moving_from_spring_to&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jandiandme.blogspot.com/2010/10/spring-vs-java-ee-and-why-i-dont-care.html?utm_source=feedburner&amp;amp;utm_medium=twitter&amp;amp;utm_campaign=Feed%3A+jandiandme2+%28J+and+I+and+Me%29&amp;amp;utm_content=Twitter&quot;&gt;Spring vs. Java EE and Why I Don&amp;#8217;t Care&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;반론들에 포함된 공통적인 내용들은 아래와 같습니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;크게 볼 때, Spring과 Java EE6 는 비슷한 프로그래밍 모델을 가진 부분이 많다.Annotation을  @Stateless을 쓸지, @Component 쓸지는 프로젝트의 성공여부를 결정지을 만큼 중요한 차이는 아니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;그러나 Spring이 Tomcat, Jetty를 비롯한 더 많은 WAS에서 실행될 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Batch, Integration, Spring Roo 같은  Java EE 스펙이 미치지 못하는 영역을  스프링에서는 제공하고 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JavaEE 진영과 스프링 쪽의 갈등은 이미 뿌리가 깊습니다.  스프링의 시초가 된 코드가 있는 &quot;Expert One-on-OneJ2EE Design and Development&quot;책에는 EJB의 단점들이 날카롭게 지적되어 있고, 로드존슨과 유겐할러가 써서최초로 스프링을 언급한 책은 이름부터가 &quot;Expert One-on-One J2EE Development withoutEJB&quot;였습니다. &quot;with Spring&quot;도 아닌 &quot;without EJB&quot;인 것이죠. EJB를 적용했을 때의 실패경험 때문인지로드존슨은 EJB에 맺힌 것이 많아 보이고, 그런 것들이 앞의 저서들을 저술한 동기를 더 강하게 하지 않았을까하는 생각도듭니다.  그래도 J2EE 시절만 해도 표준 스펙은 OS나 DB서버와 같이, low level의 기술을 제공하는 컨테이너로인식이 되었는데, Java EE5 이후  실사용자 레벨의 컴퍼넌트 모델을 제공하기 시작하면서 JavaEE와 Spring은경쟁관계로 인식이 됩니다. 사실 스펙을 정의하는 JavaEE와 구현기술인 Spring을 동등하게 비교할 수도 없고,Spring에서도 JavaEE스펙을 지원하는 부분이 있습니다. 하지만 JavaEE스펙을 더 지지하는 진영에서는 Spring을사유기술일 뿐이고 JavaEE에 근거한 기술은 표준이라는 것을 계속 강조하면서 스프링을 공격하고 있습니다.  로드존슨은 이전에올린 &lt;a href=&quot;http://blog.springsource.com/2009/04/14/enterprise-java-and-the-american-motors-gremlin/#comment-164086&quot;&gt;블로그 포스트의 댓글&lt;/a&gt;에서 오픈소스 기술인 Spring을 사유(proprietary)라고 표현한 것에 대해서는 그것은 의심스러운 용어일 뿐이라고 말한적도 있습니다.  (In any case, &quot;proprietary&quot; is a questionable term when we&amp;#8217;retalking about open source. )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마침 SpringOne2GX 컨퍼런스 직전에  또다시 이런 논쟁이 일어난 것이 흥미로웠고, 저는 로드존슨이 키노트에서 바로 이에 대한 반론을 하지 않을까 하는 기대도 했었습니다. 그런데 로드 존슨은 핵심가치 같은 큰 그림에서의 스프링이 추구하는 바를 이야기했고, 이 논쟁에 대한 직접적인이야기는 하지 않았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;바로 셋째날에 한 이 발표 &quot;Spring and Java EE 6,Synergyor Competition?&quot;에서 유겐할러가 그에 대한 구체적인 대답을 했습니다.  유겐할러는 최근 이런 논쟁들을 보고서 발표제목을 바꾸었다고 했고  이미 발표 몇시간 전  팀블로그에 아래와 같은 글을 올려서 이날 발표에서 말할 내용을 먼저 공개하기도했습니다. 저는 컨퍼런스가 끝난 후에야 이 글이 올라온 것을 보았는데, 발표에서 가장 뒷부분에 강조한 핵심내용이 정리되어있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.springsource.com/2010/10/19/spring-3-on-a-java-ee-6-server/&quot; class=&quot;bare&quot;&gt;http://blog.springsource.com/2010/10/19/spring-3-on-a-java-ee-6-server/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;뒤에서 언급되겠지만, 간단히 정리하면  JavaEE6 환경에서도 스프링을 사용하면  플랫폼에서 제공되는 기술들을 일관된 프로그래밍과 설정 모델로 조화시키고, 더 많은 기술과 환경을 조합할 수 있으면서, Java EE7보다 더 빨리 발전하는 기능들을사용할 수 있는 것이라는 주장이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;사실 유겐할러는 이 날 발표와 비슷한 주제의 발표를 Java EE6 스펙의 확정 전에도 여러 번 한적이 있었습니다. 작년에 한 발표도 아래 링크에서 동영상으로 올라와 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.infoq.com/presentations/Spring-and-Java-EE-6-Jurgen-Holler&quot; class=&quot;bare&quot;&gt;http://www.infoq.com/presentations/Spring-and-Java-EE-6-Jurgen-Holler&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;확정된 스펙을 가지고 이야기 한 것이 작년에 한 위의 발표와 달랐던 점이였습니다. 발표 중에 유겐할러는 Spring 쪽에서도오랫동안 EE 6 스펙의 확정을 기다렸다고 했고 EE6 확정 직후에 발표된 Spring 3.0에서도 EE6의 스펙을 많이지원하고 있음을 강조했습니다.  발표 제목 중에  포함된 &quot;Synergy or Competition&quot;중,  &quot;Synergy&quot;를주장하고 싶었던 것이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;스프링의_역할&quot;&gt;스프링의 역할&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 것은 이번 컨퍼런스 내내 키노트와 여러 세션에서 반복되어 언급된 말이였는데, 이 발표에서도 스프링을 사용하면 Java EE와 동등한 기능을쓰면서도 어플리케이션을 어느 서버에서도 배포될 수 있다는 것을 강조했습니다.  대상 플랫폼의 런타임에 적용되어서, 서비스추상화를 통해 공통적인 Programming model과 설정으로 개발을 할 수 있고,  전통적인  어플리케이션 서버 역할의 대안을 제공한다고 했습니다. Java EE 5의 스펙 중에서도 @PostConstruct, @PreDestoy ,@PersistenceContext, @PersistenceUnit , @Resource, @EJB, @TransationAttribute 등의 많은 Annotation을 지원하는데, 원하는 Semantics에 따라서 조합하고조화시켜(Mix &amp;amp; Match) 사용하라고 권장했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;java_ee6_스펙_리뷰와_스프링의_지원&quot;&gt;Java EE6 스펙 리뷰와 스프링의 지원&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;java_ee_프로파일&quot;&gt;Java EE 프로파일&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JavaEE프로파일은 EE 스펙 중 일부를 일부를 묶어서 구분하는 개념이였습니다. 가령 &quot;Web Profile&quot;이라고 하면 EE 스펙 중웹기술에 해당하는 Servlet, JSP, jSTL, JTA, JPA, EJB 3.1 Lite를 포함하는 것이라고 했습니다.그래서 벤더들이 초점을 두고 싶은 프로파일만 구현하고, 이미 지나간 잘 안 쓰이는 스펙에 대해서는 구현할 필요가 없이 그 분야의 프로파일에 대해서만 인증을 받을 수도 있다는 것입니다. 좋은 개념이기는 하나 현실적으로 웹프로파일만 구현하고 있는 벤더는아직까지는 없다고 말했습니다. 그런데 컨퍼런스가 끝난 뒤에 &lt;a href=&quot;http://mechsoft.com.tr/Mechsoft/software/en/acikkaynak/siwpas.html&quot;&gt;SIwpas&lt;/a&gt;라는 Web Profile만 구현한 서버를 우연찮게 발견하기도 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 스프링은 플랫폼에서 어떤 라이브러리가 제공되던지 상관 없이 사용할 수 있으므로  Java EE 프로파일은 스프링에 영향을 미치지는 않는 개념입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;servlet_3_0&quot;&gt;Servlet 3.0&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Servlet3.0 스펙에는  web.xml을 간결하게 해주는 &quot;framework auto deployment&quot;기능이 포함되고, 스프링도자동배포 기능 추가를 계획하고 있었습니다. 그리고 비동기적인 Http처리인 Comet은 Spring MVC에서 특별한request/reponse 형식으로 지원할 것이라고 했습니다. 당연히 이전 서블릿 스펙을 지원하는 서버에서의 하위호환성도유지할 것이라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jsf_2_0&quot;&gt;JSF 2.0&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JSF 2.0에는 UI 컴퍼넌트 부분에서 Ajax 지원와 페이지 선언 언어 등이 개선됩니다.. 그리고 스프링의 @Component와 @Value와 유사한 역할인@ManagedBean, @ManagedProperty 애노테이션이 있습니다. 스프링에서는 현재의 JSF지원 기능을 새 버전에맞추어 이어나가면서 3.1버전에서는 JSF 지원을 더 확장할 수 있도록 연구 중에 있다고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jpa_2_0&quot;&gt;JPA 2.0&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JPA2.0에는 쿼리 타임아웃, 표준화된 쿼리 힌트 등이 추가되었습니다. 역시 스프링에서는 현재의 JPA의 지원기능을 이어나가고,트랜잭션 관리에서 JPA 2.0의 모든 기능을 다 열어준다고 했습니다. 이미 Spring 3.0에서 Hibernate,EclipseLink, OpenJPA 등의 최신버전들과 이런 기능들을 쓸 수 있다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jsr_303_bean_validation&quot;&gt;JSR-303 Bean Validation&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JSF 2.0과 JPA 2.0에서 동시에 지원하는 스펙인데, Spring 3.0에는 SpringMVC의 Validation기능으로 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jax_rs&quot;&gt;JAX-RS&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JAX-RS((Java™API for RESTful Web Services)는 REST방식의 웹요청 처리를 지원하는 표준 API입니다. 이미Jersey같이 JAX-RS를 지원하는 프레임웍에서 Spring을 자연스럽게 같이 연결해서 사용할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 코드에 @Path 아노테이션은 JAX-RS에서 정의된 것인데, 여기에 스프링의 &lt;code&gt;@Autowired&lt;/code&gt; 를 같이 쓰고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Path(&quot;widgets&quot;)
public class WidgetsResource {
    @Autowired
    private WidgetsService service;

    @GET  @Path(&quot;{id}&quot;)  @Produces(&quot;text/html&quot;)
    public String getWidget(@PathParam(&quot;id&quot;) int id){
     ...
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링 3.0에서도 Spring web MVC에서 나름대로의 스펙을 가진 REST지원 기능이 있습니다. 사실 위의 @Path와@PathParam 은 스프링의 @RequestMapping, @RequsetParams 아노테이션과 무척 유사해보이는, 비슷한프로그래밍 모델을 가지고 있습니다. 왜 스프링개발자들이 Spring MVC에서 JAX-RS를 바로 지원 안하고 나름대로의 스펙을만들었는지에 대해서는, 스프링소스의 팀 블로그를 통해서 밝힌 적이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/&quot; class=&quot;bare&quot;&gt;http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/&lt;/a&gt; 참조&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;기존의 JAX-RS 스펙을 바로 지원하는 것도 프로토타이핑해봤지만, 자연스럽게 않게 억지로 끼워 맞추는 듯한 방식이 나왔고, 결국Spring MVC사용자들에게 더 일관적이고 편한 방식을 제공하는 나름대로의 기능을 넣기로 결정했다는 것이였습니다. 결국JAX-RS와 Spring MVC는 REST 지원부분에서 겹쳐지는 부분이 생겼고, 이를 두고 스프링은 표준 스펙을 존중하지않는다는 비난을 하는 사람도 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이날 발표에서도 유겐할러는 Spring MVC는 근본적으로 MVC구조라서 View의 rendering을 하는 부분을 분리할 수 밖에 없고, 따라서JAX-RS 방식과 달라질 수 밖에 없다고 했습니다. 그리고 &lt;a href=&quot;https://jersey.dev.java.net/&quot;&gt;Jersey&lt;/a&gt;, &lt;a href=&quot;http://www.jboss.org/resteasy/&quot;&gt;RESTEasy&lt;/a&gt;, &lt;a href=&quot;http://www.restlet.org/&quot;&gt;Restlet&lt;/a&gt;와같은 JAX-RS 구현체를 쓴다고 해도 Spring를 같이 쓸 수 있으니, 상황에 따라서 Spring MVC의 REST 기능이나JAX-RS 구현체를 모두 골라서 쓸 수 있다고 했습니다. UI페이지와 REST요청을 같이 처리해야하는 어플리케이션에서는Spring MVC로,  계층적인 리소스 구조처럼 REST 방식을 깊이까지 쓰는 어플리케이션이라면 JAX-RS 구현체를 쓰는것처럼 말이죠. 스프링은 언제나 그래왔듯이 선택에 대한 것이라는 말을 덧붙였습니다. (Spring is (and alwayswas) about choice),&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 JAX-RS 스펙은 Java EE6에서 독립적인 스펙이고, 다른웹스펙과도 연관관계가 없고, JSF와 프로그래밍 모델도 다르다고 유겐할러는 설명했습니다.  스프링은 그런 관련성이 있는 스펙들을 일관성 있게 묶어가고 있다는 것을 대비시켜 보이기 위해서 굳이 그런 언급을 한 것이 아닐까하는 생각도 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;ejb_3_1&quot;&gt;EJB 3.1&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;EJB3.1는 EJB 3.0에 singleton bean과 비동기 메소드 호출, JNDI 이름에 대한 Convention 제공 등의기능이 추가된 것입니다. 그리고 Local session Bean과 Singleton Bean만을 가지는 &quot;EJB 3.1Lite&quot;라는 것도 정의했습니다. 대부분의 서비스 객체가 원격호출이나 Object pooling이 없이 쓰이는 스프링의 방식과유사한 것인데, 과거의 그런 기능들이 대부분의 상황에서 오버엔지니어링 이였음을 다시 한번 인정하는 스펙 추가가 아닌가 하는생각이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;흥미로운 스펙은 컨테이너가 Lock 관리를 해준다는 것인데,(container-managed locking) 아래 코드에서 @Lock 아노테이션이 그런 역할을 하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Singleton @Startup
@DependsOn({&quot;OhterBean1&quot;,&quot;OtherBean2&quot;})
public class SharedService {
    private Data sharedData;
    @Lock(READ)
    public String returnSharedDataValue(){
        return this.sharedData;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 @Singleton이 붙은 클래스의 모든 메소드는 기본적으로 쓰기 잠금이 걸린다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;유겐할러는 이런 스펙이 불필요할 상황이 많을 것이라고 했는데, ConcurrentHashMap 같이 thread-safe를 감안한자료구조를 선택할 수도 있고, synchronized나 volatile와 같은 키워드를 이용해서도 개발자가 그런 것들을 제어할수도 있다고 했습니다. 아뭏든 이런 Lock에 대한 디폴트 값을 제외하고는 @Singleton으로 설정되는 Bean은Spring이 관리하는 Bean과 상당히 유사해졌다고 말했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;EJB가 컨테이너와 스프링과의 관계는 EJB3.1에서도 변하지 않는다고 했습니다. EJB 스펙은 나름대로의 Container에 의해서 지원되는 것이고, 필요하다면Spring에서 이를 접근할 수도 있는 것이죠. 그리고 EJB 3.1의 비동기 호출 스펙인 @Asynchronous은@Aysnc 로 Spring에  반영되어, 영향을 주었다고 했습니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jsr_299_web_beans_cdicontexts_and_dependency_injection&quot;&gt;JSR-299 Web Beans - CDI(Contexts and Dependency Injection)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&quot;Webbeans&quot;라는 이름으로 불렸던, 공식적으로는 &quot;Contexts and Dependency injection&quot;이라는 명칭으로붙여진 이 스펙과 스프링을 눌러싼 논쟁들은 표준 제정 과정 당시에도 가장 뜨거운 화제였었고, 이 날 발표에서도 개인적으로 가장관심이 가는 부분이였습니다.  이 스펙은 원래 JSF에서 Bean관리를  개선해서 JSF와 EJB를 잘 연결하는 역할을 위해만들어졌지만, 점점 확정된 스펙으로 발전해 나갔습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;CDI에서는  Type-safe DepedencyInjection, Interceptor, 이벤트 통지, Web conversation context 등의 풍부한Dependency Injection모델을 Annotation을 통해서 제공합니다.  javax.decorator,javax.context, javax.inject, javax.event 등에 다양한 패키지에 나눠서 들어가 있고, 이미@Resource 등의 아노테이션이 있는 JSR-250이 담당하는 패키지인 javax.annotation,javax.interceptor 에도 추가되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;발표 슬라이드에는 나와있지 않지만,여기서 유겐할러는 CDI의 부정적인 면들도 언급을 합니다. EJB 3.1이 이 Component 모델을 뒤에서 떠받치는 역할을하지만, EJB와 CDI는 각각의 나름대로의 역할과 생명주기를 가지고 있는 그렇게 다른 Semantics가 섞이면 혼란을 불러일으킬 수 있다고 합니다. 그런 혼란에서 오는 어려움은 그 날 발표 슬라이드나 어떤 슬라이드 내에서는 표현될 수 없고, 실제로개발을 해보고 디버깅을 해서 겪어봐야지 알 수 있다고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 이 스펙은  스프링의 프로그래밍 모델과겹쳐지는 부분이 있고, Spring 3.1에서는 스프링의 원래 프로그래밍 모델을 더 발전시켜서 JSR-299과 표현력과 기능을능가하겠다고 했습니다. 이미 Spring에서도 다양한 scope의 빈을 정의할 수도 있고, Stateful한 웹어플리케이션을개발할 수 있다는 Spring Web Flow가 따로 프로젝트로 나와있지만, Spring 3.1에서 추가될Conversation Management는 그런 것들을 더욱 일반화 시켜서 Spring Core 쪽으로 끌어올리겠다는 의미로해석됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;spring_on_java_ee6&quot;&gt;Spring on Java EE6&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;앞에서 언급했듯이, 유겐할러는 발표의 뒷부분에서 최근 논쟁에 대한 대답들을 정리해서  설명해줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첫째,  Java EE6 서버는 스프링이 참조하는 미들웨어를 제공하는 좋은 실행환경이라는 것입니다. Java EE6 서버에서제공하는 Servlet 3.0, JSF 2.0, JPA 2.0 등의 플랫폼 기술들은 스프링은 소비자로서 사용할 수 있다고했습니다. 그리고 다소 Java EE 스펙과 중복이 될 수 있는 부분인 EJB나 CDI관련 부분은 Java EE6 기능의일부분에 불과하다는 것이였습니다. GlassFish에서는 코드량 기준으로 5% 정도만 차지할 뿐이고, 아마 5%가 넘는 다른기능들도 사용하지 않고 있는 것이 많을 것이라고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;둘째, 스프링을 사용하면 필요에 따라더 넓은 기술을 선택할 수 있기 때문에 그것이 Java EE6 서버를 사용하는 자연스러운 방식이라느 것입니다.  JSF대신 Wicket이나 GWT,  EE clustering대신 Coherence 등을  쓸 수 있고, , 스프링의 jar파일은 4MB바이트 정도로 크지 않아서 이 용량이 문제가 되는 경우는 거의 없다고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;셋째,Java EE6 서버 중 GlassFish만이 지금 GA(Generally Available)버전 이상인 상태이고, JBoss나WebSphere는 아직 안정화된 버전이전이기 때문에 Java EE 6서버만을 선택한다면 실행환경에 제약이 있다고 했습니다.스프링은 Tomcat 5,6,7버전, Java EE5 서버, 그리고 Google App Engine 같은 것에서도 돌아갈 수있고, Java EE5를 쓰면서도 스프링을 사용하면 Hibernate 3.6 같은 구현체를 써서 EE6 스펙을 쓰는 선택도가능하다고 했습니다. 다양한 플랫폼 환경에서 스프링이 조율역할을 한다는 것입니다. 실제로 최근에 올라온 &lt;a href=&quot;http://www.dzone.com/links/r/cdi_a_major_risk_factor_in_java_ee_6.html&quot;&gt;CDI - A Major Risk Factor in Java EE 6&lt;/a&gt;라는 글에서는 아직 안정화되지 않은 버전의 서버에서 CDI를 적용하다 어려움을 겪은 이야기가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;넷째,  EE6는 2009년 초 기술환경에 맞춘 것이고, 그 후로 지금까지 분산캐쉬, Cloud 등 많은 기술들이 중요하게떠올랐는데,  그런 기술들을  스프링을 통해서 서버를 업그레이드할 필요없이 훨씬 빠른 시기에 지원받을 수 있다는 것입니다. 이번컨퍼런스 내내 NoSQL, 분산 캐쉬, Social network 같은 다양한 주제들이 강조된 것도 그런 강점을 강조하기 위한전략으로 보였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정리하자면 Spring과 Java EE6는 잘 맞는 궁합이면서 중복되는역할의 라이브러리 용량은 현실적으로 문제될 것이 없으며, 스프링으로 더 다양한 기술과 실행환경을 활용하면서 일관된 프로그래밍모델로 개발할 수 있다는 것이 핵심 주장이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>PMD ruleset 검토 의견 사례</title>
      <link>https://blog.benelog.net//2702876.html</link>
      <pubDate>Thu, 28 Oct 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2702876.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이전에 PMD 룰셋 검토했을 때 정리했던  의견입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Rule에 대한 유용성은 프로젝트의 상황에 따라서 많이 다르기 때문에, 아래 의견은 참고만 하셨으면 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 14.2857%;&quot;&gt;
&lt;col style=&quot;width: 14.2857%;&quot;&gt;
&lt;col style=&quot;width: 14.2857%;&quot;&gt;
&lt;col style=&quot;width: 14.2857%;&quot;&gt;
&lt;col style=&quot;width: 14.2857%;&quot;&gt;
&lt;col style=&quot;width: 14.2857%;&quot;&gt;
&lt;col style=&quot;width: 14.2858%;&quot;&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;Rule Set&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;Rule name&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;Message&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;분류&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;의견&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;설명, 의견 사유&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;strong&gt;참고자료&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Basic Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;EmptryInitializer&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;+&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;PMD 5.0에서 추가될 룰로 Maven plugin에서는 지원되지 않음&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Braces Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;IfStmtsMustUseBraces&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Avoid using if statements without curly braces&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;if문뒤의 한줄짜리 처리문도 \{}가 없으면 경고를 줌. 가독성을 높여주므로 포함시키는 것이 바람직.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Code Size Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;TooManyMethods&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;This class has too many methods, consider refactoring it.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;한 클래스에 지나치게 많은 메소드가 포함되면 나는 경고. 적당한 클래스 크기를 정하는데 도움을 주나, 정말 논리적 연관성이 있는 작업을 많이 담고 있는 클래스도 존재할 수 있으므로 적정 숫자에 대해서는 논의 필요함&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Controversial Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;OnlyOneReturnRule&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;A method should have only one exit point, and that should be the last statement in the method&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;메서드 중간의 return문은 복잡한 조건문의 구조를 단순하게 하는데 도움이 경우가 많고, 권장하는 사례도 있음.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;켄트백의 구현패턴 - 7장 중 &apos;보호절&apos;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Controversial Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;DataflowAnomalyAnalysis&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Found &apos;DD&apos;-anomaly for variable &amp;#8230;&amp;#8203;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Info&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;전체 데이터의 흐름을 분석해서 return값과 상관없는 변수에 값을 쓴다든지 하는 상식적이지 않은 경우를 잡아내줌. 캐쉬나 interceptor등 그런 흐름이 꼭 필요한 코드도 경고해줘서 지나치게 많은 경고를 생성함&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Controversial Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Unused Modifer&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Avoid modifiers which are implied by the context&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;interface는 당연히 모든 method가 public이니 따로 선언해줄 필요가 없는 현상등을 주의를 줌. 개발자에 따라서는 public이 있는 것이 더 명시적이라고 생각할 수도 있고, 이미 모든 인터페이스가 public으로 다 선언되어 있는 상황에서는 개발자들에게는 무의미한 수정으로 인식될 위험도 있음.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Design Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;UnnecessaryLocalBeforeReturn&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Consider simply returning the value vs storing it in local variable ….&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;return 전에 따로 local 변수로 반환할 값을 선언할 때 주는 경고. 기능적으로는 별 의미 없는 코드이나, return 문장에는 @SupressWarning 의 Annotation을 추가할 수 없기 때문에, Annotation 적용 범위를 최소화하기 위해 그런 선언이 필요한 때도 있음. Debug Mode에서도 값의 추적에 약간의 편리성이 생길 수 있음.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java Language Spec 9.7, Effective Java 2ed - Item 24&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Design Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;UncommentedEmptyConstructor&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Document empty constructor&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;빈 생성자에 comment가 없어도 코드의 이해에 무리가 없는 경우가 많음&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Design Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;UncommentedEmptyMethod&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Document empty method&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Listener류의 인터페이스의 구현에는 특정 메소드는 아무 일도 하지 않는 메소드로 남겨놓을 때도 있고, 종종 발생하는 상황임. 그리고 그 상황에 주석이 없다고 해도, 코드 이해에 무리가 없음&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Design Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;ImmutableField&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Private field &apos;&amp;#8230;&amp;#8203;&apos; could be made final; it is only initialized in the declaration or constructor.
EmptyInitializer&lt;br&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;멤버변수 중 선언되어서 한번만 값이 대입된 변수는 final로 할 수 있다고 알려주는 경고. 개발자가 Final로 의도하지 않지만, 현재 기능에서는 한번만 대입하고 있는 상황도 존재할 수 있다고 생각됨.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Jakarta Commons Logging Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;ProperLog&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Logger should be defined private static final and have the correct class&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;+&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;private static final Log log = LogFactory.getLog(해당클래스) 로 로거를 선언할 것을 알려줌. 대체로 무난히 적용가능하나, 프로젝트의 로그 정책이 특별한 경우도 있으므로 논의해볼만 함&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java Bean Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;BeanMemberShouldSerialize&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Found non-transient, non-static member. Please mark as transient or provide accessors.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java Bean의 명세를 검사하는 규칙. 일반적인 클래스는 transient하지 않으면서도 acessor가 없는 멥버 변수가 올 수 있는 경우가 많음. 스프링에서 선언하는 bean들은 setter만 가지는 경우가 많으므로 대부분 이 규칙에 어긋나게 됨. (스프링에서 bean은 java bean명세보다 보다 넓은 bean을 의미하는 것으로 생각하면 됨)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Java Loggins Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Systemprintln&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;System.out.print is used&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Error&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;웹Application에서는 반드시 피해야할 코드라서 출시시에는 꼭 걸러내야하나, 분류가 Error가 되어 있는 것에 대한 논의는 필요함. 그리고 Console에서 도는 간단한 프로그램이나 테스트코드에서는 System.out이 들어가는 것이 결함이 아니므로 폴더 범위나 네이밍룰을 추가한 보다 정교한 Rule지정하는 것이 바람직함(예를 들어 Controller, Service, DAO안에는 System.out불가)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Junit Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JunitAssertionsShouldIncludeMessage&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JUnit assertions should include a message&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;테스트 결과를 확인할 때, 메시지가 포함된 것이 바람직하나, 테스트 코드 작성을 장려하기 위해 테스트 코드에 대한 제약은 줄이는 것이 좋을 것으로 판단됨&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Junit Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JunitTestsShouldIncludeAssert&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JUnit tests should include assert() or fail()&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;assert나 fail이 있는 테스트코드가 의미가 있으나, 그렇지 않은 코드도 없는 것 보다는 나으므로, 테스트 코드 작성을 장려하기 위해 관대한 정책을 권장함&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Migration Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Junit4TestShouldUseTestAnnotation&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;JUnit 4 tests that execute tests should use the @Test annotation&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Junit3.8로 기준으로 작성된 코드에서 Junit4의 요건을 요구하면서 warning을 냄.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Naming Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;LongVariable&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Avoid excessively long variable names like …&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;표현력이 높은 변수이름을 짓는 것을 권장하기 위해서 길이제한을 두는 것은 바람직하지 않음&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Naming Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;ShortVariable&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Avoid variables with short names like…&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&quot;is&quot;,&quot;os&quot;등의 2글자 변수명일 때 나는 경고. 실제로 2글자만으로도 문맥상으로 충분한 표현력을 가지는 상황도 있고, 한 메소드가 크게 길어지지만 않는다면 local variable의 경우에는 다소 관대한 정책을 넣는 것도 무리는 없음&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Optimization Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;AvoidInstantiatingObjectsInLoops&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Avoid instantiating new objects inside loops&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Loop안에서 new keyword가 존재하면 나는 경고임. 피할 수 있는 경우라면 피해야 겠지만, GC시점이 약간이나마 늦어질 수 있고, collection.clear같은 청소 매서드 호출이 필요해진다거나, Thread safe해지지 않는 경우 등 손해가 있는 상황이 있으므로, 항상 지켜야할 지침이라고는 보기 어려움&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Optimization Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;LocalVariableCouldBeFinal&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Local variable &apos;&amp;#8230;&amp;#8203;&apos; could be declared final&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;제외요망&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;local 변수 중 final이 될 수 있는 것을 알려주는 정보. Final이 되면 경고를 주는 이와 상반된 룰도 존재함&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Optimization Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;MethodArgumentCouldBeFinal&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Parameter &apos;&amp;#8230;&amp;#8203;&apos; is not assigned and could be declared final&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;방어적 프로그래밍을 위해서 파라미터는 final로 선언하는 것이 바람직하나, 모든 메서드를 그렇게 하면 메소드 시그니처가 전부 다 길어지기 단점이 있음. 기술인프라성 공통코드에만 적용하는 방안이 바람직하다고 생각함&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Strict Exception Rules&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;SignatureDeclareThrowsException&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;A method/constructor shouldn&amp;#8217;t explicitly throw java.lang.Exception&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;메소드나 생성자가 throws Exception으로 최상위 Exception을 던질때 주는 경고. Checked Exception의 남발을 막고, 정교한 Exception선언을 돕는다는 장점도 있어서 되도록 권장하고 싶지만, 기존 코드의 Exception 선언이 throws Exception이 남용되어 있을 때 등 프로젝트 후반기에는 적용이 어려움&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;String and StringBuffer&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;AvoidDpolicateLiterals&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;The String literal &quot; rows, page &quot; appears 4 times in this file; the first occurrence is on line 70&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Warning&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;논의필요&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;중복 되는 문자열 상수가 많은 경우에 나타남. 내부적으로는 java의 상수풀을 사용하게 되므로 성능에는 영향은 없음. 반복되는 문자열도 따로 상수선언을 하게 하는, 좋은 코딩습관에 도움이 되나 ,테스트 클래스 등의 안내 메시지 등의 그리 중요하지 않은 부분에서 지나친 상수선언을 하게 할 수 있음.&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
	</description>
    </item>
    <item>
      <title>SpringOne2GX 2010 (2) Spring 3.0 &amp;#8594; 3.1 &amp;#8594; 3.2 Themes &amp;amp; Trends</title>
      <link>https://blog.benelog.net//2700328.html</link>
      <pubDate>Sat, 23 Oct 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2700328.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링 프레임웍의 핵심 개발자 유겐할러는 &quot;Spring 3.0 &amp;#8594; 3.1 &amp;#8594; 3.2 Themes &amp;amp; Trends&quot;라는 제목으로 두번째 날 첫시간에  발표를 했습니다. 로드존슨은 &lt;a href=&quot;http://www.yes24.com/24/goods/3691866&quot;&gt;어느 인터뷰&lt;/a&gt;에서 지금까지 만났던 가장 뛰어난 개발자는 생각할 것도 없이 유겐할러라고 말한 적이 있었고, 그렇게 전적인 신임을 받고 있어서 인지 유겐할러는 스프링 core 모듈의 대부분의 코드를 만들었다고 합니다. 그래서 유겐할러는 스프링 커뮤니티에서 로드존슨에 버금가는 유명인인데, 예상대로 이 날 발표 장소에는 사람들이 꽉 차게 몰려왔습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;발표에 들어가기 전에, 스프링 3.0 버전을 쓰는 사람과, 그중 실제 3.0에 들어간 기능을 쓰는 사람들은 손을 들어봐라고 유겐할러가 이야기했는데, 절반 정도의 사람들이 손을 들었던 것 같습니다. 컨퍼런스에 와서 그 발표를 들은 사람을 대상으로 한 질문이라서 당연한 것일수도 있지만 3.0버전의 기능이 커뮤니티에서는 호응이 괜찮다는 느낌이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;유겐할러는 자신이 맡은 책임 중 핵심적인 것은 클라우드, 분산 캐쉬와 같은 최신의 경향(modern day trends)에 맞추어 스프링 Core에 어떤 것이 들어갈지를 결정하는 것이라고 했습니다. 뒤에서 설명된 3.1의 캐쉬 Abstraction, 3.2의 fork-join pool 지원 등이 그런 트렌드을 쫓아가기위한 대표적인 예였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3.1과 3.2는 3.0의 자연스러운 다음버전이라고 합니다. 큰 구조변화보다는 기능이 추가되는 성격의 버전이라는 의미라고 생각되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/spring-one/spring-3-0-to-3-2.jpg&quot; alt=&quot;spring-3-0-to-3-2.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;릴리즈_일정&quot;&gt;릴리즈 일정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;발표 중간 중간에 이야기한 향후 스프링버전의 릴리즈 일정은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;3.0.5 : 2010년 10월에, 곧. 3.05 이후로 바로 3.1은 위한 마일스톤 버전으로 넘어간다고 했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.1 M1: 2010년 11월 말&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.1 M2: 2011년 1월 초?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.1 RC1 : 2011년 2월말?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.1 GA  : 2011년 3월말?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.2 :  2011년 말 경&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;뒤로 갈수록 물음표(?)가 들어가 있고, 지금까지 일정을 정확히 지킨 적이 없었던 것으로 보아 실제로 저 일정에 나올 가능성은 적다고 예상됩니다. Spring 3.0도 2008년에 이야기할 때는 2009년 초에 나올 것이라고 말한 적이 있는데, 실제로 3.0.GA 버전이 릴리즈된 것은 2009년 12월이였습니다. (&lt;a href=&quot;http://toby.epril.com/?p=459&quot;&gt;스프링 이슈트래커로 본 Spring 3.0의 전망&lt;/a&gt;, &lt;a href=&quot;https://jira.springframework.org/browse/SPR#selectedTab=com.atlassian.jira.plugin.system.project%3Aversions-panel&quot;&gt;Spring Jira의 실제 release 날짜&lt;/a&gt; 참조)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;일정관리를 엄격하게 안 하나 하는 생각도 들었는데, 공개 API의 모음인 프레임웍이라는 특성 때문에 일정보다는 완성도를 중요시 할 수 밖에 없을 것도 같았습니다. 두번째 날의 밤에 &apos;Birds of a feather session&amp;#8217;라고, 컨퍼런스의 트랙별로 발표자나 핵심개발자들과 이야기를 할 수 있는 자리를 마련해줬는데, 거기서 내부적으로 어떤 개발방법론을 쓰는지에 대한 질문이 나왔었습니다. 거기에서 스크럼 같은 일반적인 애자일 프랙티스를 따르기는 하는데, 프레임웍은 배포 한 후에 수정을 하는 것이 어렵기 떄문에 원래의 스크럼 방식과는 잘 맞지 않는 부분도 있다는 말이 답변 중에 나왔었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;a_review_3_0&quot;&gt;A review : 3.0&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3.0에 추가된 기능들을 되돌아 봤는데, 이미 널리 알려진 기능들이라서 자세히 옮기지는 않겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Annotated component model.(JSR 330, JSR 303, JavaConfig등)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rest Support&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Portlet 2.0 지원&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;스케쥴과 태스크 실행 개선&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java EE6 지원&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(자세히 아시고 싶으신 분들은 &lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/new-in-3.html&quot; class=&quot;bare&quot;&gt;http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/new-in-3.html&lt;/a&gt; 를 참조하시기 바랍니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이중 JavaEE6에 대한 내용은 세번째 날 역시 유겐할러가 발표한 &apos;Spring and JavaEE 6&amp;#8217;라는 세션에서 자세하게 설명 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;a_preview_3_1&quot;&gt;A preview : 3.1&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3.1에 추가되는 주요 테마들은 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Environment profiles for bean&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java-based applicaion configuration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cache abstraction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Conversation Managment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Servlet 3.0, JSF 2.0, Groovy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;environment_profiles_for_bean&quot;&gt;Environment profiles for bean&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 기능은 bean 설정들을 환경별로 묶는 것을 지원합니다. 즉, 개발환경, 테스트환경, 운영환경에 맞는 bean 선언들을 그룹화해서 지정할 수 있게 하는 것이죠.
Bean 선언이나 Annotation선언에 Profile을 이름을 지정하는 방식이 될 것이라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;XML선언에는 다음과 같이 profile속성이 추가됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;beans .... profile=&quot;dev&quot;&amp;gt;

  &amp;lt;bean&amp;gt;...&amp;lt;/bean&amp;gt;

  &amp;lt;bean&amp;gt;...&amp;lt;/bean&amp;gt;

&amp;lt;/beans&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Annotation으로는 @Profile 을 지정하게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Service

@Profile(&quot;dev&quot;)

class UserService\{

.....

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Inject 가능한 API 형식으로 환경에 대한 추상화도 지원할 것이라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보통 database의 URL 같은 속성들은 별도의 property 파일로 빼서 관리하는 placeholders라는 기능을 쓰는데, 이 기능도 실제 환경에 의존적으로 placeholder를 나름대로 지정할 수 있는 기능이 나온다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;java_based_applicaion_configuration&quot;&gt;Java-based applicaion configuration&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이미 3.0에 JavaConfig 기능이 포함되어서 @Configuration, @Bean 등의 Annotation이 추가되었는데, 어떤 기능들이 더 추가되는 것일지 궁금했었습니다. 3.1에 들어가는 Java-based configuration은 tx나, aop 같은 custom name-space와 같은 선언들이 Java파일로도 가능하게 하는 것이였습니다. Spring Batch나 Security, Integration에서도 이러한 custom name-space들을 많이 쓰는데, 그런 설정들이 xml로 bean 선언을 할 때는 설정을 간편하게 하지만, JavaConfig로 옮기기에는 불편해서 아쉬웠던 점이 있습니다. Spring Batch에서 ItemReader, ItemWriter 선언을 JavaConfig로 옮겨보니 훨씬 코드가 짧아지고 Compile time의 validation 범위도 넓어져서 만족했었는데, Job과 Step의 선언은 custom namespace로 하는 것이 더 간편해서, XML에 남겨두었었습니다. 그리고 유사하게&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;http://www.amazon.com/Enterprise-Integration-Patterns-Designing-Deploying/dp/0321200683/ref=sr_1_1?ie=UTF8&amp;amp;qid=1287800130&amp;amp;sr=8-1[Enterpise Interation Patterns]를 구현한 Apache Camel는 Java로 된 DSL을 지원하는데 반해서 Spring Integration은 간결한 선정을 하려면 Xml의 Custom name space를 사용해야 되어서 아쉬웠던 적이 있었습니다.(http://java.dzone.com/articles/spring-integration-and-apache 참조)&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3.1에서 그렇게 javaConfig로도 추상화정도를 높인 선언을 할 수 있는 기능이 추가된다면, 스프링포트 폴리오의 다른  프로젝트에서도 많은 개선이 이루어질 것이라고 기대됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;cache_abstraction&quot;&gt;Cache Abstraction&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재도 스프링에 org.springframework.cache라는 패키지는 존재합니다. 그런데 아직까지는 EhCache에 대한 지원클래스만 있습니다. 3.1에서는 이 패키지를 채워넣을 것이고, 분산캐쉬를 기술과 연결되는 구현체도 포함될 것이라고 합니다. 기본적으로 EhCache, GemFire, Coherence를 지원한다고 밝혔습니다. 물론 인터페이스에 맞춰서 직접 구현하는 것도 가능하고, ConcurrentHashMap을 이용한 간단한 기본 구현체도 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;발표가 끝난 뒤에 저와 같이 간 일행인 김훈민 대리가 직접 유겐할러에게 찾아가서 Memcached에 대한 지원 계획을 물어봤는데, out-of-box로 바로 쓸 수 있는 Adaptor는 아직까지는 계획에 없다고 했습니다. 여러 가지 라이센스 문제 등에 부딪힐 수 있어서 협의가 필요한데, 원한다면 이슈 등록을 하고 투표를 하라고 이야기했습니다.  개인적으로 Simple Spring Memecached( &lt;a href=&quot;http://code.google.com/p/simple-spring-memcached/&quot; class=&quot;bare&quot;&gt;http://code.google.com/p/simple-spring-memcached/&lt;/a&gt;) 프로젝트와 유사한 Memcached 지원이 Spring Core에 들어갔으면 했는데, 다소 아쉬운 부분이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;많은 곳에서 이미 Annotation과 AOP를 활용해서 Cache를 활용해서 이미 예상을 했었는데, Spring 3.1에는 아래와 같이 Cache 지원을 위한 `@Cacheable`, `@CacheEvict` 라는 Annotation이 추가됩니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Cacheable
public Owner loadOwner(int id) \{

....

}

@Cacheable(condition=&quot;name.length&amp;lt;10&quot;)
public Owner loadOwner(String name)\{

....

}

@CacheEvict
public void deleteOwner(int id)\{

....

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Annotation이 붙은 메소드의 signature와 파라미터로 캐쉬에 넣을 key로 인식하게 됩니다. 유겐할러는 이런 방식의 처리가 Cache의 전형적인 사용의 80% 유형정도를 차지할 것이라고 말했습니다. Cache Abstraction은 Cache의 모든 기능을 통합해서 같은 API로 묶는 것이라기보다는, 주요 쓰임새를 더 짧은 코드로 편하게 쓸 수 있게 하는데 초점이 맞춰진 것으로 보입니다. 정교한 캐쉬처리나 각각의 캐쉬 특성에 맞는 API사용 등은 직접 특정 Cache의 API를 당연히 사용해야겠죠.
그리고 transaction처리에 쓰는 PlatformTransactionManager와 비슷하게 CachedManager라는 SPI가 들어가고, tx namespace처럼 cache에 대한 namespace도 추가된다고 합니다. &amp;lt;cache:annotation-driven /&amp;gt;과 같은 선언은 기존에 스프링을 쓰던 사람에게는 익숙하게 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이미 EhCache를 Spring에서 활요할 때 쓰는 @Cacheable annotation이 있는데, 이 모델이 좀 더 확장될 것으로 보입니다. 아래 자료에 현재에도 사용가능한 방식이 나와 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable&quot; class=&quot;bare&quot;&gt;http://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable&lt;/a&gt;http://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://whiteship.tistory.com/1256&quot; class=&quot;bare&quot;&gt;http://whiteship.tistory.com/1256&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;converstion_management&quot;&gt;Converstion management&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Conversation session에 대한 추상화 계층을 추가될 예정이라고 합니다. 기본적으로 HttpSession을 포함하여 더욱 유연한 생존주기와 저장소를 제공한다고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보통 웹어플리케이션에서 여러 페이지간의 상태를 공유해야하는 Conversation 범위가 생길 수가 있는데, 보통 Session을 쓰는 것이 일반적이지만, 같은 윈도우에 있는 다른 탭이라도 같은 Session의 id가 먹는 상황이 있고, 수동적으로 이런 경계를 관리해야 할 때가 있습니다. 새로운 기능은 이런 것들을 위임할 수 있는 공통적인 기반을 제공한다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 기능은 Spring Webflow의 3.0에도 바탕이 될 것이고 MVC나 JSF에도 공통적으로 쓰일 수 있다고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 웹어플리케이션 뿐만이 아니라 메시징 환경에서 메시지 헤더 안에 conversation id를 포함시키는 쓰임새에도 적용가능하도록  Conversation을 위한 인터페이스는 범용적인 목적의 API가 될 것이라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;support_for_servlet_3_0&quot;&gt;Support for Servlet 3.0&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Tomcat 7, GlassFish 3 등에서 Servlet 3.0 스펙을 지원하는 Container에 대한 지원이 포함됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;web.xml에 명시적으로 프레임웍에 대한 Listener 선언을 하지 않고도  자동으로 deployment되는 옵션을 지원하고, 표준적인 파일업로드에 대한 지원이 된다고 합니다. 스프링의 MultipartResolver interface도 그 안에 포함될 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;enchance_groovy_support&quot;&gt;Enchance Groovy Support&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;lang:groovy&amp;gt;&lt;/code&gt; 로 xml 파일안에 Groovy를 쓸 수 있는 지원이 강화됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;base script classes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;custom bidings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;스프링 빈들을 이름으로 암묵적으로 접근&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Velocity나 Freemarker 대신 쓰일 수 있는 Groovy 바탕의 Template 파일. Email 템플릿 같은 것에 사용할 수 있다고 하네요&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;cnamespace&quot;&gt;&lt;code&gt;c:namespace&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;XML로 Bean 선언을 할 때 p namespace과 같은 역할을 하는 c namespace가 추가됩니다. p가 &amp;lt;property name.. &amp;gt; 에 대한 짧은 표현였다면, c는 &amp;lt;constructor-arg&amp;gt;를 간결하게 표현할 수 있게 줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래와 코드와 같이 거의 p namespace와 사용법이 똑같아 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;bean class=&quot;...&quot; c:age=&quot;10/&amp;gt;

&amp;lt;bean class=&quot;...&quot; c:family-ref=&quot;myFamily/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;a_sneak_preview_3_2&quot;&gt;A sneak preview : 3.2&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3.2에는 JDK 7을 바탕으로 추가될 수 있는 기능들을 계획하고 있었습니다. JDK 7은 2011년 7월에 release 예정이라서 3.2에 대한 Release도 당연히 그 뒤가 되겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 당연히 java 5, 6 user를 위한 기능도 있을 것이고, 그런 기능들은 Spring 3.1이 GA로 간 다음에 사용자들의 요청에 따라서 결정될 것이라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;java_se_7_support&quot;&gt;Java SE 7 Support&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring 3.2의 초점은 JRE 7 을 가장 잘 쓰는 사용법이라고 했습니다. JDBC 4.1에 대한 지원과 Java concurrent 패키지에서 개선되는 fork-join 프레임웍에 대한 지원계획이 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;멀티코어의_concurrent_프로그래밍에_초점&quot;&gt;멀티코어의 Concurrent 프로그래밍에 초점&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;동시 요청보다 Core수가 더 많은 시나리오에 초점을 맞추어서 API제공을 계획하고 있다고 합니다. 예를 들면 Spring Batch에서 큰 Xml파일을 처리할 때와 같이, 처리 요청은 하나이기 때문에 요청 건별로 병렬처리를 하기가 힘들지만 자원소모가 커서 병렬처리를 했을 때의 이득이 큰 경우를 염두에 둔 것이였습니다. 그런 곳에 사용할 수 있는 특화된 ForkJoinPool이 Application context안에 들어갈 것이라고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>SpringOne2GX 2010 (1) 키노트</title>
      <link>https://blog.benelog.net//2698818.html</link>
      <pubDate>Wed, 20 Oct 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2698818.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2010년 10월 19일부터 22일까지 시카고에서 열리는 SpringOne2GX 2010 행사에 참석하고 있는 중입니다. 정리가 완벽하게 되지 않아도 중간중간 듣고 본 것들을 올리도록 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;수정이력&quot;&gt;수정이력&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;(최초작성) 키노트의 내용을 발표 때 사용한 슬라이드 자료를 보면서 정리를 하려고 마음을 먹고 있었습니다. 그래서 특별히 메모도 하지 않고 있었는데, 키노트가 끝난 다음에서야 발표 자료가 아직 올라오지 않았다는 것을 알았습니다. 그래서 대략적으로 기억나는 것들을 같이 간 분들에게 물어보고 보완하면서 트위터에 올라온 글들 ( &lt;a href=&quot;http://twitter.com/#!/search/s2gx&quot; class=&quot;bare&quot;&gt;http://twitter.com/#!/search/s2gx&lt;/a&gt; )을 보고 회상한 내용으로  일단 정리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;키노트의_제목은&quot;&gt;키노트의 제목은?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로드존슨은 첫장에 키노트의 제목을 표시하지 않고 &apos;여기에 제목이 들어간다&amp;#8217;는 슬라이드 템플릿에 있을법한 문구를 그대로 보여줍니다. 나름대로 웃음을 주려고 했던 의도도 있었지만, 지난 12개월동안 스프링 세계에 어떤 일이 일어났는지를 키노트에서 정리해야 하는데, 이번에는 그것이 쉽지 않았음을 이야기 합니다. 발표가 한참 지나서야 제목이라고 할만한 내용들을 이야기하는데, 간단히 정리하면 &apos;스프링의 성공요인이였던 Portability, Productivity, Innovation을  다음 10년에도 새로운 영역에서 이어서 가고, 개발자들이 더욱 핵심 비지니스 가치의 전달에 전념할 수 있도록 돕는다&amp;#8217;라고 말할 수 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;그동안_스프링_세계에_있었던_일들&quot;&gt;그동안 스프링 세계에 있었던 일들..&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;키노트 발표는 이번 컨퍼런스의 주요 후원자인 Accenture, Google, Saleforce.com에 대한 스폰서들에 대한 감사의 이야기로 시작되었습니다. 이번 컨퍼런스를 통해 비니지스 어플리케이션을 전통적인 데이터 센터에서 클라우드 환경으로 올리겠다는 비전을 강조할 것이라고 미리 예상을 했었고,  인터넷 업계, 시스템통합, SaaS 업계의 최강자들인 이들 세 업체가 그런 비전을 공유하고 후원업체로 전면에 나와 있다는 생각이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이어서 얼마전에 발표되었던 vFabric cloud platform의 그림을 보여줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;참조 : &lt;a href=&quot;http://blog.springsource.com/2010/08/31/cloud-platform/&quot; class=&quot;bare&quot;&gt;http://blog.springsource.com/2010/08/31/cloud-platform/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 아키텍처에서 모니터링은 Hyperic, 메시징큐는 RabbitMQ, 캐슁을 위한 데이터그리드는 Gemfire가 들어가는데, 작년부터 스프링소스가 인수합병을 통해서 확보한 솔루션들입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이어서 이런 솔루션들이 현재 얼마나 중요한 시스템에서 쓰이고 있는지 레퍼런스를 알려줍니다. Gemfire는 NASA나 펜타곤에서, RabbitMQ는 인도의 수많은 인구들의 주민등록 정보를 관리하는 곳에 들어가 있다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 다른 스프링 포트폴리오 프로젝트의 대표적인 발전도 정리해줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Spring 3.1 : Cache abstraction, 환경에 특화된 bean 설정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring Integration 2.0 : Spring Tools Suite를 통한 plugin지원, 더 많은 Adaptor 지원&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring Web flow 3.0 : Java flow Definition 지원 (&lt;a href=&quot;https://jira.springsource.org/browse/SWF-295&quot; class=&quot;bare&quot;&gt;https://jira.springsource.org/browse/SWF-295&lt;/a&gt; 이 이슈를 말한 것으로 짐작됩니다.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Grails : Spring Tools Suite에 추가된 Grails 지원 기능을 Demo로 보여주었습니다. 프로젝트 생성,  Entity 생성, Grails command 입력등을 Eclipse 안에서 편하게 할 수 있고, Grails View에서 Grails에 구조에 맞는 디렉토리 구성을 더 편하게 볼 수 있었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개인적으로 Cache abstraction에 가장 관심이 가는데, 이 주제를 다룰 내일 유겐할러의 발표를 기대해 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;스프링의_지난_10년과_그_다음&quot;&gt;스프링의 지난 10년과 그 다음&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로드존슨은 스프링 프레임웍이 이제 새로운 10년대를 맞이하고 있음을 강조합니다. 로드존슨이 직접 밝힌 스프링에서 가장 오래된 클래스는 &lt;a href=&quot;http://www.jarvana.com/jarvana/view/org/springframework/spring-web/2.5.6.SEC02/spring-web-2.5.6.SEC02-sources.jar%21/org/springframework/web/context/support/RequestHandledEvent.java?format=ok&quot;&gt;RequstHandlerEvent.java&lt;/a&gt;로 2001년 1월 17일 처음 만들어졌습니다. (스프링의 오래된 클래스들에 대해서는 토비님이 쓰신 &lt;a href=&quot;http://toby.epril.com/?p=171&quot;&gt;스프링코드의 역사&lt;/a&gt; 글에서도 재밌는 정보들을 찾을 수 있습니다.) 로드 존슨의 큰 아들의 다음 생일이 10번째 생일인데, 걔가 스프링보다는 나이가 어리다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇게 10년을 지나오면서 스프링을 성공으로 이끌었던 핵심가치들은 Portability, Productivity, Innovation의 3가지 단어로 설명했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jetty, Tomcat을 포함한 많은 Application Server들에서 Spring 애플리케이션이 돌아갈 수 있었던 Portability는 이제 다음 목표를 이제 Google App Engine, VMForce와 같은 클라우스 서버 영역으로까지 나아가고 있습니다.  그리고 Grails, Spring Roo, Spring Tools Suite를 이용한 생산성 향상도 계속되고 있습니다. Spring Roo에서는 GWT 지원, Database reverese-engineering 기능이 포함된 다는 것을 홍보했습니다. 바이트코드 삽입(Instrumentation)을 통한 Application Monitoring 도구인 Spring Insight를 운영환경에서 사용할 수 있도록 성능저하가 없는 버전을 준비하고 있다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링이 나아가는 새로운 영역들 중에 Social media 결합, NoSQL 저장소 지원, 모바일 환경의 다양한 Client 지원 기능등이 특히나 신선한 소식이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;새로운_영역들&quot;&gt;새로운 영역들&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;nosql&quot;&gt;NoSql&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로드존슨은 NoSQL을 Not only SQL이라고 풀어줬습니다. 전통적인 데이터 저장소였던 RDB도 나름대로의 영역을 지키겠지만, 이제는 RDB만으로는 해결할 수 없는 문제들이 많아 생겼다는 것입니다. 그렇다고 RDB를 배제하는 것이 아니라는 것을 Not  only SQL이라는 말로 강조했다고 느껴졌습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이미 GORM에서는 Redis를 지원하는 Addon을 넣었다는 소식을 이미 들은 적이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.springsource.org/node/2839&quot;&gt;Grails adds GORM for Redis&lt;/a&gt; 참조&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이날은 neo4j(&lt;a href=&quot;http://neo4j.org/)를&quot; class=&quot;bare&quot;&gt;http://neo4j.org/)를&lt;/a&gt; 이용한 GraphDB 지원에 대한 코드를 보여줬습니다.  Graph DB는 친구사이 관계 같은 것이 저장되는 social media같은 서비스에 적합한 구조인데, 스프링과 neo4j의 결합은 원래 neo4j의 API를 쓰지 않고 annotation으로 필드 간의 관계를 설정하는 것이였습니다. 이것도 내부적으로는 Aspect J를 이용해서 동작한다고 했습니다. Graph DB를 위한 annotation은 JPA annotation과 같이 쓰여서 Domain object에서 관계형 DB와 Graph DB에 연결되는 정보를 동시에 볼 수 있었습니다.  로드존슨은 이를 polyglot persistence, cross persistence라고 표현했습니다. 다수의 저장소를 활용할 때 Java Object가 그 연결정보의 구심점이 되는 모습이 간단한 코드에서 잘 보여졌습니다. Spring roo에서도 Neo4j를 위한 Addon이 들어갔다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;Spring-data 프로젝트(http://git.springsource.org/spring-data)에서는 Graph DB이외에도 key-value, document, column 저장소들을 위한 하위 프로젝트가 진행되고 있었습니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;social_media와_다양한_client의_시대&quot;&gt;Social Media와 다양한 client의 시대&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로드존슨은 근래의 기술환경이 Mobile 환경의 다양한 Client와 브라우저에 대처해야 한다는 것을 상기시킵니다. 그리고 Twitter와 Facebook과 같이 Social Media와 연결된 개발도 중요한 이슈입니다. 스프링에서도 이에 대비하고 있는데, Spring-mobile(&lt;a href=&quot;http://git.springsource.org/spring-mobile)과&quot; class=&quot;bare&quot;&gt;http://git.springsource.org/spring-mobile)과&lt;/a&gt; Spring-social(&lt;a href=&quot;http://git.springsource.org/spring-mobile%29%EA%B3%BC%20Spring-social%28http://git.springsource.org/spring-social&quot; class=&quot;bare&quot;&gt;http://git.springsource.org/spring-mobile%29%EA%B3%BC%20Spring-social%28http://git.springsource.org/spring-social&lt;/a&gt;http://git.springsource.org/spring-social[&lt;a href=&quot;http://git.springsource.org/spring-social&quot; class=&quot;bare&quot;&gt;http://git.springsource.org/spring-social&lt;/a&gt;] ) 프로젝트가 이와 관련되어 있습니다. 그리고 GreenHouse(&lt;a href=&quot;http://git.springsource.org/greenhouse&quot; class=&quot;bare&quot;&gt;http://git.springsource.org/greenhouse&lt;/a&gt;)  프로젝트가 이들을 활용한 실제 예제가 되는 프로젝트입니다. GreenHouse는 &lt;a href=&quot;https://greenhouse.springsource.org/&quot; class=&quot;bare&quot;&gt;https://greenhouse.springsource.org/&lt;/a&gt; URL로 들어가볼 수 있고, Spring 개발자들의 social network 기능을 할 수 있어 보였습니다. iPhone와 Android client가 있는데, iPhone client는 시뮬레이터를 통해서 데모를 보여줬습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;missing_link_code_to_cloud&quot;&gt;Missing link - Code to Cloud&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로드존슨은 지금까지 스프링이 많은 영역들을 지원해왔지만, 그 중에 빠진 연결점(Missing Link)가 있었고, 그 부분은 소스관리, 이슈관리, Contious Integration, 배포에 관한 영역이였다고 합니다. 개발자들의 각각의 도구를 설치한다고 많은 시간을 소모하고 있는데, 이제 그 영역도 Code to Cloud라는 통합솔루션을 제공한다고 합니다. 이 솔루션은 Git + Hudson + Bugzilra + Mylyn + STS을 엮은 것이였습니다. Tasktop이라는 업체와 제휴를 통해 이를 제공했고, 시연도 직접 Tasktop의 CEO가 나와서 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;관련기사 : &lt;a href=&quot;http://www.marketwire.com/press-release/VMware-Brings-the-Cloud-to-Developers-With-Code2Cloud-Application-Lifecycle-Tools-NYSE-VMW-1337583.htm&quot; class=&quot;bare&quot;&gt;http://www.marketwire.com/press-release/VMware-Brings-the-Cloud-to-Developers-With-Code2Cloud-Application-Lifecycle-Tools-NYSE-VMW-1337583.htm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;소개와 데모 동영상: &lt;a href=&quot;http://tasktop.com/connectors/c2c.php&quot;&gt;http://tasktop.com/connectors/c2c.php&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring2gx 2010에서의 시연 동영상 (&lt;a href=&quot;http://plixi.com/p/51699717&quot;&gt;http://plixi.com/p/51699717&lt;/a&gt; )&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Eclipse에서 테스트 코드 작성을 편하게 하는 설정</title>
      <link>https://blog.benelog.net//2688165.html</link>
      <pubDate>Fri, 1 Oct 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2688165.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;static_import_설정&quot;&gt;Static Import 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Organize import (Ctrl + Shift + O) 해도 static import의 *가 풀리지 않도록 하는 설정.
Junit4 이후의 assert나, mock library, matcher 등을 사용할 때 편리하다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Windows-Preference-Orgnize import&lt;/code&gt; 의 &lt;code&gt;Number of static imports&amp;#8230;&amp;#8203;&lt;/code&gt; 를 1로&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/eclipse-test/organize-imports.jpg&quot; alt=&quot;organize-imports.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;test_메소드와_import에_대한_template_설정&quot;&gt;Test 메소드와 Import에 대한 Template 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;Window-Preference-Java-Editors-Template&lt;/code&gt; 에 자주 쓰는 템플릿을 추가한다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;각각의 항목의 의미는 다음과 같다&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;name : 축약해서 사용할 문자&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;context : 해당 템플릿을 사용하는 에디터 종류. 여기서는 Java코드를 입력하므로 &apos;Java&amp;#8217;로 선택한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pattern :  템플릿 내용&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;즉, Java에디터에서 &apos;name&apos; 으로 정한 문자열을 치고 Ctrl + Space를 누르면 &apos;pattern&amp;#8217;의 내용이 자동입력 된다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/eclipse-test/templates.jpg&quot; alt=&quot;templates.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;test_메소드_추가&quot;&gt;Test 메소드 추가&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트 메소드를 추가할 때 필요한 library import로 메소드 선언을 한꺼번에 해주는 템플릿이다.이름을 spec으로 지정해서 쓰고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@${testType:newType(org.junit.Test)}

public void ${specDescription}() {
  // given ${cursor}

  // when
  // then
  ${staticImport:importStatic(&apos;org.junit.Assert.*&apos;, &apos;org.mockito.BDDMockito.*&apos;, &apos;org.hamcrest.CoreMatchers.*&apos;)}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;test_라이브러리_import&quot;&gt;Test 라이브러리 import&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 메소드 추가 template에서 해주는 일 중 import를 하는 부분만 수행해준다.
ti라는 이름으로 지정해서 쓰고 있다.
(&lt;a href=&quot;http://wiki.kwonnam.pe.kr/java/junit/staticimports&quot; class=&quot;bare&quot;&gt;http://wiki.kwonnam.pe.kr/java/junit/staticimports&lt;/a&gt; 에서 참조했습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;${is1:importStatic(&apos;org.hamcrest.CoreMatchers.*&apos;)}${is2:importStatic(&apos;org.junit.Assert.*&apos;)}${is5:importStatic(&apos;org.mockito.Mockito.*&apos;)}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;spring_test_지원_annotation_추가&quot;&gt;Spring test 지원 Annotation 추가&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Junit4에서 Spring의 Application context를 올리는 테스트를 할 때 필요한 Annotation과 import 선언을 추가해주는 Template이다. &apos;springtest&amp;#8217;라는 이름으로 지정해서 쓰고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;${:import(&apos;org.junit.runner.RunWith&apos;,&apos;org.springframework.test.context.ContextConfiguration&apos;,&apos;org.springframework.test.context.junit4.SpringJUnit4ClassRunner&apos;)}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { &quot;/applicationContext.xml&quot;})&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;mockito_관련_템플릿_추가&quot;&gt;Mockito 관련 템플릿 추가&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JUnit에서 Mocito의 annotation을 편하게 쓸 수 있게 해주는 선언이다. &apos;mockrun&amp;#8217;이라는 이름으로 지정해서 쓰고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;${:import(&apos;org.mockito.runners.MockitoJUnitRunner&apos;)}
@RunWith(MockitoJUnitRunner.class)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;참고자료&quot;&gt;참고자료&lt;/h4&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.rapaul.com/2009/08/09/bddmockito-eclipse/&quot; class=&quot;bare&quot;&gt;http://www.rapaul.com/2009/08/09/bddmockito-eclipse/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://toby.epril.com/?p=1126&quot; class=&quot;bare&quot;&gt;http://toby.epril.com/?p=1126&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://wiki.kwonnam.pe.kr/java/junit/staticimports&quot; class=&quot;bare&quot;&gt;http://wiki.kwonnam.pe.kr/java/junit/staticimports&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://help.eclipse.org/helios/index.jsp?topic=/org.eclipse.jdt.doc.user/concepts/concept-template-variables.htm&quot; class=&quot;bare&quot;&gt;http://help.eclipse.org/helios/index.jsp?topic=/org.eclipse.jdt.doc.user/concepts/concept-template-variables.htm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;favorite_설정&quot;&gt;Favorite 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자주 쓰는 static import 등록할 수 있음. 여기에 등록하면 미리 해당 라이브러리를 static import하지 않아도 Conentnt assist(Ctrl + Space)에서 에서 나오게 됨&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Windows-Preference-&amp;#8230;&amp;#8203;&amp;#8230;&amp;#8203;- Favorites&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/eclipse-test/favorites.jpg&quot; alt=&quot;favorites.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Favorites에 등록을 추천하는 클래스
* &lt;code&gt;org.junit.Assert.&lt;strong&gt;&lt;/code&gt;
* &lt;code&gt;org.hamcrest.CoreMatchers.&lt;/strong&gt;&lt;/code&gt;
* &lt;code&gt;org.mockito.Mockito.&lt;strong&gt;&lt;/code&gt;
* &lt;code&gt;org.mockito.BDDMockito.&lt;/strong&gt;&lt;/code&gt;
* &lt;code&gt;org.mockto.Matchers.*&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;자동생성되는_메소드에_unsupportedoperationexception_던지기_설정&quot;&gt;자동생성되는 메소드에 UnsupportedOperationException 던지기 설정&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자동생성 되어서 아직 구현되지 않은 메소드를 test fail로 인식하게 함 ( &lt;a href=&quot;http://toby.epril.com/?p=706&quot; class=&quot;bare&quot;&gt;http://toby.epril.com/?p=706&lt;/a&gt; 참고)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Preference – Java – Code Style – Code Templates 안에 Code/Method Body에 아래 코드추가&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;throws new UnsupportedOperationException();&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/eclipse-test/unsupported-operation-exception.jpg&quot; alt=&quot;unsupported-operation-exception.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;테스트_코드_짤_때_자주_쓰는_단축키&quot;&gt;테스트 코드 짤 때 자주 쓰는 단축키&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;코드_생성&quot;&gt;코드 생성&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl+ 1&lt;/code&gt; : Quick fix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + Shift + O&lt;/code&gt; : import 절에 없는 클래스를 추가하거나 정리&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + Shirt + M&lt;/code&gt; : import 추가. 클래스 import를 static import로 전환할 수 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;테스트_실행&quot;&gt;테스트 실행&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Alt + Shift + X, T&lt;/code&gt; : JUnit으로 실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + F11&lt;/code&gt; : Run (JUnit 실행 가능)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;코드_사이를_이동&quot;&gt;코드 사이를 이동&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + J&lt;/code&gt; : 테스트 코드와 실제 코드 사이를 이동 (moreUnit이 설치되어 있을 때)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + Q&lt;/code&gt; : 가장 마지막에 편집한 코드가 있는 곳으로 돌아가기&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + T&lt;/code&gt; : 인터페이스에서 구현 클래스 찾을 때&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl + Shift + 위아래 방향키&lt;/code&gt; : method 단위로 커서 이동(method 하나만 test 실행할 때 사용 하기 좋음&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;리팩토링&quot;&gt;리팩토링&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Alt + Shift + R&lt;/code&gt; : 리팩토링 이름 바꾸기&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Alt + Shift + V&lt;/code&gt; : 리팩토링 – 이동&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Alt + Shift + M&lt;/code&gt; : 리팩토링 – 메소드 추출&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Alt + Shift + I&lt;/code&gt; : 리팩토링 - 메서드 인라이닝 (추출의 반대)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Alt + Shift+ L&lt;/code&gt; : 리팩토링 local 변수 추출&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;more_unit&quot;&gt;More Unit&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트 코드와 실제코드 사이를 왔다갔다 할 수 있게 하는 Eclipse plugin. TDD의 리듬 유지에 도움이 됨&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;update site : &lt;a href=&quot;http://moreunit.sourceforge.net/update-site/&quot; class=&quot;bare&quot;&gt;http://moreunit.sourceforge.net/update-site/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Maven을 쓰고 있다면 설치후 Window-Preference-More Unit에서 아래 설정을 추가하는 것이 좋다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Directory for testcases : &lt;code&gt;src/test/java&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test Suffixes : &lt;code&gt;Test&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/eclipse-test/more-unit.jpg&quot; alt=&quot;more-unit.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;eclemma&quot;&gt;Eclemma&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eclipse 내에서 Code Coverage 측정. &lt;a href=&quot;http://blog.benelog.net/2212119&quot; class=&quot;bare&quot;&gt;http://blog.benelog.net/2212119&lt;/a&gt; 참조&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;update site : &lt;a href=&quot;http://update.eclemma.org/&quot; class=&quot;bare&quot;&gt;http://update.eclemma.org/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;STS를 쓴다면  Dashboard-Extensions에서 선택해서 설치해도 됨&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>private 메소드를 어떻게 테스트해야 할까요?</title>
      <link>https://blog.benelog.net//2685835.html</link>
      <pubDate>Mon, 27 Sep 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2685835.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;private 메소드를 어떻게 테스트해야 할지에 대한 질문은 테스트 코드 작성에 대한 자주 나오는 질문 중에 하나입니다. 이에 대한 답변을 간단히 정리해봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;많은 경우에 private method는 public 메소드에서 extract method 되어서 나온 것이므로 public을 통해서 간접적으로 테스트를 하는 것이 자연스럽습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;그래도 private 영역만 따로 테스트를 해야지 더욱 다양한 테스트 케이스를 편하게 작성할 수 있다거나 디버깅이 쉬워진다면 설계를 개선하라는 신호로 해석할만합니다. private 메소드가 하는 일이 크다는 신호로 해석하고 별도의 클래스로 분리하거나, 하위 클래스에서 상속을 해서 대체할 수 있는 가능성을 고려해서 protected로 해두는 것도 고려해 볼만합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;그럼에도_불구하고_private_메서드를_테스트해야한다면&quot;&gt;그럼에도 불구하고 private 메서드를 테스트해야한다면&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설계 개선 등을 바로 할 수 없는 상황이라 당장 private 메서드를 테스트해야할때 쓸수 있는 방법을 이어서 소개합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;테스트만을 해당 메소드를 package private(default 접근자)나 protected로 바꾸어서 테스트해볼 수도 있습니다. 일반적으로 테스트를 위해서 production 코드의 접근 범위를 넓히는 것은 클래스의 노출 범위를 커지게 하므로 바람직하지 않을 수도 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reflection을 이용하면 강제적으로 private 메소드를 호출할 수 있습니다. 다만 이렇게 하면 메소드이름 부분이 String값으로 넘겨지게 되므로, compile time에 메소드명의 오타가 검증되지 못하고, refactoring으로 메소드명을 바꾸어도 자동으로 String으로 적힌 부분은 바뀌지 않는 단점이 있습니다. 부작용을 감수하고서라도 쓰겠다고 각오가 된 곳에 제한적으로 사용하기를 권장드립니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Reflection으로 private 메서드를 호출하는 방법들은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;1_powermock에서_whitebox_invokemethod_메소드_활용&quot;&gt;(1) PowerMock에서 Whitebox.invokeMethod(..) 메소드 활용&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://code.google.com/p/powermock/wiki/BypassEncapsulation&quot; class=&quot;bare&quot;&gt;http://code.google.com/p/powermock/wiki/BypassEncapsulation&lt;/a&gt;  참조&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;2_junit_addons에_포함된_privateaccessor&quot;&gt;(2) JUnit Addons에 포함된 PrivateAccessor&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;프로젝트홈 : &lt;a href=&quot;http://sourceforge.net/projects/junit-addons&quot; class=&quot;bare&quot;&gt;http://sourceforge.net/projects/junit-addons&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API 문서 : &lt;a href=&quot;http://junit-addons.sourceforge.net/junitx/util/PrivateAccessor.html&quot; class=&quot;bare&quot;&gt;http://junit-addons.sourceforge.net/junitx/util/PrivateAccessor.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;샘플코드 : &lt;a href=&quot;http://entireboy.egloos.com/4030723&quot; class=&quot;bare&quot;&gt;http://entireboy.egloos.com/4030723&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;3_직접_reflection_활용&quot;&gt;(3) 직접 Reflection 활용&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package edu.tdd;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Test;

public class ReflectionCallUtilsTest {

    @Test
    public void testCallPrivate() throws SecurityException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        UnderTest ut = new UnderTest();
        String name = &quot;jsh&quot;;
        String address = &quot;서울시 마포구&quot;;
        invoke(ut, &quot;print&quot;,name, address);
        assertThat(ut.isCalled(),is(true));
    }

    @Test
    public void testCallWithPrimitiveType() throws SecurityException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        UnderTest ut = new UnderTest();
        String name = &quot;jsh&quot;;
        int age = 35;
        boolean printed = (Boolean)invoke(ut, &quot;print&quot;, new Class&amp;lt;?&amp;gt;[]\{String.class, int.class}, name,age);
        assertThat(printed,is(true));
        assertThat(ut.isCalled(),is(true));
    }
    private Object invoke(Object ut, String methodName, Class&amp;lt;?&amp;gt;[] argTypes, Object ... args) throws SecurityException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = ut.getClass().getDeclaredMethod(methodName, argTypes);
        method.setAccessible(true);
        return method.invoke(ut, args);
    }

    private Object invoke(Object ut, String methodName, Object ... args) throws SecurityException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        int argSize = args.length;
        Class&amp;lt;?&amp;gt;[] argTypes = new Class&amp;lt;?&amp;gt;[argSize];
        for(int i=0;i&amp;lt; argSize;i++){
            argTypes[i] = args[i].getClass();
        }
        return invoke(ut,methodName, argTypes, args);
    }

    static class UnderTest {
        private boolean called = false;
        public boolean isCalled(){
            return called;
        }
        private void print(String name, String address) {
            System.out.println(name);
            System.out.println(address);
            called = true;
        }
        private boolean print(String name, int age){
            System.out.println(name);
            System.out.println(age);
            called = true;
            return true;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;필요하다면 test 코드 안에서 쓰이고 있는 invoke메소드를 따로 Util 클래스로 분리할 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 코드에서 &lt;code&gt;Object .. args&lt;/code&gt; 넘어가는 부분이 primitive type이 포함되면 &lt;code&gt;Object[]&lt;/code&gt; 로 바뀌는 과정에서 Wrapper class로 바뀌는  auto-boxing이 발생하게 됩니다. 그래서 매개변수에 primitive type이 있을 때는 &lt;code&gt;invoke(Object ut, String methodName, Object &amp;#8230;&amp;#8203; args)&lt;/code&gt; 사용하면 NoSuchMethodException이 발생하게 됩니다. 그럴 때는 type을 정확히 명시해 주는 &lt;code&gt;invoke(Object ut, String methodName, Class&amp;lt;?&amp;gt;[] argTypes, Object &amp;#8230;&amp;#8203; args)&lt;/code&gt; 을 사용하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료&quot;&gt;참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.artima.com/suiterunner/private.html&quot;&gt;Testing Private Methods with JUnit and SuiteRunner&lt;/a&gt; : 위의 글과 비슷하게 아래 4가지 방법을 제시하고 있습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Don&amp;#8217;t test private methods.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give the methods package access.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use a nested test class.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use reflection.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/24/goods/3908398&quot;&gt;채수원 저 테스트 주도개발&lt;/a&gt; 441페이지 : public을 테스트함으로서 간접적으로 테스트하는 방식을 권장하고 굳이 한다면 Reflection을 사용할 수 있다는 점을 언급하고 있으나 부서지기 쉬운 테스트 코드가 되기 쉬움을 경고 하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://xper.org/wiki/xp/TestingPrivateInterfaces&quot; class=&quot;bare&quot;&gt;http://xper.org/wiki/xp/TestingPrivateInterfaces&lt;/a&gt; : Xper에서 논의된 내용&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>로또 번호 생성기 파이썬으로 만들어보기</title>
      <link>https://blog.benelog.net//2669675.html</link>
      <pubDate>Sat, 28 Aug 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2669675.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오래 전에 블로그에 Java를 이용한 로또 번호 생성기(&lt;a href=&quot;http://blog.benelog.net/1646013&quot; class=&quot;bare&quot;&gt;http://blog.benelog.net/1646013&lt;/a&gt; )코드를 올린 적이 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;갑자기 생각이 나서, 이번에는 Python으로 코딩해봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;문제&quot;&gt;문제&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1부터 45까지의 숫자 중에 6개를 뽑는다.6개의 값이 다 달라야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;출력시 작은 숫자부터 순서대로 출력&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;풀이&quot;&gt;풀이&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;import random
balls = range(1,46)
random.shuffle(balls)
print sorted(balls[0:6])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>BeanUtils 성능비교 - Apache commons, Opensymphony, Spring</title>
      <link>https://blog.benelog.net//2626007.html</link>
      <pubDate>Thu, 17 Jun 2010 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2626007.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;div class=&quot;title&quot;&gt;수정이력&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2019.04.14&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;테스트한 코드를 &lt;a href=&quot;https://github.com/benelog/beanutils-test&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/beanutils-test&lt;/a&gt; 로 올림&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2010.06.21&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://colus.egloos.com/&quot;&gt;EP&lt;/a&gt;님의 지적으로 Apache commons Beanutils에서도 &lt;a href=&quot;http://commons.apache.org/beanutils/api/org/apache/commons/beanutils/PropertyUtils.html#getPropertyDescriptor(java.lang.Object,%20java.lang.String)&quot;&gt;PropertyUtils.getPropertyDescriptor&lt;/a&gt;를 사용한 방식으로 비교해봤습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.tuning-java.com/&quot;&gt;이상민님&lt;/a&gt;의 지적으로 직접 Bean의 setter를 호출하는 방식의 자료도 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BeanUtils.populate의 경우 다양한 type을 가진 property 변환을 하는 등의  실제로 많은 기능을 수행함을 설명 추가를 했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;div class=&quot;title&quot;&gt;덧붙임&lt;/div&gt;
&lt;p&gt;2019년 2월 시점에 다시 이글을 보니, JMH로 테스트하는 것이 좋았을것 같다는 생각이 듭니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;조사_내용&quot;&gt;조사 내용&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;getter, setter가 있는 Java Bean들의 property들을 복사할 때 apache commons  beanutils(&lt;a href=&quot;http://commons.apache.org/beanutils/&quot; class=&quot;bare&quot;&gt;http://commons.apache.org/beanutils/&lt;/a&gt; ) 가 많이 쓰이고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Spring이나 Opensympony 쪽에도 간단한 BeanUtils가 따로 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Spring BeanUtils : &lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/beans/BeanUtils.html&quot; class=&quot;bare&quot;&gt;http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/beans/BeanUtils.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Opensymphony BeanUtils  : &lt;a href=&quot;http://www.opensymphony.com/oscore/api/com/opensymphony/util/BeanUtils.html&quot; class=&quot;bare&quot;&gt;http://www.opensymphony.com/oscore/api/com/opensymphony/util/BeanUtils.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이러한 BeanUtils들은 reflection을 내부적으로 쓰고 있으니 성능이 안 좋을 것이라는 우려를 할 수 있습니다. 어느 정도 성능 손해는 개발 편의성을 위해서 희생할 수 있는 경우도 많습니다. 그런데, 유사한 용도로 Bean복사를 하더라도 Apache commons BeanUtils쪽이 성능이 더 안 좋다는 소문은 있고 아래 자료에 의하면 100k byte object를 다룰 때는 Spring의 beanUtils에 비하면 거의 5~6배정도의 성능차이가 나는 것으로 알려져있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.christianschenk.org/blog/java-bean-mapper-performance-tests/&quot; class=&quot;bare&quot;&gt;http://www.christianschenk.org/blog/java-bean-mapper-performance-tests/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이는 commons BeanUtils가 단순히 bean을 복사하는 것보다는 많은 기능을 가진 상위수준의 라이브러리 이기 때문에 당연한 결과라고 보여집니다.
Spring이나 Opensymphony의 BeanUtils는 단순히 Bean간의 데이터 복사, 속성정보를 얻어오는 정도인데, apache commons의 BeanUtils는 DynaBean 등 보다 넓은 개념들을 확장해서 제공하고 있습니다.
그리고 BeanUtils.populate는 복사하려는 type이 정확하게 일치하지 않을 경우에도 복사를 해주는 등, 더 확장된 기능을 제공하고 있습니다.
(첨부한 소스의 BeanConverterTypeConversionTest 파일을 보면 Map에 String으로 들어간 속성이 Bean에는 Integer, BigDecimal로 선언되어 있을 때, BeanUtils.populate는 제대로 복사를 해주는 것을 보여줍니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다면 type이 일치하는 단순한 Bean간의 복사등에는 굳이 apache commons beanutils까지 사용할 필요가 없은 경우도 많을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래에서는 &lt;code&gt;List&amp;lt;Map&amp;gt;&lt;/code&gt; &amp;#8594; &lt;code&gt;List&amp;lt;Bean&amp;gt;&lt;/code&gt; 로 변환을 시켜주는 기능을 각각의 library로 구현해서 성능비교를 해보았습니다. 소스들은 Eclipse project형태로 첨부파일에 들어가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;비교군은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;By Hand : 직접 map.get과 bean의 setter를 이용해서 복사&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring(property descriptor)  : Spring의 BeanUtils.getPropertyDescriptors를 사용. Spring의 BeanUtils에는 직접적으로 Map에서 Bean으로 복사해주는 메소드가 없어서 이 방식을 이용했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apache Commons(property descriptor) : 2번과 같은 방식을 사용하고, 라이브러리만 Apache commons BeanUtils의 PropertyUtils.getPropertyDescriptor를 사용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apache Commons(populate) : Apache comons BeanUtils의 BeanUtils.populate 메소드를 사용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenSymphony(setValues) : Opensymphony의 BeanUtils.setValues를 사용&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;실행_시간_비교_결과&quot;&gt;실행 시간 비교 결과&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;1만번부터 10만번까지 변환갯수를 늘여주면서(X축), 밀리세컨트 단위로 간단히 수행시간(Y축)을 측정했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/beanutils/graph-1.jpg&quot; alt=&quot;image&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Opensymphony 쪽이 너무 많이 차이가 나서 제외하고 나머지 4개만 다시 그려봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/beanutils/graph-2.jpg&quot; alt=&quot;image&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all stretch&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;col style=&quot;width: 25%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-right valign-top&quot;&gt;size&lt;/th&gt;
&lt;th class=&quot;tableblock halign-right valign-top&quot;&gt;By hand&lt;/th&gt;
&lt;th class=&quot;tableblock halign-right valign-top&quot;&gt;Spring (property descriptor)&lt;/th&gt;
&lt;th class=&quot;tableblock halign-right valign-top&quot;&gt;Apache Commons (property descriptor)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;0&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;10,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;15&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;78&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;63&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;20,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;32&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;93&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;125&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;30,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;15&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;141&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;141&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;40,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;32&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;188&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;187&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;50,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;15&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;218&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;250&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;60,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;15&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;250&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;297&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;70,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;32&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;297&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;359&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;80,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;32&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;359&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;360&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;90,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;46&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;406&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;438&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;100,000&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;63&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;454&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-right valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;484&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;결론&quot;&gt;결론&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;19개의 속성을 가진 Bean을 대상으로 했을 때, Map&amp;#8594;Bean 변환의 10만건의 경우 순위는 아래와 같았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;By Hand &amp;gt; Spring(property descriptor) = Apache Commons(property descriptor) &amp;gt; Apache Commons(populate) &amp;gt; OpenSymphony(setValues)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;직접 손으로 한 것이 Sprng BeanUtils나 Commons의 PropertyUtils로 propery descriptor를 통해 호출한 것보다 7배 이상 빨랐습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 PropertyDescriptor를 활용한 방식들이 그 다음 순위로, 비슷한 실행속도가 나왔습니다. Spring BeanUtils쪽에서는 Bean객체의 정보를 &lt;a href=&quot;http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/beans/CachedIntrospectionResults.html&quot;&gt;CachedIntrospectionResults&lt;/a&gt;라는 클래스에 저장을 해 두고 있습니다. 그리고 Apache commons의 PropertyUtils에서도 유사하게 PropertyUtilsBean안에 descriptorsCache라는 속성으로 Bean정보를 Cache하고 있습니다. 그래서 실행속도가 거의 비슷하게 나온듯 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Apache commons BeanUtils.populate는 2,3위 순위의 Property descriptor를 활용한 것들보다  6배 정도 실행시간이 더 걸리는 것으로 나왔습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;BeanUtils.populate가 더 느린 이유는 두가지로 분석이 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;위에서 말한 것처럼 좀 더 확장된 type변환을 지원하기 떄문입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PropertyDescriptor의 배열을 순회하는 방식이 아닌, Map의 keySet을 순회하는 방식을 쓰고 있는데, 아무래도 배열의 순회보다는 성능에는 불리할 것 같습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;Iterator names = properties.keySet().iterator();
while (names.hasNext()) {
    // Identify the property name and value(s) to be assigned
    String name = (String) names.next();
    if (name == null) {
        continue;
    }
    Object value = properties.get(name);
    // Perform the assignment for this property
    setProperty(bean, name, value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Opensymphony쪽은 70배이상 더 느린데, 제가 구현한 방식이 문제가 있는 건지도 모르겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아뭏든 위의 결과를 봐서는 되도록 성능이 민감한 곳에는 직접 setter를 호출해서 복사를 하고, BeanUtils.populate의 다양한 기능이 필요하지 않다면 Spring의 BeanUtils나 Apache commons PropertyUtils를 통해 캐쉬된 PropertyDescriptor를 정보를 통해 Bean에 접근하는 것이 성능에는 유리하다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;소스&quot;&gt;소스&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/benelog/beanutils-test&quot; class=&quot;bare&quot;&gt;https://github.com/benelog/beanutils-test&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Apache commons BeanUtils의 BeanUtils.populate 활용&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public &amp;lt;T extends Map&amp;lt;String,Object&amp;gt;, C&amp;gt; List&amp;lt;C&amp;gt; convertMapToBean(List&amp;lt;T&amp;gt; list,
  Class&amp;lt;C&amp;gt; clazz) {
    List&amp;lt;C&amp;gt; beanList = new ArrayList&amp;lt;C&amp;gt;();

    for (T item : list) {
        C bean = null;
        try {
            bean = clazz.newInstance();
            BeanUtils.populate(bean, item);
        } catch (InstantiationException e) {
            new IllegalArgumentException(&quot;Cannot initiate class&quot;,e);
        } catch (IllegalAccessException e) {
            new IllegalStateException(&quot;Cannot access the property&quot;,e);
        } catch (InvocationTargetException e) {
            new IllegalArgumentException(e);
        }
        beanList.add(bean);
    }
    return beanList;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Apache commons BeanUtils : PropertyUtils.getPropertyDescriptors활용&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public &amp;lt;T extends Map&amp;lt;String, Object&amp;gt;, C&amp;gt; List&amp;lt;C&amp;gt; convertMapToBean(
    List&amp;lt;T&amp;gt; list, Class&amp;lt;C&amp;gt; clazz) {
    List&amp;lt;C&amp;gt; beanList = new ArrayList&amp;lt;C&amp;gt;();

    for (T source : list) {
        C bean = null;

        try {
            bean = clazz.newInstance();

            PropertyDescriptor[] targetPds = PropertyUtils.getPropertyDescriptors(clazz);

            for (PropertyDescriptor desc : targetPds) {
                Object value = source.get(desc.getName());
                if (value != null) {
                    Method writeMethod = desc.getWriteMethod();
                    if (writeMethod != null) {
                        writeMethod.invoke(bean, new Object[] { value });
                    }
                }
            }
        } catch (InstantiationException e) {
            new IllegalArgumentException(&quot;Cannot initiate class&quot;,e);
        } catch (IllegalAccessException e) {
            new IllegalStateException(&quot;Cannot access the property&quot;,e);
        } catch (InvocationTargetException e) {
            new IllegalArgumentException(e);
        }
        beanList.add(bean);
    }
    return beanList;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;OpenSymphony BeanUtils : setValues 활용&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public &amp;lt;T extends Map&amp;lt;String, Object&amp;gt;, C&amp;gt; List&amp;lt;C&amp;gt; convertMapToBean(List&amp;lt;T&amp;gt; list, Class&amp;lt;C&amp;gt; targetClass) {
    List&amp;lt;C&amp;gt; beanList = new ArrayList&amp;lt;C&amp;gt;();

    for (Map&amp;lt;String, Object&amp;gt; map : list) {
        C bean = null;
        try {
            bean = targetClass.newInstance();
            BeanUtils.setValues(bean, map, null);
        } catch (InstantiationException e) {
            new IllegalArgumentException(&quot;Cannot initiate class&quot;, e);
        } catch (IllegalAccessException e) {
            new IllegalStateException(&quot;Cannot access the property&quot;, e);
        }
        beanList.add(bean);
    }
    return beanList;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Spring BeanUtils : getPropertyDescriptors 활용&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public &amp;lt;T extends Map&amp;lt;String, Object&amp;gt;, C&amp;gt; List&amp;lt;C&amp;gt; convertMapToBean(
    List&amp;lt;T&amp;gt; list, Class&amp;lt;C&amp;gt; clazz) {
    List&amp;lt;C&amp;gt; beanList = new ArrayList&amp;lt;C&amp;gt;();
    for (Map&amp;lt;String, Object&amp;gt; source : list) {
        C bean = toBean(source, clazz);
    beanList.add(bean);

    }
    return beanList;
}

private &amp;lt;C&amp;gt; C toBean(Map&amp;lt;String, Object&amp;gt; source, Class&amp;lt;C&amp;gt; targetClass) {
    C bean = null;
    try {
        bean = targetClass.newInstance();
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(targetClass);

        for (PropertyDescriptor desc : targetPds) {
            Object value = source.get(desc.getName());
            if (value != null) {
                Method writeMethod = desc.getWriteMethod();
                if (writeMethod != null) {
                    writeMethod.invoke(bean, new Object[] { value });
                }
            }
        }
    } catch (InstantiationException e) {
        new IllegalArgumentException(&quot;Cannot initiate class&quot;,e);
    } catch (IllegalAccessException e) {
        new IllegalStateException(&quot;Cannot access the property&quot;,e);
    } catch (InvocationTargetException e) {
        new IllegalArgumentException(e);
    }
    return bean;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;성능측정_코드&quot;&gt;성능측정 코드&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@Test
public void testApacheCommonsBeanUtils() {
    BeanConverter converter = new ApacheCommonsBeanUtilsBeanConverter();
    executeIncrementally(converter);
}

@Test
public void testApacheCommonsPropertyUtils() {
    BeanConverter converter = new ApacheCommonsPropertyUtilsBeanConverter();
    executeIncrementally(converter);
}

@Test
public void testOpenSymphony() {
    BeanConverter converter = new OpenSymphonyBeanConverter();
    executeIncrementally(converter);
}

@Test
public void testSpring() {
    BeanConverter converter = new SpringBeanConverter();
    executeIncrementally(converter);
}

@Test
public void testByHand() {
    BeanConverter converter = new UserConverter();
    executeIncrementally(converter);
}

private void excuecteBeanConverter(BeanConverter converter, int iterations) {
    List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; testList = createMapListForTest(iterations);
    long start = System.currentTimeMillis();
    List&amp;lt;User&amp;gt; beanList = converter.convertMapToBean(testList, User.class);
    long end = System.currentTimeMillis();
    System.out.printf(&quot;%s,%d times, %d milliseconds \r\n&quot;, converter.getClass().getSimpleName(), iterations, (end - start));
}

private void executeIncrementally(BeanConverter converter) {
    for (int i = 0; i &amp;lt;= 100000; i += 10000) {
        excuecteBeanConverter(converter, i);
    }
}

private List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; createMapListForTest(int iterations) {
    List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; list = new ArrayList&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;();

    Map&amp;lt;String, Object&amp;gt; user = new HashMap&amp;lt;String, Object&amp;gt;();
    user.put(&quot;id&quot;, 1);
    user.put(&quot;age&quot;, 1);
    user.put(&quot;name&quot;, &quot;내이름&quot;);
    user.put(&quot;name1&quot;, &quot;내이름&quot;);
    user.put(&quot;name2&quot;, &quot;내이름&quot;);
    user.put(&quot;name3&quot;, &quot;내이름&quot;);
    user.put(&quot;name4&quot;, &quot;내이름&quot;);
    user.put(&quot;name5&quot;, &quot;내이름&quot;);
    user.put(&quot;name6&quot;, &quot;내이름&quot;);
    user.put(&quot;name7&quot;, &quot;내이름&quot;);
    user.put(&quot;name8&quot;, &quot;내이름&quot;);
    user.put(&quot;name9&quot;, &quot;내이름&quot;);
    user.put(&quot;name10&quot;, &quot;내이름&quot;);
    user.put(&quot;income&quot;, new BigDecimal(&quot;1000100100&quot;));
    user.put(&quot;address&quot;, &quot;오늘 아침 내가 행복한 이유는 이런거지 오늘아침 내가 서러운 이유는 그런거야 &quot;);
    user.put(&quot;introduce&quot;, &quot;오늘 아침 내가 행복한 이유는 이런거지 오늘아침 내가 서러운 이유는 그런거야 &quot;);
    user.put(&quot;married&quot;, true);
    user.put(&quot;nickName&quot;, &quot;뻐꾸기&quot;);

    for (int i = 0; i &amp;lt; iterations; i++) {
        list.add(user);
    }
    return list;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Customer tag library에 대한 테스트 코드 작성</title>
      <link>https://blog.benelog.net//2421450.html</link>
      <pubDate>Tue, 8 Sep 2009 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2421450.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JSP의 커스텀 태그 라이브러리는 그 실행결과를 확인하는 것이 많이 번거롭습니다. 따로 테스트 코드를 짜지 않는다면, Web Application 를 띄우고 커스템 태그를 사용하는 JSP를 직접 실행한 다음에 나오는 텍스트값을 확인해서 눈으로 값이 제대로 찍히는지 검증하고, 틀리면 다시 코드를 고치는 방식을 반복하는 경우도 많습니다. 그리고 보통 커스텀태그에서는 setter로 지정된 속성에 따라서 조건분기도 많이 들어가기 때문에 더욱 디버깅이 까다롭습니다. 이 때 위와 같이 JSP를 거치지 않고 바로 출력될 값을 찎어주고, 검증로직을 추가할 수 있는 테스트 코드를 짠다면 개발할 때 많은 시간이 절약될 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.docjar.org/docs/api/javax/servlet/jsp/tagext/TagSupport.html&quot;&gt;javax.servlet.jsp.tagext.TagSupport&lt;/a&gt;를 상속한 클래스라면, TagSupport.setPageContext 메소드를 활용해서 mock같은 테스트용 객체들을 삽입하면 됩니다. 이 메소드를 통해서 PageContext와 PageContext.getOut으로 돌려주는 javax.servlet.jsp.JspWriter객체를 모두 mock으로 지정할 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring에서는 이를 더욱 간편하게 할 수 있는 &lt;a href=&quot;http://static.springsource.org/spring/docs/2.5.6/api/org/springframework/mock/web/MockPageContext.html&quot;&gt;MockPageContext&lt;/a&gt;라는 객체를 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이를 활용한 Custom 태그 테스트 코드는 아래와 같이 만들 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;ButtonTag tag = new ButtonTag();
tag.setFunctionName(&quot;alert&quot;);
tag.setType(&quot;basic&quot;);

PageContext pageContext = new MockPageContext();
tag.setPageContext(pageContext);

assertEquals(TagSupport.EVAL_BODY_INCLUDE, tag.doStartTag());
assertEquals(TagSupport.EVAL_PAGE, tag.doEndTag());
String output = ((MockHttpServletResponse) pageContext.getResponse()).getContentAsString();
System.out.println(output);
assertTrue(output.contains(&quot;&amp;lt;span class=&apos;r&apos;&amp;gt;&quot;));
//출력된 결과에 대한 추가  검증&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Oracle을 사용해 입출력하는 Map-Reduce</title>
      <link>https://blog.benelog.net//2271302.html</link>
      <pubDate>Wed, 25 Mar 2009 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2271302.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hadoop의 Map-Reduce처리에서는 DB를 바로 연결해서 처리할 수 있는  &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBInputFormat.html&quot;&gt;DBInputFormat&lt;/a&gt;, &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBOutputFormat.html&quot;&gt;DBOutputFormat&lt;/a&gt;의 클래스가 제공되고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 이 클래스들은 이름이 &apos;DB&amp;#8217;가 붙어있는 것이 무색하게 Oracle과 연결해서 사용해보면 에러가 납니다. &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBInputFormat.html&quot;&gt;DBInputFormat&lt;/a&gt;에서는 웹에서의 페이지 처리 쿼리처럼 데이터를 잘라서 가지고 오기 위해 원래 쿼리에다 LIMIT와 OFFSET 키워드를 붙이는데, 이 것은 Oracle에서는 지원되지 않습니다. 그리고 &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBOutputFormat.html&quot;&gt;DBOutputFormat&lt;/a&gt;에서는 insert문의 맨 뒤에 세미콜론(;)을 붙여버리는데, 이것 역시 Oracle의 JDBC를 사용할 때는 에러를 냅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서, 결국 이 클래스들을 Oracle에서 쓸 수 있도록 상속해서 구현을 해 줄수 밖에 없었습니다. 얼핏 생각하면 쿼리만 바꾸어주면 되는 것이니 메소드 하나만 오버라이딩 해주면 될 것으로 예상했으나, 원래 클래스들의 구조가 그 정도로 단순하지 않았습니다.Inner클래스가 많아서 여러 클래스와 메서드들을 다 overriding해 줄 수 밖에 없었습니다. 더군다나, 새로 상속한 클래스의 내부에서 꼭 호출해야 하는 DBConfiguration클래스의 생성자가 public이 아닌 package private(아무것도 선언안한 디폴트 접근자)인 탓에, 패키지를 원래의 &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBInputFormat.html&quot;&gt;DBInputFormat&lt;/a&gt;, &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBOutputFormat.html&quot;&gt;DBOutputFormat&lt;/a&gt;와 같은 패키지로 맞추어야 하는 불편함도 있었습니다. protected로 선언된 메소드들이 많은 것보면 분명히 상속해서 덮어쓰라고 만들어놓은 클래스 같은데, 막상 그렇게 활용하기에는 간편하지 않았던 것이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 또 구조적으로 아쉬운 점은 두 클래스가 같은 DBConfiguration을 보게 있어서 Map에서 입력자료를 얻어오는 DB와 Reduce에서 쓰는 DB가 다를 때는 다시 별도의 클래스를 만들어주어야 한다는 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring Batch에서도 &lt;a href=&quot;http://static.springsource.org/spring-batch/apidocs/org/springframework/batch/item/database/JdbcPagingItemReader.html&quot;&gt;JdbcPagingItemReader&lt;/a&gt;라는 약간 유사한 클래스가 있습니다. &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBInputFormat.html&quot;&gt;DBInputFormat&lt;/a&gt;이 하나의 쿼리에서 가지고 올 데이터를 동시에 여러번 쿼리해서 나누어 가지고 오는 반면에 &lt;a href=&quot;http://static.springsource.org/spring-batch/apidocs/org/springframework/batch/item/database/JdbcPagingItemReader.html&quot;&gt;JdbcPagingItemReader&lt;/a&gt;에서는 부분씩 가지고 오더라도 순차적으로 쿼리를 하는 차이점이 있기는 합니다. 그래도, 페이지 처리 쿼리처럼, 데이터를 나누어서 가지고 오는 쿼리를 제공한다는 점에서는 유사합니다. &lt;a href=&quot;http://static.springsource.org/spring-batch/apidocs/org/springframework/batch/item/database/JdbcPagingItemReader.html&quot;&gt;JdbcPagingItemReader&lt;/a&gt;에서는 내부적으로 &lt;a href=&quot;http://static.springsource.org/spring-batch/apidocs/org/springframework/batch/item/database/PagingQueryProvider.html&quot;&gt;PagingQueryProvider&lt;/a&gt; 라는 인터페이스를 사용하게 되어 있고, 이 인터페이스는 각 DB종류별로 OraclePagingQueryProvider, HsqlPagingQueryProvider, MySqlPagingQueryProvider, SqlServerPagingQueryProvider, SybasePagingQueryProvider 등의 구현클래스를 가지고 있습니다.
Hadoop의 &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/lib/db/DBInputFormat.html&quot;&gt;DBInputFormat&lt;/a&gt;도 이런 구조였다면 이를 응용하려는 개발자가 훨씬 쉽게 클래스 확장방법을  이해했을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아뭏든 지금까지 현재 공개된 API만으로는 Hadoop의 DB연결 지원 클래스들은 빈약해 보이고, API도 좋은 설계요건을 갖추었다고 느껴지지는 않습니다. 아무래도 포털 등에서 대용량 데이터를 처리하는 곳에 쓰이다보니 DB와 함께 연결되는 쓰임새가 그리 많지는 않았나봅니다. 더군다나 Oracle에서는 한번도 안 돌려본 클래스가 버젓이 DB&amp;#8230;&amp;#8203;로 시작되는 이름으로 들어간 것 보면 Oracle이 쓰이는 동네와 Hadoop이 사는 곳은 아주 멀리 떨어져 있었던 것 같습니다. 그러나, 앞으로  엔터프라이즈 환경에서도 Hadoop이 쓰이려면 DB와의 integration은 반드시 거쳐야할 다리인 것 같습니다. &lt;a href=&quot;http://www.jaso.co.kr/283&quot;&gt;Enterprise 시장에서의 mapreduce&lt;/a&gt; 링크를 보아도 이미 그런 시도들이 시작된 것을 알 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한편, Hadoop의 &lt;a href=&quot;http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/mapred/FileInputFormat.html&quot;&gt;FileInputFormat&lt;/a&gt;가 Spring batch의 &lt;a href=&quot;http://static.springsource.org/spring-batch/apidocs/org/springframework/batch/item/file/FlatFileItemReader.html&quot;&gt;FlatFileItemReader&lt;/a&gt;와 유사한 것 등이나 Spring batch도 2.0에서 아직 분산, 동시처리 등을 지원하기 시작했다는 점은 두 프레임웍의 겹치는 지점이 늘어날 수도 있다는 생각도 듭니다. 뭐 아직 Spring batch의 분산지원은 걸음마 단계이기는 합니다만, DB에서 HDFS에 들어가는 파일을 쓸 때 Spring batch의 API를 활용하는 것 같은 활용법은 시도해 볼만하다고 생각됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;소스&quot;&gt;소스&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;OracleInputFormat&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package org.apache.hadoop.mapred.lib.db;

import java.io.IOException;
import java.sql.SQLException;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapred.InputSplit;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.RecordReader;
import org.apache.hadoop.mapred.Reporter;

public class OracleInputFormat&amp;lt;T extends DBWritable&amp;gt; extends DBInputFormat&amp;lt;T&amp;gt;{

    private DBConfiguration dbConf = null;
    private DBInputSplit split;

    @Override
    public RecordReader&amp;lt;LongWritable,T&amp;gt; getRecordReader(InputSplit split, JobConf job,
            Reporter reporter) throws IOException {
        dbConf = new DBConfiguration(job);
        this.split = (DBInputSplit)split;

        @SuppressWarnings(&quot;unchecked&quot;)
        Class inputClass = dbConf.getInputClass();
        try {
            @SuppressWarnings(&quot;unchecked&quot;)
            RecordReader&amp;lt;LongWritable,T&amp;gt; reader = new OracleRecordReader((DBInputSplit) split, inputClass, job);
            return reader;
        } catch (SQLException ex) {
            throw new IOException(ex.getMessage());
        }
    }

    public static void setInput(JobConf job, Class&amp;lt;? extends DBWritable&amp;gt; inputClass,
              String inputQuery, String inputCountQuery) {
        DBInputFormat.setInput(job, inputClass, inputQuery, inputCountQuery);
        job.setInputFormat(OracleInputFormat.class);
     }

    protected class OracleRecordReader extends DBRecordReader{
        protected OracleRecordReader(DBInputSplit split, Class&amp;lt;T&amp;gt; inputClass,
                JobConf conf) throws SQLException {
            super(split, inputClass, conf);
        }

        @Override
        protected String getSelectQuery() {
            long length = 0;
            long start = 0;
            try{
                length = split.getLength();
                start = split.getStart();
            } catch(IOException e){
                throw new IllegalArgumentException
                        (&quot;cannot read length or start variable from DBInputSplit&quot;,e);
            }
            StringBuilder query = new StringBuilder();
            query.append(&quot; SELECT * \r\n&quot;);
            query.append(&quot; FROM (SELECT m.* , ROWNUM rno &quot;);
            query.append(&quot;       FROM ( &quot;);
            query.append(              dbConf.getInputQuery());
            query.append(&quot;             )  m&quot;);
            query.append(&quot;       WHERE ROWNUM &amp;lt;= &quot; + start + &quot; + &quot; + length + &quot;)&quot;);
            query.append(&quot; WHERE RNO &amp;gt; &quot; + start);
            System.out.println(query.toString());
            return query.toString();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;OracleOutputFormat&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package org.apache.hadoop.mapred.lib.db;
import org.apache.hadoop.mapred.JobConf;

public class OracleOutputFormat&amp;lt;K  extends DBWritable, V&amp;gt; extends DBOutputFormat&amp;lt;DBWritable, V&amp;gt;{
    @Override
    protected String constructQuery(String table, String[] fieldNames) {
            if(fieldNames == null) {
              throw new IllegalArgumentException(&quot;Field names may not be null&quot;);
            }
            StringBuilder query = new StringBuilder();
            query.append(&quot;INSERT INTO &quot;).append(table);

            if (fieldNames.length &amp;gt; 0 &amp;amp;&amp;amp; fieldNames[0] != null) {
              query.append(&quot; (&quot;);
              for (int i = 0; i &amp;lt; fieldNames.length; i++) {
                query.append(fieldNames[i]);
                if (i != fieldNames.length - 1) {
                  query.append(&quot;,&quot;);
                }
              }
              query.append(&quot;)&quot;);
            }
            query.append(&quot; VALUES (&quot;);

            for (int i = 0; i &amp;lt; fieldNames.length; i++) {
              query.append(&quot;?&quot;);
              if(i != fieldNames.length - 1) {
                query.append(&quot;,&quot;);
              }
            }
            query.append(&quot;)&quot;);
            return query.toString();
          }
    public static void setOutput(JobConf job, String tableName, String... fieldNames) {
        DBOutputFormat.setOutput(job, tableName, fieldNames);
        job.setOutputFormat(OracleOutputFormat.class);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Job 구성 예&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class SampleJob {

    public static void main(String args[]) throws IOException, URISyntaxException{
        JobConf conf = new JobConf(SampleJob.class);
        initClasspath(conf);
        conf.setJobName(&quot;sampleJob&quot;);
        DBConfiguration.configureDB(conf, &quot;oracle.jdbc.driver.OracleDriver&quot;,
                &quot;jdbc:oracle:thin:@localhost:1525:TEST&quot;,
                &quot;myuser&quot;, &quot;mypassword&quot;);
        OracleInputFormat.setInput(conf, Query.class,
                &quot;SELECT query, category, user_id FROM query_log &quot;,
                &quot;SELECT COUNT(*) FROM query_log&quot;);
        conf.setOutputKeyClass(Query.class);
        conf.setOutputValueClass(IntWritable.class);
        conf.setMapperClass(SampleMapper.class);
        conf.setReducerClass(SampleReducer.class);
        conf.setCombinerClass(SampleReducer.class);
        OracleOutputFormat.setOutput(conf, &quot;category&quot;, &quot;user_id&quot;,&quot;cnt&quot;);

        JobClient.runJob(conf);
    }

    private static void initClasspath(JobConf conf) throws URISyntaxException,
            IOException {
        DistributedCache.addCacheFile(new URI(&quot;lib/ojdbc5-11.1.0.6.jar&quot;), conf);
        DistributedCache.addFileToClassPath(new Path(&quot;lib/ojdbc5-11.1.0.6.jar&quot;), conf);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;hadoop의_dbinputformat_참고자료&quot;&gt;hadoop의 DBInputFormat 참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.net/blogs/hadoop/DBInputFormat.ppt&quot; class=&quot;bare&quot;&gt;http://developer.yahoo.net/blogs/hadoop/DBInputFormat.ppt&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.cloudera.com/blog/wp-content/uploads/DBInputFormat.pdf&quot; class=&quot;bare&quot;&gt;http://www.cloudera.com/blog/wp-content/uploads/DBInputFormat.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.cloudera.com/blog/2009/03/06/database-access-with-hadoop/&quot; class=&quot;bare&quot;&gt;http://www.cloudera.com/blog/2009/03/06/database-access-with-hadoop/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.cloudera.com/blog/tag/dbinputformat/&quot; class=&quot;bare&quot;&gt;http://www.cloudera.com/blog/tag/dbinputformat/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Java의 auto boxing과 unbox은 어떻게 컴파일될까?</title>
      <link>https://blog.benelog.net//2229687.html</link>
      <pubDate>Tue, 10 Feb 2009 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2229687.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 코드는 [프리미티브 타입과 Wrapper 클래스, 자동 Boxing, 자동 UnBoxing]이라는 글에 나오는 것을 입력해 본 것입니다. int와 java.lang.Integer객체를 &quot;==&quot;와 &quot;equals()&quot;메소드로 비교하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/java/auto-boxing-test.jpg&quot; alt=&quot;auto boxing test&quot; title=&quot;auto-boxing-test.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 코드에서 생성한 .class파일을 역컴파일해보면 아래와 같은 코드가 나옵니다. autoboxing과 unboxing이 어떤 방식으로 이루어 지는지 잘 확인할 수 있습니다. java.lang.Integer가 int로 바뀔 때는 intValue() 메소드, int가 java.lang.Integer로 바뀔 때는 Integer.valueOf()메소드를 사용하고 있습니다. JDK 1.4이하 버전과의 하위호환성을 위해서 이런 방식을 쓰는 것이겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;int a = 1;
int b = 1;
Integer c = new Integer(1);
Integer d = new Integer(1);
System.out.println((new StringBuilder(&quot;1:&quot;)).append(System.identityHashCode(Integer.valueOf(a))).toString());
System.out.println((new StringBuilder(&quot;2:&quot;)).append(System.identityHashCode(Integer.valueOf(b))).toString());
System.out.println((new StringBuilder(&quot;3:&quot;)).append(System.identityHashCode(c)).toString());
System.out.println((new StringBuilder(&quot;4:&quot;)).append(System.identityHashCode(d)).toString());
System.out.println((new StringBuilder(&quot;5:&quot;)).append(a == b).toString());
System.out.println((new StringBuilder(&quot;7:&quot;)).append(c == d).toString());
System.out.println((new StringBuilder(&quot;8:&quot;)).append(c.equals(d)).toString());
System.out.println((new StringBuilder(&quot;9:&quot;)).append(a == c.intValue()).toString());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;new Integer()&lt;/code&gt; 로 생성자를 호출하면 매로 새로 객체를 생성하는데, Integer.valueOf()메소드를 사용하면 캐쉬된 값을 사용할 수 있습니다. 이 메소드의 소스를 보면 -128부터 127까지의 static영역에 캐쉬로 쌓아두고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt; static {
  cache = new Integer[256];
  for (int i = 0; i &amp;lt; cache.length; i++)
  cache[i] = new Integer(i - 128);
}

public static Integer valueOf(int i) {
  if (i &amp;gt;= -128 &amp;amp;&amp;amp; i &amp;lt;= 127)
  return IntegerCache.cache[i + 128];
  else
  return new Integer(i);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음에 나왔던 저의 Eclipse 캡쳐화면을 보면 findbugs 플러그인에서 new Integer()사용 코드에 대한 경고를 보여주고 있습니다. 자세한 설명을 보니, &lt;code&gt;Integer.valueOf()&lt;/code&gt; 를 사용할 경우 약 3.5배 정도 실행속도가 빠르다고 하네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;M P Bx] Method invokes inefficient Number constructor; use static valueOf instead [DM_NUMBER_CTOR]

Using new Integer(int) is guaranteed to always result in a new object whereas Integer.valueOf(int) allows caching of values to be done by the compiler, class library, or JVM. Using of cached values avoids object allocation and the code will be faster.

Values between -128 and 127 are guaranteed to have corresponding cached instances and using valueOf is approximately 3.5 times faster than using constructor. For values outside the constant range the performance of both styles is the same.

Unless the class must be compatible with JVMs predating Java 1.5, use either autoboxing or the valueOf() method when creating instances of Long, Integer, Short, Character, and Byte.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 원리들을 잘 염두해 둬서, auto boxing과 unboxing이 사용될 때 필요없는 객체가 생성되지 않는지 유의해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음 코드는 Effective Java 2nd Edition의 Item 5 - &apos;불필요한 객체를 생성하지 마라&amp;#8217;에 나오는 예제입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;Long sum = 0L;
for(long i=0;i&amp;lt; Integer.MAX_VALUE;i++){
   sum += i;
}
System.out.println(sum);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;이 코드를 컴파일한 후 다시 역컴파일 해보면 다음과 같이 나옵니다.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;Long sum = Long.valueOf(0L);
for(long i = 0L; i &amp;lt; 0x7fffffffL; i++)  sum = Long.valueOf(sum.longValue() + i);

System.out.println(sum);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;java.lang.Double.valueOf(double) 메서드는 매번 새로운 객체를 생성하게 되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public static Double valueOf(double d) {
  return new Double(d);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇듯 불필요한 객체생성을 막기 위해 되도록 primitive type을 선호해야 합니다. auto boxing은 Collection이나 Map에 들어가는 요소로 변수가 쓰일 때, generics가 적용한 코드를 작성할 때 등이 적절한 사용의 예입니다. (Effective Java 2nd Edition, Item 49 참조)&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring MVC 2.5을 활용한 파일업로드</title>
      <link>https://blog.benelog.net//2228221.html</link>
      <pubDate>Sun, 8 Feb 2009 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2228221.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;간단한 파일 업로드 기능을 만들어야 할 일이 생겨서, Spring 2.5의 annotation을 이용한 Action에서 이를 처리하게 했습니다. 실무에서 썼던 것을 더 단순한 예제로 재구성해서 정리해봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Maven의 pom.xml에 파일업로드 기능에서 참조하는 commons-fileupload 라이브러리에 대한 dependency를 추가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;dependency&amp;gt;
   &amp;lt;groupId&amp;gt;commons-fileupload&amp;lt;/groupId&amp;gt;
   &amp;lt;artifactId&amp;gt;commons-fileupload&amp;lt;/artifactId&amp;gt;
   &amp;lt;version&amp;gt;1.2.1&amp;lt;/version&amp;gt;

&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;web.xml에는 applicationContext 파일의 위치를 지정하고, *.do를 스프링에서 처리하도록 설정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;servlet&amp;gt;
   &amp;lt;servlet-name&amp;gt;dispatcher&amp;lt;/servlet-name&amp;gt;
   &amp;lt;servlet-class&amp;gt;org.springframework.web.servlet.DispatcherServlet&amp;lt;/servlet-class&amp;gt;
   &amp;lt;init-param&amp;gt;
     &amp;lt;param-name&amp;gt;contextConfigLocation&amp;lt;/param-name&amp;gt;
     &amp;lt;param-value&amp;gt;classpath:applicationContext.xml&amp;lt;/param-value&amp;gt;
   &amp;lt;/init-param&amp;gt;
   &amp;lt;/servlet&amp;gt;
   &amp;lt;servlet-mapping&amp;gt;
     &amp;lt;servlet-name&amp;gt;dispatcher&amp;lt;/servlet-name&amp;gt;
     &amp;lt;url-pattern&amp;gt;*.do&amp;lt;/url-pattern&amp;gt;
   &amp;lt;/servlet-mapping&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;업로드 기능을 간단히 테스트할 수 있는 jsp페이지를 만들어봅니다. 단순히 파일 1개를 &quot;file&quot;이라는 변수명으로 업로드 요청을 하는 페이지입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;jsp&quot;&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;%@ taglib prefix=&quot;spring&quot; uri=&quot;http://www.springframework.org/tags&quot;%&amp;gt;
&amp;lt;%@ taglib prefix=&quot;form&quot; uri=&quot;http://www.springframework.org/tags/form&quot;%&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;업로드 테스트&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;form action=&quot;/study/upload.do&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&amp;gt;
&amp;lt;p&amp;gt;
    &amp;lt;label for=&quot;file&quot;&amp;gt;파일1 &amp;lt;/label&amp;gt;
    &amp;lt;input type=&quot;file&quot; name=&quot;file&quot;&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;
    &amp;lt;input type=&quot;submit&quot; value=&quot;전송&quot;/&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 applicationContext파일을 아래와 같이 선언합니다. &lt;code&gt;DefaultAnnotationHandlerMapping&lt;/code&gt; 을 이용해서 annotation을 이용한 Controller 설정을가능하게 합니다. &lt;code&gt;&amp;lt;context:component-scan/&amp;gt;&lt;/code&gt; 태그를 사용해서, 설정을 scan할 패키지를지정합니다. 그리고, 파일이 저장될 디렉토리는 &lt;code&gt;${repository.path}&lt;/code&gt; 속성으로 표시했습니다.
Maven의 resource filter기능을 이용해서 실행환경에 따라 다른 값을 넣게 하면 편리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
    xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
    xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
    xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&amp;gt;

    &amp;lt;bean  class=&quot;org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping&quot;&amp;gt;
        &amp;lt;property name=&quot;alwaysUseFullPath&quot; value=&quot;true&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
    &amp;lt;bean id=&quot;multipartResolver&quot;
        class=&quot;org.springframework.web.multipart.commons.CommonsMultipartResolver&quot;/&amp;gt;

    &amp;lt;bean id=&quot;respository&quot; class=&quot;study.repository.FileRepository&quot;&amp;gt;
        &amp;lt;constructor-arg value=&quot;${repository.path}&quot; /&amp;gt;
    &amp;lt;/bean&amp;gt;
    &amp;lt;context:component-scan    base-package=&quot;study.action&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;실제적인 파일의 저장기능을 담당하는 클래스인 FileRepository에서는 간단하게 UUID를 이용해서 키를 생성하고 path필드로 지정된 디렉토리에 저장을 해줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package study.repository;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

import org.springframework.web.multipart.MultipartFile;

public class FileRepository {
    private String path;
    public FileRepository(String path) {
        this.path = path;
        File saveFolder = new File(path);
        if(!saveFolder.exists() || saveFolder.isFile()){
            saveFolder.mkdirs();
        }
    }

    public String saveFile(MultipartFile sourcefile) throws IOException{
        if ((sourcefile==null)||(sourcefile.isEmpty())) return null;
        String key = UUID.randomUUID().toString();
        String targetFilePath = path+&quot;/&quot;+ key;
        sourcefile.transferTo(new File(targetFilePath));
        return key;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;추가적으로 키값을 넣어주면 파일을 반환해주는 메소드나, 파일의 종류나 날짜에 따라서 하위 디렉토리를 구분해서 생성하는 기능도 넣을 수있을 것입니다. 그리고 더 확장한다면, 따로 주요 메서드를 선언한 Repository 라는 인터페이스를 정의하고, DB를저장소로 활용하는 DbRepository 와 같이 이름 붙인 구현 클래스도 만들어 볼 수 있겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 @Controller , @RequestMapping, @RequestParam, @Autowired의 Anntation을활용해서 Controller 클래스를 작성합니다. 업로드 후 화면에 키값만 뿌도록 해서 java.io.Writer클래스를 화면출력을 위해 사용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package study.action;

import java.io.IOException;
import java.io.Writer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import study.repository.FileRepository;

@Controller
public class FileAction {
     private FileRepository respository;

    @Autowired
    public void setRespository(FileRepository respository) {
        this.respository = respository;
    }
    @RequestMapping(&quot;/upload.do&quot;)
    public void execute(@RequestParam(&quot;file&quot;) MultipartFile file,
            Writer out) throws IOException{
        String key = respository.saveFile(file);
        out.write(key);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;MultipartFile 클래스를 파라미터로 받는 메서드는 &lt;a href=&quot;http://static.springsource.org/spring/docs/3.0.0.M1/javadoc-api/org/springframework/mock/web/MockMultipartFile.html&quot;&gt;MockMultiPartFile&lt;/a&gt;를 이용해서 테스트하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;참고로 &lt;a href=&quot;http://chanwook.tistory.com/758&quot;&gt;최근 로드존슨이 인터뷰에서 한 말&lt;/a&gt;에 따르면 기존 Spring MVC의 Controller interface는 삭제될 것이라고 하네요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;spring_mvc_2_5_관련_자료&quot;&gt;Spring MVC 2.5 관련 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.infoq.com/articles/spring-2.5-ii-spring-mvc&quot; class=&quot;bare&quot;&gt;http://www.infoq.com/articles/spring-2.5-ii-spring-mvc&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://chanwook.tistory.com/576&quot;&gt;스프링 2.5 애노테이션 기반 MVC 예제&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://springtutorial.googlecode.com/svn/trunk/moimApp/src/spring/tutorial/web/MoimController.java&quot;&gt;http://springtutorial.googlecode.com/svn/trunk/moimApp/src/spring/tutorial/web/MoimController.java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://steelheart.kr/tc/173&quot;&gt;Spring 2.5 Annotation기반 Controller 끄적거림&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://steelheart.kr/tc/entry/using-web-argument-resolver?category=0&quot;&gt;Annotation기반 컨트롤러에 custom argument 적용하기 (WebArgumentResolver)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://chanwook.tistory.com/548&quot;&gt;Annotated Spring MVC Controller&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://corund.egloos.com/1815311&quot;&gt;Spring Framework 2.5의 Annotation based Controller의 메서드 파라미터에서 주의점&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>EMMA + Eclipse + Maven2 + Hudson</title>
      <link>https://blog.benelog.net//2212119.html</link>
      <pubDate>Fri, 23 Jan 2009 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2212119.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://emma.sourceforge.net/&quot;&gt;EMMA&lt;/a&gt;는 테스트코드가 검증해 주는 코드영역에 대해서 보고해 주는 도구입니다. 전체 코드 중 몇 %가 테스트 코드를 거쳐가고 있는지 쉽게 수치를 낼 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;EMMA의  Eclipse plugin인 EclEmma는 &lt;a href=&quot;http://update.eclemma.org/를&quot; class=&quot;bare&quot;&gt;http://update.eclemma.org/를&lt;/a&gt; plugin의 update site로 지정하면 설치할 수 있습니다.  설치 후 프로젝트에서 우클릭-Coverage As - JUnit Test 메뉴를 선택하면 전체 Junit Test를 실행하고, Test Coverage에 대한 보고서를 생성해 줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/emma/eclipse-emma-menu.jpg&quot; alt=&quot;eclipse-emma-menu.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;EMMA의 Maven2 plugin인 &lt;a href=&quot;http://mojo.codehaus.org/emma-maven-plugin/&quot;&gt;Emma Maven Plugin Maven&lt;/a&gt;을  pom.xml파일의 reporting 선언에 아래와 같은 설정을 추가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;reporting&amp;gt;
  &amp;lt;plugins&amp;gt;
   ...
    &amp;lt;plugin&amp;gt;
      &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;emma-maven-plugin&amp;lt;/artifactId&amp;gt;
      &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
&amp;lt;/reporting&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://hudson.gotdns.com/wiki/display/HUDSON/Emma+Plugin%20&quot;&gt;Hudson의 위키에 있는 Emma plugin 설명페이지&lt;/a&gt;나 &lt;a href=&quot;http://mojo.codehaus.org/emma-maven-plugin/usage.html&quot;&gt;Maven의 emma-plugin 설명페이지&lt;/a&gt;를 보면 build절에도 plugin 설정을 추가하라고 되어있는데, 그렇게 하지 않아도 잘  실행이 되었습니다.
오히려 그렇게 추가를 하니, mvn test site처럼 test와 site phrase가 같이 돌아갈 때 아래와 같은 에러가 발생했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;java.lang.IllegalStateException: class [......] appears to be instrumented already&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Emma plugin 설정을 reporting 부분에 넣으면 테스트가 실행되므로 일부러 test phrase를 넣어줄 필요는 없습니다. 즉 이 때는 mvn site만 돌려주시면 테스트 결과와 Coverage Report를 모두 얻을 수 있습니다.
Emma plugin이 있을 때 &apos;mvn test site&amp;#8217;로 실행시켜서 테스트가 두번 돌면 Hudson에서 보고하는 테스트 개수도 2번씩 중복 집계가 되므로 더 혼동만 줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://mojo.codehaus.org/emma-maven-plugin/usage.html&quot;&gt;Maven의 emma-plugin 설명페이지&lt;/a&gt;에 따르면,  maven-surefire-plugin 설정에서 별도의 JVM으로 테스트를 실행시키위해 forkMode를 always로 설정하는 것은 중요하다고 합니다.
EMMA가 JVM의 종료 때도 기록을 하기 때문에 그렇다는군요. 다음과 같이 설정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;maven-surefire-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;inherited&amp;gt;true&amp;lt;/inherited&amp;gt;
        &amp;lt;configuration&amp;gt;
          &amp;lt;forkMode&amp;gt;always&amp;lt;/forkMode&amp;gt;
          &amp;lt;reportFormat&amp;gt;xml&amp;lt;/reportFormat&amp;gt;
        &amp;lt;/configuration&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 Maven을 통해 생성한 리포트를  Huson으로 보기 위해서는 &lt;strong&gt;Manage Hudson&amp;gt;Manage Plugins&lt;/strong&gt; 메뉴에서 Hudson Emma plugin을 설치하고 Hudson을 재시작합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;설치가 잘 되었다면, 프로젝트의 Configure 메뉴에서 EMMA의 보고서에 대해서 설정할 수 있는 항목이 생깁니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/emma/hudson-emma-config.jpg&quot; alt=&quot;hudson-emma-config.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여기에서 Emma XML report 항목에 workspace를 기준으로 한 상대적인 경로로 coverage.xml의 위치를 지정해줍니다.
&lt;a href=&quot;http://hudson.gotdns.com/wiki/display/HUDSON/Emma+Plugin%20&quot;&gt;Hudson의 위키에 있는 Emma plugin 설명페이지&lt;/a&gt;에 보면 이 항목이 target/site/emma/coverage.xml으로 되어 있어서 혼동이 오기 쉬운데, trunk와 같은 Local module directory를 반드시 포함시켜줘야 합니다.
보통 SVN을 쓸 때 프로젝트 설정에서 별도로 지정을 하지 않으면 trunk같은 SVN의 path의 마지막 디렉토리가 Local module directory로 지정됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/emma/hudson-svn-config.jpg&quot; alt=&quot;hudson-svn-config.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Emma 보고서 설정란에서 the work space root라는 링크를 눌러도 금방 확인 할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위와 같이 설정된 프로젝트를 build하면 빌드의 맨 끝에 Recording Emma reports trunk/target/site/emma/coverage.xml 와 같은 메시지가 Console out 화면에서 뜰 것입니다.  그렇게 생성된 보고서는 &quot;프로젝트명&amp;gt;빌드번호&amp;gt;Coverage Report&quot; 메뉴에서 보실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/emma/hudson-emma-menu.jpg&quot; alt=&quot;hudson-emma-menu.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/emma/hudson-emma-report.jpg&quot; alt=&quot;hudson-emma-report.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;만약 Hudson에서 Emma를 실행한 빌드번호의 메뉴에서 &apos;Coverage Report&amp;#8217;라는 메뉴가 보이지 않는다면 &lt;a href=&quot;http://blog.benelog.net/2208375&quot;&gt;Hudson plugin 수동으로 빌드&amp;amp;업로드&lt;/a&gt;를 참조해서 최신 버전으로 플러그인을 업데이트 해보시기 바랍니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>JDepend + Eclipse + Maven2</title>
      <link>https://blog.benelog.net//2208368.html</link>
      <pubDate>Mon, 19 Jan 2009 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2208368.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://clarkware.com/software/JDepend.html&quot;&gt;JDepend&lt;/a&gt;는 Java패키지간의 의존성에 대한 수치들을 알려 주는 도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDepend의 Eclipse의 Plugin은 &lt;a href=&quot;http://andrei.gmxhome.de/eclipse/를&quot; class=&quot;bare&quot;&gt;http://andrei.gmxhome.de/eclipse/를&lt;/a&gt; Update site에 추가하면 설치할 수 있습니다. 다음의 링크들에서 보다 자세한 내용을 참조할 수 있습니다. 분석을 하고자 하는 소스폴더 위에서 우클릭을 한 후 &apos;Run JDepend Analysis&apos; 메뉴를 선택하면 의존성 분석 결과가 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jdepend/eclipse-jdepend.jpg&quot; alt=&quot;eclipse-jdepend.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이를 Maven을 통해서 생성하는 &lt;a href=&quot;http://mojo.codehaus.org/jdepend-maven-plugin/&quot;&gt;jdepend-maven-plugin&lt;/a&gt; 은 pom.xml에 아래와 같이 추가할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;reporting&amp;gt;

.....

&amp;lt;plugin&amp;gt;
  &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jdepend-maven-plugin&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;2.0-beta-2&amp;lt;/version&amp;gt;
&amp;lt;/plugin&amp;gt;

&amp;lt;/reporting&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;mvn jdepend:generate&lt;/code&gt; 또는 &lt;code&gt;mvn site&lt;/code&gt; 명령을 통해서 보고서가 생성됩니다. mvn site로 실행했다면 Hudson의 프로젝트 홈에서 Maven Generated Site 메뉴를 통해서도 확인할 수 있습니다.
&lt;a href=&quot;http://mojo.codehaus.org/jdepend-maven-plugin/examples/jdepend-report.html&quot;&gt;샘플페이지&lt;/a&gt; 에 생성된 보고서의 형식이 나와있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jdepend/jdepend-report.jpg&quot; alt=&quot;jdepend-report.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://clarkware.com/software/JDepend.html&quot;&gt;JDepend 첫페이지&lt;/a&gt;나 생성된 보고서 안에서도 위의 요약 테이블에  수치들에 대한 설명이 잘 나와있습니다. 간단히 요약해서 정리하면,&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;TC (Total Classes) : 전체 클래스 수. CC + AC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CC (Concrete Classes) : Inteface나 추상클래스가 아닌 구상 클래스 수&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AC (Abstract Classes) : Interface나 Abstract Class로 선언된 클래스 수&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ca (&lt;span id=&quot;intelliTxt&quot;&gt;Afferent Couplings&lt;/span&gt;) : 이 패키지를 의존하고 있는 다른 패키지의 수. 이 패키지의 책임감을 나타내는 지표&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ce (&lt;span id=&quot;intelliTxt&quot;&gt;Efferent Couplings&lt;/span&gt;) : 이 패키지가 의존하고 있는 클래스가 있는 다른 패키지의 수. 이 패키지의 독립성을 나타내는 지표.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A (&lt;span id=&quot;intelliTxt&quot;&gt;Abstractness&lt;/span&gt;) : 총 클래스 갯수 중 인터페이스나 추상클래스의 비율. 1이라면 해당 패키지는 추상클래스나 인터페이스 밖에 없는 것.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I (&lt;span id=&quot;intelliTxt&quot;&gt;Instability&lt;/span&gt;) :   총 결합도 중 이 패키지의 외부의존성의 비율 (Ce / (Ce + Ca)). 변화에 대한 내성을 나타내는 지표. I=0이라면 완전하게 안정적인 것.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;D &lt;span id=&quot;intelliTxt&quot;&gt;(Distance from Main Sequence&lt;/span&gt;) :  이상적인 균형의 상태인  A + I = 1 의 함수에서 수직으로 떨어진 거리. (아래 그래프 참조)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;http://www.onjava.com/onjava/2004/01/21/graphics/figure3.gif&quot; alt=&quot;onjava/2004/01/21/graphics/figure3.gif&quot; title=&quot;onjava/2004/01/21/graphics/figure3.gif&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이미지 출저 : &lt;a href=&quot;http://www.onjava.com/pub/a/onjava/2004/01/21/jdepend.html&quot;&gt;Managing Your Dependencies with JDepend&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;가장 중요한 것은 패키지 간의 순환참조를 보여주는 Cycles 부분입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/jdepend/jdepend-cycles.jpg&quot; alt=&quot;jdepend-cycles.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;순환참조 관계의 패키지들은 부분적으로 배포될 수도 없고, 한 패키지를 변경할 때 그 영향력을 파악하기도 힘들게 만듭니다. 순환 참조에 대한 자세한 내용은 아래의 링크를 참조하시기 바랍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://toby.epril.com/?p=263&quot;&gt;Code Organization &amp;amp; Cyclic Dependency Problem&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.kirkk.com/index.php?itemid=30&quot;&gt;Cyclic Dependencies&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.onjava.com/pub/a/onjava/2004/01/21/jdepend.html&quot;&gt;Managing Your Dependencies with JDepend&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://whiteship.tistory.com/1767&quot;&gt;Code Organization Guidelines for Large Code Bases - 유겐 휄러&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;관련자료&quot;&gt;관련자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://younghoe.info/50&quot;&gt;JDepend에서 지원하는 설계 품질 측정치(design quality metrics)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.powerjava.net/blog/soulcarta/entry/Eclipse-Plugin-JDepend%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EA%B2%80%EC%82%AC&quot;&gt;[Eclipse Plugin&lt;/a&gt; JDepend를 이용한 의존성 검사 개발관련]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://whiteship.tistory.com/967&quot;&gt;Eclipse에서 JDepend 사용하기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>JavaNCSS + Maven2 + Hudson</title>
      <link>https://blog.benelog.net//2204823.html</link>
      <pubDate>Fri, 16 Jan 2009 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2204823.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JavaNCSS를 Maven2을 통해 실행하고, Hudson을 통해 리포트를 확인하는 과정을 정리했습니다. &lt;a href=&quot;http://www.kclee.de/clemens/java/javancss/&quot;&gt;JavaNCSS&lt;/a&gt;는 주석이 아닌 라인수(Non Commenting Source Statements,NCSS)와 순환복잡도 수 (Cyclomatic Comlexity Number, CCN) 등을 측정해 주는 도구입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JavanNCSS에 관한 eclipse 플러그인도 찾을 수는 있었지만(&lt;a href=&quot;http://sourceforge.net/projects/jncss4eclipse/&quot; class=&quot;bare&quot;&gt;http://sourceforge.net/projects/jncss4eclipse/&lt;/a&gt; ) 가장 최신버전이 2002년에 올라온 것이라서 적용하지 않기로 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고, JavaNCSS에 대한 Maven의 플러그인도  &lt;a href=&quot;http://mojo.codehaus.org/javancss-maven-plugin/&quot;&gt;javancss-maven-plugin&lt;/a&gt;과 &lt;a href=&quot;http://maven-plugins.sourceforge.net/maven-javancss-plugin/&quot;&gt;maven-javancss-plugin&lt;/a&gt;의 2가지가 존재했는데, &lt;a href=&quot;http://mojo.codehaus.org/javancss-maven-plugin/&quot;&gt;javancss-maven-plugin&lt;/a&gt; 쪽이 보다 문서화가 잘 되어 있어서 그 쪽을 선택했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;pom.xml&lt;/code&gt; 에 아래 선언을 추가합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;reporting&amp;gt;

...

   &amp;lt;plugin&amp;gt;

     &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;
     &amp;lt;artifactId&amp;gt;javancss-maven-plugin&amp;lt;/artifactId&amp;gt;
     &amp;lt;version&amp;gt;2.0-beta-2&amp;lt;/version&amp;gt;
      &amp;lt;configuration&amp;gt;
      &amp;lt;forceEncoding&amp;gt;utf-8&amp;lt;/forceEncoding&amp;gt;
       &amp;lt;/configuration&amp;gt;
    &amp;lt;/plugin&amp;gt;
  &amp;lt;/plugins&amp;gt;
&amp;lt;/reporting&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고는 &lt;code&gt;mvn javancss:report javancss:check&lt;/code&gt; 로 플러그인을 실행시켜 봅니다. 각각의 골에 대한 설명은 아래 페이지에서 보시면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://mojo.codehaus.org/javancss-maven-plugin/report-mojo.html&quot;&gt;javancss:report&lt;/a&gt; : 리포트 생성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://mojo.codehaus.org/javancss-maven-plugin/check-mojo.html&quot;&gt;javancss:check&lt;/a&gt; : CCN이나 NCSS 값이 너무 크면 빌드를 실패시킴&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제외하거나 추가할 파일을 지정하려면 아래의 configuration 태그 아래에 includes, excludes 태그로 지정하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;configuration&amp;gt;

.....
  &amp;lt;includes&amp;gt;
    &amp;lt;include&amp;gt;**/*foo.java&amp;lt;/include&amp;gt;
  &amp;lt;/includes&amp;gt;
  &amp;lt;excludes&amp;gt;
    &amp;lt;exclude&amp;gt;**/bar.java&amp;lt;/exclude&amp;gt;
  &amp;lt;exclude&amp;gt;**/foobar.java&amp;lt;/exclude&amp;gt;
  &amp;lt;/excludes&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;CCN이나 NCSS값의 한계 허용치는 아래와 같이 지정합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;configuration&amp;gt;
....
  &amp;lt;failOnViolation&amp;gt;true&amp;lt;/failOnViolation&amp;gt;
  &amp;lt;ccnLimit&amp;gt;12&amp;lt;/ccnLimit&amp;gt;
  &amp;lt;ncssLimit&amp;gt;90&amp;lt;/ncssLimit&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한가지 주의할 점은, 2.0-beta-2 버전을 실행할 경우, 메서드 내부에 annotation선언이 있다면 아래와 같은 에러메시지를 받게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;Encountered &quot;@ SuppressWarnings ( \&quot;unused\&quot; ) int notUsedVariable =&quot; at line xxx, column yyy.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 것은   &lt;a href=&quot;http://mojo.codehaus.org/javancss-maven-plugin/&quot;&gt;javancss-maven-plugin&lt;/a&gt;  버전 2.0-beta-2가 이 의존하고 있는 JavaNCSS 버전 29.49 에서 가지고 있는 버그입니다. (&lt;a href=&quot;http://jira.codehaus.org/browse/MJNCSS-16&quot; class=&quot;bare&quot;&gt;http://jira.codehaus.org/browse/MJNCSS-16&lt;/a&gt; 참조) JavaNCSS 버전 29.50에서는 해결된 문제이고, 이 플러그인의 최신 버전인 &lt;a href=&quot;http://mojo.codehaus.org/javancss-maven-plugin/&quot;&gt;javancss-maven-plugin&lt;/a&gt; 2.0-beta-3버전에서는 이런 문제가 발생하지 않는다고 합니다. 그런데, codehause의 Maven Repository를 뒤져보니 (&lt;a href=&quot;http://repository.codehaus.org/org/codehaus/mojo/javancss-maven-plugin/&quot; class=&quot;bare&quot;&gt;http://repository.codehaus.org/org/codehaus/mojo/javancss-maven-plugin/&lt;/a&gt;) 아직 2.0-beta-3버전은 올라와 있지 않습니다. snapshop 버전을 찾아서 쓸 수도 있겠지만, 그것보다는plugin 설정에서 dependency설정을 덮어쓰는 편이 더 간편하게 느껴졌습니다. &lt;code&gt;pom.xml&lt;/code&gt; 의 build절에 아래와같이 추가하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;build&amp;gt;

   &amp;lt;plugin&amp;gt;
     &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;javancss-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.0-beta-2&amp;lt;/version&amp;gt;
        &amp;lt;dependencies&amp;gt;
          &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;javancss&amp;lt;/groupId&amp;gt;
              &amp;lt;artifactId&amp;gt;javancss&amp;lt;/artifactId&amp;gt;
              &amp;lt;version&amp;gt;29.50&amp;lt;/version&amp;gt;
             &amp;lt;/dependency&amp;gt;
         &amp;lt;/dependencies&amp;gt;
     &amp;lt;/plugin&amp;gt;
     &amp;lt;/plugins&amp;gt;
  &amp;lt;/build&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;생성된 리포트를 보기 위해서, Hudson에서는 프로젝트에서 Configure 설정에서 Publish Java NCSS report를 선택을 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/javancss/hudson-javancss-config.jpg&quot; alt=&quot;hudson-javancss-config.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고는 &lt;a href=&quot;http://mojo.codehaus.org/javancss-maven-plugin/report-mojo.html&quot;&gt;javancss:report&lt;/a&gt;  Goal이 포함된 빌드를 실행시켰다면 프로젝트 홈 화면에서 Java NCSS Report라는 링크가 생기게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/javancss/hudson-javancss-menu.jpg&quot; alt=&quot;hudson-javancss-menu.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;클릭을 하면 생성된 리포트를 볼 수가 있죠. (패키지명은 제가 지웠습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/javancss/hudson-javancss-report.jpg&quot; alt=&quot;hudson-javancss-report.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Maven에서 mvn site명령으로 site 절(phrase)까지 들어갔다면 리포트를 모은 페이지를 따로 생성해 주기도합니다. Hudson의 프로젝트 메인화면에서 Maven-generated site 링크를 통해서 보실 수 있습니다.Hudson에서 생성해주는 페이지가 더 이쁘기는하지만, JavaNCSS의 보고서는 Maven-generated site가 더많은 정보를 제공합니다. 예를 들면 가장 주석을 제외한 라인수(NCSS)가 많은 30위까지의 클래스들, 메소드들 같은 순위등같은 것들을 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/javancss/mvn-site-reports.jpg&quot; alt=&quot;mvn-site-reports.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>PMD + Eclipse + Maven2 + Hudson</title>
      <link>https://blog.benelog.net//2176171.html</link>
      <pubDate>Fri, 19 Dec 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2176171.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코드 검사도구인 &lt;a href=&quot;http://pmd.sourceforge.net/&quot;&gt;PMD&lt;/a&gt; 를 Eclipse plugin을 설정하고, Maven을 통해서도 같은 규칙으로 코드를 검사한 보고서를 생성하고, Hudson을 통해서 확인하는 과정을 정리해 봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eclipse에서는 update site를 &lt;a href=&quot;http://pmd.sf.net/eclipse&quot; class=&quot;bare&quot;&gt;http://pmd.sf.net/eclipse&lt;/a&gt; 로 지정해서 플러그인을 설치합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eclipse 메뉴의 Window- Preferences를 가면 Rule설정 파일을 export, import할 수 있는 기능이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/pmd/eclipse-pmd-rule.jpg&quot; alt=&quot;eclipse-pmd-rule.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Project의 Properties에도 PMD관련 설정이 있습니다. 외부에서 만든 Rule파일을 바로 참조해도 되고, 여기서 설정된 것을파일로 생성할 수도 있습니다. 이미 있는 프로젝트에서 Rule를 설정할 때는 Project의 Properties에서 Rule들을고른 후에 생기는 warning이나 error를 보고 warning이 안 뜨게 소스를 고치거나 Rule을 제외한 후, 최종결정Rule들로 Ruleset 정의 파일을 생성하는 것이 편리할 것입니다. 저는 처음에 모든 Rule을 다 선택한 다음에warning들을 없애가면서 Rule들을 하나하나 검토해 나갔었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/pmd/eclipse-pmd-project.jpg&quot; alt=&quot;eclipse-pmd-project.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 화면에서 Enable PMD를 선택하면 .project파일에 아래와 같은 부분이 추가될 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;natures&amp;gt;
      ....
   &amp;lt;nature&amp;gt;net.sourceforge.pmd.eclipse.plugin.pmdNature&amp;lt;/nature&amp;gt;
&amp;lt;/natures&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이제 해당 프로젝트는 Eclipse의 Problems View에서 PMD에서 보고하는 error, warning까지 모두 다 뜨게됩니다. 개발을 하면서 Rule 준수에 대한 빠른 피드백을 얻기 위해 되도록 이 기능을 사용하는 것이 좋습니다. 대신 이미Project에 warning이 많으면 새로운 warning들이 잘 눈에 띄지 않게 되므로, Project를 warning없이깨끗이 정리한 다음에 사용할 것을 권장해 드립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;모든 Rule을 다 적용하면 아마 굉장히 많은 warning과 때로는 에러까지도 나올 것입니다. 그런 것들을 다 검토해서 포함시킬지를 결정해야 합니다. &lt;a href=&quot;http://pmd.sourceforge.net/rule-guidelines.html를&quot; class=&quot;bare&quot;&gt;http://pmd.sourceforge.net/rule-guidelines.html를&lt;/a&gt; 참조하셔서, 프로젝트 상황에 맞게 적용해야겠죠. 그중 몇가지 Rule에 대해서만 언급을 하고 넘어가겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://pmd.sourceforge.net/rules/basic.html&quot;&gt;Basic Rules&lt;/a&gt;-EmptryInitializer:  PMD 5.0에서 추가된 룰로 Maven의 PMD plugin버전 2.4에서는 PMD 버전 4.2.2를 참조하기 때문에 이Rule은 지원되지 않습니다. 따라서 PMD의 Eclipse plugin에서 이를 지원한다고 할지라도 Maven plugin과같이 쓰기 위해서는 이 Rule을 반드시 제외해야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://pmd.sourceforge.net/rules/optimizations.html&quot;&gt;Optimization Rules&lt;/a&gt;-LocalVariableCouldBeFinal과 Controversial Rules-AvoidFinalLocalVariable : 서로 상반되는 Rule로 한쪽 Rule을피하면 다른 쪽에 걸려드는 Rule입니다. 그래서 warning을 안보려면 둘 중에 하나는 꼭 제외해야 합니다. 그런데,final을 Local variable에 일일히 선언하는 것도 번거로운 일이고, 메소드 내의 inner class에서 참조해야되어서 꼭 final이 되어야하는 local variable도 있으므로, 둘 다 제외하는 것도 좋습니다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://pmd.sourceforge.net/rules/controversial.html&quot;&gt;Controversial Rules&lt;/a&gt;-OnlyOneReturnRule :  메서드에서 return문이 여러 개일 경우 경고를 주는데, 메서드 중간의 return문은 복잡한 조건문의 구조를 단순하게 하는데 도움이 경우가 많고, &lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2824034&amp;amp;CategoryNumber=001001003016001006&quot;&gt;켄트벡의 구현패턴&lt;/a&gt; 7장 중 &apos;보호절&amp;#8217;을 보면 이를 권장하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://pmd.sourceforge.net/rules/design.html&quot;&gt;Design Rules&lt;/a&gt;-UnnecessaryLocalBeforeReturn: return 전에 따로 local 변수로 반환할 값을 선언할 때 주는 경고인데, 기능적으로는 별 의미 없는 코드이나,return 문장에는 @SupressWarning 의 Annotation을 추가할 수 없기 때문에,  Annotation 적용범위를 최소화하기 위해 그런 선언이 필요한 때도 있습니다. (Java Language Spec 9.7, EffectiveJava 2nd Edition Item 24 참조)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;검토해보니 가장 부담없이 적용가능한 RuleSet이 괄호에 대한 규칙을 정의하는 Braces Rules이고, Controversial Rules가 이름 그대로 가장 제외할 것이 많은 Rule Set입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 과정을 거쳐서 선별된 Rule 정의 파일이 만들어지면 그것을 Maven의 PMD plugin에서도 참조할 수 있게 설정합니다. 저는 Rule설정 파일이름을 .ruleset으로 하고 pom.xml에 추가했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;  &amp;lt;reporting&amp;gt;
    &amp;lt;plugins&amp;gt;
      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;maven-pmd-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.4&amp;lt;/version&amp;gt;
        &amp;lt;configuration&amp;gt;
          &amp;lt;rulesets&amp;gt;
            &amp;lt;ruleset&amp;gt;$\{basedir}/.ruleset&amp;lt;/ruleset&amp;gt;
          &amp;lt;/rulesets&amp;gt;
          &amp;lt;sourceEncoding&amp;gt;utf-8&amp;lt;/sourceEncoding&amp;gt;
          &amp;lt;targetJdk&amp;gt;1.6&amp;lt;/targetJdk&amp;gt;
          &amp;lt;minimumTokens&amp;gt;10&amp;lt;/minimumTokens&amp;gt;
        &amp;lt;/configuration&amp;gt;
      &amp;lt;/plugin&amp;gt;
  &amp;lt;/reporting&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고는 mvn site 혹은 mvn pmd:pmd pmd:cpd 처럼 PMD plugin의 goal을 포함시킨 빌드를 한번 실행시켜 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;문제가 없이 돌아갔으면 hudson에도 PMD플러그인을 설정합니다.. PMD 플러그인의 goal이 포함된 빌드를 돌리고 나면PMD warning라는 링크가 해당 프로젝트에 생기고, 거기서 아래와 같은 보고서를 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/pmd/hudson-pmd-report.jpg&quot; alt=&quot;hudson-pmd-report.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;관련자료&quot;&gt;관련자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ibm.com/developerworks/kr/library/j-pmd/&quot;&gt;PMD로 버그 잡기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://pmd.sourceforge.net/rule-guidelines.html&quot;&gt;PMD Rule guideline&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://moai.tistory.com/541&quot;&gt;PMD로 코드리뷰 자동으로 수행하기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.empas.com/seeiris/14381547&quot;&gt;PMD 이클립스 플러그인 사용법&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://ecogeo.tistory.com/70&quot;&gt;Hudson : 리포트/차트 보기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Effective &amp;amp; Agile Java Generics</title>
      <link>https://blog.benelog.net//2173103.html</link>
      <pubDate>Tue, 16 Dec 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2173103.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;div class=&quot;title&quot;&gt;수정이력&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2019.04.13&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;유효하지 않은 JavaDoc 문서 링크 최신화&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예제에서 Diamond 문법 활용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JUnit4에 대한 일반적인 설명 링크 제거&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`ServletRequest.getParameterMap()`에 대한 최신 JavaDoc 내용 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`List&amp;lt;ScheduledFuture&amp;lt;?&amp;gt;&amp;gt;`가 쓰인 예시를 Spring Integration의 소스에서 LogBack의 것으로 변경&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;generics가_들어간_테스트_코드를_통과시켜_봅시다&quot;&gt;Generics가 들어간 테스트 코드를 통과시켜 봅시다.&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래에 있는 테스트 1~5까지의  테스트 코드들을 모두 한번에 통과시키는 ListUtils.max메서드는 어떻게 선언하고 구현해야 할까요?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Generics를 써보신 분이라면 &lt;a href=&quot;http://benelog.springnote.com/pages/2231980/attachments/1026628?dummy=1229420647470&quot;&gt;ListUtilsTest.java&lt;/a&gt;를 다운 받으셔서 한번 풀어 보시기 바랍니다. Collections.max()를 아시는 분도 그 메소드를 참고하시지 마시고 직접 메서드를 만들어 보시면 재미있으실 겁니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제약조건은 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@SuppressWarnings(&quot;unchecked&quot;)&lt;/code&gt; 를 쓰지 않고도 Generics에 대한 warning이 없고, Casting도 한번도 하지 않아야 하고&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컴파일 시점에서 ListUtils.max 메소드에 Comparable 인터페이스를 구현한 객체들을 쌓은 List가 넘어온다는 것을 검증할 수 있어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;테스트 1 :  빈 리스트가 넘어오면 null값 반환&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    @Test
    public void getNullIfEmptyList(){
        List&amp;lt;Integer&amp;gt; numbers = new ArrayList&amp;lt;&amp;gt;();
        Integer max = ListUtils.max(numbers);
        assertThat(max,is(nullValue()));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;테스트 2 :  Integer객체의 최대값 구하기&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    @Test
    public void getMaxInteger(){
        List&amp;lt;Integer&amp;gt; numbers = new ArrayList&amp;lt;&amp;gt;();
        numbers.add(Integer.valueOf(1))
        numbers.add(Integer.valueOf(2));
        Integer max = ListUtils.max(numbers);
        assertThat(max,is(Integer.valueOf(2)));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;테스트 3 :  BigInteger객체의 최대값 구하기&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    @Test
    public void getMaxBigInteger(){
        List&amp;lt;BigInteger&amp;gt; numbers = new ArrayList&amp;lt;&amp;gt;();
        numbers.add(BigInteger.ZERO);
        numbers.add(BigInteger.ONE);
	BigInteger max = ListUtils.max(numbers);
        assertThat(max,is(BigInteger.ONE));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;테스트 4: java.sql.Date 객체의 최대값 구하기&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    @Test
    public void getMaxDate(){
        java.sql.Date now = new java.sql.Date(new Date().getTime());
        java.sql.Date afterAWhile = new java.sql.Date(new Date().getTime()+6000);
        List&amp;lt;java.sql.Date&amp;gt; dates = new ArrayList&amp;lt;java.sql.Date&amp;gt;();
        dates.add(now);
        dates.add(afterAWhile);
        java.sql.Date max = ListUtils.max(dates);
        assertThat(max,is(afterAWhile));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;테스트5 : ScheduledFuture를 구현한 객체의 최대값 구하기&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    @Test
    public void getMaxScheduledFuture() throws InterruptedException{
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
        ScheduledFuture&amp;lt;?&amp;gt; after1Second = executor.schedule(getTask(&quot;first&quot;),1,TimeUnit.SECONDS);
        ScheduledFuture&amp;lt;?&amp;gt; after2Seconds = executor.schedule(getTask(&quot;second&quot;),2,TimeUnit.SECONDS);
        List&amp;lt;ScheduledFuture&amp;lt;?&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;ScheduledFuture&amp;lt;?&amp;gt;&amp;gt;();
        futures.add(after1Second);
        futures.add(after2Seconds);
        ScheduledFuture&amp;lt;?&amp;gt; max = ListUtils.max(futures);
        long maxDelay = max.getDelay(TimeUnit.SECONDS);
        assertThat(maxDelay,is(after2Seconds.getDelay(TimeUnit.SECONDS)));
        Thread.sleep(3000);
        assertTrue(max.isDone());
    }
    private Runnable getTask(final String message) {
        Runnable task = new Runnable(){
            public void run() {
                System.out.println(message);
            }
        };
        return task;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/index.html?java/util/concurrent/ScheduledFuture.html&quot; class=&quot;bare&quot;&gt;https://docs.oracle.com/javase/8/docs/api/index.html?java/util/concurrent/ScheduledFuture.html&lt;/a&gt;]  API문서 참조)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;풀이와_설명&quot;&gt;풀이와 설명&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트 1,2,3번 까지의 코드만이라면 아래와 같이 선언하셔도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트 1: 간단한 max 메서드 선언&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public static &amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt; T max(List&amp;lt;T&amp;gt; list)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 &lt;code&gt;&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; 처럼 Type parameter가 그 자신이 포함된 표현으로 그 범위가 선언되는 것을 recursive type bound라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Integer와 BigDecimal의 클래스 선언을 보면 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트 2: Integer 와 BigInteger 클래스 선언부&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public final class Integer extends Number implements Comparable&amp;lt;Integer&amp;gt;

public class BigInteger extends Number implements Comparable&amp;lt;BigInteger&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;두 클래스 모두 자신의 타입이 Parameterized type으로 들어간 Comparable 인터페이스를 구현하고 있기 때문에 리스트3의 메소드 선언으로도 Integer나 BigInteger가 담긴 리스트를 받을 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 리스트 1의 선언으로는 테스트4,5에 있는 메서드에서 컴파일 에러가 날 것입니다. 그 이유는 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;테스트4의 &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/sql/Date.html&quot;&gt;java.sql.Date&lt;/a&gt;는 &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/Date.html&quot;&gt;java.util.Date&lt;/a&gt;를 상속한 클래스입니다.
그런데 &lt;code&gt;java.sql.Date&lt;/code&gt; 는 따로 comparesTo 메서드를 재정의하고 있지 않고, 상위클래스인 &lt;code&gt;java.util.Date&lt;/code&gt; 에 있는 메서드를 그대로 쓰고 있습니다.
&lt;code&gt;java.sql.Date&lt;/code&gt; 는 &lt;code&gt;Comparable&amp;lt;java.sql.Date&amp;gt;&lt;/code&gt; 한 것이 아닌 &lt;code&gt;Comparable&amp;lt;java.util.Date&amp;gt;&lt;/code&gt; 를 구현한 것이라고 볼 수 있습니다.
(두 클래스의 이름이 같아서 혼동이 되실 수도 있습니다.
&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2600965&amp;amp;CategoryNumber=001001003016003014&quot;&gt;Java Puzzler&lt;/a&gt;에서는 이 두 클래스의 예를 들면서 자바 플랫폼 설계자가 이름을 지으면서 깜빡 존 듯하다고 언급하고 있습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 테스트5의 &lt;code&gt;java.util.concurrent.ScheduledFuture&lt;/code&gt; 인터페이스는 &lt;code&gt;Comparable&amp;lt;ScheduledFuture&amp;gt;&lt;/code&gt; 를 구현한 것이 아닌, &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Delayed.html&quot;&gt;Delayed&lt;/a&gt;라는 인터페이스를 상속한 것이고, 이 Delayed는 &lt;code&gt;Comparable&amp;lt;Delayed&amp;gt;&lt;/code&gt; 를 상속한 인터페이스입니다. 리스트 3의 인터페이스 선언을 보시면 쉽게 이해가 되실 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트 3: Delayed 인터페이스 선언부&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public interface ScheduledFuture&amp;lt;V&amp;gt; extends Delayed, Future&amp;lt;V&amp;gt;

public interface Delayed extends Comparable&amp;lt;Delayed&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 경우도 모두 통과할 수 있게 &lt;code&gt;ListUtils.max()&lt;/code&gt; 메서드를 선언하고 구현하면 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트4: ListUtils 구현&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class ListUtils {
    public static &amp;lt;T extends Comparable&amp;lt;? super T&amp;gt;&amp;gt; T max(List&amp;lt;T&amp;gt; list){
        T result = null;
        for(T each : list) {
            if (result==null) result = each;
            if(each.compareTo(result)&amp;gt;0) result = each;
        }
        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;public static &amp;lt;T extends Comparable&amp;lt;? super T&amp;gt;&amp;gt; T max(List&amp;lt;T&amp;gt; list)&lt;/code&gt; 라는 긴 메서드 선언입니다. 이 선언 안에는 recursive type bound, wild card, upper bound, lower bound가  다 들어가 있습니다. 이 정도 메서드를 설계할 수 있어야지, Java generics를 제대로 아는 것이라고 할 수 있겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;bounded wild card를 적용하는 기준은 Effective Java 2nd Edition에 나와 있는 PECS(Producer-extends, Consumer-super)원칙을 기억하시면 도움이 됩니다. 원래 &lt;a href=&quot;http://engdic.daum.net/dicen/contents.do?query1=ED00866730&quot;&gt;PECS의 뜻&lt;/a&gt;은 가슴 근육이라는군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;t_extends_comparable&quot;&gt;&lt;code&gt;&amp;lt;T extends Comparable&amp;#8230;&amp;#8203;.&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Comparable인터페이스를 구현한 클래스가 그 대상이어야 max내부에서 Comparable.compareTo를 이용해서 최대값을 구할 수 있습니다. 그래서 타입 T는 T extends Comparable이 되어야 합니다. PECS원칙으로도 리턴값으로 생산되는 (Producer) 타입이 T이므로 extends를 쉽게 연상하실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;comparable_super_t_부분&quot;&gt;&lt;code&gt;Comparable&amp;lt;? super T&amp;gt; 부분&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;max 메서드 내부에서 타입 T는 Comparable.compareTo(T o)메서드 뒤에 파라미터로 넘어가는, 소비되는(Consumer) 대상으로 쓰이기에 PECS원칙으로 super로 연결시킬 수 있습니다. 테스트5의 코드를 예로 보면, ScheduledFuture는 ScheduledFuture의 상위 인터페이스인 Delayed가 Comparable의 Parameterized type으로 넘어가는 Comparable&amp;lt;Delayed&amp;gt;형태의 Comparable인터페이스를 상속하고 있습니다.  T를 ScheduledFuture로 봤을 때 Comparable&amp;lt;? super T&amp;gt;는 Comparable&amp;lt;Delayed&amp;gt;와 잘 맞아떨어집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 리스트4의 ListUtils.max 메서드는 &lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2416238&amp;amp;CategoryNumber=002&quot;&gt;Effective Java 2nd Edition&lt;/a&gt;의 Item28에 나오는 코드를 보고서 약간 변경을 해 본 것입니다. 원래 책에 나오는 코드는 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트5: Effective Java 2nd Edition에 있는 max메서드&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public static &amp;lt;T extends Comparable&amp;lt;? super T&amp;gt;&amp;gt; T max(List&amp;lt;? extends T&amp;gt; list){
  Iterator&amp;lt;? extends T&amp;gt; i = list.iterator();
  T result = i.next();
  while(i.hasNext()){
            T t = i.next();
            if (t.compareTo(result)&amp;gt;0) result = t;
   }
   return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;메서드 선언이 &lt;code&gt;public static &amp;lt;T extends Comparable&amp;lt;? super T&amp;gt;&amp;gt; T max(List&amp;lt;? extends T&amp;gt; list)&lt;/code&gt; 로 예제보다 더 늘어난 부분이 있습니다. 끝에 있는 &lt;code&gt;List&amp;lt;? extends T&amp;gt;&lt;/code&gt; 가 추가된 것입니다. 이 부분은 PECS원칙에 따르면 List객체로부터 T를 생산(Producer)해 오기 때문에 &lt;code&gt;? extends T&lt;/code&gt; 로 하는 것이 적절해 보이는 합니다. 그러나 테스트1~5의 코드에서는 &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; 만으로도 컴파일러가 수행하는 형추론(type inference)에 문제가 없었기에 제가 만든 코드인 리스트4에는 추가하지는 않았습니다. 컴파일러가 수행하는 Type inference는 굉장히 복잡하고, Java Language Spec에서 16페이지나 차지한다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 리스트5에서는 길이가 0인 List가 넘어간 값일 때는 첫번째 i.next();에서 NoSuchElementException을 내게 되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#max-java.util.Collection-&quot;&gt;java.util.Collections.max&lt;/a&gt; 메서드에서도 같은 결과가 나오는 것으로 보아서, 유사한 구현방식이 쓰인 것으로 추측됩니다. 제가 만든 문제에서는 Collection.max와 약간 다른 부분을 만들어 보고 싶어서 길이가 0일 list가 올 때는 null을 반환하는 방식으로 바꾸어 보았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다면 `java.util.Collections.max`의 메서드 시그니처는 어떻게 되어 있을까요?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트6: java.util.Collections.max 메서드&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;static &amp;lt;T extends Object &amp;amp; Comparable&amp;lt;? super T&amp;gt;&amp;gt; T max(Collection&amp;lt;? extends T&amp;gt; coll)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;일단 대상이 List보다 확장된 Collection이니 Parameter가 Collection인 것이 눈에 들어 옵니다. 그런데 T의 제약조건이 &amp;lt;T extends Object &amp;amp; Comparable&amp;lt;? super T&amp;gt;&amp;gt;로 선언되어 있는 것이 리스트5의 코드보다 &apos;Object &amp;amp;&apos; 부분이 더 들어가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이것은 java1.4와의 하위 호환성을 위한 것입니다. Java에서는 하위호환성 지원 때문에 컴파일 시에 Generics관련 정보를 모두 검사한 후에는 실제로는 Generics 정보가 전혀 없는 바이트코드를 생성하게 되어 있습니다. 그래서 리스트5처럼 메서드를 선언했을 때에는 런타임시에는 리스트 7과 같은 코드와 같은 바이트코드가 생성됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트 7: 리스트5의 메서드 선언이 자료형 지우기가 수행된 뒤의 모습&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;static Comparable max(Collection c)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 이전 버전에서의 max메서드의 모습은 다음과 같았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트8: Java5 이전의 Collections.max 메서드&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public static Object max(Collection c)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서 리스트7처럼 Comparable을 반환하게 된다면 이것은 이전버전의 메서드 Signature를 바꿔버린 것이 되므로 하위버전에서 컴파일된 코드에서 Collections.max를 호출할 때 에러를 발생시키게 됩니다. 그래서 Object &amp;amp;이 더 추가된 것이죠. (Agile Java의 Lesson 12 중 Additional Bounds에서 참조)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여기까지 이해하셨으면, 실무에서 어떤 Generics 관련 코드를 봐도 이제 쉬워보이실 겁니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;generics의_표현력&quot;&gt;Generics의 표현력&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;하나의 예제로 Generics의 많은 부분을 설명하기 위해서 다소 복잡한 코드를 보여드렸습니다. 혹시나 Generics를 이제 막 적용하시고 싶으신 분들의 마음을 어둡게 한 것이 아닌가 걱정이 되기도 합니다. 그러나 대부분의 Generics적용 사례는 훨씬 간단하고, 특히 Collection 선언에 genercics를 활용하는 정도는 어렵지 않습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Generics는 컴파일시점에서의 에러 검출 영역을 넓혀줘서 보다 이른 시점에 버그를 잡을 수 있게 해주고, 코드I의 설명력을 높여줘서 API사용자들이 보다 쉽게 API를 쓸 수 있게 해줍니다. 컴파일타입의 에러체크 능력은 위의 예제를 통해서 설명했으니, 표현력에 대해서도 제가 겪은 사례를 이야기해 볼까 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오래　전에 저는 Java 인터페이스를 엑셀파일로 만드는 산출물 작업을 했었는데, 리턴타입이 List인 메서드들은 그 안에 어떤 객체들이 들어있는지 메서드 시그니처만으로는 표현할 수 없어서 답답했던 적이 있었습니다. 그래서 아예 List대신 배열을 쓸까도 고민하다가 List가 가진 편의성들을 버릴 수가 없어서 List를 쓰고 따로 문서에 그 안에 어떤 객체가 들어가 있는지를 적을 수 밖에 없었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/javaee/5/api/javax/servlet/ServletRequest.html#getParameterMap()&quot;&gt;javax.servlet.ServletRequest.getParameterMap()&lt;/a&gt;를 사용할 때는 API사용자로서 아쉬움을 느꼈었습니다. API문서를 보면 이 메서드가 반환하는 Map에는 key로 String이, value로 String배열이 들어가 있는 것으로 설명되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/java/request-getParametersMap.gif&quot; alt=&quot;request getParametersMap&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 처음에 이 문서를 안 보고 key가 String, 값이 하나일 때는 그냥 String, 값이 2개 이상이면 String배열이 들어가 있지 않을까하는 추측을 바탕으로 한 코드를 짜서 몇번 에러를 냈었었습니다.
결국 API문서를 보고서 어떤 형식으로 자료가 들어가 있는지 알게 되어있습니다.
이 메소드의 리턴타입이 &lt;code&gt;Map&amp;lt;String,String[]&amp;gt;&lt;/code&gt; 과 같이 선언되어 있었다면, 문서를 안 보고도, Runtime 에러를 안 겪고도 바로 올바른 자료형으로 사용이 가능했을 것입니다.
이렇듯 Generics를 잘 활용할 수 없는 API를 쓸 때에는 소비자 입장에서 불편합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;나중에 저는 이 메서드를 호출하는 부분을 아래와 같이 감싸는 부분을 넣었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;리스트9: &lt;code&gt;javax.servlet.ServletRequest.getParameterMap()&lt;/code&gt; 메서드를 Generics를 이용한 코드로 감싸기&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;@SuppressWarnings(&quot;unchecked&quot;)
Map&amp;lt;String,String[]&amp;gt; requestMap = request.getParameterMap();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;@SuppressWarnings(&quot;unchecked&quot;)&lt;/code&gt; 은 어쩔 수 없는 경우에만 써야 하고, 쓸 때도 클래스 단위, 메서드 단위가 아닌 이런 최소 라인 단위로 써야 합니다.
Effective Java 2nd Edition Item 24참조)
이 경우는 Generics지원하지 않는 외부 인터페이스를 호출하는 것이라서 불가피한 경우입니다.
형에 대해서는 API 문서에 명시된 내용라서 이렇게 @SuppressWarnings을 선언해도 문제가 없습니다.
필요에 따라서 이 requestMap을 리턴해 준다면 그것을 쓰는 코드에서는 더 이상 이 안에 무엇이 들어있는지 문서를 찾아보지 않아도 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/javaee/6/api/javax/servlet/ServletRequest.html#getParameterMap()&quot;&gt;
JavaEE6 버전의 JavaDoc&lt;/a&gt;을 보니 이제는 &lt;code&gt;request.getParamersMap`의 반환형이 `Map&amp;lt;String,String[]&amp;gt;&lt;/code&gt; 으로 바뀌어 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/java/request-getParametersMap-ee6.png&quot; alt=&quot;request getParametersMap ee6&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;관련자료_모음&quot;&gt;관련자료 모음&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;generics_관련_자료&quot;&gt;Generics 관련 자료&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 포스트는 주로 아래 두 책을 보면서 얻은 정보를 통해 작성되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2416238&amp;amp;CategoryNumber=002&quot;&gt;Effective Java 2nd Edition&lt;/a&gt; : &lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2416238&amp;amp;CategoryNumber=002&quot; class=&quot;bare&quot;&gt;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2416238&amp;amp;CategoryNumber=002&lt;/a&gt;Chater 4: Item23 ~ Item 29&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=1534922&amp;amp;CategoryNumber=002001026004&quot;&gt;Agile Java&lt;/a&gt; : &lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=1534922&amp;amp;CategoryNumber=002001026004&quot; class=&quot;bare&quot;&gt;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=1534922&amp;amp;CategoryNumber=002001026004&lt;/a&gt;Lesson 14&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Agile java처럼 테스트 코드를 먼저 보여주었고 , ListUtils.max 메서드는 Effective Java의 내용을 주로 참조해서 작성했습니다. Effective Java에서는 ScheduledFuture의 경우에 대해서 언급만 되어 있고 예제코드가 없는 것이 아쉬워서 테스트5의 코드를 작성했고, 비슷한 사례의 보다 친숙한 클래스를 찾다가  Agile java에서 java.sql.Date 클래스가 예제에 많이 쓰인 것을 보고 테스트4를 추가했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2416238&amp;amp;CategoryNumber=002&quot;&gt;Effective Java 2nd Edition&lt;/a&gt; 에 포함된 내용 중 Java5 관련 내용은 Joshua Bloch이 했던 발표에 잘 요약되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://developers.sun.com/learning/javaoneonline/2006/coreplatform/TS-1512.pdf&quot;&gt;Effective Java Reloaded, JavaOne Conference 2006&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://javapolis.libsyn.com/index.php?post_id=380974&quot;&gt;Effective Java Reloaded - 2008&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그외 Generics에 관한 자료들의 링크는 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf&quot;&gt;Genercis tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://mkseo.pe.kr/blog/?p=1691&quot;&gt;Summary on some java generics presentations/postings&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ibm.com/developerworks/kr/library/j-jtp04298.html&quot;&gt;자바 이론과 실습: 제네릭스 해부, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://whiteship.me/1932&quot;&gt;Generic 팩토리 메소드&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ibm.com/developerworks/kr/library/j-jtp07018.html&quot;&gt;자바 이론과 실습: 제네릭스 해부, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://gafter.blogspot.com/2006/12/super-type-tokens.html&quot;&gt;Super Type Tokens&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ibm.com/developerworks/java/library/j-jtp01255.html&quot;&gt;Generics gotchas&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://whiteship.tistory.com/1614&quot;&gt;Generic 메타데이터 활용하기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;concurrent_관련자료&quot;&gt;Concurrent 관련자료&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Effective Java에서 언급한 ScheduledFuture를 이용한 예제코드를 만들다 보니 Concurrent관련 API들이 몇개 포함되었습니다. 그 API들에 관심이 있으신 분은 아래 자료를 참조하시면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=3015162&amp;amp;CategoryNumber=001001003016003014&quot;&gt;자바 병렬프로그래밍 :&lt;/a&gt; 6장 작업실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;관련 API 문서&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/Executors.html&quot;&gt;java.util.concurrent.Executors&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ScheduledExecutorService.html&quot;&gt;java.util.concurrent.ScheduledExecutorService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ScheduledThreadPoolExecutor.html&quot;&gt;java.util.concurrent.ScheduledThreadPoolExecutor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;List&amp;lt;ScheduledFuture&amp;lt;?&amp;gt;&amp;gt;&lt;/code&gt; 의 코드가 실전에서 쓰인 것이 없을까해서 찾아보니 로깅　프레임워크인　Logback의　소스 코드에서 그런 코드가 발견되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/qos-ch/logback/blob/master/logback-core/src/main/java/ch/qos/logback/core/ContextBase.java#L52［ContextBase.java］&quot; class=&quot;bare&quot;&gt;https://github.com/qos-ch/logback/blob/master/logback-core/src/main/java/ch/qos/logback/core/ContextBase.java#L52［ContextBase.java］&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>스프링 프레임웍의 최근 소식들 모음 등</title>
      <link>https://blog.benelog.net//2166348.html</link>
      <pubDate>Wed, 10 Dec 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2166348.html</guid>
      	<description>
	&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://javajigi.tistory.com/207&quot;&gt;Spring Source 진영의 향후 RoadMap&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksug.org/77&quot;&gt;SpringSource의 또 다른 야심작, tc Server&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksug.org/72&quot;&gt;&quot;Spring Python&quot; 파이썬 개발자에게도 봄(?)을&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksug.org/68&quot;&gt;Spring, Grails를 품에 안다&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksug.org/79&quot;&gt;Spring One America 2008 참석기 링크 모음&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.springsource.com/2008/12/05/spring-framework-30-m1-released/&quot;&gt;First Spring Framework 3.0 milestone released&lt;/a&gt;http://whiteship.me/2091[]&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://whiteship.me/2091&quot;&gt;스프링 프레임워크 3.0 M1 배포~&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;역시 최근 들어 스프링소스의 발걸음이 더욱 빨라졌습니다. Grails, Spring Python, tc Server, 3.0M1 발표.. 현란하게 소식이 쏟아지는군요. 그만큼 공부할 것도 더 늘겠지만, 그래도 반갑고 기대감을 가지게 하는 소식들입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대규모 투자를 받고, 일을 더 벌이고 하면서 스프링소스가 돈맛에 초심을 잃을까봐 걱정을 하시는 분도 많이 계시는 듯 합니다. 그래도 성공적인 사업 구조가 정착되어서 그 수익이 개발자들에게 도움이 되는 제품들을 더 잘 개발하는데 쓰인다면야 굳이 돈을 많이 벌게 되는 것이 나쁘다고는 생각하지 않습니다. 유료로 제공되는 제품이 많아진다고 해도, 그럴만한 가치가 있는 제품이면 돈을 내고, 그것이 아니라도 프레임웍이나 서버, 개발도구의 시장은 독점 시장이 아니니 다른 선택을 할 수 있는 대안이 많을 것입니다. 물론 지금까지의 스프링의 행보를 봐서는 충분히 앞으로도 가치있는 결과물을 만들 것이라고 기대하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여러 소식들 중에 솔직히 가장 재미있었던 것은, Spring one America 2008에 참석하신 분들을 통해 알게된, 로드존슨의 나이였습니다. 스프링 핵심 개발자인 유겐할러의 말에 의하면 로드존슨은 38살 정도라고 하네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;왜 저는 로드존슨을 당연히 40대라고 생각했을까.. 하고 분석해보니, Expert one-on-one J2EE Design and Developement 책 때문에 그런 선입관을 가지게 된 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책이 나온 것이 2003년인데, 그렇다면 이 때는 33살이였단 말이되는군요.
이 책을 쓰는데도 2년 정도가 걸렸다고 들은 것 같습니다.
그리고 책 표지에 보면 로드존슨은 음악학(Musicology)에 박사학위가 있다는 말도 있습니다. 그래서, 다른 분야도 박사까지 하고 컴퓨터 분야에서도 이정도 경지까지 가려면 적어도 이 책 출판시에 30대 중후반은 되지 않았을까.. 하는 추측을 한 것이죠.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>FindBugs + Eclipse + Maven2 + Hudson</title>
      <link>https://blog.benelog.net//2079841.html</link>
      <pubDate>Tue, 30 Sep 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2079841.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://findbugs.sourceforge.net/&quot;&gt;FindBugs&lt;/a&gt;를 이용한 코드검사를 Maven2을 통해 실행하고, Hudson을 통해 확인하는 설정을 정리해 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eclipse에서 findbugs로 코드검사를 해볼 수 있는 툴은 &lt;a href=&quot;http://findbugs.cs.umd.edu/eclipse/&quot; class=&quot;bare&quot;&gt;http://findbugs.cs.umd.edu/eclipse/&lt;/a&gt; 를 update site로 지정하면 설치할 수 있습니다. 설치가 잘 되었다면 소스 폴더를 선택하고 마우스 우클릭을 하면 &apos;Find Bugs&amp;#8217;라는 메뉴가 생긴 것이 보일 것입니다.
그 메뉴를 통해 원하는 프로젝트를 검사하고, Bug Explorer 탭을 선택해보면 아래와 같은 화면이 나옵니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/findbugs/eclipse-findbugs.jpg&quot; alt=&quot;eclipse-findbugs.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Bug Explorer 탭에서는 버그를 유형별로 정리해서 보여주고, 소스탭에서는 해당하는 코드에 벌레 모양 아이콘을 찍어줍니다. 그리고 Problems 창에서는 Eclipse에서 잡아내는 다른 경고처럼 warning으로 해당 소스를 표시해 줍니다. Bug Details 탭을 누르면 버그에 대한 자세한 설명도 볼 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;프로젝트의 Properties 메뉴에서 FindBugs 설정란으로 가면 검사할 규칙 등을 선택할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/findbugs/eclipse-findbugs-config1.jpg&quot; alt=&quot;eclipse-findbugs-config1.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 설정화면에서 &apos;Run FindBugs automatically&amp;#8217;를 선택하면, 소스가 바뀔 때마다 자동으로 검사를 수행해 줍니다. 이 기능이 선택되어 있지 않다면, 지적된 소스를 수정해도 다시 수동으로 검사를 돌려야지 경고메시지 지워지므로, 이클립스가 아주 느리다는 느낌이 안 들정도라면 선택하는 것이 좋습니다. 이 기능을 선택해서, Eclipse의 Problems 탭에서 코드 작성 즉시 에러와 경고를 확인할 수 있게 되었다면 .project 파일에 아래와 같은 부분이 추가되어 있을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;natures&amp;gt;

    ....

  &amp;lt;nature&amp;gt;edu.umd.cs.findbugs.plugin.eclipse.findbugsNature&amp;lt;/nature&amp;gt;

&amp;lt;/natures&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;FindBugs 설정 화면 중 Detector Configuration 탭에서는 검사할 규칙들을 지정할 수 있고, Reporter configuration 탭에서는 보고해 줄 버그의 경고단계와 분류를 선택할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/findbugs/eclipse-findbugs-config2.jpg&quot; alt=&quot;eclipse-findbugs-config2.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Filter files 탭에서는 별도의 XML파일로 선언된 포함하거나 제외시킬 버그와 파일에 대한 설정을 가지고 올 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/findbugs/eclipse-findbugs-config3.jpg&quot; alt=&quot;eclipse-findbugs-config3.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래에 자세히 설명하겠지만, Maven 설정에서 참조하는 findBugsExclude.xml을 Eclipse plugin에서도 똑같이 지정해서 Maven과 Eclipse에서 같은 기준으로  검사가 수행되도록 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Maven2의 &lt;a href=&quot;http://mojo.codehaus.org/findbugs-maven-plugin/&quot;&gt;findbugs-maven-plugin&lt;/a&gt;은  pom.xml에 아래와 같이 설정됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&amp;lt;plugin&amp;gt;
  &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;findbugs-maven-plugin&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;2.4.0&amp;lt;/version&amp;gt;
  &amp;lt;configuration&amp;gt;
    &amp;lt;findbugsXmlOutput&amp;gt;true&amp;lt;/findbugsXmlOutput&amp;gt;
    &amp;lt;findbugsXmlWithMessages&amp;gt;true&amp;lt;/findbugsXmlWithMessages&amp;gt;
    &amp;lt;xmlOutput&amp;gt;true&amp;lt;/xmlOutput&amp;gt;
    &amp;lt;excludeFilterFile&amp;gt;$\{basedir}/findBugsExclude.xml&amp;lt;/excludeFilterFile&amp;gt;
   &amp;lt;/configuration&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 설정에 들어가는 속성들에 대해서는 &lt;a href=&quot;http://mojo.codehaus.org/findbugs-maven-plugin/findbugs-mojo.html&quot;&gt;findbugs-maven-plugin 설명 페이지&lt;/a&gt;에서 자세한 내용을 보실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 제외할 검사규칙을 지정하기 위해서 excludeFilterFile속성에 findBugsExclude.xml을 지정했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;findBugsExclude.xml의 내용은 아래와 같이 설정했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;....
&amp;lt;FindBugsFilter&amp;gt;
....
....
  &amp;lt;Match&amp;gt;
....
....
   &amp;lt;Bug code=&quot;Se,SnVI,Dm,UwF,EI,EI2&quot; /&amp;gt;
....
....
  &amp;lt;/Match&amp;gt;
....
....
&amp;lt;/FindBugsFilter&amp;gt;
....&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제외할 것을 선언하는 파일에 이렇게 적었으니 Bug code가 &quot;Se,SnVI,Dm&quot;에 해당하는 버그검사는 제외한다는 의미입니다. Filter의 설정 방법에 대해서는 &lt;a href=&quot;http://mojo.codehaus.org/findbugs-maven-plugin/findbugs-mojo.html&quot;&gt;findbugs의 매뉴얼&lt;/a&gt;을 참조하시면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;버그 코드 중 Se,SnVI는 serialVersionUID에 관한 것이고 Dm은 String.toUpperCase 등의 메소드에서 Local설정을 권유하는 검사입니다.   (&lt;a href=&quot;http://findbugs.sourceforge.net/bugDescriptions.html&quot;&gt;버그 코드에 대한 설명 페이지&lt;/a&gt; 참조)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이렇게 설정을 하고 &lt;code&gt;mvn findbugs:findbugs&lt;/code&gt; 로 maven을 실행시키면 필요한 라이브러리들을 다운로드 받고 빌드가 실행됩니다. 실행이 성공했다면 목적지 폴더에 findbugs.xml과 findbugsXml.xml파일이 생성이 되었을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이것을 Hudson을 통해서 보기 위해서는 Hudson에서도 findbugs plugin을 설치해야 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hudson 첫 화면에서 Manage Hudson - Manage Plugins 메뉴를 찾아갑니다. Available 탭에서 findbugs를 선택하고 화면 우측하단의 &apos;install&amp;#8217;버튼을 누르면 Hudson이 알아서 라이브러리를 다운 받아줍니다. 설치한 plug-in이 실행되기 위해서는 Hudson을 재시작해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런 다음에 findbugs를 적용하고자 하는 프로젝트에 가서 Configure메뉴를 선택하면 아래와 같이 Publish FindBugs Analysis Result라는 부분이 추가된 것을 보실 수 있을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/findbugs/hudson-findbugs-config.jpg&quot; alt=&quot;hudson-findbugs-config.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이것을 선택하고 원하는 기준값이 있을 경우 입력한 뒤에 &quot;save&amp;#8217;버튼을 누르고 build를 해보면 됩니다. 물론 build에는 findbugs:findbugs goal이 포함되어야 하겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;빌드가 성공하는 것을 보고 프로젝트의 메뉴를 보면 FindBugs Warnings라는 메뉴가 추가된 것을 확인하실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/findbugs/hudson-findbugs-menu.jpg&quot; alt=&quot;hudson-findbugs-menu.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 메뉴를 누르면 생성된 보고서가 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/findbugs/hudson-findbugs-report.jpg&quot; alt=&quot;hudson-findbugs-report.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;warning이 존재할 경우 건수를 클릭하면 해당하는 클래스들이 나오고, 클래스를 선택하면 소스에서 warning을 발생시키는 부분까지 보여줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;만약 hudson의 findbugs plugin을 실행할 때 Cannot find setter nor field in org.apache.maven.plugins.site.SiteMojo for &apos;xmlOutput&apos; 와 같은 에러가 난다면 &lt;a href=&quot;http://blog.benelog.net/2208375&quot;&gt;Hudson plugin 수동으로 빌드&amp;amp;업로드&lt;/a&gt;를 참조해서 최신 버전으로 플러그인을 업데이트 해보시기 바랍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;관련_자료&quot;&gt;관련 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://okjsp.tistory.com/1165643579&quot;&gt;Sun Techdays 2008 Lightning Talk 발표자료; findbugs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://okjsp.tistory.com/1165643626&quot;&gt;Hudson의 Findbugs 플러그인 이용하기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://okjsp.tistory.com/1165643570&quot;&gt;findbugs eclipse plugin 설치&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.buggymind.com/177&quot;&gt;FindBugs: 코드의 정적 분석을 통한 버그 탐색&lt;/a&gt; : 동영상 강연을 보니 구글에서도 이 도구를 사용하고 있다고 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Cubrid에서 commit시에 Statement가 닫히는 현상</title>
      <link>https://blog.benelog.net//2073073.html</link>
      <pubDate>Thu, 25 Sep 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2073073.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음의  코드는 1000건을 insert하면서 100건마다 한번씩 commit을 하는 기능을 수행합니다. 대용량 데이터를 입력 할 때 이렇게 주기적으로 commit을 해주는 것은 일반적인 처리방식입니다. Cubrid DB에서는 이와 같은 코드를 실행시키면 &apos;Attempt to access a closed PreparedStatement.&apos;라는 에러 메시지를 받게 될 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Properties;

public class JDBCTest {

  public static void main(String[] args) {
    String query = &quot;INSERT into common_cd(cd_type, cd, cd_name) values(?, ?, ?);&quot;;
    Properties cubrid = new Properties();
    cubrid.put(&quot;url&quot;,&quot;jdbc:cubrid:127.0.0.1:43003:test:::&quot;);
    cubrid.put(&quot;driver&quot;,&quot;cubrid.jdbc.driver.CUBRIDDriver&quot;);
    cubrid.put(&quot;user&quot;,&quot;test&quot;);
    cubrid.put(&quot;password&quot;,&quot;test&quot;);

    insertAndCommit(cubrid, query);
 }

  private static void insertAndCommit(Properties prop,String query) {
    Connection con =  null;
    PreparedStatement psmt  = null;
    try{
     Class.forName (prop.getProperty(&quot;driver&quot;));
     con = DriverManager.getConnection(prop.getProperty(&quot;url&quot;),prop);
     con.setAutoCommit(false);
     psmt = con.prepareStatement(query);
     for(int i=1;i&amp;lt;=1000;i++)\{
       psmt.setString(1, &quot;00&quot;);
       psmt.setString(2, String.valueOf(i));
       psmt.setString(3, &quot;code:&quot;+i);
       psmt.execute();
       System.out.println(i+&quot;th row set&quot;);
       if(i%100==0) {
         con.commit();
         System.out.println(&quot;commit-------------------------------------&quot;);
       }
     }
    } catch (Exception ex){
      ex.printStackTrace();
    } finally{
     try{ psmt.close();} catch(Exception e){}
     try{ con.close();} catch(Exception e){}
    }
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;commit 을 했을 뿐인데, Statement가 닫혀버리는 현상이 발생한 것이죠. 원인을 찾기 위해 Cubrid JDBC드라이버의 클래스 파일들을 역컴파일 해서 봤더니, 아래와 같은 completeAllStatements()라는 메소드가 commit 메소드 안에서 호출이 되는 것이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;private void completeAllStatements() throws SQLException {
    for (int i = 0; i &amp;lt; statements.size(); i++) {
        UniSQLStatement unisqlstatement = (UniSQLStatement) statements.get(i);
        if (unisqlstatement instanceof UniSQLPreparedStatement) {
            statements.remove(i);
            if (u_con.brokerInfoStatementPooling())   unisqlstatement.complete();
            else   unisqlstatement.close();
        } else{
        unisqlstatement.complete();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Connection의 close시에 Statement가 close되는 것은  JDBC명세에 나와있지만 commit시에 close될 수 있다는 것은 의외였습니다. 혹시나 싶어서 MySql, Oracle, Hsql의 JDBC구현들을 다 살펴보았지만, 비슷한 동작을 할 가능성이 있는 코드들은 찾을 수가 없었습니다. Cubrid만의 독특한 점이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 문제를 해결하는 방법은 DB 서버에 있는 설정파일인 Cubrid_broker.conf 파일에서 STATEMENT_POOLING속성을 ON으로 표시해주는 것이라고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 속성은 서버에서 Statement Pooling을 하겠다는 의미가 아니고, Connection을 받아서 쓰는 Client 쪽에서 Connection Pooling을 하고 있으니 Commit시에 Statement를 끊지 말라고 알려주는 것이라고 합니다. 위에 있는 completeAllStatements메소드 안의  u_con.brokerInfoStatementPooling()코드가 그것을 검사하는 것 같습니다. 사실 앞의 코드의 commit을 여러번 하는 예는 Statement pooling도 아니고 한 번 만들어진 Statement를 계속 쓰는 것 뿐인데, 이것을 Statement pooling을 쓰는 것으로 인식시켜야 한다니 논리적으로 이상하기는 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고, Cubrid에 접근하는 Client는 CAS(Common Application Server)라는 것을 거치게 되어 있는데, 이 CAS보다 요청하는 Client의 갯수가 많을 때에는 항상 에러를 내는 것이 아니고, 이미 연결되어 있는 다른 Connection이 Transaction 중이 아니라면 그 Connection의 Statement자원을 해제시켜 버리고 가져와서 쓸 수 도 있다고 합니다. 이것을 막는 설정이 KEEP_CONNECTION이라는 속성입니다. CUBRID Manager Client프로그램에서 바꿀 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/cubrid-keep-config.jpg&quot; alt=&quot;Cubrid설정.JPG&quot; title=&quot;Cubrid설정.JPG&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;또, Cubrid는 하나의 Connection에서 동시에 두개의 Prepare를 오픈 할 수 없는 구조라고 하네요. (참고자료:http://cubrid.com/bbs/view.php?id=faq&amp;amp;no=318&amp;amp;category=3[] &lt;a href=&quot;http://cubrid.com/bbs/view.php?id=faq&amp;amp;no=318&amp;amp;category=3&quot; class=&quot;bare&quot;&gt;http://cubrid.com/bbs/view.php?id=faq&amp;amp;no=318&amp;amp;category=3&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 특징들을 보면, Cubrid는 Client의 명시적인 요청이 없이도 자원을 최대한 빨리 해제시켜 버리는 것이 사상이 아닌가 하는 생각도 듭니다. 어쩌면  제대로 자원 해제를 못하는 JDBC 코드가 많았던 시절에 그로 인해 생기는 문제들을 대응하면서 생긴 구조일 것 같다는 추측도 해봅니다.(참고: &lt;a href=&quot;http://blog.benelog.net/1898928&quot;&gt;JDBC에서 Connection, Statement,ResultSet의 close&lt;/a&gt; ) 그러나 대부분 프레임웍 기반의 개발을 하고 필요할 때 자원을 close시켜주는 코드들이 정착되고 있는 지금 시점에서는 큰 의미가 없는 특징이고, 오히려 다른 DB나 JDBC명세에 없는 동작으로 인한 혼란만 불러 일으킨다고 생각됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그래서 결론은 Cubrid에서 개발자의 의도보다 Statement를 먼저 해제시킬 수 있는 기능을 안 쓰려면 STATEMENT_POOLING과 KEEP_CONNECTION속성을 ON으로 하면 된다는 것입니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring batch 국내 자료링크, 그리고 KSUG 연재</title>
      <link>https://blog.benelog.net//2005178.html</link>
      <pubDate>Mon, 4 Aug 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2005178.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(수정이력: 2008.01.19 - 스프링배치 관련 링크들은 &lt;a href=&quot;http://www.ksug.org/94로&quot; class=&quot;bare&quot;&gt;http://www.ksug.org/94로&lt;/a&gt; 옮기고, 최근 자료를 포함시켰습니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;7월31일에 Spring Batch 1.1.1이 나왔군요. 버그수정 외에 특별한  기능의 update 사안은 없기는 합니다만, 진도가 빠른 느낌입니다. ( &lt;a href=&quot;http://www.springframework.org/node/722&quot; class=&quot;bare&quot;&gt;http://www.springframework.org/node/722&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring batch에 대한 국내 자료도 점점 많이 생겨나고 있습니다.  &lt;a href=&quot;http://www.ksug.org/&quot;&gt;KSUG&lt;/a&gt;(한국 스프링사용자모임)에서 만난 분들 중 Spring batch에 관심을 가지고 계신 분들이 몇 분 계셔서, 새로운 자료들도 속속 올라올 것 같군요. 그런 분들과 정보를 주고 받을 수 있으니, 포럼(&lt;a href=&quot;http://forum.ksug.org&quot; class=&quot;bare&quot;&gt;http://forum.ksug.org&lt;/a&gt;) 개설 등 최근 &lt;a href=&quot;http://www.ksug.org/&quot;&gt;KSUG&lt;/a&gt;의 변화에 따른 수혜를 제가 많이 받고 있다고 느껴집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;스프링배치 국내 자료 모음 - &lt;a href=&quot;http://www.ksug.org/94&quot;&gt;http://www.ksug.org/94&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 제가 마이크로소프트웨어지에 연재하고 있는 글도 시일이 좀 지난 기사에 한해서 담당기자분의 허락을 얻어 &lt;a href=&quot;http://www.ksug.org/&quot;&gt;KSUG&lt;/a&gt;의 블로그에 올리고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이미 잡지에 나왔던 내용이지만, 인쇄되어 나올 원고를 쓸 때와는 달리 링크와 인용을 더 편하게 할 수 있기에 웹사이트에 올릴 수 있는 기회가 온 것을 반가워하고 있습니다. 연재용 원고를 쓸 때는 참조자료의 URL이 들어가면 긴 주소를 독자들이 직접 쳐서 웹사이트에 들어가 볼까 하는 의구심도 들고, 주소가 차지하는 지면 공간도 많고 해서 사소한 것까지 웹사이트 주소를 쓰기가 망설여 지더군요. 웹에 올릴 때는 API문서 등 사소한  링크도 다 넣을 수 있어서 속이 후련했습니다. 그리고 티스토리에서 제공되는 &apos;미주&amp;#8217;를 다는 기능도 정말 마음에 듭니다. 개인블로그도 이글루스에서 이사가고 싶은 마음이 생길 정도입니다;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 연재 내용을 포함한 스프링에 대한 모든 질문과 토론은 &lt;a href=&quot;http://forum.ksug.org/&quot;&gt;http://forum.ksug.org&lt;/a&gt;을 통해서 하시면 됩니다~!&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Apache commons DbUtils 활용하기</title>
      <link>https://blog.benelog.net//1978345.html</link>
      <pubDate>Tue, 15 Jul 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1978345.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개발을 하다보면 간단한 화면 1~2개만 독립적으로 돌아가는 웹어플리케이션을 만들 때도 있습니다. 예를 들면 로그조회 프로그램 같은 것들이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런 곳에는 Hibernate나 iBatis를 쓰기에는 너무 거창하다는 느낌이 들기도 합니다. 그렇다고 JDBC로 날코딩하기는 성가실때, 이럴 때는 &lt;a href=&quot;http://commons.apache.org/dbutils/&quot;&gt;Apache Commons DbUtils&lt;/a&gt;를 써볼만 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;다운로드 : &lt;a href=&quot;http://commons.apache.org/downloads/download_dbutils.cgi&quot; class=&quot;bare&quot;&gt;http://commons.apache.org/downloads/download_dbutils.cgi&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API 문서 : &lt;a href=&quot;http://commons.apache.org/dbutils/apidocs/index.html&quot; class=&quot;bare&quot;&gt;http://commons.apache.org/dbutils/apidocs/index.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/1898928&quot;&gt;JDBC에서 Connection, Statement,ResultSet의 close&lt;/a&gt; 글에 나온 것처럼 Connection을 닫는 번거로운 처리가 DbUtils.closeQuietly(con);로 끝나는 것만 해도 상당히 편합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아래 예제는  DBUtils + JSTL로 간단한 조회화면을 만들어 본 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(Connection은 실무에서는 DataSource를 통해 얻어와야합니다.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;jsp&quot;&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=EUC-KR&quot;
    pageEncoding=&quot;EUC-KR&quot;%&amp;gt;
&amp;lt;%@ page import = &quot;java.sql.*&quot; %&amp;gt;
&amp;lt;%@ page import = &quot;java.util.Properties&quot; %&amp;gt;
&amp;lt;%@ page import = &quot;org.apache.commons.dbutils.DbUtils&quot; %&amp;gt;
&amp;lt;%@ page import = &quot;org.apache.commons.dbutils.QueryRunner&quot; %&amp;gt;
&amp;lt;%@ page import = &quot; org.apache.commons.dbutils.handlers.MapListHandler&quot; %&amp;gt;
&amp;lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot; %&amp;gt;
&amp;lt;%!
  private static final String SELECT_STMT =
                  &quot;SELECT id, name, email, cell_phone_number FROM quiz_user&quot;;
%&amp;gt;
&amp;lt;%
 String url = &quot;jdbc:hsqldb:hsql://localhost/sampledb&quot;;
 Properties prop = new Properties();
 prop.put(&quot;user&quot;,&quot;sa&quot;);
 prop.put(&quot;password&quot;,&quot;&quot;);
 Connection con =  null;
 try\{
  Class.forName (&quot;org.hsqldb.jdbcDriver&quot;);
  con = DriverManager.getConnection(url,prop);
        QueryRunner runner = new QueryRunner();
        Object resultList = runner.query(con,SELECT_STMT, new MapListHandler());
        request.setAttribute(&quot;list&quot;,resultList);
   } catch (SQLException ex) \{

      throw new RuntimeException(ex);
   } finally \{
     DbUtils.closeQuietly(con);
   }
 %&amp;gt;
&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=EUC-KR&quot;&amp;gt;
&amp;lt;title&amp;gt;사용자&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;사용자  조회&amp;lt;/h1&amp;gt;
  &amp;lt;h2&amp;gt;사용자  목록&amp;lt;/h2&amp;gt;
  &amp;lt;table class=&quot;list&quot;&amp;gt;
   &amp;lt;tr&amp;gt;
    &amp;lt;th&amp;gt;id&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;이름&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;전화번호&amp;lt;/th&amp;gt; &amp;lt;th&amp;gt;이메일&amp;lt;/th&amp;gt;
 &amp;lt;/tr&amp;gt;
    &amp;lt;c:forEach var=&quot;item&quot; items=&quot;$\{list}&quot; varStatus=&quot;status&quot;&amp;gt;
 &amp;lt;tr&amp;gt;
  &amp;lt;td&amp;gt;$\{item.id}&amp;lt;/td&amp;gt;
  &amp;lt;td align=&quot;center&quot;&amp;gt;$\{item.name}&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;$\{item.cell_phone_number}&amp;lt;/td&amp;gt;
  &amp;lt;td align=&quot;center&quot;&amp;gt;$\{item.email}&amp;lt;/td&amp;gt;
 &amp;lt;/tr&amp;gt;
 &amp;lt;/c:forEach&amp;gt;
  &amp;lt;/table&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;몇 년전에 DbUtils와 비슷한 클래스를 만든 적이 있었는데, 그때도 좀 찾아볼 걸 그랬나봅니다. 그러고 보면 저도 Apache Commons에 이미 있는 것을 많이도 만들어본 삽질의 시간들을 겪었었습니다. 신입 때 &lt;a href=&quot;http://commons.apache.org/beanutils/&quot;&gt;commons beanutils&lt;/a&gt;하고 &lt;a href=&quot;http://commons.apache.org/io/&quot;&gt;commons io&lt;/a&gt;에 포함된 것 비슷한 유틸리티 만들어 놓고 혼자서 뿌듯해 했었죠 -_-;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료&quot;&gt;참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/levin01/100011050694&quot;&gt;Commons-DbUtils&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/webman/30000419500&quot;&gt;dbutils 활용방법&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>이미 35개 이상의 Accenture 고객사에 Spring batch가 적용되고 있다</title>
      <link>https://blog.benelog.net//1970645.html</link>
      <pubDate>Thu, 10 Jul 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1970645.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring batch 프레임웍은  스프링 프레임웍의 본부인 SpringSource와 컨설팅 회사 Accenture가 협업해서 개발하고 있습니다. SpringSource의 기술력과 Accenture의 현장경험이 합쳐진 것이죠. 작년 5월에 국내에도 이 소식이 보도된 기사가 있군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://news.naver.com/main/read.nhn?mode=LSD&amp;amp;mid=sec&amp;amp;sid1=105&amp;amp;oid=092&amp;amp;aid=0000013437&quot;&gt;엑센추어, 오픈소스 프로젝트 「스프링 배치」참가&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://forum.springframework.org/forumdisplay.php?f=41&quot;&gt;Spring batch 포럼&lt;/a&gt;에서 보면 이미 현장에서 적용하고 있는 사람들이 많아 보이기는 해도, 아직 얼마나 쓰이고 있는지 짐작하기는 어려웠습니다. 그런데, 얼마전에 Accenture에서 내놓은 기사에 따르면 이미 35개 이상의 고객사에서 활용되고 있다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://accenture.tekgroup.com/article_display.cfm?article_id=4703&quot;&gt;Accenture and SpringSource Team to Deliver Production Version of Open Source Framework for Batch Processing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지난 5월에 Spring batch에 대해서 공개 세미나에서 발표를 한 적이 있었습니다. 그 때 받은 질문 중에 하나가 &apos;금융권 등에서는 민감하고 고성능이 필요한 프로그램들이 배치로 돌고 있고, DB의 Stored procedure로 되어 있는 것이 많다. 그런 것들이 다 SpringBatch로 가능하겠느냐&amp;#8217;는 것이였습니다. 그 때 저는 &apos;성능에 대해서는 구체적으로 나온 자료가 없다. DB를 많이 쓰는 작업이라면 DB내부에서 돌아가는 Stored Procedure가 더 빠를 가능성이 크다. 그러나 배치 어플리케이션의 프로그래밍에서 추구할 우선적인 가치는 업무의 특성에 따라 다를 것이다. 그것이 성능일 때도 있고, 생산성이나 유지보수성일 때도 있다.  빠른 성능이 절대적으로 중요한 프로그램이 Spring batch의 모듈을 썼을 때 그 기대치가 안 나온다면 Store procedure로 주요부분을 짜고 그것을 Spring batch 내부에서 호출하는 방법도 가능하다.&apos;라고 대답을 했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 때 위의 기사의 사례들을 알았다면 &apos;이미 Accenture의 많은 고객사에서 사용하고 있는 것으로 보아 기업환경의 실무에서 충분히 사용 가능한 수준이라고 말할 수 있다.&apos;라는 말을 덧붙일수 있었을 건데 아쉽군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우리나라의 SI에서도 적용될만한 곳이 분명 있을 것입니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring 3.0에 대한 소식</title>
      <link>https://blog.benelog.net//1937831.html</link>
      <pubDate>Tue, 17 Jun 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1937831.html</guid>
      	<description>
	&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.springify.com/archives/15&quot;&gt;Live from SpringOne 2008: Collected bits about Spring 3.0&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;벌써 3.0에 대한 이야기가 나오고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;잘 하면 2008년 4분기 정도에 GA(General Availability) release(일반사용 가능 버전)가 나온다고 하는군요. Spring Core 모듈이 Java 5에 대해 본격적으로 의존하고 이제 더 이상  Java 1.4에서는 돌아갈 수 없다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이미 어느 정도 윤곽이 잡혀있듯이,  Spring MVC에서 REST에 대한 full scale 지원이 포함된다는군요. 그리고  Spring batch에서 지원되었던 repeat, retry, resume의 개념들과  Spring WS의 &lt;a href=&quot;http://static.springframework.org/spring-ws/site/reference/html/oxm.html&quot;&gt;Spring OXM&lt;/a&gt; 등이 보다 상위 모듈에서 포함되어서 제공될지도 모르겠습니다. 또, Junit 3.8 기반의 통합테스트 클래스들은 deprecated로 표시될 것 같습니다. (아마도 &lt;a href=&quot;http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/test/AbstractSingleSpringContextTests.html&quot;&gt;AbstractSingleSpringContextTests&lt;/a&gt;같은 클래스들을 이야기하는 것이겠죠?)&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Q33N의 비밀 - 뉴욕의 프로그래머 중</title>
      <link>https://blog.benelog.net//1936267.html</link>
      <pubDate>Mon, 16 Jun 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1936267.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;존경하는 임백준님의 책 &apos;&lt;a href=&quot;http://blog.hanb.co.kr/11&quot;&gt;뉴욕의 프로그래머&lt;/a&gt;&apos;에 있는 내용입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;노트패드로 할 수 있는 일이 생각보다 많다는 사실을 알고들 있는지?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;어느 날 신나게 구라를 풀던 콜린이 말했다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;쌍 둥이 빌딩이 무너졌을 때 건물을 들이받은 비행기의 번호가 Q33N이었다는 것을 기억하는 사람은 없겠지만, 아무튼 그렇거든. 노트패드를 열고 Q33N을 입력해봐. 그리고 폰트 크기는 72로 최대한 키우고. 그 다음 글자체를 Wingdings로 선택하라고. 화면에 뭐가 나타나는지 보라고.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;story 3. &quot;무정부주의자 콜린&quot; 중에서&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전 책에서 이 부분을 읽자 마자 따라서 해봤고, 화면에 나온 결과를 보고 정말 신기하다고 느꼈죠. windows개발자가 몰래 911를 추도하기 위해서 넣은 것일 거라고 생각했었습니다. 나아가서 집에 있는 윈도우 2000이 깔린 컴퓨터에서도 되는 걸 보고 MS가 911이후에 2000버전의 노트패드에도 패치를 한건지, 아니면 개발자 중에 놀라운 예언가가 있었던것일까 하는 상상도 했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그.러.나.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;혹시나 해서 찾아봤더니, 실제의 비행기 번호는 그와 전혀 다른 번호였고, 위의 이야기는 정말로 &apos;구라&amp;#8217;였습니다. 원래 폰트가 그렇게 생겨먹은 겁니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.hoax-slayer.com/wingdings-911.html&quot; class=&quot;bare&quot;&gt;http://www.hoax-slayer.com/wingdings-911.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음 링크의 아래쯤에 가면 &apos;Q33NY&amp;#8217;라는 문자열로 그런 소문이 도는데 사실이 아니라고 밝히고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.hoax-slayer.com/wingdings-911.html&quot;&gt;http://www.hoax-slayer.com/wingdings-911.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;네이버의 지식인에서도 비슷한 질문들이 오갔고, 여기서보니 호기심 천국에도 옛날에 나왔다고 하는군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://kin.naver.com/detail/detail.php?d1id=1&amp;amp;dir_id=106&amp;amp;eid=mEFZPG/iLDaISzjuCO7V4mXyyWV1VCWm&amp;amp;qb=cTMzbnk=&quot; class=&quot;bare&quot;&gt;http://kin.naver.com/detail/detail.php?d1id=1&amp;amp;dir_id=106&amp;amp;eid=mEFZPG/iLDaISzjuCO7V4mXyyWV1VCWm&amp;amp;qb=cTMzbnk=&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;원래 저는 의심이 많은 편인데, 이번에는 완전 속을 뻔 했습니다 ^^; 아마 임백준님도 사실이 아니라는 것을 아시면서도 재미로 책에 넣으신거겠죠? ^^&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Spring batch와 Spring integration의 만남</title>
      <link>https://blog.benelog.net//1935914.html</link>
      <pubDate>Mon, 16 Jun 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1935914.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring portfolio의 일부 중 최근에 관심을 제가 관심을 가지고 있는  Spring batch와 Spring integration을 연결한 프로토타입 코드들이 나왔습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring batch form에 최근에 올라온 글입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://forum.springframework.org/showthread.php?t=55863&quot;&gt;Spring Integration Prototype&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 6월 18일에 이 것을 주제로 한 온라인 세미나 같은것이 있나 보네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.springframework.org/node/687&quot;&gt;Webinar: Enterprise Integration with Spring Batch&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 코드들은 Java 5를 사용하기 때문에 Spring batch 1.1 정식 배포판에는 들어가지 않을거라고 합니다. Spring batch 프로젝트에서는 2.0전까지 Java5 으로 가지는 않을거라고 하네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://zepag.blogspot.com/2008/06/springone08-spring-batch.html&quot;&gt;SpringOne 08의 Spring batch 주제의 발표&lt;/a&gt;에서도 관련 내용이 들어갈 것 같네요. Spring batch와 Spring integration의 convergence가 있는 것으로 보인다는 언급이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.egloos.com/egloo/insert.php?eid=d0032573&quot; class=&quot;bare&quot;&gt;http://www.egloos.com/egloo/insert.php?eid=d0032573&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring Batch와 Spring WS, Spring Integration은 얽히고 섞힌 관계라고 보입니다. Spring integration의 Web Service Adapters에서 Spring Web Service를 사용하고 있고, Spring batch에서도 XML파일 처리에 Spring WS의 일부인 &lt;a href=&quot;http://static.springframework.org/spring-ws/site/reference/html/oxm.html&quot;&gt;Spring OXM&lt;/a&gt;를 통한 위임처리를 할 수 있습니다. 그리고 위와 같이 Spring integration는 Spring Batch도 연결시켰군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring Batch의 Job은 Spring Application Platform 통해서 OSGi 번들로도  실행가능하게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.springsource.com/main/2008/05/30/running-a-spring-batch-job-in-the-springsource-aplication-platform/&quot;&gt;Running a Spring Batch Job in The SpringSource Application Platform&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;향후 버전에서는 더욱 더 배치에 특화된 기능들이 추가될 것이라고 하네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring batch + Spring Integration + Spring WS + Spring Application Platform.. 그 미래가 어떻게 될지 정말 기대가 됩니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Effective Java 2nd Edition</title>
      <link>https://blog.benelog.net//1912122.html</link>
      <pubDate>Fri, 30 May 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1912122.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Amazon에서 예약주문 단계에서 판매상태로 바뀌었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1212115691&amp;amp;sr=8-1&quot;&gt;http://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1212115691&amp;amp;sr=8-1&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;회사에서 Yes24에서 책을 주문할 수 있는 쿠폰을 주기 때문에 저는 공짜로 보기 위해서 거기서 주문할 수 있을 때까지 기다리고 있는 중입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Joshua Bloch과의 인터뷰를 보니 역시 Generics등 Java5 이후의 주제들에 대해서 내용이 보강되었다고 하네요. 정확히는 21개의 항목과 83페이지가 늘어났다고 합니다. 기대가 되는군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.infoq.com/articles/bloch-effective-java-2e&quot;&gt;Book Excerpt and Interview: Effective Java, Second Edition&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;더불어 121페이지의 예제코드의 실수도 언급되어 있길래 옮겨봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;지금 인쇄판에 들어간 코드&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;static Object reduce(List list, Function f, Object initVal) {
    Object[] snapshot = list.toArray(); // Locks list internally
    Object result = initVal;
    for (Object o : list) // 오류 부분
        result = f.apply(result, o);
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;수정된 내용&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;static Object reduce(List list, Function f, Object initVal) {
    Object[] snapshot = list.toArray(); // Locks list internally
    Object result = initVal;
    for (Object o : snapshot) // 수정된 부분 (list 대신 snapshot을 참조)
        result = f.apply(result, o);
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Joshua Bloch의 말에 따르면 다음에 찍는 인쇄판에서는 수정을 하겠지만 현재 시중에 나가는 판에는 이 오류가 포함이 되어 있다고 합니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Java에서 Checked Exception은 언제 써야 하는가?</title>
      <link>https://blog.benelog.net//1901121.html</link>
      <pubDate>Fri, 23 May 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1901121.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java 의 Exception 처리는 C++로부터 도입되었지만 checked exception은 Java만의 독특한 특징입니다. 아시다시피, 컴파일러가 exception을 꼭 처리해라고 강요하는 것이죠.  이것은  Java 이후에 설계된 언어인 C#이나 루비에도 채택되지 않았습니다. 즉 Java 이외의 다른 언어들의 Exception 처리 방식은 Java의 unchecked exception과 동일한 방식입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java의 초기에는 checked Exception을 사용하라고 권장했지만, 지금은 많은 반론이 제기되고 있습니다. 극단적으로 Java언어에서checked Exception 도입 자체가 실패라고 주장하는 사람도 많습니다. Thinking in Java의 저자인 Bruce Eckel도 그 중 한 사람입니다.  Spring framework의 아버지 Rod Johnson도 Checked Exception이 쓰여야 할 때도 있지만 그 것이 과도하게 선호되어 온 것은 지적하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;어쨓든 Exception 부분은 Java로 API 설계와 코딩를 할 때 가장 어려운 부분이라고 느껴집니다. Java 아키텍트와 개발팀의 실력을 측정하는 좋은 방법은 그들이 만든 Exception 처리 코드를 보라는 말까지 있으니까요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;exception_처리_방식_참고자료&quot;&gt;Exception 처리 방식 참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mgp/book-notes/blob/master/effective-java-2nd-edition.markdown#chapter-9-exceptions&quot;&gt;Effective Java 2nd Edition Chapter 9 (Item 57 ~ 65)&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-57-use-exceptions-only-for.html&quot;&gt;Item 57&lt;/a&gt; : 예외는 예외상황에서만 써야 한다. 예외를 생성하고 던지고 잡는 것은 비용이 많이 드는 작업이고 JVM의 최적화 대상에서 빠질 수 있다. 프로그램 흐름을 예외로 제어하려 하면 안된다. 좋은 API는 클라이언트가 프로그램 흐름을 제어할 때 예외를 쓸 수 밖에 없도록 만들지 않는다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-58-use-checked-exceptions-for.html&quot;&gt;Item 58&lt;/a&gt;: 복구 가능한 조건에 Checked Exception을, 프로그래밍 에러에 RuntimeException을써라. Error의 하위 클래스는 만들지 말고 처리하지 않는 예외는 모두 RuntimeException의 하위클래스이어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/blog-post_07.html&quot;&gt;Item 59&lt;/a&gt; : Checked Exception은 꼭 필요할 때만 던져라. catch절에서 특별한 할 일이 없이 없는 API를 checked exception으로 처리하는 것은 프로그램만 더 복잡하게 만들 뿐이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-60-favor-use-of-standard.html&quot;&gt;Item 60&lt;/a&gt; : 표준예외를 선호하라. IllegalArgumentException, IllegalStatementException, UnsupportedOperationException, ConcurrentModificationException 등 Java의 표준예외를 활용해라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-61-throw-exceptions-appropriate-to.html&quot;&gt;Item 61&lt;/a&gt; : 예외를 적절하게 추상화 하라. 높은 계층에서 낮은 계층의 예외를 잡아서 높은 계층의 추상화 수준에 맞게 변환해서 던져야 한다. 예외변환(Exception translation) 패턴은 하위 레벨에 영향받지 않는 Exception 전파에 유리하지만 너무 남용하지는 마라. 가능하면 low-level exception이 없이 성공하도록 유도하는 것이 바람직하다. Exception chaining은 적절한 변환을 하면서도 세부 원인을 보존하는 장점이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-62-document-all-exceptions-thrown.html&quot;&gt;Item 62&lt;/a&gt; : 메소드가 던지는 모든 예외를 명세문서에 기술하라 Checked exception은 메소드 선언부에 하나씩 선언하고, @throws 태그를 써서 모든 예외가 발생하느 상황을 정확하게 문서화하라. 단지 귀찮다는 이유만으로, 공통 상위타입으로 예외를 던지려 하지 마라. unchecked exception은 @throws 태그를 써서 명세문서에 기술하지만, 메소드 선언의 throws 절에는 나타나지 말아야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-63-include-failure-capture.html&quot;&gt;Item 63&lt;/a&gt; : 실패에 대한 자세한 정보를 상세 메시지에 담아라 실패원인을 포착하려면, 예외의 문자열 표현에 반드시 예외 발생에 영향을 준 모든 필드와 인자의 값이 들어 있어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-64-strive-for-failure-atomicity.html&quot;&gt;Item 64&lt;/a&gt; : 실패 원자성을 얻기위해 노력하라. 메소드 호출이 실패하더라도 객체상태는 메소드 호출 전과 같아야 한다. 오류(error)는 예외(exception)와 달리 보통 복구할수 없기 때문에 오류가 발생했을 때 실패 원자성을달성하기 위해 애쓸 필요가 없다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jtechies.blogspot.kr/2012/07/item-65-dont-ignore-exceptions.html&quot;&gt;Item 65&lt;/a&gt; : 예외를 잡아서 버리지 마라. 빈 catch block은 &quot;예외 사항을 처리하라&quot;라고 알려주는 예외의 존재 이유 자체를 짓밟는 것이다. catch block 안에서 정말 아무 것도 할 것이 없다면, 최소한 왜 예외를 잡아서 처리하지 않고 버리는지 그 이유라도 주석으로 달아 놓아야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.amazon.ca/Expert-One-One-Design-Development/dp/customer-reviews/0764543857&quot;&gt;Expert One-on-One J2EE Design and Development&lt;/a&gt; 중 Chapter 4 Design Techniques and Coding Standards for J2EE Projects, Exception Handling 부분&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;alternative return value가 있는 경우에는 Checked exception&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;data connection 생성 실패와 같이 뭔가 크게 잘못 되고 있어서 호출한 쪽에서 아무도 이를 처리할 수 없을 때는 Runtime exception.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;소수의 호출자만이 Exception을 받아서 처리해야 할 때도 Runtime exception.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;불명확하면 Runtime exception.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/haruma95/80050223953&quot;&gt;checked Exception과 Runtime exception&lt;/a&gt; 페이지에서 일부 내용이 번역되어 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=1943792&amp;amp;CategoryNumber=001001003016003012&quot;&gt;Spring 프레임웍크 워크북&lt;/a&gt; (박재성 저) 88쪽에도 인용되어 있는 원칙입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Barry Ruzek의  &lt;a href=&quot;http://dev2dev.bea.com/pub/a/2006/11/effective-exceptions.html&quot;&gt;EFFECTIVE JAVA EXCEPTIONS&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fault Handling(Unplanned condition)에는 RuntimeException, Contingency(Expected condition, alternative method result)에는 return type, exception, error code 등의 전략을 사용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fault를 한곳에서 잡아서 처리하는 Fault barrier pattern 사용 권장. Struts라면 org.apache.struts.action.ExceptionHandler, SpringMVC라면 SimpleMappingExceptionResolver&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AOP 적용이 도움이 될 수도 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;황상철님의 블로그 &lt;a href=&quot;http://moai.tistory.com/322&quot;&gt;Effective Java Exceptions 발표자료&lt;/a&gt; 페이지에서 이 내용이 요약된 pdf 파일을 받을 수 있습니다. (&lt;a href=&quot;http://moai.tistory.com/attachment/dk180000000000.pdf&quot;&gt;effective java exceptions.pdf&lt;/a&gt; )&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jim Cushing  &lt;a href=&quot;http://today.java.net/pub/a/today/2003/12/04/exceptions.html&quot;&gt;Three Rules for Effective Exception Handling&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Be specific, Throw Early, Catch Late의 3가지 원칙을 제시하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.manageability.org/blog/stuff/exceptional-exception-handling-techniques/view&quot;&gt;13 Exceptional Exception Handling Techniques&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Checked Exception을 RuntimeException으로 감싸기, throws 절에 RuntimeException이라도 선언해주기 등의 기법을 추천하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gunjan Doshi의 &lt;a href=&quot;http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html&quot;&gt;Best Practices for Exception Handling&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Client code가 할 일이 있을 때는 checked, 없을 때는 unchecked. progamming error에는 unchecked exception.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;적절한 캡슐화. 비지니스 layer에서 SqlException을 던지지 말것.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rob Walling의 Exception Handling에 관한 글 &lt;a href=&quot;http://www.softwarebyrob.com/2007/08/27/two-fundamental-no-frills-square-one-rules-exception-handling/&quot;&gt;The Two Fundamental, No Frills, Square One Rules of Exception Handling&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;에러메시지에 도움이 되는 정보를 더할 수 없다면, Exception을 잡지마라, 2. Exception을 잡았으면 기록하라.&quot; 두가지 원칙을 말하고제시하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alan Griffiths의 &lt;a href=&quot;http://www.octopull.demon.co.uk/java/ExceptionalJava.html&quot;&gt;Exceptional Java&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;이 주제에 대해 비교적 초기에 나온 글로써, 다른 글에도 많이 인용되고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exception 처리에 대한 전통적인 원칙이 캡슐화 저해, 정보손실, 정보 과적의 문제를 일으킨다고 이야기합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;public 메소드에서 던지는 Exception은 해당 패키지에 소속된 클래스일것, 다른 패키지에서는 이를 부를 때를 Exception을 전파시키지 말고 그 패키지의 Exception으로 감쌀 것을 추천하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.onjava.com/pub/a/onjava/2006/01/11/exception-handling-framework-for-j2ee.html?page=1&quot;&gt;An Exception Handling Framework for J2EE Applications&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;J2EE application에서의 Exception처리 전략에 대해서 설명하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;checked_exception에_대한_부정적_견해를_정리한_글&quot;&gt;Checked Exception에 대한 부정적 견해를 정리한 글&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Brian Goetz의 글: &lt;a href=&quot;http://www.ibm.com/developerworks/java/library/j-jtp05254.html&quot;&gt;Java theory and practice: The exceptions debate&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bruce Eckel, Rod Johnson, Joshua Bloch등의 주장을 정리해 놓은 글입니다. Checked Exception이 상세한 구현을 부적절하게 노출함,불안정한 메소드 시그너처,읽기힘든 코드,Exception 삼키기, 너무 많은 Exception wrapping의 문제점 있는 것을 나열하고 있습니다. unchecked exception은 Documentation이 더욱 중요하다고 강조하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rod Waldhoff의 글 : &lt;a href=&quot;http://radio.weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html&quot;&gt;Java&amp;#8217;s checked exceptions were a mistake&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java의 Exception Handling은 실패한 실험이라고 주장하는 내용입니다. Checked Exception방식은 일부 low level에서만 의미가 있다고 말합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bruce Eckel의 견해(Thinking in Java의 저자): &lt;a href=&quot;http://www.mindview.net/Etc/Discussions/CheckedExceptions&quot;&gt;Does Java need Checked Exceptions?&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Error report 방식을 통일했다는 점에서는 의미가 있지만, 오히려 개발자들이 Exception을 그냥 삼키는 코드를 많이 짜게 하는 결과가 생겼다고 지적합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bill Venners(C#의 아키텍트)와 Bruce Eckel의 인터뷰 : &lt;a href=&quot;http://www.artima.com/intv/handcuffs.html&quot;&gt;The Trouble with Checked Exceptions&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;versioning, scalability의 문제 때문에 C#에  Checked Exception이 도입되지 않았다고 밝히고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;checked_exception에_대한_논의가_진행된_페이지&quot;&gt;Checked Exception에 대한 논의가 진행된 페이지&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.c2.com/cgi/wiki?JavaExceptionsAreParticularlyEvil&quot;&gt;Java Exceptions Are Particularly Evil&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.c2.com/cgi/wiki?CheckedExceptionsAreOfDubiousValue&quot;&gt;Checked Exceptions Are Of Dubious Value&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.c2.com/cgi/wiki?ExceptionTunneling&quot;&gt;Exception Tunneling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.theserverside.com/news/thread.tss?thread_id=19192&quot;&gt;Opinion: The Eternal Debate on Checked Exceptions&lt;/a&gt;# : theserverside에서 벌어진 논쟁&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;이클립스_exception처리_코드_템플릿_관련_자료&quot;&gt;이클립스 Exception처리 코드 템플릿 관련 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://lastmind.net/blog/2007/11/exception-handling.html&quot;&gt;Exception Handling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://whiteship.tistory.com/1612&quot;&gt;이클립스의 기본 try-catch 탬플릿 비추&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;기타_자료&quot;&gt;기타 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Exception-Handling Antipatterns : &lt;a href=&quot;http://today.java.net/pub/a/today/2006/04/06/exception-handling-antipatterns.html&quot; class=&quot;bare&quot;&gt;http://today.java.net/pub/a/today/2006/04/06/exception-handling-antipatterns.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Error handling, A Pattern Language : &lt;a href=&quot;http://www.objectarchitects.de/arcus/cookbook/exhandling/index.htm&quot; class=&quot;bare&quot;&gt;http://www.objectarchitects.de/arcus/cookbook/exhandling/index.htm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Best practices in EJB exception handling :  &lt;a href=&quot;http://www-106.ibm.com/developerworks/java/library/j-ejbexcept.html&quot;&gt;http://www-106.ibm.com/developerworks/java/library/j-ejbexcept.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.javaworld.com/jw-07-1998/jw-07-techniques.html&quot;&gt;http://www.javaworld.com/jw-07-1998/jw-07-techniques.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.javaworld.com/jw-07-1998/jw-07-exceptions.html&quot;&gt;http://www.javaworld.com/jw-07-1998/jw-07-exceptions.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.javaworld.com/javaworld/jw-08-2001/jw-0803-exceptions.html&quot;&gt;http://www.javaworld.com/javaworld/jw-08-2001/jw-0803-exceptions.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.javaworld.com/javaworld/jw-09-2001/jw-0914-exceptions.html&quot;&gt;http://www.javaworld.com/javaworld/jw-09-2001/jw-0914-exceptions.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Beware the dangers of generic Exceptions : &lt;a href=&quot;http://www.javaworld.com/javaworld/jw-10-2003/jw-1003-generics.html&quot; class=&quot;bare&quot;&gt;http://www.javaworld.com/javaworld/jw-10-2003/jw-1003-generics.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.codemonkeyism.com/archives/2008/12/16/7-good-rules-to-log-exceptions/&quot; class=&quot;bare&quot;&gt;http://www.codemonkeyism.com/archives/2008/12/16/7-good-rules-to-log-exceptions/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://northconcepts.com/blog/2013/01/18/6-tips-to-improve-your-exception-handling/&quot; class=&quot;bare&quot;&gt;http://northconcepts.com/blog/2013/01/18/6-tips-to-improve-your-exception-handling/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>JDBC에서 Connection, Statement,ResultSet의 close</title>
      <link>https://blog.benelog.net//1898928.html</link>
      <pubDate>Wed, 21 May 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1898928.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이미 많이 알려진 내용이지만, 아직도 문제를 많이 일으키는 주제입니다. 그래서 보다 이 주제를 검색엔진에서 쉽게 찾을 수 있었으면 하는 마음에서 이 글을 정리해봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null; // &amp;lt;---- !!!
  try{
     conn = ...&amp;lt;getConnection()&amp;gt;...;
     pstmt = conn.prepareStatement(&quot;select .....&quot;);
     rs = pstmt.executeQuery(); // &amp;lt;----- !!!
     while(rs.next()){
       ......
     }
  }  catch(Exception e){
     ....
  }  finally {
     if ( rs != null ) try{rs.close();}catch(Exception e){}
     if ( pstmt != null ) try{pstmt.close();}catch(Exception e){}
     if ( conn != null ) try{conn.close();}catch(Exception e){}

 }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이것이 JDBC API 사용시에 권장되는 코딩방식입니다. 코드는 참조자료에 있는 이원영님의 글에서 인용했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDBC 스펙을 찾아보면 Statement가 닫힐 때 ResultSet은 닫히고, Connection이 닫히면 Statement도 닫힌다고 되어 있습니다. 하지만 Staement close 시에 Exception이 발생한다면 이것이 따로 Exception을 catch되지 않고서는 뒤에 Connection을 닫는 코드가 실행되지 않습니다. 그리고 Connection pool에서 얻어온 Connection객체는 connection.close()로 처리하는 것이 pool로의 반환을 의미하는 것이지 실제로 connetion을 close하는 것이 아니기 때문에 Statement까지 닫아준다고 장담할 수 없습니다. ResultSet의 경우도 WAS에도 제공하는 Statement cache 기능 때문에 명시적으로 close해주는 것이 확실한 자원해제를 보장할 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;DBMS에서 &quot;maximum open cursor exceed !&quot; 나 &quot;Limit on number of statements exceeded &quot; 에러를 내고 있다면 위와 같이 코딩했는지 한번 확인해보시기 바랍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;각 벤더별 드라이버의 구현이나 WAS의 Connection Pool의 구현등에 따라서 저 정도까지 안 해도 문제가 안 생길 수도 있습니다. 그리고 독립적으로 돌아가는 배치프로그램이나 커넥션풀을 쓰지 않는 경우에는 보다 덜 엄격해도 될 때도 있습니다.  그래도 어떠한 경우에도 안심하고 있을만한 코드는 위와 같은 구조입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;javaservice.net에서 이원영님이 처음에 이 문제에 대한 글을 쓰신것이 2000년 9월입니다. 그래서  많은 분들이 알고 계시지만 그래도 정말 반복적으로 만나게 되는 문제입니다.  저의 경험이 편향된지도 모르겠지만, 지금까지 제가 만났던 JDBC AP를 그대로 쓰는 개발팀은 세 팀이였었는데, 모두 이렇게 코딩하지 않을 경우 문제가 생길 가능성이 있다는 것을 모르고 있었습니다. 결국 그 중 한 팀은 시스템 전체를 몇 일동안 매시간마다 재부팅시키게 만들게 했었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;미국의 모 대형항공사의 예약시스템을 3시간동안 멈춘 코드도 위와 같은 방식을 따르지 않았었습니다. finally절이 다음과 같았다고 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;} finally{
    if (stmt!=null) stmt.close();
    if (conn!=null) conn.close();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 예약 시스템은 이중화된 DB로 구성되어 있었고, 그 DB들은 가상IP주소로 어플리케이션과 연결되어 있었습니다.  정기 점검을 위해 DB중 하나를 수동  fail-over 시키는 순간 내려간 DB의 JDBC연결에서 나온 statement객체의 close문장은 Exception을 일으켰습니다. 이 문장은 별도로 catch 되지 않았기 때문에 그 다음의 conn.close()는 실행되지 않았습니다. 결국 이 때문에 반환되지 않은 Connection 자원들로 인해 리소스 풀은 곧 바닥이 났습니다. 그 후에 새로 Connection을 얻고자 하는 다른 프로그램들은 블록되어서 전체 시스템을 멈추었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아마도 JDBC API를 쓰는 곳에는 언제나 생길 수 있는 문제일 것입니다. 좋은 API는 문서를 안 보고 자연스럽게 써도 사용하기 쉽고 문제를 안 일으키는 것일텐데, JDBC는 제대로 사용하기가 오히려 더 어려운 API입니다. 위의 항공사 사건 같이 전 세계에서 JDBC로 인해 야기된 장애,생산성 저하를 다 따져본다면, 가히 이 API가 인류에게 끼친 해악이 엄청나다는 생각까지도 듭니다.  요즘은 Framework 기반 개발로 JDBC를 직접 안 쓰는 것이 이런 점에서는 다행입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JDBC API에서 대표적으로 지적받는 문제점은 Checked Exception을 남발했다는 것입니다. catch 절에서 아무 것도 하지 않는 것은 바람직하지 않은 코딩이지만 JDBC API에서는 정말 할 것이 없습니다. 그래서 이런 문제점을 알고서 그 후에 나온 JDBC를 활용한 API들, Spring의 &lt;a href=&quot;http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/jdbc/core/JdbcTemplate.html&quot;&gt;JdbcTemplet&lt;/a&gt;, &lt;a href=&quot;http://www.hibernate.org/&quot;&gt;Hibernate&lt;/a&gt;의  &lt;a href=&quot;http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Query.html&quot;&gt;Query&lt;/a&gt; 인터페이스,  &lt;a href=&quot;http://en.wikipedia.org/wiki/Java_Persistence_API&quot;&gt;JPA&lt;/a&gt;의 &lt;a href=&quot;http://java.sun.com/javaee/5/docs/api/javax/persistence/Query.html&quot;&gt;Query&lt;/a&gt; 인터페이스, &lt;a href=&quot;http://java.sun.com/jdo/&quot;&gt;JDO&lt;/a&gt;의 &lt;a href=&quot;http://db.apache.org/jdo/api21/apidocs/javax/jdo/Query.html&quot;&gt;Query&lt;/a&gt; 인터페이스에서는 Checked Exception인 SqlException을 볼 수 없게 설계되어 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 Java6 이전의 JDBC에서는 접속에러, 쿼리에러, 제약조건 에러 등 다양한 원인으로 생기는 Exception을 SqlException 1개로 다 때우는 문제도 있었습니다. Spring에서는 이것을 더 섬세하게 구분한  Exception들을 정의를 하고 있습니다. &lt;a href=&quot;http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/dao/DataAccessException.html&quot;&gt;DataAccessException&lt;/a&gt;의 하위 클래스를 보면 &lt;a href=&quot;http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/dao/CleanupFailureDataAccessException.html&quot;&gt;CleanupFailureDataAccessException&lt;/a&gt;, &lt;a href=&quot;http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/dao/DataIntegrityViolationException.html&quot;&gt;DataIntegrityViolationException&lt;/a&gt;, &lt;a href=&quot;http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/dao/DataRetrievalFailureException.html&quot;&gt;DataRetrievalFailureException&lt;/a&gt; 등이 보입니다. Java6에 포함된 JDBC 4.0에서는  &lt;a href=&quot;http://docs.oracle.com/javase/7/docs/api/java/sql/SQLNonTransientException.html&quot;&gt;SQLNonTransientException&lt;/a&gt;, &lt;a href=&quot;http://docs.oracle.com/javase/7/docs/api/java/sql/SQLRecoverableException.html&quot;&gt;SQLRecoverableException&lt;/a&gt;, &lt;a href=&quot;http://docs.oracle.com/javase/7/docs/api/java/sql/SQLTransientException.html&quot;&gt;SQLTransientException&lt;/a&gt; 등의 하위 클래스가 생겼고, ,Spring에서는 이런 클래스도 잘 인식해서 적절한 DataAccessException의 하위 클래스로 변환해줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/1901121&quot;&gt;Java의 Checked Exception 처리 문제에 관한 글&lt;/a&gt;은 따로 정리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료&quot;&gt;참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;미국_항공사_장애_사건_관련&quot;&gt;미국 항공사 장애 사건 관련&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2753365&amp;amp;CategoryNumber=001001003016002003&quot;&gt;Release It 릴리스 잇 : 성공적인 출시를 위한 소프트웨어 설계와 배치&lt;/a&gt;  (마이클 나이가드 저/신승환,정태중 역, 위키북스)&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2장 사례연구 : 항공사를 정지시킨 예외(Exception) 사건&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jdbc_관련&quot;&gt;JDBC 관련&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Javaservice.net에 이원영님이 올린 글&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(죽은 링크) &lt;a href=&quot;http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&amp;amp;b=servlet&amp;amp;c=r_p&amp;amp;n=968185187&amp;amp;k=JDBC&amp;amp;d=tb&quot;&gt;서블렛 + JDBC 연동시 코딩 고려사항 -제1탄-&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(죽은 링크) &lt;a href=&quot;http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&amp;amp;b=servlet&amp;amp;c=r_p&amp;amp;n=968522077&quot;&gt;서블렛 + JDBC 연동시 코딩 고려사항 -제2탄-&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현 시점에서 찾을수 있는 해당 글의 복사본&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://winmargo.tistory.com/46?category=428145&quot; class=&quot;bare&quot;&gt;https://winmargo.tistory.com/46?category=428145&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.zdnet.co.kr/view/?no=00000010048177&quot;&gt;서블렛 + JDBC 연동시 코딩 고려사항 4&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.zdnet.co.kr/view/?no=00000010048223&quot;&gt;서블렛 + JDBC 연동시 코딩 고려사항 6&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Digester를 이용한 Naver Open API Java Client 모듈</title>
      <link>https://blog.benelog.net//1891482.html</link>
      <pubDate>Fri, 16 May 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1891482.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;네이버 Open API Cafe에서 검색 API의 Java Client 모듈을 보게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://cafe.naver.com/ArticleRead.nhn?clubid=11906219&amp;amp;amp;page=1&amp;amp;amp;menuid=3&amp;amp;amp;boardtype=L&amp;amp;amp;articleid=1372&quot; class=&quot;bare&quot;&gt;http://cafe.naver.com/ArticleRead.nhn?clubid=11906219&amp;amp;amp;page=1&amp;amp;amp;menuid=3&amp;amp;amp;boardtype=L&amp;amp;amp;articleid=1372&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://insford.tistory.com/116&quot; class=&quot;bare&quot;&gt;http://insford.tistory.com/116&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 모듈을 참고해서 같은 역할을 하는 모듈을 다르게 구현해봤습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;특징은 아래와 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RSS를 파싱하는 부분을 XML parsing API로 널리 알려진 Digester(&lt;a href=&quot;http://commons.apache.org/digester/)를&quot; class=&quot;bare&quot;&gt;http://commons.apache.org/digester/)를&lt;/a&gt; 사용했습니다. Digester의 예제 코드로 제공되는 RSS파싱모듈을 그대로 써서 짧은 코드로 파싱이 가능했습니다.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;API 문서 : (&lt;a href=&quot;http://commons.apache.org/digester/commons-digester-1.5/docs/api/org/apache/commons/digester/rss/RSSDigester.html&quot;&gt;RSSDigester&lt;/a&gt; , &lt;a href=&quot;http://commons.apache.org/digester/commons-digester-1.5/docs/api/org/apache/commons/digester/rss/Channel.html&quot;&gt;Channel&lt;/a&gt; , &lt;a href=&quot;http://commons.apache.org/digester/commons-digester-1.5/docs/api/org/apache/commons/digester/rss/Item.html&quot;&gt;Item&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;요청 파라미터를 담는 클래스를 따로 뺐습니다.  (RequestParameter.java) 이중 Target 값은 (blog, news 등 검색할 컨텐츠 유형을 선택하는 파라미터입니다.) enum으로 해서 정해진 값이 아닐 경우 compile이 안 되게 했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;open API key값은 필수값이므로 OpenApiClient클래스의 생성자의 파라미터로 받았습니다. 대신 키 값이 없이 이 객체가 생성될 수 없도록 default 생성자는 private으로 돌려놨습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;첨부한 파일은 이클립스에서 Dynamic Web Project로 생성한 폴더를 압축한 것입니다. 테스트 실행 서버는 Tomcat 5.5를 사용했습니다. Eclipse WTP가 설치되어 있는 환경이면 실행이 가능합니다. 그리고 enum을 썼기에 Java5이상이어야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Open API에 대한 자세한 사용법은 &lt;a href=&quot;http://openapi.naver.com/index.nhn&quot; class=&quot;bare&quot;&gt;http://openapi.naver.com/index.nhn&lt;/a&gt; 를 참조하시면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;소스코드&quot;&gt;소스코드&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;NaverSearchClient.java&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package openapiclient;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;

import org.apache.commons.digester.rss.Channel;
import org.apache.commons.digester.rss.RSSDigester;

public class NaverSearchClient {

    private static final String OPEN_API_URL = &quot;http://openapi.naver.com/search&quot;;
    private String key;

    @SuppressWarnings(&quot;unused&quot;)
    private NaverSearchClient(){};

    public NaverSearchClient(String key){
    this.key = key;
    }
    public Channel search(RequestParameter param) throws Exception{
        RSSDigester digester = new RSSDigester();
        URL requestUrl = getRequestUrl(param);
        InputStream is = requestUrl.openConnection().getInputStream();
     return (Channel) digester.parse(is);
    }

    private URL getRequestUrl(RequestParameter param) throws UnsupportedEncodingException, MalformedURLException {
        StringBuffer serverUrl = new StringBuffer(OPEN_API_URL);
        serverUrl.append(&quot;?target=&quot; + param.getTarget());
        serverUrl.append(&quot;&amp;amp;key=&quot; + key);
        serverUrl.append(&quot;&amp;amp;start=&quot; + param.getStart());
        serverUrl.append(&quot;&amp;amp;display=&quot; + param.getDisplay());
        serverUrl.append(&quot;&amp;amp;query=&quot; + URLEncoder.encode(param.getQuery(), &quot;UTF-8&quot;));
        if(param.getSort()!=null) serverUrl.append(&quot;&amp;amp;sort=&quot; + param.getSort());
        return new URL(serverUrl.toString());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;RequestParameter.java&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;package openapiclient;

public class RequestParameter {

 public enum Category{
  KIN,BLOG,CAFE,DOC,WEBKR,BOOK, SHOP, ENCYC,
  KRDIC, JPDIC, ENDIC, NEWS, LOCAL, VIDEO,IMAGE;
  public String toString(){
   return super.toString().toLowerCase();
  }
 }

 private Category target;
 private String sort;
 private int start;
 private int display;
 private String query;

 // getter and setters 생략
 }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;jsp에서_사용한_예제&quot;&gt;JSP에서 사용한 예제&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JSTL을 함께 사용해서 찍어본 예제입니다. &lt;a href=&quot;http://openapi.naver.com/index.nhn&quot; class=&quot;bare&quot;&gt;http://openapi.naver.com/index.nhn&lt;/a&gt; 에 가셔서 API key를 발급 받으시고 소스 중간에 밑줄로 표시된 부분에 그 값을 넣으시고 돌려주시면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;jsp&quot;&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=EUC-KR&quot;   pageEncoding=&quot;EUC-KR&quot;%&amp;gt;
&amp;lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot; %&amp;gt;
&amp;lt;%@ page import=&quot;openapiclient.RequestParameter&quot; %&amp;gt;
&amp;lt;%@ page import=&quot;openapiclient.NaverSearchClient&quot; %&amp;gt;
&amp;lt;%@ page import=&quot;org.apache.commons.digester.rss.Channel&quot; %&amp;gt;
&amp;lt;%
 String KEY = &quot;????&quot;; // Open API key값을 넣으세요
 NaverSearchClient client = new NaverSearchClient(KEY);
 RequestParameter param = new RequestParameter();
 param.setDisplay(10);
 param.setStart(1);
 param.setQuery(&quot;미역국&quot;);
 param.setTarget(RequestParameter.Category.NEWS);
 Channel result = client.search(param);
 result.render(System.out); // 콘솔에 받아온 내용을 확인삼아 찍어봄
 request.setAttribute(&quot;result&quot;, result);
%&amp;gt;
&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=EUC-KR&quot;&amp;gt;
&amp;lt;title&amp;gt;Naver Open API를 이용한 검색&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;c:forEach var=&apos;item&apos; items=&apos;${result.items}&apos;&amp;gt;
  &amp;lt;p&amp;gt;
  &amp;lt;a href=&quot;${item.link}&quot;&amp;gt; ${item.title} &amp;lt;/a&amp;gt; &amp;lt;br/&amp;gt;
  ${item.description}
  &amp;lt;/p&amp;gt;
&amp;lt;/c:forEach&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;실행결과화면&quot;&gt;실행결과화면&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/my-program/naver-open-api-client-in-eclipse.jpg&quot; alt=&quot;openApiClient.JPG&quot; title=&quot;openApiClient.JPG&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Batch job에서의 바람직한 트랜잭션 처리 방식</title>
      <link>https://blog.benelog.net//1889589.html</link>
      <pubDate>Thu, 15 May 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1889589.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;일반적인 온라인 어플리케이션에서는 사용자가 보내는 event 한번에 하나의 트랜잭션을 묶어서 처리합니다. 하지만 배치처리에서는 하나의 처리가 하나의 트랜잭션으로 관리되는 것은 때로는 문제가 될 여지가 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;몇 만 건 이상의 행과 같이 대용량 데이터를 다루는 경우에는 roll back segment 같은 공간의 부족현상이 생길 위험이 있습니다. 그리고 상대적으로 긴 처리 시간동안 DB에 lock을 걸리게 해서 전체 시스템에 병목을 만들지도 모릅니다. 사용자가 많은 웹어플리케이션이 동시에 접근하는 DB에서 배치처리를 돌릴 때는 이를 더 염두에 두어야 할 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;또, 데이터가 건마다 독립성을 가지고 있는 경우라면 한 두건의 처리실패 때문에 앞에 성공한 건들도 다 Roll back이 되어버린다면 더 많은 건의 데이터가 의도하지 않은 상태로 오래 유지됩니다. 배치처리의 에러가 발생 즉시 해결되지 않을 수도 있기 때문에 때문에 업무 규칙상으로 가능한 경우라면 부분적으로 성공한 건이라도 DB에 반영되는 것이 좋습니다. 그렇지 않을 경우에 재시도시에 다시 모든 데이터를 처리해야 하니 그 처리시간이 더 길어지게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다고 해서 데이터 매 건마다 따로 트랜잭션 처리를 하는 것도 성능상 좋지 못합니다. 트랜잭션을 시작하고 끝낼 때 드는 비용도 감안해야 되기 때문입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;따라서 일정한 행 단위를 묶어서 처리를 하는 것이 배치처리에서는 더 바람직한 방식입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;WebSphere XD Compute Grid에서는 check-point algorithm을 time-based와 record-based 두 가지 방식으로 제공해서 이런 문제에 대한 해결책을 제시하고 있습니다.
Spring batch에서는 SimpleStepFactoryBean 클래스에서 commitInterval이라는 속성으로 주기적인 commit을 설정할 수 있습니다. 데이터 특성이나 업무규칙에 따라서 적절한 값을 트랜잭션 단위로 묶이는 건수를 지정할 수 있는 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료_1&quot;&gt;참고자료 1&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.devx.com/Java/Article/20791/1763/page/1&quot;&gt;High-volume Transaction Processing in J2EE&lt;/a&gt; :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;세번째 페이지의 Job Partitioning and Chunking 단락에서 아래와 같이 설명하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Running your job as one long-lived transaction isn&amp;#8217;t a good idea. Long-lived transactions are more fragile than short-lived transactions. They are more susceptible to errors and DBMS locking problems because the locks are held longer. Smaller chunks are more reliable and less likely to exceed your connection and session timeouts.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료2&quot;&gt;참고자료2&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.speakeasy.org/~jwilton/oracle/dedicated-rbs.html&quot;&gt;Misconception: Big batch jobs should use a ‘dedicated’ rollback segment&lt;/a&gt; :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Oracle의 경우를 설명한 글입니다. DBA들이 보통 긴 transaction을 유지하는 batch job에 전용 role back segment를 할당하는데 실제로는 이것이 별도움이 못 된다고 말합니다. 결국 다른 session들이 사용할 수 있는 roll back segment를 사용하는 것은 똑같기 때문에 ORA-01555 에러를 유발할 수 있다고 합니다. 결국 보다 작은 transaction 단위를 가지고 가는 것이 근본적인 해결책입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 실패시 재시도를 위해서 지난 번 처리한 영역까지 표시하거나 앞에 성공한 건들을 다 지워줘야 하는 경우 때문에  큰 transaction을 가지고 가는 문제가 .생기는데, 재시도를 위한 기능을 트랜잭션에서 제공받기 전에 응용프로그램에서 적절한 로직으로 처리하는 것이 필요하다고 나와 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The problem that needs to be addressed in cases such as these is the design of the batch processes that require such a huge transaction.  Transactions in Oracle should normally be kept fairly short.  While it is undesirable to commit for every row processed (which will cause excessive redolog buffer flushing and high waits on “log file sync”), it makes sense to have batch processes commit for every few hundred rows processed.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Often the greatest barrier to changing batch jobs to commit continuously is failure tolerance.  If a batch job that commits continuously fails part way through, then there must be a way to restart that batch job where it left off, or clean up from the first attempt so that the job can be started over.  Whereas before this restart capability was provided by rolling back the large transaction, the proposed rollback-friendly model requires that the appropriate application logic be built into the batch processing software.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you allow your semi-frequently committing batch jobs to randomly select rollback segments like all the rest of the transactions in your system, you will be less likely to overwrite recently committed changes, since the burden of the batch transactions is spread around, rather than concentrated in a single rollback segment&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료3&quot;&gt;참고자료3&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ibm.com/developerworks/websphere/library/techarticles/0606_antani/0606_antani.html&quot;&gt;Solving Business Problems with WebSphere Extended Deployment&lt;/a&gt; :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;WebSphere XD에서 제공하고 있는 check-point algorithm에 대한 설명을 볼 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료4&quot;&gt;참고자료4&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://static.springframework.org/spring-batch/spring-batch-docs/reference/html/execution.html#d0e3602&quot;&gt;Spring batch documentation - 4.4.1.2. Configuring a CommitInterval&lt;/a&gt; :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring batch에서 주기적인 commit을 설정하는 방법입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>매쉬업(Mashup) 도구들</title>
      <link>https://blog.benelog.net//1857319.html</link>
      <pubDate>Wed, 23 Apr 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1857319.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;yahoo_pipe&quot;&gt;Yahoo Pipe&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://pipes.yahoo.com/pipes/&quot; class=&quot;bare&quot;&gt;http://pipes.yahoo.com/pipes/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;설명 : &lt;a href=&quot;http://en.wikipedia.org/wiki/Yahoo_Pipes&quot; class=&quot;bare&quot;&gt;http://en.wikipedia.org/wiki/Yahoo_Pipes&lt;/a&gt; , &lt;a href=&quot;http://radar.oreilly.com/archives/2007/02/pipes-and-filters-for-the-inte.html&quot;&gt;Pipes and Filters for the Internet&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;예제&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://pipes.yahoo.com/pipes/pipe.info?_id=vvW1cD212xGMiR9aqu5lkA&quot;&gt; New York Times thru Flickr&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;사용법&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://usefulvideo.blogspot.com/2007/02/yahoo-pipes-tutorials.html&quot;&gt;Yahoo! Pipes tutorials&lt;/a&gt; &lt;a href=&quot;http://www.jumpcut.com/fullscreen?id=C086AA92568811DCAB02000423CF381C&amp;amp;amp;type=movie&quot;&gt;How to Translate a Feed Using Pipes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.jumpcut.com/fullscreen?id=594F555C568011DC9D24000423CEF5B0&amp;amp;amp;type=movie&quot;&gt;Learn How to Build a Pipe in Just a Few Minutes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://alexeysmirnov.name/blog/?page_id=198&quot;&gt;Alexey&amp;#8217;s Yahoo Pipes tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;ms_popfly&quot;&gt;MS Popfly&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.popfly.com/&quot; class=&quot;bare&quot;&gt;http://www.popfly.com/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;설명 : &lt;a href=&quot;http://en.wikipedia.org/wiki/Popfly&quot; class=&quot;bare&quot;&gt;http://en.wikipedia.org/wiki/Popfly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Silverlight 기술에 기반&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;시연동영상 : &lt;a href=&quot;http://www.youtube.com/watch?v=hkTdJAYb&amp;#8212;&amp;#8203;M&quot;&gt;Three Minut Mashup - Microsoft Popfly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;ibm_lotus_mashup&quot;&gt;IBM lotus mashup&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ibm.com/lotus/mashups&quot; class=&quot;bare&quot;&gt;http://www.ibm.com/lotus/mashups&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2008년 중반에 공개 예정&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;google_mashup_editor&quot;&gt;Google Mashup Editor&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.googlemashups.com/&quot; class=&quot;bare&quot;&gt;http://www.googlemashups.com/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;설명 : &lt;a href=&quot;http://en.wikipedia.org/wiki/Google_Mashup_Editor&quot; class=&quot;bare&quot;&gt;http://en.wikipedia.org/wiki/Google_Mashup_Editor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intel_mash_maker&quot;&gt;Intel Mash maker&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://mashmaker.intel.com/web/&quot; class=&quot;bare&quot;&gt;http://mashmaker.intel.com/web/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;웹브라우저에 확장된 기능을 제공하는 형식&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;시연동영상 : &lt;a href=&quot;http://blip.tv/file/get/Intel_SW-IntelMashMakerOverviewVideo814.wmv&quot; class=&quot;bare&quot;&gt;http://blip.tv/file/get/Intel_SW-IntelMashMakerOverviewVideo814.wmv&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;관련링크&quot;&gt;관련링크&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://uzys.tistory.com/97&quot;&gt;mashup 도구&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Effective Java에 대한 서평에 재미있는 댓글</title>
      <link>https://blog.benelog.net//1847991.html</link>
      <pubDate>Thu, 17 Apr 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1847991.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Effective Java의 Second Edition이 나올 때가 되지 않았나 싶어서 아마존에 가봤더니 아직이군요. 5월28일로 발매가 예정되어 있습니다. (&lt;a href=&quot;http://www.amazon.com/Effective-Java-Second-2nd/dp/0321356683/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1208402810&amp;amp;sr=8-1&quot; class=&quot;bare&quot;&gt;http://www.amazon.com/Effective-Java-Second-2nd/dp/0321356683/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1208402810&amp;amp;sr=8-1&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;대신 Effective Java의 서평 중에서 재밌는 내용을 발견했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.amazon.com/review/R296QBPY6ZC2LB/ref=cm_cr_pr_cmt?_encoding=UTF8&amp;amp;ASIN=0201310058#wasThisHelpful&quot; class=&quot;bare&quot;&gt;http://www.amazon.com/review/R296QBPY6ZC2LB/ref=cm_cr_pr_cmt?_encoding=UTF8&amp;amp;ASIN=0201310058#wasThisHelpful&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spend a couple years writing Java code. Then come back, re-read the book, and revisit your review. You will change your mind, or career.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음에 서평쓴 사람이 컴퓨터 과학전공 졸업자로서 35%의 내용정도만 유익했거나 재미있었다고 하니 밑에 답글로 &apos;2년 정도 Java코드를 작성해보고 다시 책을 읽고 다시 서평을 쓴 것을 찾아봐라. 너는 생각을 바꾸게 되거나 진로를 바꾸게 될 것이다.&apos; 라고 적었군요. ^^;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>마인드맵(mind map) 그리기 도구들</title>
      <link>https://blog.benelog.net//1846356.html</link>
      <pubDate>Wed, 16 Apr 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1846356.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;마인드맵_관련_자료&quot;&gt;마인드맵 관련 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://moai.tistory.com/197&quot;&gt;생각을 확장해주는 마인드 맵(Mind Map)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://news.naver.com/main/read.nhn?mode=LSD&amp;amp;amp;mid=sec&amp;amp;amp;sid1=101&amp;amp;amp;oid=008&amp;amp;amp;aid=0000858692&quot;&gt;마인드맵핑, 회의 생산성 200% 향상&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;마인드맵_도구와_관련_포스트&quot;&gt;마인드맵 도구와 관련 포스트&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.mindmeister.com/&quot;&gt;MindMeister&lt;/a&gt; : 웹기반 공유 마인드맵.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://hsgames.com/blog/160&quot;&gt;웹2.0 마인드맵도구 Mindmeister&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://ypshin.com/2690262&quot;&gt;웹상에서 작성하는 무료 MindMap - mindmester.com&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://dirtybit.tistory.com/118&quot;&gt;mindmeister를 소개합니다&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.thoughtex.net/&quot;&gt;Thoughtex&lt;/a&gt; : 웹기반, WPF 기반&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/sjkang73/80038617402&quot;&gt;WPF 기반의 온라인 마인드 맵 도구 입니다.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.mind42.com/&quot;&gt;Mind42&lt;/a&gt; : 웹기반&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://alones.kr/blog/824&quot;&gt;웹 기반의 멋진 마인드맵 (mindmap) - Mind42 를 써보자&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.conceptdraw.com/&quot;&gt;ConceptDraw&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://boomup.tistory.com/entry/%EB%A7%88%EC%9D%B8%EB%93%9C%EB%A7%B5%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%B6%81-%EB%A7%8C%EB%93%A4%EA%B8%B0-ConceptDraw-53&quot;&gt;ConceptDraw MINDMAP 5.3 비주얼 이북 만들기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.thinkwise.co.kr/main/main.asp&quot;&gt;ThinkWise&lt;/a&gt; :&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/nihilbox/140050405578&quot;&gt;MindManager에서 ThinkWise로&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.mindmapper.com/&quot;&gt;MindMapper&lt;/a&gt; : &lt;a href=&quot;http://www.thinkwise.co.kr/main/main.asp&quot;&gt;ThinkWise&lt;/a&gt;의 해외 버전&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.mindjet.com/us/&quot;&gt;MindManager&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://freemind.sourceforge.net/&quot;&gt;freemind&lt;/a&gt; : Java 기반, 오픈소스&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://skcha.tistory.com/10&quot;&gt;마인드 맵을 그리는 FreeMind&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://hanburn.tistory.com/59&quot;&gt;무료 마인드맵(Mind Map) - Freemind&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.smartdraw.com/&quot;&gt;SmartDraw&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.conceptleader.com/&quot;&gt;ConceptLeader&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;자료나 생각을 정리할 때 마인드맵을 활용하고 싶다는 생각은 오래 전부터 해왔지만 쉽게 실현하지는 못했습니다. 제가 마인드맵을 처음 접한 것은 고등학교 때였는데, 몇 번 정도 공책필기 내용을 정리해보다가 그만두고 말았었습니다. 정리하는 내용이 재미가 없어서도 그랬었겠지만, 아무래도 책에서 본 예제처럼 이쁘게 마인드맵이 그려지지 않았던 이유도 컸었던 것 같습니다. 그후로도 몇번 마인드맵과 마주쳤지만 선뜻 작성해볼 의지가 생기지는 않았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위에 소개된  마인드맵 작성을 도와주는 프로그램들이 저 같은 사람에게 도움이 될 것입니다. 여러 도구를 다 써봐도 여전히 손으로 그리는 것이 가장 효율적이라고 생각하시는 분도  계실 것 같습니다만, 저의 경우는 손재주가 없어서 인지 프로그램이 더 편하게 느껴지더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 중에 &lt;a href=&quot;http://www.mindmeister.com/&quot;&gt;MindMeister&lt;/a&gt;, &lt;a href=&quot;http://www.mind42.com/&quot;&gt;Mind42&lt;/a&gt; , &lt;a href=&quot;http://www.thoughtex.net/&quot;&gt;Thoughtex&lt;/a&gt; 는 별도의 프로그램 설치없이 웹에서 마인드맵을 그릴 수 있습니다. 공동작업을 하거나 작업결과를 웹으로 공개할 때 편하게 쓸 수 있어 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 얼마전에 알게된 &lt;a href=&quot;http://www.conceptleader.com/&quot;&gt;ConceptLeader&lt;/a&gt; 는 연결선의 시각적 효과가 돋보입니다.
제가 읽었던 마인드맵을 설명하는 책에서는  선을 뿌리 쪽에는 두껍께, 말단부로 갈 수록 가늘게 그리라고 되어있는데, 다른 프로그램들에서는 이런 효과를 줄 수 없었습니다.  &lt;a href=&quot;http://www.conceptleader.com/&quot;&gt;ConceptLeader&lt;/a&gt;는 홈페이지에서 사용자 등록을 하면 상업적이지 않은 목적의 개인사용자는 무료로 쓸 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;http://conceptleader.com/conceptleadercokr/01_pro/images/screen_01.jpg&quot; alt=&quot;conceptLeader.JPG&quot; width=&quot;740&quot; height=&quot;672&quot; title=&quot;conceptLeader.JPG&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Java Coding Convention 관련 문서들</title>
      <link>https://blog.benelog.net//1823165.html</link>
      <pubDate>Tue, 1 Apr 2008 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1823165.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;coding_convention_문서&quot;&gt;Coding Convention 문서&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;sun에서_제시한coding_convention&quot;&gt;Sun에서 제시한Coding convention&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/codeconv/index.html&quot; class=&quot;bare&quot;&gt;http://java.sun.com/docs/codeconv/index.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html&quot;&gt;http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/codeconv/html/CodeConventions.doc5.html#16817&quot;&gt;http://java.sun.com/docs/codeconv/html/CodeConventions.doc5.html#16817&lt;/a&gt; : 블럭 내에서 변수선언 위치&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Effective Java (Joshua Bloch저 이해일역)의 7장 프로그래밍 일반 - 항목29 (지역변수의 유효범위를 최소화하라)에서 나온 의견과 다소 어긋남&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;기타&quot;&gt;기타&lt;/h3&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://g.oswego.edu/dl/html/javaCodingStd.html&quot; class=&quot;bare&quot;&gt;http://g.oswego.edu/dl/html/javaCodingStd.html&lt;/a&gt; : Doug Lea&amp;#8217;s Java Coding standard&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.chimu.com/publications/javaStandards/part0003.html&quot; class=&quot;bare&quot;&gt;http://www.chimu.com/publications/javaStandards/part0003.html&lt;/a&gt; : Chimy Inc coding standards.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ambysoft.com/javaCodingStandards.html&quot; class=&quot;bare&quot;&gt;http://www.ambysoft.com/javaCodingStandards.html&lt;/a&gt; : Scott Ambler&amp;#8217;s coding convention&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;한글_자료&quot;&gt;한글 자료&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html&quot; class=&quot;bare&quot;&gt;http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/hyowong/80000631451&quot;&gt;http://blog.naver.com/hyowong/80000631451&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/jeany4u/20003876157&quot; class=&quot;bare&quot;&gt;http://blog.naver.com/jeany4u/20003876157&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;그림&quot;&gt;그림&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/2246773D5186152416&quot; alt=&quot;2246773D5186152416&quot; width=&quot;926&quot; height=&quot;1140&quot; title=&quot;xplor/xp0002f/codingstd.gif&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>shuffle을 이용한 로또생성기</title>
      <link>https://blog.benelog.net//1646013.html</link>
      <pubDate>Fri, 14 Dec 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1646013.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;바로 아래 포스팅에서 간단한 로또생성기 코드를 만들어봤었죠. (&lt;a href=&quot;http://blog.benelog.net/1642193&quot; class=&quot;bare&quot;&gt;http://blog.benelog.net/1642193&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;같은 문제에 대한 dak99님의 풀이(&lt;a href=&quot;http://dak99.egloos.com/3529054)를&quot; class=&quot;bare&quot;&gt;http://dak99.egloos.com/3529054)를&lt;/a&gt; 보고 다시 다른 버전을 만들어봤어요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;shuffle을 이용해도 &lt;a href=&quot;http://java.sun.com/j2se/1.5.0/docs/api/java/util/Collections.html&quot;&gt;Collections&lt;/a&gt;의 메소드들을 불러서 쓰니 코드가 별로 안 복잡해 지는군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class LottoMachine \{
    public static void main(String[] args) \{
        List&amp;lt;Integer&amp;gt; numberCards = new ArrayList&amp;lt;Integer&amp;gt;();
        for(int i=1;i&amp;lt;=45;i++) numberCards.add(i);
        Collections.shuffle(numberCards);
        List&amp;lt;Integer&amp;gt; picked = numberCards.subList(0,5);
        Collections.sort(picked);
        System.out.println(picked);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;n이 클수록 속도차이가 클겁니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>로또생성기 java로 만들어보기</title>
      <link>https://blog.benelog.net//1642193.html</link>
      <pubDate>Wed, 12 Dec 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1642193.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;javaservice.net에서 보고 저도 한번 짜봤어요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( 원글: &lt;a href=&quot;http://www.javaservice.net/%7Ejava/bbs/read.cgi?m=resource&amp;amp;b=qna2&amp;amp;c=r_p&amp;amp;n=1196139164&amp;amp;p=3&amp;amp;s=t#1196139164&quot;&gt;http://www.javaservice.net/~java/bbs/read.cgi?m=resource&amp;amp;b=qna2&amp;amp;c=r_p&amp;amp;n=1196139164&amp;amp;p=3&amp;amp;s=t#1196139164&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;문제&quot;&gt;문제&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1부터 45까지의 숫자 중에 6개를 뽑는다.6개의 값이 다 달라야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;java.util.Random를 이용해서 임의의 값을 구한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;출력시 작은 숫자부터 순서대로 출력&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;저의_풀이&quot;&gt;저의 풀이&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;간단하게 짠다면 일단 저는 아래와 같이 해보고 싶어요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;
public class LottoMachine1 {
    public static void main(String[] args) {
        SortedSet&amp;lt;Integer&amp;gt; pickedNumbers = new TreeSet&amp;lt;&amp;gt;();
        Random random = new Random();
        while(pickedNumbers.size()&amp;lt; 6) pickedNumbers.add(random.nextInt(45)+1);
        System.out.println(pickedNumbers);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>무엇인가를 만들고 싶다는 꿈</title>
      <link>https://blog.benelog.net//1617365.html</link>
      <pubDate>Thu, 29 Nov 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1617365.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마이크로소프트웨어지 2007년 11월호의 특집기사는 &apos;개발고수 7인의 천기누설, 슈퍼개발자로 가는 길&amp;#8217;이였습니다. 왠지 얄팍해 보이는 제목이 마음에 들지 않았고,  내용을 읽기 전까지는 이제 이 잡지도 소재가 고갈되었나 하는 생각까지도 들었습니다. 그러나 거기에 포함된 델파이 고수 양병규님의 기사는 충격적일 정도로 감명을 주는 내용이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;취재기자의 글 : &lt;a href=&quot;http://www.acornpub.co.kr/blog/158?TSSESSION=6fcc3ea66788b21722c8145a1dedad41&quot;&gt;당신의 가슴 속에 품은 꿈은 무엇인가요?&lt;/a&gt;, &lt;a href=&quot;http://flytgr.tistory.com/193&quot;&gt;꿈꾸는 개발자의 희망스토리&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 기사에서는 양병규님이 PC원격제어 프로그램을 혼자서 2개월만에 개발한 이야기가 나옵니다. 양병규님은  다른 PC원격 제어 프로그램을 처음 보는 순간부터  그런 프로그램을 직접 만들어보고 싶은 마음이 들었고, 그후로 부터 실제 개발을 시작한 시기 사이의 2년동안 틈틈히 아이디어를 생각하고 메모를 했다고 합니다. 화면 캡쳐, 이미지 압축, 소켓 전송 등 프로그램에 필요한 구체적인 것까지 다 미리 생각해 둔 덕분에 실제 개발기간인 2개월동안에는 코딩만 하니 끝이였다고 하네요. 대단하지 않습니까?  아래는 그 기사에서 인용한 내용입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 방법으로 작은 수첩하나를 가슴에 품고 자신의 생각들을 차곡차곡 쌓아가다 보면, 방금 산 복권을 양복 주머니에 넣고 희망찬 모습으로 걸어가는 모습의 복권 포스터 주인공 못지 않게 희망찬 하루하루를 살 수 있을 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/wogud71/20006856915&quot;&gt;델마당 양병규 님의 40대 초보 프로그래머에 대한 답변&lt;/a&gt; 글에 있는 양병규님의 첫 프로그램 이야기도 감동적입니다. 저의 부모님께서도 예전에 어렵게 생활하셨던 분들이라, 어른들의 젊었을 적에 고생한 사연은 어릴 때부터 하도 많이 들어왔기에 왠만한 이야기들은 저는 무덤덤하게 느껴집니다. 하지만 그런 저도 양병규님의 이야기를 읽은 뒤에는 저절로 고개가 숙여집니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 글들을 다 보고나니, 제가 프로그램 개발을 하게 된 이유를 한동안 잊어먹고 있었다는 것을 깨달았습니다. 뭔가를 만들어보고 싶다는 마음, 만드는 과정의 즐거움, 결과물을 볼 때의 뿌듯함, 혹시나 다른 사람에게 도움이 될 수 있을까 하는 기대감.. 그런 감정들이였습니다.  다른 많은 분들도 마찬가지 일 듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런 시작을 잊고 살다 보니 만들어보고 싶은 것이 생각나도 &apos;시간나면 해봐야지&amp;#8217;하는 마음으로 흘려보낸 시간들이 많았습니다. 앞으로는 자주 안 잊어먹도록 여기에도 적어놓습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제 꿈은 아래에 나오신 분들 같은 &apos;발명 할아버지&amp;#8217;가 되는 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;이명(99세) 할아버지 :  &lt;a href=&quot;http://news.naver.com/main/read.nhn?mode=LSD&amp;amp;mid=sec&amp;amp;sid1=102&amp;amp;oid=037&amp;amp;aid=0000003035&quot;&gt;나이도 못 막은 열정 ‘98세 발명왕’&lt;/a&gt;  (주간동아)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;최태진(70세) 할아버지 : &lt;a href=&quot;http://www.cbs.co.kr/Nocut/Show.asp?IDX=163494&quot;&gt;밥 먹다가도 작업실 뛰어가&lt;/a&gt; (노컷뉴스)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;정선영(70세) 할아버지 : &lt;a href=&quot;http://news.chosun.com/site/data/html_dir/2007/11/21/2007112100029.html&quot;&gt;“죽기전에 딱 5개만 더 발명하는게 소원” ‘정력팬티’로 100개국 특허 따낸 정선영씨&lt;/a&gt; (조선일보)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;정병일(73세) 할아버지 : &lt;a href=&quot;http://news.naver.com/main/read.nhn?mode=LSD&amp;amp;mid=sec&amp;amp;sid1=102&amp;amp;oid=005&amp;amp;aid=0000197682&quot;&gt;농기계 발명 구슬땀 귀농 70대 부부 화제&lt;/a&gt; (국민일보) , &lt;a href=&quot;http://netv.sbs.co.kr/skin/skin_naver.jsp?uccid=10000181896&quot;&gt;발명 삼매경에 빠진 할아버지&lt;/a&gt; (SBS 생방송 모닝와이드 1818화 3부)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;김병문(65세) 할아버지 : &lt;a href=&quot;http://netv.sbs.co.kr/portal/review.jsp?vod_id=V0000210215&amp;amp;vod_cnt1=02556&amp;amp;vod_cnt2=03&quot;&gt;가위 든 맥가이버&lt;/a&gt;(SBS 출발 모닝와이드 2556회 3부 )&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(간혹 방송이나 인터넷에서  몇 분을 보기는 했는데 찾아보니 이런 할아버지들 꽤 많군요)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2050년쯤 &apos;발명 할아버지&amp;#8217;로 검색했을 때 어느 시골의 마을신문에서 낸 기사로 &apos;집념의 발명 할아버지 정상혁&amp;#8217;과 비슷한 제목이 뜬다면 제가 꿈을 이뤘다고 보셔도 됩니다. 혹시나 그 기사에 &apos;그러나 만든 것 중 쓸모있다고 인정받은 것은 하나도 없음&apos;, &apos;주변에서 계속 뜯어 말리고 있으나 소용없음&apos;,&apos;가족들은 갈수록 헛소리가 심해진다고 걱정이 많음&amp;#8217;과 같은 내용이 포함된다고 해도 말이죠.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Ship it! 성공적인 소프트웨어 개발 프로젝트를 위한 실용 가이드</title>
      <link>https://blog.benelog.net//1546671.html</link>
      <pubDate>Mon, 22 Oct 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1546671.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=2657930&amp;amp;CategoryNumber=001001003005006001&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;http://image.yes24.com/momo/TopCate57/MidCate09/5683498.jpg&quot; alt=&quot;momo/TopCate57/MidCate09/5683498.jpg&quot; width=&quot;168&quot; height=&quot;220&quot; title=&quot;momo/TopCate57/MidCate09/5683498.jpg&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마이크로소프트웨어지에 &apos;커뮤니티 노트&amp;#8217;라는 컬럼을 연재하고 계신 &lt;a href=&quot;http://kaistizen.net/&quot;&gt;최재훈님&lt;/a&gt;이 번역하신 책입니다. 사실 처음 책의 목차만을 보았을 때는 &apos;실용주의 프로그래머&amp;#8217;와 겹치는 내용이 많을 것 같아서 큰 기대를 하지는 않고 읽기 시작했었습니다. 아마  제가 블로그를 통해서 이 책을 알지 못하고 서점에서 우연찮게 만났다면 책을 안 사고 지나쳤을 법도 합니다. 그런데 다 읽고나니 다음에 프로젝트 시작할 때는 사비로라도 이 책을 사서 프로젝트 팀원들에게 돌리고 싶다는 생각이 들더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책에서는 프로젝트를 성공적으로 이끄는 해결책으로 다음의 방법들을 제시하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;소스코드 버전관리 프로그램을 이용해라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;테스트 자동화를 해라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;프로젝트 첫날 빌드스크립트를 작성해서 자동화하고, 지속적으로 통합, 빌드, 테스트를 해라.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;일일회의로 개발자들간의 의사소통을 활발히 해라&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;상호 코드 검토를 해라.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아마 이미 식상한 이야기라고 느끼실 분도 많을 것 같네요. 저도 요약한 것만 봤다면 그렇게 느꼈을 것 같습니다. 그러나 이 책의 조언들은 현장감이 넘칩니다. 상세한 행동 방법들과 잘 되어 가고 있는지 확인할 수 있는 검토사항들을 알려주고 있습니다. 부록으로 책에서 말한 지침을 실천하는데 도움이 되는 테스트 프레임웍 같은 도구들의 URL을 모아서 정리해주는 친절함도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;요즘과 같이 지식이 잘 전파되는 환경에서 위의 조언들을 모르고 있는 개발조직은 거의 없을 것입니다.  &apos;외국에서나 통하지 우리나라 현실에 안 맞는 방법이다. &apos; , &apos;실무를 모르고 책상에 앉아서 만든 이야기일 것이다&apos;, &apos;내가 프로젝트를 많이 해봐서 아는데 다 이상적인 내용일 뿐이다.&apos; 등의 생각으로 적용을 안하고 경우도 많습니다. 또는 당장 급하게 돌아가게 있는 프로젝트에서 변화를 주는 것은 위험하다고 판단하기도 하겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저도 그런 경험들을 거쳐왔었습니다.   몇년전에 제가 경험했던 프로젝트에서는  본사차원에서 버전관리 프로그램(PVCS Merant version manager)를 쓰도록 표준이 정해졌었고, 그 프로그램에 대한 설치,  개발자와 고객에게 같이 교육까지도 지원되었습니다. 당시에 저도 버전관리 프로그램이 좋다는 말은 책에서 들어서 알고 있었지만, 막상 쓸려고 하니 괜히 소스관리에 시간만 더 들이게 되는 것이 아닌가 하는 생각이 들더군요. 그리고 PVCS에 check in 한 다음에도 취합, 컴파일, 파일업로드는 한 개발자의 PC에서 이루어 졌기 때문에 실제로 버전관리는 해봤자 백업의 의미밖에 없었습니다. 소스의 최종버전은 결국 개발자들의 각자의 PC에 저장되어 있었고, 버전관리는 프로젝트 기간동안 한 두번 정도 한꺼번에 파일을 올리는 식으로 형식적으로 하고 넘어갔습니다. 파일서버에 각 개발자들이 그날 작업한 것을 폴더 형식을 맞춰서 올리면 한 명이 개인PC로 복사, eclipse로 컴파일, 알ftp로 업로드하는 일을 했는데, 한번에 30~40분은 걸리는 작업을 거의 매일 했었던 것으로 기억합니다.  통합,컴파일, 업로드까지 한꺼번에 해주는 빌드스크립트를 작성하면 좋겠다는 생각도 있었지만, 당장 매일매일 오늘은 프로그램 몇개 짰다고 엑셀파일에 체크를 안 하면 죄인이 되는 것 같은 프로젝트 분위기에 잠깐 다른 일을 할 엄두가 안 나더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음에 이어진 프로젝트는 연속사업이였기 때문에 운영서버에서 돌아가는 소스와 새로 개발되는 소스가 복잡하게 꼬일 위험이 있었습니다. 그 때부터는 버전관리의 필요성을 팀원 모두가 어느정도 인식하기 시작했었습니다.  그래서 빌드 스크립트를 작성하는데 시간을 쓸 수가 있고, 컴파일, 새로 바뀐 파일만 복사, 소스업로드, 서버 리부팅, 로그 남기기의 작업을 한번에 해주는 ant build script를 작성했습니다. 빌드 스크립트를 윈도우 스케쥴러에 걸어서 하루에 두 번 돌리고 로그만 아침에 출근해서 한번, 점심 먹고 한번 확인해주고 에러난 것 있으면 개발자에게 통보해 주는 절차를 거쳤습니다.  당시의 제 지식의 한계로  &lt;a href=&quot;http://cruisecontrol.sourceforge.net/&quot;&gt;크루즈컨트롤&lt;/a&gt; 같은 툴을 활용해서 완벽한 CI를 해보는 수준까지 생각 못한 것이 아쉽기는 합니다.  그래도 그 수준이라도 만들고 나니  전에 프로젝트에서 했던 원시적인 작업방식들은 이제 다시는 불편해서 못할 것만 같았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개발자들의 버전관리 프로그램을 통해서만 소스를 수정한다는 것을 고객이 인식을 하고 나니, 몇몇 고객들은 자신의 PC에 버전관리 프로그램을 설치해 달라고 요구하기 시작했습니다. 그리고 간단한 글자수정 정도는 스스로 하는 고객도 나왔습니다. 비록 &amp;lt;font&amp;gt; 태그로 제가 별로 맘에 안 드는 색깔로 도배를 해서 찜찜해진 페이지도 있었지만, 그래도 고객에 취향에 맞는 결과를 제가 손대지 않고 얻었으니 만족해야겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지금 돌아보면 이슈관리를 위한 Tracker도 적극적으로 사용했어야 하는데 하는 생각이 듭니다. 연속사업의 첫번째 프로젝트에서는 Tracker도 본사에서 교육지원을 나온 인력이 고객과 개발팀 모두에게 사용법을 강의해 줬으나, 거의 활용되지 못했습니다. 두번째 프로젝트에서는 개발팀내에서만 진도관리 정도만을 체크하는 수준에서 Tracker를 사용했었습니다. 그때는 &apos;어차피 고객이 안 쓰니까 우리만 쓰는 용도로 쓸 수 밖에 없다&amp;#8217;라고 생각했었는데, Version Manager처럼 적극적으로 우리가 쓰서 고객을 끌어들일 시도조차 하지 않고서 너무 쉽게 단정을 내렸었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;프로젝트가 끝난 뒤에 특정 방식이 얼마나 생산성을 향상시켰는지 객관적으로 비교하는 것은 어렵습니다.  프로젝트는 정해진 시간에 딱 한번 하는 것이기 때문에 &apos;조건을 통제한 반복실험&amp;#8217;이 불가능하기 때문입니다. 마음이 받아들이지 못하는 방식이라면 설령 그 프로젝트가 무사히 끝났어도 &apos;그것만 없었으면 더 프로젝트를 편하게 했을텐데&amp;#8217;라고 생각할 것이고, 믿고 있는 방식이라면 지연된 프로젝트라도 &apos;그래도 그거 썼으니깐 이정도로 끝낼 수 있었지.&apos;라고 여길 수도 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 새로운 방식의 적용은 사람의 습관을 바꾸는 일입니다. 누구나 몸에 익은 편한 방식이 있고 그 방식으로도 큰 실패는 없을 것이라고 믿고 있습니다. 더 나아진다는 확실한 증거가 없는데도 기존의 방식을 흔쾌히 바꿀 사람은 흔치 않을 것입니다.  그렇게 공감을 얻는 방식을 같이 찾아간다는 것이 쉽지 않은 일임에도 프로젝트에서 그것을 위한 소통의 노력들은 부족할 때가 많습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저의 경험처럼 아무리 위에서 프로세스를 정해주고 지원을 해줘도 그것을 수행할 사람들이 그 필요성에 대해서 공감하지 못한다면 어떤 좋은 도구나 방법론들도 다 형식적인 것에 그치게 되고, 오히려 프로젝트 진행의 발목을 잡는 짐이 되기도 합니다.  어쩌면 가장 좋은 도구나 방법론은 위에서 똑똑한 사람들이 칼같이 정해준 것이 아닌, 팀원 중 다수가 그것이 좋다고 믿음을 가지고 있고, 그 방식의 전도사가 될 준비가 되어있는 것들일 것입니다. 그 믿음을 공유하고 있다면 팀원들은 그것을 증명하기 위해서 더욱 그 방법을 쓰는 작업에 몰입을 하고 정성을 다할 것이고, 공감하지 못한 상태라면 &apos;거봐~ 그거 내가 안 될거라고 했잖아~&apos; 라는 말을 언제라도 하고 싶어서 마음에 품고 다닐 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;몇년 전의 그 정신없던 프로젝트를 마치고 나서 가장 크게 들었던 후회했던 점은 &apos;왜 그때 더 좋은 방법을 찾아볼 노력을 더 안 해봤고, 찾은 것을 알릴려고 애쓰지 않았을까?&apos; 라는 것이였습니다. 빡빡한 일정은 사람의 시야를 좁게 만들어서 당장 오늘,내일을 위한 임시 방편들만을 양산했고, 결국에는 그것이 더 돌아가는 길이라는 것도 깨달을 수도 없었습니다. 프로젝트 초기에 전체 개발팀 회의만 몇번 더 하고 코드리뷰만 몇번 했더라면 그 약간의 시간이 그냥 앉아서 코딩하는 시간의 몇배가 가치가 있었다는 것을 확신하지 못했고, 그렇게 투자하지 못했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책에서 제가 가장 감명 깊게 읽은 내용은 다음 부분이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;최고의 아키텍처는 상아탑의 &apos;아키텍트&amp;#8217;에게서 나오지 않습니다. 공동의 노력을 기울여야 최고의 아키텍처가 나옵니다. 전문가 한 명이 독주해서 완성한 아키텍처 문서를 여러분 앞에 던져 놓고 가게 하지 말고, 팀이 함께 일해서 모든 이의 경험을 발휘하고 쌓아나가도록 하세요.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지난 7월7일, &lt;a href=&quot;http://p-camp.tistory.com/entry/about&quot;&gt;P-camp&lt;/a&gt;라는 행사에 참가했을 때, 저는 &apos;&lt;a href=&quot;http://benelog.springnote.com/pages/349170&quot;&gt;함께하는 개발표준 만들어 가기&lt;/a&gt;&apos;라는 글을 썼던 적이 있습니다. 프로젝트 후에 가장 절실하게 느꼈던 점을 정리했던 것인데, 비슷한 이야기를 이 책에서 보니 당연히 반가웠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;책을 읽다보니 계속 &apos;다음 프로젝트에서는 이런이런 일을 하고, 이런 말을 사람들에게 해야지..&apos; 하는  상상과 기대를 해보게 되었습니다. 힘들고 황폐한 프로젝트라도 조금이나마 불행을 덜어줄 수 있는 일을 제가 할 수 있었으면 하는 꿈이 다시 불어넣어졌습니다. 앞으로의 현실이 어떨지는 몰라도, 이책을 보면 &apos;그래, 이제 제대로 해보는 거야.&apos;라는 말이 이 책의 요약으로 머리속에 떠오를 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;책에 첫장에는 다음과 같은 명언이 인용되고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재의 우리는 반복적으로 하는 행동의 결과이다. 그러므로 탁월함이란 행동이 아니라 습관이다. -아리스토텔레스&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 개발자들이 가질 수 있는 탁월한 습관은 &apos;더 나은 방법이 있다는 것을 믿고, 찾고, 나누는 습관&amp;#8217;이라고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>일본서점 컴퓨터 책 코너 탐방기</title>
      <link>https://blog.benelog.net//1486908.html</link>
      <pubDate>Fri, 21 Sep 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1486908.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지난 8월4~7일 여름휴가로 일본 동경을 갔다왔습니다. 동경의 번화가를 구경하는데 4일을 다 썼어도 아쉬움이 남을 정도로 구경거리가 많더군요. 그 와중에서도 일본에는 어떤 컴퓨터 책들이 나오고 있을까 하는 호기심을 참지못해 시부야에 있는 북퍼스트와  신주쿠의 기노쿠니아 서점을 가 봤었습니다. 사실 일본어는 &apos;다꾸앙&apos;,&apos;와리바시&apos;,&apos;덴뿌라&apos; 수준의 단어 밖에 모르고, 글자는 전혀 읽을 줄 모르기 때문에 책표지만 감상할 수 밖에 없었습니다. 그래도 전체적인 기류는 명확히 와 닿았기에 인상적인 경험이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/kinoku.jpg&quot; alt=&quot;kinoku.jpg&quot; width=&quot;320&quot; height=&quot;240&quot; title=&quot;01kinoku.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/book1st.jpg&quot; alt=&quot;book1st.jpg&quot; title=&quot;book1st.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;역시 웹2.0, SNS(Social Network Service), SEM(Search Engine Marketing), SEO(Search Engine Optimization, 검색엔진 최적화) 에 관련된 책들이 컴퓨터 서적코너의 앞 쪽을 차지하고 있었습니다. 인터넷 비지니스에 대한 열기는 일본에서도 뜨겁게 느껴집니다.
SEM/SEO는 아직 국내에서는 책 제목으로 붙일 정도로 보편화된 용어는 아닌 것 같은데 일본에서는 어느정도 알려졌나 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/web2-0-magazine.jpg&quot; alt=&quot;web2-0-magazine.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/web2-0_1.jpg&quot; alt=&quot;web2-0_1.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/web2-0_2.jpg&quot; alt=&quot;web2-0_2.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/web2-0_3.jpg&quot; alt=&quot;web2-0_3.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;구글,  Yahoo, Myspace등 인터넷 서비스에 대한 책들도 많이 보였습니다. 서비스 사용자를 위한 이용법에 대한 내용도 있었지만, 개발자를 위한 Open API관련 서적이 눈에 띄게 많았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/google.jpg&quot; alt=&quot;google.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/google-api.jpg&quot; alt=&quot;google-api.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/google-yahoo.jpg&quot; alt=&quot;google-yahoo.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/google_1.jpg&quot; alt=&quot;google_1.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Goole earth에 관한 책들이 몇권보이는 마지막 사진은 컴퓨터 코너가 아닌 기노쿠니아 서점의 지학(땅 지 ,배울 학) 코너에서 찍은 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Second life에 대한 책도 진열장의 잘 보이는 곳을 차지하고 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/214615&quot; alt=&quot;secondlife.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/214610&quot; alt=&quot;secondLife3.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/214609&quot; alt=&quot;secondLife1.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;마지막 사진은 기노쿠니아 원서 코너에서 찍은 것인데, 그 서점에는 컴퓨터 원서가 몇권 없었음데도 second life와 my space에 대한 책이 보였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java관련 책들을 찾아봤는데 반가운 책들을 여러 권 발견했습니다. Effective Java, Expert one and one J2EE developement, Core J2EE pattern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/java_1.jpg&quot; alt=&quot;java_1.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/java_2.jpg&quot; alt=&quot;java_2.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/java_3.jpg&quot; alt=&quot;java_3.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Eclipse 관련 서적들도 비중있게 자리가 배정되어 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/eclipse_1.jpg&quot; alt=&quot;eclipse_1.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/eclipse_2.jpg&quot; alt=&quot;eclipse_2.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;오픈소스 프레임웍에 관한 책들도 찾아보았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/opensource-fw.jpg&quot; alt=&quot;opensource-fw.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/spring2-0.jpg&quot; alt=&quot;spring2-0.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 국내에서는 보기 힘든 책의 번역판도 보였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.amazon.com/Inside-JavaOS-TM-Operating-System/dp/0201183935/ref=sr_1_1/103-8441353-8681409?ie=UTF8&amp;amp;amp;amp;amp;amp;amp;amp;amp;s=books&amp;amp;amp;amp;amp;amp;amp;amp;amp;qid=1190370137&amp;amp;amp;amp;amp;amp;amp;amp;amp;sr=8-1&quot;&gt;Inside JavaOS&lt;/a&gt;라는 책인데 돌아와서 찾아보니 yes24에서는 외국서적으로도 검색이 안 되는 책이더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/java-os.jpg&quot; alt=&quot;java-os.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;함수형 언어 Haskell에 대한 책도 발견했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/haskel.jpg&quot; alt=&quot;haskel.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;두 서점을 통들어 가장 강렬한 느낌을 주었던 책장은 &lt;a href=&quot;http://www.wingbus.com/asia/japan/tokyo/shibuya/book_first/&quot;&gt;북퍼스트&lt;/a&gt;에서 본 Oreli 원서를 모아둔 곳이였습니다. 잘 정리된 모습이 깔끔해 보이네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/oreli_1.jpg&quot; alt=&quot;oreli_1.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 바로 옆에는 Oreli 번역서를 모아둔 책장이 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/japanese-book/oreli_2.jpg&quot; alt=&quot;oreli_2.jpg&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ruby를  일본 사람이 만들었으니 Ruby관련 서적이 한 칸은 차지하고 있지 않을까 하는 상상을 했었으나, 그렇지는 않았습니다. 국내와 비슷해보이는 몇권 정도였던것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;더 구경을 하고 하고 싶었으나,불만 가득한 아내 때문에 적당히 보고 나올 수 밖에 없었습니다. 여기까지 와서 이런 데를 오는 것이 이해가 안 간다고 하더군요. 아내가 옷가게 구경할 때 저는 불평없이 있었다는 사실이 별로 고려되지 않은 듯합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;서점에 가기 전에도 일본이 우리나라보다 인구가 많아서 시장이 넓으니 국내보다 풍부한 책들을 구경할 수 있을 것이라고 예상을 했었습니다.
둘러보니 역시 다양한 책들을 많이 볼 수 있었습니다
.
&lt;a href=&quot;http://www.amazon.com/Inside-JavaOS-TM-Operating-System/dp/0201183935/ref=sr_1_1/103-8441353-8681409?ie=UTF8&amp;amp;amp;amp;amp;amp;amp;amp;amp;s=books&amp;amp;amp;amp;amp;amp;amp;amp;amp;qid=1190370137&amp;amp;amp;amp;amp;amp;amp;amp;amp;sr=8-1&quot;&gt;Inside JavaOS&lt;/a&gt; 같은 책은 소수만이 관심을 가질 것으로 보이고 , 국내에서 이런 책을 읽고 싶은 사람은 아마  아마존에서 직접 주문해서 읽을 것입니다. 이런 전문적인 책들이 일본에 번역판이 나왔다는 사실은 크게 부럽지는 않습니다. 그러나 Open API에 대한 책에 대해서는 아쉬움이 남습니다. 이런 책도 역시 적극적인 의지를 가진 사람은 인터넷으로 자료를 찾거나 원서를 사 보면서 공부할 수 있겠죠. 하지만 영어로 읽는 것이 속도가 늦거나 약간이라도 저렴한 번역서를 사고 싶어하는 사람들을 끌어드릴 수 있다면 더욱 그 분야가 활성화 될 수 있을 것입니다. 즉  대중화하는 것이 의미가 있는 분야의 책은 수요를 보다 도전적으로 창출해야 한다고 생각합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;계속 open API를 예로 들면,
&apos;막연하게 뭔가 open api로 프로그램을 만들고 싶다는 사람이 있다.
그런데 그는 바쁜 업무를 핑계로 적극적으로 관련 자료를 찾아보지 못하고 있다.
우연찮게 그는 서점을 갔다가 Open API에 대한 책을 발견하고 사게 된다.
그는 출퇴근 길이나 집에서 뒹구는 시간에 그 책을 읽고 더욱 흥미를 느끼고, 결국에는 매쉬업 서비스를 만들어서 공개하게 된다.&apos; 와 같은 일도 생길 수 있지 않겠습니까?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음, 네이버, 스프링노트 같은 국내 서비스의 open API에 대한 입문서가 있다면 좋겠다는 생각도 듭니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;아뭏든 읽지도 못하는 책을 신나게 구경하게 온, 특이한 경험이였습니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>JavaScript, JSUnit으로 풀어본 뉴질랜드 화폐 문제</title>
      <link>https://blog.benelog.net//1483100.html</link>
      <pubDate>Wed, 19 Sep 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1483100.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://yaldex.com/JSFactory_Pro.htm&quot;&gt;1st JavaScript editor&lt;/a&gt;의 공짜 사용기한이 얼마 남지 않아서 JavaScript로 빨리 뭐라도 만들어봐야 겠다는 생각이 들었습니다. 프로젝트 업무 중에는 그런 일이 보이지 않아서 회사의 다른 팀원들이 하고 있는 스터디 모임에서 나왔던 &lt;a href=&quot;http://blog.naver.com/i1j1jsy/70011900056&quot; class=&quot;bare&quot;&gt;http://blog.naver.com/i1j1jsy/70011900056&lt;/a&gt;뉴질랜드 화폐문제를 풀어보았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;문제&quot;&gt;문제&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;원래 제가 문제를 봤던 URL : &lt;a href=&quot;http://rdp.ahamoment.org/wiki/Dollars&quot; class=&quot;bare&quot;&gt;http://rdp.ahamoment.org/wiki/Dollars&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;요약&quot;&gt;요약&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;뉴질랜드 화폐는 $100, $50, $20, $10, $5 지폐와   $2, $1, 50센트, 20센트, 10센트, 5센트  동전으로 이루어져 있다. 주여진 합계액에 대해서 몇 가지의 방법으로 돈을 구성할 수 있는지 세는 프로그램을 작성해라.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;예) 20센트는 4가지 방법으로 만들수 있다. : 1개의 20센트, 2개의 10센트, 10센트+ 2개의 5센트, 4개의 5센트&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;원래 문제는 파일에서 입력값을 받게 되어 있지만, JavaScript로 풀다보니 그 부분은 생략했습니다. 직접 풀어보실 분들은 풀이를 나중에 보세요~&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;풀이&quot;&gt;풀이&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&amp;lt;script language=&quot;JavaScript&quot; type=&quot;text/JavaScript&quot; src=&quot;app/jsUnitCore.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script language=&quot;JavaScript&quot; type=&quot;text/JavaScript&quot;&amp;gt;

    var unitList = [100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05];


    function countWaysOfAmount(totalMoneyAmount, unitIndex, ways)\{
        if (unitIndex == null) unitIndex = 0;
        if (ways == null) ways = 0;

        var unit = unitList[unitIndex];
        var moneyLeft = totalMoneyAmount;

        do\{
          if (moneyLeft == 0) ways++;
          else if (unitIndex &amp;lt; unitList.length)
            ways = countWaysOfAmount(moneyLeft,unitIndex+1,ways);
          moneyLeft =  Math.round( (moneyLeft - unit) *100) /100;
        } while(moneyLeft &amp;gt;= 0)
        return ways;
    }

    function testDollars()\{
        assertEquals(&quot;0.05&quot;, countWaysOfAmount(0.05), 1);
        assertEquals(&quot;0.1&quot;,  countWaysOfAmount(0.1 ), 2);
        assertEquals(&quot;0.15&quot;, countWaysOfAmount(0.15), 2);
        assertEquals(&quot;0.2&quot;,  countWaysOfAmount(0.2 ), 4);
        assertEquals(&quot;2&quot;,  countWaysOfAmount(2), 293);
    }

    alert(countWaysOfAmount(2));
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코드를 더 다듬을 여지가 있는 것 같지만 일단 올려봅니다&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;moneyLeft =  Math.round( (moneyLeft - unit) *100) /100;&lt;/code&gt; 부분은 다른 언어들과 마찬가지로 JavaScript에서도 소숫점 계산이 정확하지 않아서 붙인 부분입니다. 이걸 안해주니 5가 나와야하는 값이 4.999999  같이 나오더군요. 이 문제만 아니면 moneyLeft -= unit 으로 간단하게 쓸수 있는데 말이죠,
Java로 이걸 짠다면 BigDecimal을 쓰거나 별도의 클래스를 정의해야겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;JSUnit 없이 로컬에서 수정해 보실 분은 `&amp;lt;script language=&quot;JavaScript&quot; type=&quot;text/JavaScript&quot; src=&quot;app/jsUnitCore.js&quot;&amp;gt;&amp;lt;/script&amp;gt; 부분과 테스트 메서드만 지우고 돌려 보시면 되겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.naver.com/i1j1jsy/70011900056&quot;&gt; Internet Explorer Developer Toolbar&lt;/a&gt;, &lt;a href=&quot;http://yaldex.com/JSFactory_Pro.htm&quot;&gt;1st JavaScript editor&lt;/a&gt;, &lt;a href=&quot;http://www.jsunit.net/&quot;&gt;JSUnit&lt;/a&gt;, &lt;a href=&quot;https://addons.mozilla.org/ko/firefox/addon/1843&quot;&gt;FireBug&lt;/a&gt; 를 한꺼번에 써서 코딩하니 이클립스가 부럽지 않았습니다 ^^; 그리고 jsunit에서 debug(&quot;로그&quot;); 로 찍어보는 것도 상당히 유용했구요. 1st JavaScript edtior나 FireBug가 모두 디버거 기능이 훌륭하지만 반복문의 값을 추적하는 것은 break point 거는 것보다는 로그로 쫙 찍어보는 것이 편하더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>확장된 나선형 배열 출력 문제(Spiral Array)</title>
      <link>https://blog.benelog.net//1483053.html</link>
      <pubDate>Wed, 19 Sep 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1483053.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지난 2월에 회사 워크샵에서 풀었던 나선형 배열 문제에 대한 글을 올린 적이 있었지요. ( &lt;a href=&quot;http://blog.benelog.net/901106&quot; class=&quot;bare&quot;&gt;http://blog.benelog.net/901106&lt;/a&gt; )&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;알고보니 xper.org에도 이 문제가 올라와 있더군요 (&lt;a href=&quot;http://xper.org/wiki/seminar/SpiralArray&quot; class=&quot;bare&quot;&gt;http://xper.org/wiki/seminar/SpiralArray&lt;/a&gt;). 거기에 가면 다른 분들의 풀이도 많이 볼 수 있습니다.  저는 이 문제를 실컷 가지고 논 후에 더 이상 손 댈 것이 없다고 느껴질때 다른 분들의 풀이를 꼼꼼히 볼 생각입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우연찮게 이에 대한 확장된 문제를 다른 블로그에서 보게 되었습니다. (&lt;a href=&quot;http://blog.naver.com/itioma?Redirect=Log&amp;amp;logNo=40040118386&quot; class=&quot;bare&quot;&gt;http://blog.naver.com/itioma?Redirect=Log&amp;amp;logNo=40040118386&lt;/a&gt; ). 그 포스트에서 본 문제에는 아래의 조건들이 더 추가되어 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;작성된 코드에서 한 글자만 수정해서 &apos;숫자는 1씩 늘어난다&amp;#8217;를 &apos;숫자는 2씩 늘어난다&amp;#8217;로 바꾸시오.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;코드에서 한 문장만 수정해서 시작점 좌표(x,y)를 마음대로 바꿀 수 있도록 만드시오&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;코드에서 한 문장만 수정해서 시작할 때의 진행방향을 (위/아래/왼쪽/오른쪽 중에서 ) 바꿀 수 있도록 만드시오&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;배열의 크기를 마음대로 정할 수 있도록 만드시오.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;코드에서 한 문장만 수정해서 &apos;시계방향으로 90도&amp;#8217;를 &apos;반시계 반향으로 90&amp;#8217;도로 바꾸시오&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;코드에서 swich 문이나 else 문을 이용한 3단계 이상의  조건분기가 있다면 모두 제거하시오.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;코드에서 한 글자만 수정해서 &apos;한칸씩 진행하며&amp;#8217;를 두칸씩 건너뛰며&amp;#8217;로 바꾸시오.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1~7에서 수정되었던 문장/값/변수들이모두 하나의 함수 또는 블럭안에 존재하도록 고치시오.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고   숫자는 1에서 시작하고 , 채워지지 않은 부분이 있다면 0으로 표시된다는 점이 제가 처음에 풀었던 문제와 다르네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;확장된 조건을 다 수용할 수 있는  만족시키는 코드를 다시 한번 만들어보았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;소스코드&quot;&gt;소스코드&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class MatrixMain {
    public static void main(String[] args) {
       System.out.println(&quot;0.원래의 matrix&quot;);
       ExtMatrix mp = new ExtMatrix(6,6);
       mp.print();

       System.out.println(&quot;1.숫자가 2씩 늘어나게&quot;);
       mp.setIncrement(2);
       mp.print();

       System.out.println(&quot;2.시작점을 1,1로&quot;);
       mp.setStartPoint(1, 1);
       mp.print();

       System.out.println(&quot;3.시작방향을 아래부터&quot;);
       mp.setStartDirection(ExtMatrix.DOWN);
       mp.print();

       System.out.println(&quot;4.배열크기 바꾸는건 생성할때 파라미터 바꾸면 됨.&quot;);
       System.out.println();
       System.out.println(&quot;5.반시계 반향으로 회전&quot;);
       mp.setReverseClockWiseTurn();
       mp.print();

       System.out.println(&quot;6.그런 조건 분기 없음.&quot;);
       System.out.println();

       System.out.println(&quot;7.한칸씩이 아닌 두칸씩 건너뛰며.&quot;);
       mp.setMoveStep(2);
       mp.print();
    }
}

public class ExtMatrix {
    public static final int RIGHT = 0;
    public static final int DOWN = 1;
    public static final int LEFT = 2;
    public static final int UP  = 3;

    private static final int INIT_VALUE = 0;
    private static final int CLOCK_WISE_TURN = 1;
    private static final int REVERSE_CLOCK_WISE_TURN = -1;

    private int m = 0;
    private int n = 0;
    private int startX = 0;
    private int startY = 0;
    private int[][] matrix;
    private int startDirection = RIGHT;
    private int increment = 1;
    private int moveStep = 1;

    private int turnDirection = CLOCK_WISE_TURN;


    public ExtMatrix(int m, int n){
        this.m = m;
        this.n = n;
        matrix = new int[m][n];
    }

    public void setIncrement(int increment){
        this.increment = increment;
    }

    public void setStartPoint(int startX, int startY){
        this.startX = startX;
        this.startY = startY;
    }

    public void setClockWiseTurn(){
        turnDirection = CLOCK_WISE_TURN;

    }

    public void setReverseClockWiseTurn(){
        turnDirection = REVERSE_CLOCK_WISE_TURN;
    }

    public void setMoveStep(int moveStep){
        this.moveStep = moveStep;
    }

    public void setStartDirection(int startDirection){
        this.startDirection = startDirection;
    }

    public void print(){
        locateNumbers();
        for (int i=0;i&amp;lt; m ;i++){
            for (int j=0;j&amp;lt;n;j++) System.out.print(matrix[i][j] + &quot;\t&quot;);
            System.out.println();
       }
       System.out.println();
    }

    private void locateNumbers() \{
       init();
       int[] startPosition = new int[]\{startX,startY};
       matrix[startX][startY]= 1;
       int direction = startDirection;
       while(move(startPosition ,direction)) direction= getNextDirection(direction);
    }

    private void init(){
        for (int i=0;i&amp;lt; m ;i++){
            for (int j=0;j&amp;lt;n;j++) matrix[i][j] = INIT_VALUE;
        }
    }

    private boolean move(int[] position, int direction){
        int nowNumber = matrix[position[0]][position[1]];
        boolean moved = false;
        int[] nextPosition =getNextPosition(position,direction);
        while(isMovable(nextPosition[0],nextPosition[1])){
            moved = true;
            nowNumber+= increment;
            position[0] = nextPosition[0];
            position[1] = nextPosition[1];
            matrix[position[0]][position[1]]= nowNumber;
            nextPosition = getNextPosition(position,direction);
        }
        return moved;
    }

    private int[] getNextPosition(int[] position,int direction){
        int x = position[0];
        int y = position[1];
        if (direction == RIGHT) y+= moveStep;
        else if (direction == DOWN) x+= moveStep;
        else if (direction == LEFT) y-= moveStep;
        else if (direction == UP)  x-= moveStep;
        return new int[]\{x,y};
    }

    private int getNextDirection(int direction){
        direction+= turnDirection;
        if (direction&amp;lt;0) direction+=4;
        direction = direction %4;
        return direction;
    }

    private boolean isMovable(int x, int y){
        if (x&amp;gt;=m) return false;
        if (y&amp;gt;=n) return false;
        if (x&amp;lt;0) return false;
        if (y&amp;lt;0) return false;
        if (matrix[x][y]!= INIT_VALUE) return false;
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;방향을 int로 나타내는 것이 처음에는 좋은 아이디어 라고 생각했는데  &lt;code&gt;mp.setStartDirection(ExtMatrix.DOWN);&lt;/code&gt; 부분을 보니 타입안전열거형이나 enum을 도입해야지 좀더 코드가 이뻐질 것 같네요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Sybase 날짜형(datetime)의 문자열 변환시 style number</title>
      <link>https://blog.benelog.net//1478801.html</link>
      <pubDate>Mon, 17 Sep 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1478801.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;sybase에서 select convert(varchar,getdate(),112)로 찍어보면 오늘 날짜가 20070831 형식으로 나오죠. 이런 형식들이 정리된 자료를 한 번 만들어 보았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 쓰는 sybase 버전은 12.5.3입니다. (select @@version)으로 확인할 수 있죠)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;select convert(varchar,날짜데이터, &lt;strong&gt;&lt;em&gt;convertType&lt;/em&gt;&lt;/strong&gt;)`&lt;/code&gt; 형식으로 쓰고 &lt;em&gt;*convertType *&lt;/em&gt; 위치에 숫자가  들어갈 때 옆에 적힌 형식대로 나온다고 보시면 됩니다. 예시로 옆에 찍힌 날짜는 2007년 8월27일입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;style_number&quot;&gt;Style number&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;0 = Aug 27 2007  5:28PM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1 = 08/27/07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2 = 07.08.27&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3 = 27/08/07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4 = 27.08.07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5 = 27-08-07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6 = 27 Aug 07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7 = Aug 27, 07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8 = 17:23:35&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;9 = Aug 27 2007  5:28:08:563PM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;10 = 08-27-07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;11 = 07/08/27&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;12 = 070827&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;13 = 07/27/08&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;14 = 08/07/27&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;15 = 27/07/08&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;16 = Aug 23 2007 17:28:08&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;18 = 15:17:08&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;19 = 5:11:39:086PM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;20 = 17:12:30:633&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;21 = 07/08/27&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;22 = 07/08/27&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;100 = Aug 27 2007  5:28PM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;101 = 08/27/2007&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;102 = 2007.08.07&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;103 = 27/08/2007&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;104 = 27.08.2007&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;105 = 27-08-2007&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;106 = 27 Aug 2007&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;107 = Aug 27, 2007&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;108 = 17:28:08&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;109 = Aug 27 2007 5:28:08:563PM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;110 = 08-27-2007&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;111 = 2007/08/27&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;112 = 20070827&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;113 = 2007/27/08&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;114 = 08/2007/27&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;115 = 27/2007/08&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;116 = Aug 23 2007 17:28:08&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;응용&quot;&gt;응용&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;select convert(char,GETDATE(),112)&lt;/code&gt; : 오늘날짜를 YYYYMMDD로&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;select convert(char(8), DATEADD(DD,-1,getdate()) ,112)&lt;/code&gt; : 현재 날짜 하루전을 yymmdd형식으로 출력&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;select convert(char(8), DATEADD(DD,-1,&apos;20070827&apos;) ,112)&lt;/code&gt; : 2007년 8월 27일 전날을 출력. string &amp;#8594; datetime은 convert라는 함수를 사용하지 않고 내부적(implicit)으로 자동으로 변경됩니다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;select str_replace( convert(varchar,getdate(),20),&apos;:&apos;,null)&lt;/code&gt; : 현재 분일초,밀리세컨드까지: 152515853&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;select convert(varchar,GETDATE(),112) || str_replace( convert(varchar,getdate(),20),&apos;:&apos;,null)&lt;/code&gt; : 현재 연월일시분초밀리세컨드를 다 붙여서&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>CSS 선택자 정리</title>
      <link>https://blog.benelog.net//1456715.html</link>
      <pubDate>Thu, 6 Sep 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1456715.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;주로 Head first HTML/CSS에 있는 내용을 정리했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;여러개의_element_선택&quot;&gt;여러개의 element 선택&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;h1, h2 { }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;class_선택자&quot;&gt;class 선택자&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;p.greentea { } /* class가 greentea인 &amp;lt;p/&amp;gt; 선택 */

.greentea { } /* class가 greentea인 모든 태그 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;id_선택자&quot;&gt;id 선택자&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;#footer {} /* id가 footer인 모든 태그 */

p#footer {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;자식child_element_선택&quot;&gt;자식(Child) element 선택&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;div h2 {color:red} /* &amp;lt;div/&amp;gt; 하위에 있는&amp;lt;h2/&amp;gt; 선택  */

.detail p {}   /* class가 &quot;detail&quot;로 지정되어 있는 하위의 &amp;lt;p/&amp;gt; 선택 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;상태에_따른_선택자&quot;&gt;상태에 따른 선택자&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;a:visited\{}

a:link \{}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;상태는 active, hover, link, visited, first-child 등&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;의사psdudo_element_선택자&quot;&gt;의사(Psdudo) element 선택자&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;p:first-letter {}

p:first-line {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;속성attribute_선택자&quot;&gt;속성(Attribute) 선택자&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;img[width] {border:black thin solid;} /* width 속성을 가진 모든 이미지 선택 */

img[height=&quot;300&quot;] {border:red thin solid; } /* 값이 300인 height 속성을 가진 모든 이미지 선택 */

image[alt~=&quot;flowers&quot;] {border:red thin solid; }  /* &quot;flowers&quot;라는 단어를 포함하는 alt 속성을 가진 모든 이미지 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;형제sibling_element_선택자&quot;&gt;형제(Sibling) element 선택자&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;h1+p {}  /* &amp;lt;h1&amp;gt; 다음에 오는 &amp;lt;p&amp;gt; 선택 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;선택자_결합&quot;&gt;선택자 결합&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;div#greentea &amp;gt; blockquote {}  /* &amp;lt;blockquote/&amp;gt;의 부모가 되어야하는 &quot;greentea&quot; id를 가진 &amp;lt;div/&amp;gt; 자손 선택자 */

div#greentea &amp;gt; blockquote p {} /* &amp;lt;blockquote/&amp;gt;의 자손이자 &quot;greentea&quot; id를 가진 &amp;lt;div/&amp;gt;의 자손인 &amp;lt;p/&amp;gt; 선택 */

div#greentea &amp;gt; blockquote p:first-line /* 그 &amp;lt;p/&amp;gt;의 첫 줄 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>java의 List와 반복문(loop), 그리고 변수 선언 위치에 대해서</title>
      <link>https://blog.benelog.net//1382604.html</link>
      <pubDate>Fri, 3 Aug 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1382604.html</guid>
      	<description>
	&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;수정이력&quot;&gt;수정이력&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;20190409&quot;&gt;2019/04/09&lt;/h4&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Github로 글 이전하면서 이미지, 첨부파일 내용 복원&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;20070813&quot;&gt;2007/08/13&lt;/h4&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&apos;들어가며&apos;, &apos;마치며&apos; 대폭 수정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;오타 수정  : 여러개의 메소드에서 같이 써는 코드였었습니다.=&amp;#8658; 여러개의 메소드에서 같이 쓰는 코드였었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;20070809&quot;&gt;2007/08/09&lt;/h4&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;첫 단락 소제목 변경 ( &quot;ArrayList나 Vector에는 iterator가 성능이 더 안 좋다.&quot;에서 )&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;20070808&quot;&gt;2007/08/08&lt;/h4&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;소제목을 h3태그로&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;iterator를 쓸때 고려할 점 설명에 내용 보강&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;반복될 필요가 없는 코드의 예 설명에서 오타수정, 문장 다듬기&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;바이트 코드설명 부분의 한 문장 추가, 문장 다듬기&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;들어가며&quot;&gt;들어가며&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 요즘 유지보수 프로젝트에서 일하고 있습니다. 많은 시간을 코드를 다듬는데 쓰고 있고  다른 개발자들의 코드를 많이 볼 수 있는 기회가 되고 있습니다.  그러다 보니 List의 크기만큼 반복문을 돌리는 코드에서 많은 개발자들은  루프 블럭 안에서만 쓰는 변수의 선언을 밖에다 하고 있다는 것을 발견하게 되었습니다.  이에 대해서 개발기간에 대화를 해볼 기회가 없었다는 것이 아쉽게 느껴지더군요. 그래서 그 내용과 함께 list와 loop에 관한 몇가지 이야기들을 같이 묶어서 글로 정리해 보게 되었습니다. 첫번째 소제목과 두번째 소제목 아래의 내용은 성능을 약간이라도 더 개선하고 싶을 때 도움이 될 정보들이고, 세번째 소제목에서 위에서 말한 많은 개발자들의 습관이 실제로는 성능에는 아무 영향이 없음을 설명할려고 했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;arraylist와_vector는_randomaccess_inteface를_구현하고_있다&quot;&gt;ArrayList와 Vector는 RandomAccess inteface를 구현하고 있다.&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;List의 크기만큼 반복문을 도는 방법에는 크게 두 가지 방법이 많이 쓰이고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html&quot;&gt;java.util.List&lt;/a&gt;의 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#size%28%29&quot;&gt;size()&lt;/a&gt;로 크기를 구해서 그 갯수만큼 반복문을 돌아서 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#get%28int%29&quot;&gt;get(int index)&lt;/a&gt;로 List안에 있는 객체를 가지고 온다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://http://java.sun.com/j2shttp://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#iterator%28%29&quot;&gt;iterator()&lt;/a&gt; 로 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/Iterator.html&quot;&gt;java.util.Iterator&lt;/a&gt; 객체를 얻은 후 이 객체의 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/Iterator.html#hasNext%28%29&quot;&gt;hasNext()&lt;/a&gt;가 true인 동안 반복문을 돌아서 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/Iterator.html#next%28%29&quot;&gt;next()&lt;/a&gt;로 List안의 객체를 가지고 온다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그런데 그 객체가 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html&quot;&gt;java.util.ArrayList&lt;/a&gt;나 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/Vector.html&quot;&gt;java.util.Vector&lt;/a&gt;가 확실할 때는 1번의 방법이 더 빠르다고 합니다. 그 이유는 API문서를 보시면 확인할 수 있듯이 ArryList와 Vector는 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/RandomAccess.html&quot;&gt;java.util.RandomAccess&lt;/a&gt; interface를 구현하고 있기 때문입니다. RandomAccess의 API문서에는 아래와 같이 적혀있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Marker interface used by List implementations to indicate that they support fast (generally constant time) random access.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;List의 구현체가 빠른 임의접근을 지원한다는 것을 나타내는 표시 interface라는 말입니다. 선언되어 있는 메서드도 아무것도 없지요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;API문서에는 이렇게 적혀있지만 성능이 항상 우선순위의 가치는 아니므로 RandomAccess List에는 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#get%28int%29&quot;&gt;get(int index)&lt;/a&gt;을 써야 한다는 법칙을 만들 수는 없을 것입니다. 가령 List와 다른 Collection 객체, 배열 등을 한꺼번에 묶어서 처리할 때는 iterator pattern이 유용합니다. (참고자료5: Head first design pattern).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;하지만 성능이 중요한 코드를 짜는 경우에는  객체를 생성하는 부분에서 ArrayList나 Vector를 생성한다는 정보를 확실히 가지고 있고, 그것이 LinkedList같은 다른 객체로 변경될 가능성이 없다면 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/RandomAccess.html&quot;&gt;java.util.RandomAccess&lt;/a&gt; interface의 명세는 무시할 수만은 없을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;List 처리  시에 성능을 극대화 시키기 위해서 RandomAccess interface를 구현했는지 검사해서  &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#get%28int%29&quot;&gt;get(int index)&lt;/a&gt;를 쓸지 &lt;a href=&quot;http://http//java.sun.com/j2shttp://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#iterator%28%29&quot;&gt;iterator()&lt;/a&gt;를 사용할지 결정하는 코드도 있기는 합니다. (참고자료2의 자바퍼포먼스 튜닝 중).  보통 그렇게까지 성능을 쥐어짤 경우는 흔치 않겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;list의_크기를_반복해서_구할_필요가_없다&quot;&gt;List의 크기를 반복해서 구할 필요가 없다.&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위에서 1번으로 제시된 것처럼 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#get%28int%29&quot;&gt;get(int index)&lt;/a&gt; 를 쓰는 방법을 쓸 때도 아래와 같은 코드를 많이 보게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;for( int i = 0; i &amp;lt; list.size(); i++)\{

//일하기

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;흔하게 보는 코드죠? 그런데 위 코드에도 굳이 필요없는 성능의 손실이 있습니다. 바로 list의 크기를 구하는 &lt;a href=&quot;http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html#size%28%29&quot;&gt;size()&lt;/a&gt; 메서드가 매번 반복해서 호출된다는 것입니다. 반복문 내에서 list의 크기가 변하는 경우가 아니라면 for문의 초기화 때 한번으로 충분합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;for(int i = 0, n = list.size(); i &amp;lt; n; i++)\{

//일하기

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;n을 for문 앞에서 선언하는 방법도 있지만, for block 밖에서 n이 필요한 경우가 아니라면 n이 for의 초기화 부분에 선언되고 할당되는 것이 좋습니다. 변수의 유효범위가 최소화되기 때문이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;loop안에서만_사용하는_변수의_선언을_loop밖에_해야_할까&quot;&gt;Loop안에서만 사용하는 변수의 선언을 loop밖에 해야 할까?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코드의 중복을 없애라거나 적절한 API를 쓴다거나 등의 바람직한 코드를 위한 지침에는 항상 다음과 같은 이유들이 붙어다닙니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;코드의 가독성이 좋아진다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;유지보수가 편해진다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;오류의 발생 가능성이 줄어든다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지역변수의 유효범위를 최소화하라는 지침에도 위의 이유들이 역시나  인용되고 있지요. (참고자료1 Effective Java, 참고자료 3 Code completed 2nd Edition). 변수의 선언과 초기화, 사용 사이에 있는 코드가 많을 수록  취약성 있는 코드가 들어갈 여지는 커집니다. 이른 시점부터 쓰이지도 않는 객체가 초기화되어서 메모리를 낭비하고 있을 가능성도 있고, 선언되고 한번도 쓰이지 않는 변수들도 눈에 잘 들어오지 않게 됩니다. (뭐 쓰이지 않는 변수는 Eclipse의 warning으로 잡아낼수 있기는 합니다만)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지역변수를 쓰기 바로 전에 초기화하고, 선언과 함께 초기화 하는 좋습니다. 즉 가능한 변수가 처음 사용되는 곳의 가장 근접한 위치에서 선언되고 초기화되어야 한다는 말이죠. 변수의 수명을 가능한 짧게 유지하라는 말로도 표현됩니다. 하지만 많은 개발자들은 메소드의 처음에서 C언어처럼 쭉 변수를 선언하고 시작하고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;반복문에서는 while보다는 for를 쓰는 것이 변수의 범위관리에 유리합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보통 iterator와 while이 아래와 같이 많이 쓰이고 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;Iterator i = c.iterator();

while (i.hasNext())\{

 doSomething(i.next());

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 것을 for문으로 쓴다면 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;for (Iterator i = c.iterator() ; i.hasNext(); ) {

 doSomething(i.next());

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;작은 차이지만 Iterator i는 for block을 벗어나는 순간  잊어버려도 되는 것이니 block 밖에서 개발자의 머리는 조금이나마 가벼워 질 수 있습니다. 이것은 캡슐화의 원칙인 class의 맴버 중 밖에서 볼 필요 없는 것들은 private으로 선언해야 하는 이유와 일맥상통합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그렇다면 다음의 경우는 어떠할 까요?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;1.루프밖에서 list에서 꺼내서 담을 변수 선언&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    static int countOfIncluded(List list, String str){
        int count = 0;
        String element;
        for (int i=0,n=list.size();i&amp;lt;n;i++)\{
           _*element = (String) list.get(i);*_
            if (element.indexOf(str)!= -1 ) count++;
        }
        return count;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2.루프안에서 list에서 꺼내서 담을 변수 선언&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;    static int countOfIncluded(List list, String str){
        int count = 0;

        for (int i=0,n=list.size();i&amp;lt;n;i++){
            String element = (String) list.get(i);
            if (element.indexOf(str)!= -1 ) count++;
        }
        return count;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;많은 분들이 1번과 같이 코드를 작성하고 있고, 1번이 성능이 좋다는 &apos;믿음&amp;#8217;을 가지고 계실 것입니다. 실제로 한 번 테스트 해볼까요? 크기가 10000개인 리스트를 생성해서 100번씩 반복해서 실행시간을 찍어보는 코드를 만들어보았습니다. 1번 방법은 &lt;a href=&quot;http://benelog.springnote.com/pages/386996/attachments/166292&quot;&gt;ListReader1.java&lt;/a&gt;, 2번 방법은 &lt;a href=&quot;http://benelog.springnote.com/pages/386996/attachments/166293&quot;&gt;ListReader2.java&lt;/a&gt;, 시간을 찍어보는 코드는 &lt;a href=&quot;http://benelog.springnote.com/pages/386996/attachments/166297&quot;&gt;LoopTester.java&lt;/a&gt; 로 첨부하였습니다. 이런 비교 시에는 실행순서에 따라서도 실행시간이 영향을 받으므로 1,2,2,2,1,1,2의 순서로 몇번씩 사이에 걸린 시간을 밀리세컨드로 출력하게했습니다. 실행결과는 다음과 같았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;test1:밖에 선언 2243
test2:안에 선언 2153
test2:안에 선언 2184
test1:밖에 선언 2253
test1:밖에 선언 2213
test2:안에 선언 2123&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;거의 차이가 없거나 오히려 안에 선언한 쪽이 미묘하게 빠르기도 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이번에는 &lt;code&gt;javap -c&lt;/code&gt; 를 이용해서 컴파일된 byte 코드를 분석해 보겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;&amp;gt; javap -c ListReader1

Compiled from &quot;ListReader1.java&quot;
public class looptest.ListReader1 extends java.lang.Object{
public looptest.ListReader1();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object.&quot;&amp;lt;init&amp;gt;&quot;:()V
   4:   return

static int countOfIncluded(java.util.List,java.lang.String);
  Code:
   0:   iconst_0
   1:   istore_2
   2:   iconst_0
   3:   istore  4
   5:   aload_0
   6:   invokeinterface #2,  1; //InterfaceMethod java/util/List.size:()I
   11:  istore  5
   13:  iload   4
   15:  iload   5
   17:  if_icmpge       50
   20:  aload_0
   21:  iload   4
   23:  invokeinterface #3,  2; //InterfaceMethod java/util/List.get:(I)Ljava/la
ng/Object;
   28:  checkcast       #4; //class String
   31:  astore_3
   32:  aload_3
   33:  aload_1
   34:  invokevirtual   #5; //Method java/lang/String.indexOf:(Ljava/lang/String
;)I
   37:  iconst_m1
   38:  if_icmpeq       44
   41:  iinc    2, 1
   44:  iinc    4, 1
   47:  goto    13
   50:  iload_2
   51:  ireturn

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;javap -c ListReader2
Compiled from &quot;ListReader2.java&quot;
public class looptest.ListReader2 extends java.lang.Object{
public looptest.ListReader2();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object.&quot;&amp;lt;init&amp;gt;&quot;:()V
   4:   return

static int countOfIncluded(java.util.List,java.lang.String);
  Code:
   0:   iconst_0
   1:   istore_2
   2:   iconst_0
   3:   istore_3
   4:   aload_0
   5:   invokeinterface #2,  1; //InterfaceMethod java/util/List.size:()I
   10:  istore  4
   12:  iload_3
   13:  iload   4
   15:  if_icmpge       49
   18:  aload_0
   19:  iload_3
   20:  invokeinterface #3,  2; //InterfaceMethod java/util/List.get:(I)Ljava/la
ng/Object;
   25:  checkcast       #4; //class String
   28:  astore  5
   30:  aload   5
   32:  aload_1
   33:  invokevirtual   #5; //Method java/lang/String.indexOf:(Ljava/lang/String
;)I
   36:  iconst_m1
   37:  if_icmpeq       43
   40:  iinc    2, 1
   43:  iinc    3, 1
   46:  goto    12
   49:  iload_2
   50:  ireturn

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/java/variable-scope-byte-code-diff.jpg&quot; alt=&quot;variable scope byte code diff&quot; title=&quot;Bytecode 비교&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;노란 줄이 많은 것은 라인수가 1라인 차이가 나고, local variable이 저장되는 공간의 index번호가 달라서입니다. 내용을 보시면 거의 똑같이 실행되고 있는 것을 알 수 있으실 것입니다.  루프가 도는 goto문장을 봐도 (왼쪽의 47라인과 오른쪽의 46라인) 같은 곳으로 (13라인과 12라인)으로 이동을 하기 때문에 특별히 오른쪽 예제2의 경우가 더 일을 하는 것은 없습니다. 다만 3번째 라인 istore n 이였던 것이 istore_n 으로 바뀌는 등 언더바(_)가 들어간 부분이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;각각의 명령어의 의미는 다음 링크를 확인해 보시면 나와있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc6.html#istore&quot;&gt;istore&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc6.html#istore_n&quot;&gt;istore_n&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc6.html#iload&quot;&gt;iload&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc6.html#iload_n&quot;&gt;iload_n&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecIX.fm9.html&quot;&gt;명령어의 인덱스페이지&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;내용을 보면 istore과 istore_n은  전자가 암시적이라는 것만 빼고는 같다고 나옵니다. (Each of the istore_&amp;lt;n&amp;gt; instructions is the same as istore with an index of &amp;lt;n&amp;gt;, except that the operand &amp;lt;n&amp;gt; is implicit. ) iload의 경우도 마찬가고요.  어떤 차이가 있을까 해서 검색해 봤더니 아래와 내용을 발견했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&apos;istore_&amp;lt;n&amp;gt;&apos; is functionally equivalent to &apos;istore &amp;lt;n&amp;gt;&apos;, although it is typically more efficient and also takes fewer bytes in the bytecode&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.google.com/search?q=cache:SwAZqu5poI0J:www.cat.nyu.edu/%7Emeyer/jvmref/ref&amp;#8212;&amp;#8203;37.html+istore_%3Cn%3E+more&amp;amp;hl=ko&amp;amp;ct=clnk&amp;amp;cd=1&amp;amp;gl=kr&quot;&gt;원본링크&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;istore_n 쪽이  오히려 효율적이라는 말이 나와 있습니다. 만약 iload_n도 마찬가지라면 goto가 찾아가는 라인에서는 iload_n이 있는 예제2가 더 효율적인 코드일 수도 있다는 것입니다. 어쨓든 이런 작은 차이를 접어둔다면 코드가 하는 일의 절차는 차이가 없습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;결국 &lt;strong&gt;&quot;루프 안에서 반복되는 변수 선언을 밖으로 빼는 것은 성능상에 아무런 이점이 없고 소스에서 변수의 유효범위만 늘어나게 한다. &quot;&lt;/strong&gt; 는 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;여기서 이런 말을 하실 분이 계실 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&quot;원래는 변수를 loop안에 생성하는 루프가 돌 때마다 String 객체의 참조를 저장하기 위한  공간이 따로 할당되는 것인데 위의 경우는 JVM에서 최적화를 해 준 것 아니냐?   JVM에 따라서 이런 최적화가 안 되는 버전도 있을 수가 있는데 개발자는 어떤 JVM에서도 한번만 선언이 되도록 루프 밖에 변수를 선언해야 하지 않겠냐? &quot;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그러나 &lt;a href=&quot;http://www.informit.com/bookstore/product.asp?isbn=0201432943&amp;amp;aid=9f15cdfa-4e22-40dc-bfc9-cdc6322be0fd&amp;amp;rl=1&quot;&gt;&lt;em&gt;Java™ Virtual Machine Specification, The, 2nd Edition&lt;/em&gt;&lt;/a&gt; 이라는 책을 보면 다음과 같은 내용이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The sizes of the local variable array and the operand stack are determined at compile time and are supplied along with the code for the method associated with the frame .&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 문장은 메소드가 호출될 때 생성되는 저장공간인 frame에 대한 설명에서 인용한 것입니다. (원문링크 : &lt;em&gt;&lt;a href=&quot;http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html#15722&quot; class=&quot;bare&quot;&gt;http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html#15722&lt;/a&gt;&lt;/em&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;풀이하면  메소드 안의 local variable들의 값들은 고정된 크기의 배열에 저장되고 그 배열의 크기는 compile시에 결정된다는  내용이 있습니다. 즉 한 메소드 안에서 사용할 local variable이 저장될 공간의 갯수는 이미 compile 시에 정해져 있는 것이지 동적으로 변하는 것이 아니라는 말입니다. 만약 위의 소스에서 list의 갯수에 따라서 local variable의 값이 저장되는 공간(객체가 저장되는 공간을 가리키는 말이 아닙니다.)이 달라진다면 위의 설명과 모순되는 일입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://forum.java.sun.com/thread.jspa?threadID=707455&amp;amp;messageID=4098210&quot; class=&quot;bare&quot;&gt;http://forum.java.sun.com/thread.jspa?threadID=707455&amp;amp;messageID=4098210&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;비슷한 설명과 논쟁이 인터넷의 여러 곳에서 이미 벌어졌으니 아래의 링크를 읽어보셔도 재밌을 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;local variables in Java : &lt;a href=&quot;http://rmathew.blogspot.com/2007/01/local-variables-in-java.html&quot; class=&quot;bare&quot;&gt;http://rmathew.blogspot.com/2007/01/local-variables-in-java.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Myth - Defining loop variables inside the loop is bad for performance : &lt;a href=&quot;http://livingtao.blogspot.com/2007/05/myth-defining-loop-variables-inside.html&quot; class=&quot;bare&quot;&gt;http://livingtao.blogspot.com/2007/05/myth-defining-loop-variables-inside.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://forum.java.sun.com/thread.jspa?threadID=707455&amp;amp;messageID=4098210&quot; class=&quot;bare&quot;&gt;http://forum.java.sun.com/thread.jspa?threadID=707455&amp;amp;messageID=4098210&lt;/a&gt;http://forum.java.sun.com/thread.jspa?threadID=707455&amp;amp;messageID=4098210&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://weblogs.java.net/blog/ddevore/archive/2006/08/declare_variabl_1.html&quot; class=&quot;bare&quot;&gt;http://weblogs.java.net/blog/ddevore/archive/2006/08/declare_variabl_1.html&lt;/a&gt;http://weblogs.java.net/blog/ddevore/archive/2006/08/declare_variabl_1.html&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.theserverside.com/news/thread.tss?thread_id=41857&quot; class=&quot;bare&quot;&gt;http://www.theserverside.com/news/thread.tss?thread_id=41857&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://weblogs.java.net/blog/ddevore/archive/2006/08/declare_variabl_1.html&quot; class=&quot;bare&quot;&gt;http://weblogs.java.net/blog/ddevore/archive/2006/08/declare_variabl_1.html&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;1,2번 링크가 바이트코드 역어셈블 결과와 함께 내용을 잘 풀어서 설명하고 있습니다. 3번 링크에서 Dru Devore는 primitive type일 때는 똑같고 Object type일때는 다르다는 결론을 쓰고 있으나, 사실 그가 든 object type의 예제는 &apos;객체를 한번 생성하느냐&apos; 대 &apos;반복해서 생성하느냐&apos;의 문제이므로 경우가 다릅니다. 3번 링크의 댓글에서 다른 사람들도 그 점을 지적하고 있고 2번 링크의 글에서도 관련 설명이 있습니다. 어쨓든 루프밖에 있으나 안에 있으나 바이트코드의 실행과정은 같고 성능은 차이가 없다는 내용은 모든 페이지에 다  있군요.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 오해들은 전에 &lt;a href=&quot;http://benelog.springnote.com/pages/36726&quot;&gt;&lt;em&gt;Java의 호출은 pass by value&lt;/em&gt;&lt;/a&gt; 에서 말했던 객체선언에 대한 오해와 어느 정도 관련이 있다고 봅니다. 객체를 생성이나 사용을 안 하더라도 선언 자체만으로 저장을 위한 큰 공간이 할당된다고 믿는 경우가 많은 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전에 이런 코드를 본적이 있습니다. 클래스의 멤버 변수로 임시 객체를 생성해 놓고 그것을 여러개의 메소드에서 같이 쓰는 코드였었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;Class MyClass {
    String  temp;

    void work(List list){
        for (int i=0; i = list.size(); i++)\{
            temp = (String) list.get(i);
            //기타...
        }
    }

    void play(List list)\{
        for (int i=0; i = list.size(); i++)\{
            temp = (String) list.get(i);
            //기타...
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이런 코드를 짰던 사람도 경력도 어느 정도 되고, 실력도 있어보이는 사람이였죠. 아마 이 개발자는 이런 구조가 temp 객체가 저장될 공간을 상당히 아꼈다는 뿌듯함을 가지고 있었을 지도 모릅니다. 그러나 객체는 Heap 메모리에 있는 것이기에 지역변수가 선언이 하나 덜 되었다고 해서 객체가 줄어든 것은 아닙니다. 그리고 여기서는 list에서 받아오지 새로 생성되는 객체는 없습니다. 더군다나 설계상 의미로도 temp는 Class의 멤버로써 아무런 의미가 없습니다. 성능측면에서 봐도  클래스 멤버 변수는 지역변수에 비해서 더 비용이 큽니다. (참고자료 2: 자바 퍼포먼스 튜닝)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;한편으로는 변수의 범위를 줄인다고 해서 반복적으로 Loop 안에서 실행해줄 필요가 없는 작업까지 loop안으로 끌고 들어가서는 안되겠죠. 다음과 같은 코드를 본적이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;for (int i=0, n=userList.size(); i&amp;lt;n;i++)\{

    UserBiz biz = (UserBiz) lookup(UserBiz.ROLE);#*_ //비지니스 컴포넌트 객체를 얻어온다.
    User user = (User)userList.get(i);
    biz.add(user;
 }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 코드에서 비지니스 컴포넌트를 얻어오는 부분은 반복적으로 수행될 필요가 없으므로 loop 밖으로 나가는 것이 맞습니다.  위의 biz 객체를 loop안에서만 쓴다고 하여도 위와 같이 해당 코드가 안으로 들어가 있다면 한번만 얻어오는 되는 객체를 얻어오는 작업이 계속 반복될 것 입니다. 사실 위의 경우는 비지니스 객체에서 List를 받아서 처리하는 메소드를 하나 더 만드는 것이 나을 것 같네요. 아키텍쳐에 따라 다르지만 비지니스 객체의 호출은 비싼 작업일 경우가 많고 그런 호출의 횟수는 최대한 줄일수록 좋겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;개선된_for문&quot;&gt;개선된 for문&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java 5의 개선된 for문을 아시는 분이라면 아래의 방법을 많이 쓰실 것입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ArrayList&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;Integer&amp;gt;();&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;for (Integer i : list) \{ &amp;#8230;&amp;#8203; }&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;( &lt;a href=&quot;http://java.sun.com/developer/technicalArticles/releases/j2se15/&quot;&gt;J2SE 5.0 in a Nutshell&lt;/a&gt; 에서)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보기에도 이쁘고, 변수의 유효범위가 loop을 벗어나지도 않는 좋은 문법이네요. ^^ 프로젝트에서 Java 5 이상을 쓰시고 계신다면 변수를 loop안에 넣느냐로 논쟁하기 전에 개선된 for문을 먼저 적용해 보는것이 나을 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 내용을 묶어서 결론을 내리자면, &quot;&lt;strong&gt;&lt;em&gt;loop안에서만 쓰는 변수 선언을 밖으로 빼는 것은 성능에 아무런 영향이 없으며, 굳이 성능을 개선하고 싶다면 RandomAccess 인터페이스의 고려, 반복되는 list크기 계산의 제거 등을 먼저 신경쓰라. 그리고 Java5을 쓰고 있다면 개선된 for문을 쓰라.&lt;/em&gt;&lt;/strong&gt;&quot;는 것으로  정리하고 싶습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;사실 성능에 대한 고려는 병목이 발견될 때 필요하다면 하는 것이 바람직한 순서겠죠. 개발자는 알아보기 싶고, 관리하기 쉬운 코드를 짜는 것에 먼저 집중을 해야 할 것입니다. 그리고 고용량의 CPU가 돌아가는 환경이라면 개발자가 성능을 고려해서 짠 코드와 그렇지 않은 코드의 차이도  대부분 사람이 인지하지 못하는 정도의 미미한 것일 가능성이 높습니다. (TA팀 이상민 선임님의 테스트와 조언). 그런 점에서 본다면 이 글에서 가장 강조되어야 할 내용은 &apos;변수의 유효범위의 최소화&amp;#8217;라고 생각됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다른 분들이 추가로 자료를 찾는 번거로움을 덜어 드릴려고 관련 자료를 되도록 인용하고 링크를 걸었습니다. 그렇게는 했어도 혹시나 제가 잘못 이해해서 오류가 있는 부분이 있는지도 모르겠습니다.  고수분들은 그런 부분을 발견하신다면 즉각 지적해 주시기 바랍니다~ ^^&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 이 글은 계속적으로 갱신해서 개선을 하고 있는 중입니다. 제가 가입해 있는 커뮤니티 사이트 중 두 곳에도 이 글을 올렸는데, 거기에서 조언을 주신 많은 분들께도 감사드립니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료&quot;&gt;참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Java API문서와 링크외의 책에서 참고한 내용입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Effective Java , Joshua Bloch저 이해일역&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;7장 프로그래밍 일반 - 항목29 - 지역변수의 유효범위를 최소화하라.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;자바퍼포먼스 튜닝, Jack Shirazi 저 서민구 역&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;11장 적절한 자료구조와 알고리즘&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;질의 최적화 - 불필요한 반복적 메소드 호출 제거&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RandomAccess 인터페이스&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6장 예외 단언, 캐스팅, 변수 - 변수&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Code completed 2nd Edtion, Streve McConnell 저 서우석 역&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;10장 변수사용시 일반적인 문제 - 10.3 변수의 초기화에 대한 지침&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Head First Design Pattern&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;9장 이터레이터와 컴포지트 패턴&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>구글 검색 기록과 스위칭 코스트</title>
      <link>https://blog.benelog.net//1375660.html</link>
      <pubDate>Tue, 31 Jul 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1375660.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;많은 분들이 그렇듯이 저도 생활정보는 네이버로 찾고  일과 관련된 정보나  웹을 구석구석 뒤지고 싶을 때는 google을 쓰고 있습니다.  google을 써오기는 해도  &quot;검색기록&quot;이라고는 것을 신경도 안 쓰고 있었는데, 얼마 전에 한번 찍어보니 꽤나 신기하더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.google.co.kr/history/&quot; class=&quot;bare&quot;&gt;http://www.google.co.kr/history/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 기능을 의식을 하고 쓴 것은 아니였는데 , 제가 어떤 키워드로 검색을 했는지 오래전의 기록부터 쫙 나오니깐 기분이 묘했습니다. 상위질문, 상위사이트, 상위클릭, 월별 일별 통계까지 나오니 좀 섬뜻하기도 했어요  삭제하고 싶은 키워드도 있는데  어떻게 삭제하는지 모르겠습니다 ^^;다음부터는 기록에 남기고 싶지 않는 검색어는 &quot;일시중지&quot;를 선택하고 검색해야겠군요. 얼마 전에 &quot;&lt;a href=&quot;http://blog.daum.net/suk5kyu/11605503&quot;&gt;구글, 사용자 검색기록 보관기간 18개월로 단축 &lt;/a&gt;&quot; 이라는 기사를 보았었는데, 정말 이것이 축적되면  무서운 도구가 될 수도 있을 것 같아요. 찜찜하신 분들은 구글에서 로그인하지 않은 상태로 검색하거나  기록 기능을 꺼야겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/google/google-search-history1.jpeg&quot; alt=&quot;google search history1&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/google/google-search-history2.jpeg&quot; alt=&quot;google search history2&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;검색기록의 첫 페이지에 나와 있는 다음의 문구가 눈에 띕니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;맞춤 검색은 Google 검색을 개선한 기능으로서 이전의 검색을 토대로 귀하의 검색 결과를 표시합니다. 처음에는 큰 차이를 느낄 수 없지만 검색 기록이 축적되면서 맞춤 검색 결과가 지속적으로 향상됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이번달 16일, &lt;a href=&quot;http://www.joonj.com/&quot;&gt;jmirror&lt;/a&gt;님이 주최하신 &apos;&lt;a href=&quot;http://www.joonj.com/wordpress/archives/319&quot;&gt;차세대 검색에 대한 블러거 토론회&lt;/a&gt;&apos;에 참석했습니다. 저는 특별한 발언없이 경청만 하다 왔지만, 다른 분들의 깊이 있는 말씀을 듣고 생각을 정리해볼 수 있는 자리였습니다. 거기서 몇몇 분들이 &apos;검색의 개인화&amp;#8217;가 앞으로 필요하다고 말씀하셨습니다. 그 때 저는 그 실제 적용 사례가 생각나지 않았었는데, 바로 이 구글의 검색기록을 한 번 찍어보고 갔었더라면 위의 화면이 머리 속에서 연결이 되었을건데 하는 아쉬움이 지금에서야 듭니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위의 토론회에서 들은 말인데, 구글이 유투브 동영상 검색에서 야후를 야후의 동영상 검색을 누르는데 걸린 시간은 6주였고, 국내  3위 업체였던 네이버는 지식검색으로  야후,  엠파스를 4주만에 추월했다고 합니다. 그렇게 짧은 시간내에 그것이 가능했던 이유는 검색엔진을 바꾸는 것에 대한 이사비용(스위칭코스트)이 거의 없기 때문이 아닐까 합니다. 메일, 블로그,커뮤니티의 경우에는 서비스를 바꾼다면 데이터 이전, 바뀐 주소 홍보 등의 번거로움을 감수해야하는 것에 비해서 검색은 그렇지 않다는 것이죠. 그리고 검색결과의 상호비교가 쉽기 때문에  조금이라도 나은 점이 보이는 서비스로 지체없이 옮겨갈 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;검색에서도 구글 검색 기록과 같이 쌓여가는 데이터가 있고, 그것을 바탕으로 개인화된 서비스를 제공할 수 있다면  스위칭코스트를 낳을 수 있다고 봅니다. 그리고 개인 검색기록  데이터를 쌓고  그 것을 바탕으로 개인화된 검색결과를 내놓을 수 있는 기술은 쉽게 따라할수 있는 일이 아닐 것 같아요. 네이버에 있는 지식인의 자료, 광고, 알바가 정리한 정보도 큰 자산이지만  그것만으로 사용자를 완벽히 묶어둘 수는 없지 않을까요?&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>AOP 관련 용어 정리</title>
      <link>https://blog.benelog.net//1348296.html</link>
      <pubDate>Thu, 19 Jul 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1348296.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;부서원들과 같이 하는 세미나를 위해서 정리했었던 자료 중의 일부입니다. 대충 알고 있었던 것도 막상 누가 물어보면 한마디로 설명하는 것은 어렵다고 종종 느낍니다.  세미나 발표 준비를 하면서 모르면서 알고 있었다고 착각했던 부분을 많이 발견할 수 있어서 좋았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;약간 엉뚱한 이야기지만, 포스트를 작성하는 동안 &apos;그림1&amp;#8217;을 다시 보면서 최근 읽고 있는 &lt;a href=&quot;http://www.amazon.com/World-Flat-Updated-Expanded-Twenty-first/dp/0374292795/ref=pd_bbs_sr_1/105-2068841-6541209?ie=UTF8&amp;amp;s=books&amp;amp;qid=1184811573&amp;amp;sr=8-1&quot;&gt;&apos;The world is flat&lt;/a&gt;&apos;이라는 책의 내용이 생각났습니다. 그 책의 처음에는 기업의 아웃소싱에 대한 사례가 많이 나옵니다.  기업의 핵심 업무가 아니고 밖에서 할 수있는 업무들은 보다 싼 비용으로 처리할 수 있는 해외의 조직으로 넘기는 사례가 늘어나고 있다는 것이죠. 각 업무단의 코드에는 핵심로직만 남기고 공통적인 처리는 따로 정의해 둔 것을 weaving해서 쓴다는 내용과 왠지 비슷하게 보이더군요.  (그런데 책을 아직 1장 밖에 못 읽었어요)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;aop_용어&quot;&gt;AOP 용어&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Aspect-oriented_programming&quot;&gt;AOP&lt;/a&gt; :보안, 예외등과 같이 프로그램에 처리해야 하는 다양한 관점을 분리(&lt;a href=&quot;http://en.wikipedia.org/wiki/Separation_of_concerns&quot;&gt;Separation of concerns&lt;/a&gt;)시키고 각각의 관점별로 구현을 한 후 이를 조합하여 완성된 프로그램을 만들고자 하는 개념. (참고자료1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;횡단관점(&lt;a href=&quot;http://en.wikipedia.org/wiki/Cross-cutting_concern&quot;&gt;crosscutting concerns&lt;/a&gt;) : 분리된 모듈로 작성하기 힘든 시스템 전반적으로 산재된 기능(예로 보안, 로그, 인증)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;핵심관점(main concern) : 해당시스템의 핵심가치와 목적이 드러난 관심영역 (primary concern, &lt;a href=&quot;http://en.wikipedia.org/wiki/Core_concern&quot;&gt;core concern&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;조인포인트(&lt;a href=&quot;http://en.wikipedia.org/wiki/Join_point&quot;&gt;join point&lt;/a&gt;) : 횡단 관심모듈의 기능이 삽입되어 호출될 수 있는 실행가능한 특정위치&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;포인트컷(&lt;a href=&quot;http://en.wikipedia.org/wiki/Pointcut&quot;&gt;point cut&lt;/a&gt;) : 조인포인트의 집합. 어떤 클래스의 어느 조인포인트를 사용할 것인지 결정하는 선택 기능. Code와 advice를 연결해주는 설정정보.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;어드바이스(&lt;a href=&quot;http://en.wikipedia.org/wiki/Advice_in_aspect-oriented_programming&quot;&gt;advice&lt;/a&gt;) : 각  조인포인트에서 행하는 행위, 삽입될 수 있는 코드. cross-cutting concern을 구현한 코드.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;조합(weaving) : 핵심관점코드와 어드바이스를 조합해 애플리케이션으로 완성하는 과정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;기술적 용어로서의 asepct : Advice와 point-cut을 함께 지칭하는 단어&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;참고자료&quot;&gt;참고자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;마이크로소프트웨어 2005/11 테크니컬 컬럼 - 객체지향을 넘어 관점지향으로 AOP (이일민)   (&lt;a href=&quot;http://www.zdnet.co.kr/builder/dev/java/0,39031622,39147106,00.htm&quot; class=&quot;bare&quot;&gt;http://www.zdnet.co.kr/builder/dev/java/0,39031622,39147106,00.htm&lt;/a&gt; )&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;마이크로소프트웨어 2006/09 S/W로 여는 세상 - 관점지향의 개념잡기 -상황중심의 프로그래밍 (임백준) (&lt;a href=&quot;http://www.zdnet.co.kr/builder/dev/etc/0,39031619,39154322-1,00.htm&quot; class=&quot;bare&quot;&gt;http://www.zdnet.co.kr/builder/dev/etc/0,39031619,39154322-1,00.htm&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3번 자료에 대한 반론 - &lt;a href=&quot;http://toby.epril.com/?p=229&quot; class=&quot;bare&quot;&gt;http://toby.epril.com/?p=229&lt;/a&gt; (이일민님의 블로그)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;마이크로소프트웨어 2007/03 Cover Story- OOP한계 극복패러다임 AOP (이일민,김백기,박재성, 류대원)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;한빛네트워크에 올라온 AOP소개 기사 : &lt;a href=&quot;http://network.hanb.co.kr/view.php?bi_id=968&quot; class=&quot;bare&quot;&gt;http://network.hanb.co.kr/view.php?bi_id=968&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;위키페디아&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>로그인별 process 숫자를 집계하는 sybase 쿼리</title>
      <link>https://blog.benelog.net//1328079.html</link>
      <pubDate>Tue, 10 Jul 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1328079.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;sa 계정으로 로그인해서,&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;select
    p.hostname,
    p.ipaddr,
    p.cnt,
    (select l.name from syslogins l where l.suid = p.suid) name
from
    (select s.hostname, s.ipaddr, s.suid,
        count(*) cnt from sysprocesses s
group by s.hostname, s.ipaddr ,s.suid ) p&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;현재 어느 로그인 아이디에서 많이 프로세스가 붙어있는지 볼 때 쓰면 됩니다.
내장 함수 중에 비슷한게 있는지 모르겠네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;간단한 내용이지만 필요하신 분들이 검색엔진을 타고 오셔서 찾아가시길 바라는 의도에서 올립니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>HTML &amp;amp; CSS 정리해본 예</title>
      <link>https://blog.benelog.net//1314658.html</link>
      <pubDate>Wed, 4 Jul 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1314658.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;제가 있는 프로젝트의 UI 표준은  2004년 10월경에 처음 정해졌습니다.
연속 사업 중 첫 번째 사업 때 정해진 틀을 수정없이 Copy&amp;amp;paste로 입혀서 쓰고 있는 중이였습니다.
디자이너가 HTML로 파일을 만들어 주었을 당시에는 팀원 중에 웹표준에 대해서 신경을 썼던 사람이 없었습니다.
지금 다시 들여다보니 CSS를 잘 정의했으면 훨씬 깔끔하게 중복없이 만들수 있었던 코드가 많이 보입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저도 작년에 파주시 프로젝트에서 웹표준에 맞춰서 개발을 하고 있다는 말을 듣기 전까지는 스스로 css를 정리해볼 생각은 없었습니다.
그런 작업은 디자이너의 영역이라고 안이한 생각을 했었습니다.
새로 프로젝트 들어가면 깔끔하게 UI표준 만들어주는 사람을 만나기를 바라는 마음만 있을 뿐이였습니다.
그러다가 정동인씨나 최효근씨 같은 후배들에게 배우고 느낀 바가 많아서 저도 현재의 프로젝트에서도 서서히 적용하겠다는 의지를 가지게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이번에 고객이 다른 업무와 독립적으로 쓰여질 프로그램을 요청했는데, 그 페이지에는 새로운 CSS를 만들어서 적용하고 싶었습니다.
현재 프로젝트에 남은 3명의 인원만으로는 모든 페이지를 다 갈아 엎는 것은 무리라고 생각했었는데 적당한 시작점을 찾은 것이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;처음 수정한 부분은 화면의 큰 제목과 하위 제목이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/markup/header-sample.gif&quot; alt=&quot;image&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;와 같이 보여지는데, 기존 코드로는&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&amp;lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&amp;gt;
 &amp;lt;!-- 제목 --&amp;gt;
 &amp;lt;tr&amp;gt;
  &amp;lt;td&amp;gt;
   &amp;lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td class=&quot;title1&quot;&amp;gt;&amp;lt;img src=&quot;/image/title_header.gif&quot; align=&quot;absmiddle&quot;&amp;gt;&amp;amp;nbsp;감사사항 목록&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td class=&quot;tilte_space&quot;&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td class=&quot;title_line&quot;&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td class=&quot;tilte_space&quot;&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
   &amp;lt;/table&amp;gt;
  &amp;lt;/td&amp;gt;
 &amp;lt;/tr&amp;gt;
 &amp;lt;!-- 조회조건 --&amp;gt;
 &amp;lt;tr&amp;gt;
  &amp;lt;td&amp;gt;
   &amp;lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td align=&quot;left&quot; class=&quot;title_small&quot;&amp;gt;
            &amp;lt;img src=&quot;/image/title_small.gif&quot; align=&quot;absmiddle&quot;&amp;gt;&amp;amp;nbsp;조회조건
        &amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
   &amp;lt;/table&amp;gt;
  &amp;lt;/td&amp;gt;
 &amp;lt;/tr&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;로 나와있는 부분입니다. CSS를 새로 만들고 나니 마크업은 아래와 같이 딱 두줄로 바꿀수가 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;  &amp;lt;h1&amp;gt;감사사항 목록&amp;lt;/h1&amp;gt;
  &amp;lt;h2&amp;gt;조회조건&amp;lt;/h2&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;CSS에 다음과 같이 배경이미지, 선 등을 넣어서 정리했습니다.
매 페이지마다 반복되는 글자 앞의 이미지, 선과 여백을 위한 &lt;code&gt;table&lt;/code&gt;, &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;td&lt;/code&gt; 문장들이 모두 정리됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;h1 {
  font-size:14px; color:#5C5C5C;
  font-family: 돋움, arial;
  font-weight:bold;
  height:14px;
  padding-left: 20px;
  padding-bottom: 10px;
  margin: 10px 10px 2px 10px;
  background-image: url(/image/title_header.gif);
  background-position: 0% 10% ;
  background-repeat: no-repeat;
  border-color:#C0C3C6;
  border-width:1px;
  border-bottom-style: solid;
}

h2 {
  font-family: 돋움, arial;
  font-weight:bold;
  font-size:12px;
  height:13px;
  color: #4f4f30;
  padding-left: 20px;
  margin-bottom: 4px
  padding-bottom: 10px;
  margin: 10px;
  background-image: url(/image/title_small.gif);
  background-position: 0% 0% ;
  background-repeat: no-repeat;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 새로 개발되는 화면에 좌측 메뉴부분이 있는데,  css에서   float:left 선언을 이용하니 table없이 레이아웃을 잡을 수가 있었습니다. 웹표준을 설명하는 자료에서 많이 드는 예제이죠. 반복되는 class 지정에도 선택자를 잘 활용하면 클래스 지정없는 태크만으로 처리할 수 있는 부분이 많아보였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이제 기초적인 것을 적용시킨 것 뿐이고 제가 공부할 것이 아직 많아서 갈 길이 멀다는 기분이에요. 그래도 간결한 jsp를 보니 몇달만에 방청소 했을 때보다 더 속이 시원합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그동안 UI단 코드의 중복에는 지나치게 관대하게 살아왔었다고 반성했습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;웹표준_관련_권장_자료&quot;&gt;웹표준 관련 권장 자료&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(부서 후배 최효근씨가 추천해준 자료입니다)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;한국소프트웨어진흥원의 실전웹표준가이드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Head First HTML(XHTML, CSS) -한빛미디어-&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;실용예제로 배우는 웹표준 -에이콘-                // 본격적인 활용기&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;웹2.0을 이끄는 방탄웹 -에이콘-                    // 아이디어가 넘치는 책&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CSS 마스터 전략 -에이콘-&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CSS designing without tables -sitepoint-(원서)    // 무릎을 탁 치게 되는 책&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CSS the css anthology -sitepoint-(원서)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>코드와 예술</title>
      <link>https://blog.benelog.net//1230312.html</link>
      <pubDate>Fri, 1 Jun 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1230312.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.creativecommons.or.kr/&quot;&gt;CCK(Creative Commons Korea)&lt;/a&gt;의 &lt;a href=&quot;http://ccsalon.tistory.com/&quot;&gt;&apos;Code can be an art.&apos; 행사 소식&lt;/a&gt;을 처음 들었을 때, 생각났던 자료들을 정리해서 올려봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음은 임백준님의 책 &apos;누워서 읽는 알고리즘&apos; 중 &apos;문학적 프로그래밍&amp;#8217;이라는 장에서 소개 된 내용입니다.  Art of computer proramming으로 유명한 카누스 교수가 1974년 11월 미국 샌디에이고에서 열린 ACM(Association for computer Machinery) 연례회의에서 &apos;예술로써의 컴퓨터 프로그래밍(Computer programming as an Art)라는 제목의 강연을 했다고 합니다. 그 강연은 다음과 같은 말로 끝이 맺어졌습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;우리는 컴퓨터 프로그래밍을 하나의 예술로 생각한다. 그것은 그 안에 세상에 대한 지식이 축적되어 있기 때문이고 기술과 독창성을 요구하기 때문이고 그리고 아름다움의 대상을 창조하기 때문이다. 어렴풋하게나마 자신을 예술가라고 의식하는 프로그래머는 스스로 하는 일을 진정으로 즐길 것이며, 또한 남보다 더 훌륭한 작품을 내놓을 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;We have seen that computer programming is an art, because it applies accumulated knowledge to the world, because it requires skill and ingenuity, and especially because it produces objects of beauty. A programmer who subconsciously views himself as an artist will enjoy what he does and will do it better. Therefore we can be glad that people who lecture at computer conferences speak about the state of the Art.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;위 강연의 PDF파일을 찾았는데 7페이지 밖에 안 되는군요. 이정도면 출력해서 퇴근길에 읽어볼만 한것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://faq.ktug.or.kr/faq/LiterateProgramming?action=download&amp;amp;value=knuth-turingaward.pdf&quot; class=&quot;bare&quot;&gt;http://faq.ktug.or.kr/faq/LiterateProgramming?action=download&amp;amp;value=knuth-turingaward.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 1992년에 출판된 책 &apos;문학적 프로그래밍(Literate Programming, Center for the Study of Language and Information,1992)은 카누스 교수의 논문과 &apos;생각하는 프로그래밍&amp;#8217;의 저자 존베틀리의 칼럼이 묶여진 책입니다. 이 책의 서문은 다음과 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;컴퓨터 프로그램을 작성하는 일은 재미있다. 그리고 잘 작성된 프로그램을 읽는 것도 재미있다. 세상에서 가장 즐거운 일 중 하나는 여러분이 작성한 컴퓨터 프로그램을 다른 사람들 혹은 여러분 자신이 읽고 기쁨을 얻는 것이다. 컴퓨터 프로그램은 또한 유용한 작업을 수행할 수도 있다. 세상에서 가장 큰 성취감을 맛보는 순간은 여러분이 창조한 무엇이 사회의 부와 진보에 기여한다는 사실을 깨닫는 순간이다. 어떤 사람들은 컴퓨터 프로그램을 작성함으로써 돈을 벌기도 한다. 따라서 프로그래밍은 세 가지 측면에서 결실을 맺는 행위다. 미학적으로, 인류학적으로 그리고 경제적인 면이 바로 그러한 결실에 해당한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;카누스 교수의 관련 논문도 PDF파일로 구할 수 있었습니다. &lt;a href=&quot;http://faq.ktug.or.kr/faq/LiterateProgramming?action=download&amp;amp;value=knuthweb.pdf&quot; class=&quot;bare&quot;&gt;http://faq.ktug.or.kr/faq/LiterateProgramming?action=download&amp;amp;value=knuthweb.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;얼마 전에 &apos;문장강화&apos;(이태준 저)라는 글쓰기에 관한 책을 읽었습니다. 1940년에 처음 나온 책임에도 불구하고 아직까지  이 책을 넘어서는 글쓰기 공부책이 없다고 말하는 사람이 많더군요.  책에 들어간 예문이 다  옛날 글이라는 것이 아쉽지만, 그럼에도 이 책이 그런 찬사를 받는 것에는 그럴만한 이유가 있다고 느껴졌습니다.  제가 제일 감탄을 했던 내용은 글을 고쳐서 다듬는 &apos;퇴고&amp;#8217;에 대한 예시였습니다. 저자는 얼핏 보기에 별 흠이 없어 보이는 문장을 점차적으로 고치는 과정을 보여줍니다. 그 부분을 읽을 때 저는 마틴파울러가 쓴 책 &apos;리팩토링&apos; 제1장을 읽을 때와 굉장히 흡사한 느낌을 받았습니다. 퇴고가 코드를 리팩토링하는 것과 비슷하게 느껴진 것이죠. 유심히 안 보면  이상 없어 보이는 코드나 문장들이  여러 관점에서 따져가니 고칠 점이 투성이인  것들이었습니다. 그리고 고쳐진 최종결과물들은 처음에 비해서 훨씬 명확하고 이쁜 코드와 문장들이였고요. 그때 다시 한번 코드와 문학, 넓게는 예술과 유사점이 있다는 것을 떠올렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;많은 분들이 아시겠지만 &apos;해커와 화가&amp;#8217;라는 책에서는 미술과 프로그래밍의 유사점을 이야기하고 있습니다. 이 책의 저자인 폴그레이엄은 특이하게도 미술과 전산을 같이 전공한 사람입니다. 그 예 중의 하나가,세계적인 명화들을 엑스레이 투시해보면 그 아래에는 수많은 스케치 후에 많은 시행착오를 거친 선의 흔적들이 남아있다고 합니다. 코딩도 마찬가지의 과정을 거치는 것이고, 그것을 죄악시 할 필요가 없다고 저자는 이야기합니다. 저도 자바 코딩을 할 때 처음에는 전체의 클래스 선언과 비어있는 메소드부터 만들어서 틀을 잡은 다음에 나중에 메서드 안의 내용을 구체적으로 채워넣을 넣는 순서로 진행을 합니다. 그러고보니 그것이 스케치하는 것과 비슷하게도 느껴지더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;재미있게도 이외수님의 &apos;글쓰기의 공중부양&amp;#8217;이라는 책에도 비슷한 내용이 나옵니다. 여기서 저자는 글을 쓸 때 처음부터 한꺼번에 모든 부분을 구체적으로 쓸려고 하지말고 글 전체의 틀을 구어체로 스케치한 후에 나중에 세부묘사를 채워넣으라고 합니다. 인물화를 그릴 때 처음부터 이목구비의 정밀묘사를 하면서 그려나간다면 나중에 이목구비가 따로 노는 그림이 된다는 비유가 나오더군요.  이렇게 그림,글, 코딩이 다 통하는게 있나봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전 한때는 코딩 혹은 프로그래밍은 예술이라는 말이 지나치게 순진하다고 느껴졌었기에 밖으로 꺼내기에는 민망하다고 느낀 적도 많았습니다. 개발자 커뮤니티 사이트에 이런 글이 올라간다면 &apos;코딩은 노가다인 걸 아직 모르냐?&apos;,&apos;고생을 덜해봤냐?&apos; 같은 욕을 바가지로 퍼먹여줄 글이 될 수도 있겠죠 ^^; 하지만 프로그래밍을 하면 할수록 그 것이 진실이라는 생각만 강해지고만 있어요. 위에서 유명한 아저씨들도 다 그렇게 이야기하고 있는 것 보니 위안이 되네요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;인용자료_출처&quot;&gt;인용자료 출처&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&apos;KTUG(Korean TeX User Group)의 문학적 프로그래밍에 관한 전반적인 소개&apos; &lt;a href=&quot;http://faq.ktug.or.kr/faq/LiterateProgramming&quot; class=&quot;bare&quot;&gt;http://faq.ktug.or.kr/faq/LiterateProgramming&lt;/a&gt;
ACM 강연의 원문, PDF 파일&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;누워서 읽는 알고리즘, 임백준
( ACM 강연의 번역내용, &apos;문학적 프로그래밍&apos; 서문 번역내용&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Code can be an art 행사 참석 후기</title>
      <link>https://blog.benelog.net//1231656.html</link>
      <pubDate>Fri, 1 Jun 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1231656.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;지난 2007/05/19(토)에  &lt;a href=&quot;http://www.creativecommons.or.kr/&quot;&gt;CCK(Creative Commons Korea)&lt;/a&gt;에서 주최한  &lt;a href=&quot;http://ccsalon.tistory.com/&quot;&gt;ccSalon&lt;/a&gt; 행사가 있었습니다 . 이날의 행사 제목은 &apos;Code can be an art&amp;#8217;였었죠&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;관련 글과 같이 올릴려고 하다 보니 거의 2주가 지나서야 후기를 쓰게 되네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;관련 글&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/1230429&quot;&gt;QB로 만든 Code can be an art 출력&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/1230312&quot;&gt;코드와 예술&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저는 이 행사를 &lt;a href=&quot;http://www.goodhyun.com/&quot;&gt;김국헌님의 블로그&lt;/a&gt;에서 알게되어 혼자 행사장을 찾아갔었습니다. 제가 워낙 길치라서 노트북에다 약도를 저장해서 띄워놓고 화면을 계속 보고 갔는데도  힘들게 행사장을 찾아갔습니다. -_- 길에서 걷는 중에 노트북을 펴놓고 화면과 주위를 번갈아 가는 모습을 이상하게 본 사람도 있었을 것 같습니다.^^;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;주요 행사 내용 중에 다음과 같은 내용들이 인상깊었었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;dj성우님의_발표&quot;&gt;DJ성우님의 발표&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개발자와 DJ의 공통점으로 &apos;밤늦게 컴퓨터 앞에서 작업하는 일이 많다&apos;,&apos;이미 있는 것으로부터 새로운 창조물을 만든다&apos; 등을 드신 것이 기억납니다. 그리고 개발자들도 DJ들처럼 공동작업 그룹 같은것을 자발적으로 결성해서 활동하면 어떨까 하는 제안도 신선하게 들렸습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;미디어 아티스트  &lt;a href=&quot;http://beingmedia.tistory.com/entry/%EC%9E%85%EC%9E%90%EC%9D%B8%EC%83%9DNet&quot;&gt;최승준&lt;/a&gt;님의 발표&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;최승준 님의 &apos;입자인생&apos;(&lt;a href=&quot;http://beingmedia.tistory.com/entry/%EC%9E%85%EC%9E%90%EC%9D%B8%EC%83%9DNet&quot;&gt;http://beingmedia.tistory.com/entry/입자인생Net&lt;/a&gt; )이라는 작품은 예술적이면서 철학적인 설명을 해주셔서 심오하게 까지 느껴졌습니다. 그런 것 프로그래밍 하려면 물리학이나 수학도 많이 알아야 될 것 같다고 생각하니 저런 작품들은 여러 분야들의 종합성과물이라는 생각이 들었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;황리건님의_발표&quot;&gt;&lt;a href=&quot;http://reagan.egloos.com/226206&quot;&gt;황리건&lt;/a&gt;님의 발표&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;황리건님의 작품 중에는 맨 마지막 것이 가장 기억에 남습니다. 화면에 보이는 선의 한 부분을 클릭하면 거기서 원이 추가 되고 선분은 나누어져서 새로운 움직임을 보여주는 프로그램이였습니다. 이 작품을 꼭 보여주시고 싶어 하시면서, 이 것을 한달동안 힘들게 만드셨다고 말씀하시는데, 창작자로써의 뿌듯함이 느껴지시는 것 같았습니다. 요즘은 고객이 만들라고만 한 프로그램만 만들고 있는 저로서는 부러웠어요 ^^;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;코드잼&quot;&gt;코드잼&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.benelog.net/1230429&quot;&gt;QB로 만든 Code can be an art 출력&lt;/a&gt; 글에서 썼듯이 코드만 짜가고 발표는 못했습니다. 그래도 기획하신 분의 의도, 발표하신 분들의 기분은 알 수 있을 것 같았습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;패널토론_8bit_키드의_생애&quot;&gt;패널토론  8Bit 키드의 생애&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;80년대 초반 컴퓨터를 처음 시작한 세대들의 추억담이였습니다. 저보다는 앞세대의 분들이였고 사용한 기계와 환경은 달랐지만, 그런 이야기를 하시면서 어떤 기분이 드실지는 왠지 알 것 같았습니다. 아마 제가 &apos;허큘리스&amp;#8217;라는 말만 들어도  느껴지는 기분과 비슷하지 않을까요? ^^;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;패널토론_예술로서의_코드&quot;&gt;패널토론  예술로서의 코드&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;타이틀이 맞는지 모르겠군요. 김창준님이 귀여니의 시구를 인용하시던 부분에서 웃지 않을수 없었습니다. 앞의 다른 순서들이 &apos;코드로 만든 결과물인 예술&apos;, &apos;만든다는 것의 기쁨&amp;#8217;에 초점이 맞춰진 것이라면 이 순서는 &apos;코드 자체로써의 예술&amp;#8217;에 중심을 둔 것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;모든 순서가 끝난 뒤에 뒤풀이가 있었습니다. 저는 아는 사람도 없고 해서 분위기가 어색하지 않을까 걱정을 했었는데, 저녁 식사 때부터 같은 테이블에 앉았던 자원봉사자 분들이 재밌는 자리를 만들어주셔서 편하게 술자리를 할 수 있었습니다 ^^; 그리고 개인적으로 평소 애독하는 블로그의 주인이신 류한석님, 김국헌님을 가까이서 뵐 수 있어서 영광이였습니다~ ^^ 같은 테이블에 앉았던  대학생 분은 그 두분께  블로그 내용이 인쇄된 종이에다가 사인을 해달라고  부탁하더군요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;보통 저는 술자리에 참석하면 끝까지 남거나 쓰러지거나 둘 중에 하나였었는데, 이날은 아내가 근처까지 데리러 오는 바람에 먼저 일어날수 밖에 없어서 무척 아쉬웠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다음 모임때부터는 저도 자원봉사자로 참여하겠다고 했는데 빨리 그 때가 왔으면 좋겠네요~&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>QB로 만든 Code can be an art 출력</title>
      <link>https://blog.benelog.net//1230429.html</link>
      <pubDate>Fri, 1 Jun 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1230429.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.creativecommons.or.kr/&quot;&gt;CCK(Creative Commons Korea)&lt;/a&gt;의 Code can be an art 행사에서는 &apos;코드잼&amp;#8217;이라는 순서가 있었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&apos;Code can be an art&amp;#8217;라는 문자열을 출력하는 프로그램을 자유롭게 짜서 발표하는 것이었죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;코드잼 설명 :  &lt;a href=&quot;http://ccsalon.tistory.com/4&quot; class=&quot;bare&quot;&gt;http://ccsalon.tistory.com/4&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저도 이날 점심을 먹는 것도 잊고 나름대로 코드를 준비해 갔는데, 발표를 하지는 못했네요 .  발표자가 적은 분위기면 앞에 나가볼려고 노트북까지 들고 가기는 했었습니다 ^^; 그런데 열띤 호응으로  많은 분들이 발표를 하셨고, MSX 에뮬레이터로 하신 분 등 제가 말하고 싶었던 내용을 다른 분들이 더 훌륭히 말씀해 주셔서 큰 아쉬움은 없습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;8bit 화면 같은 행사 로고를 보고 저도  제 세대에 맞게 Quick Basic으로 코드를 짜볼 생각을 해봤습니다. 이 행사 중에 &apos;8bit kid의 생애&amp;#8217;라는 패널토론 주제도 있었었죠. ^^; 저는 그 다음 시대인 IBM XT세대입니다. 허큘리스 그래픽카드, 5.25인치 FDD, 조금 지나서 ADLIB card, 2400bps MNP 모뎀 등이 저에게는 가슴을 파고드는 핵심단어들이에요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그 추억의 시절에 제가 가장 많아 가지고 놀았던 프로그램 언어가 Quick-Basic이였습니다. Basic에서 행번호를 없애고 구조화프로그래밍 개념을 도입한, 당시 저에게는 충격적으로 대단한 언어였어요.   이 도구에다  basic을 배웠던 사람이면 누구나 회상할 추억의 명령어 line(선긋기), play(음악연주), read,data(데이터 읽는 구문)을 넣어서 코딩을 해봐야겠다고 마음 먹었습니다. 대신 제 PC환경상 그때의 Quick-basic 대신 Window XP에 내장되어 있는 q-basic을 사용할 수 밖에 없었어요. 프롬프트에서 qbasic이라고 치면 언제나 튀어나오는 도구죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Quick-Basic과 q-basic이 뭐가 다르냐구요? 문법은 거의 똑같은것 같아요. Quick-basic은 따로 패키지로 나와서 버전4.5까지 발표되었었는데, q-basic은 어느 새 MS-DOS의 상위버전에 포함되어서 나오더군요 . Quick-Basic에서는 실행파일(.exe)도 만들 수 있고, 각종 라이브러리도 많았었습니다. 아뭏든 대타로 q-basic을 썼기에 실행할때는  아래와 같이 치면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;프롬프트&amp;gt; qbasic/run cba2.bas&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;단순히 화면에 큰 글자를 찍어주는 것 밖에 없지만, 꽤나 시행착오 작업을 많이 거쳤었습니다. 예전 Quick-Basic에서는 screen 12라는 화면모드를 주로 썼었었는데, 이번 환경에서는 screen 2 밖에 안 먹는 것이였습니다. 화면 전체 해상도가 몇곱하기 몇 인지도 모르고 그냥 무작정 네모를 그리는 숫자값을 바꿔가면서 화면을 만들어 봤습니다. 결국 한참 후에야 그럭저럭 화면중간에 크게 들어간 글자를 얻어낼 수가 있었죠. 평소에 일할 때 이렇게 무식하게 작업을 한다면 스스로 한심하게 느껴졌겠지만 이번에는 그것조차도 옛 추억을 되살리는 즐거움이였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고 이 프로그램에 들어간 멜로디를 아시는 분이라면 아마 저하고 비슷한 컴퓨터 세대이실 것 같아요. 불후의 고전명작게임 &apos;Loom&amp;#8217;의 메인테마 입니다. 처음에 노트북을 들고 행사장에 갈때까지만 해도 프로그램에서는 단순히 &apos;도미솔&apos; 정도의 소리만 났었습니다. 그러다가 행사가 끝난 다음 주에 문득 여기에다 듣기좋은 멜로디를 넣어보고 싶다는 충동을 느꼈고, 그때 게임 &apos;Loom&amp;#8217;의 음악이 머리 속에 떠올랐습니다. 당연히 악보 같은 건 없었고 기억하는 멜로디를 스스로 휘파람으로 불어서 청음으로 코드를 만들었습니다. 남다른 음감각을 가진 것도 아니어서 계속 시행착오를 거치면서  play문에 들어가는 음표코드를 넣었죠. 어쩐지 박자와 음이 약간 어설프고 불안정한 느낌이 들지 않나요?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;행사 때 김국헌님께서 발표하시는 분들마다 물어보셨던 말이 있었습니다. &apos;이 작업을 하시면서 즐거웠습니까?&apos;&apos; 저도 여기에 그때 다른 분들이 하셨던 말과 똑같이 &apos;네. 굉장히 즐거웠습니다&amp;#8217;라고 말할 수 밖에 없네요. 한 때는 왠만한 단축키는 다 외웠었던 Quick-Basic이였는데 오랜만에 쓸려니 함수 선언방법 조차도 가물가물 하더군요. 그래도 기억을 되살려 가면서 작업을 하다보니 이런 도구로 고등학교 때 축제 전시회 때 내놓을 게임을 만들던 일이 생각이 났었습니다. 그 때 정말 밥 먹는 시간도 잊어 버릴 정도로 매달려서 어설픈 게임을 만들었고, 어쩌면, 아니 확실히 그런 기억들 때문에 지금도 컴퓨터 코드를 짜는 일을 하고 있다는 사실도 새삼스럽게 떠올랐습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;뭐 어떤 분들은 이 글을 보시면, 몇줄 안되고 실행결과도 허접한 프로그램 짜놓고 제목은 아트니 뭐니 되어 있고 되게 거창한 척을 한다고 느끼실 것 같습니다. 하지만 누가 뭐래든 이 코드를 짜던 순간, 그리고 Quick-Basic이 어떤거니 하는 , 요즘은 별 정보도 되지 않을 이런 글을 주절주절 쓰고 있는 이 순간까지도 행복감의 여운을 느낍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;다 해놓고 보니 코드는 그리 길지는 않네요~&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;basic&quot;&gt;DECLARE SUB display (c$, p$)

SCREEN 2
lg$ = &quot; Code can be an art! &quot;
DATA &quot;t90O1p4&quot;,&quot;e8&quot;,&quot;a8&quot;,&quot;b8&quot;,&quot;&amp;gt;c+8&quot;,&quot;g8&quot;,&quot;f8&quot;,&quot;&amp;lt;a8&quot;
DATA &quot;&amp;gt;d8&quot;,&quot;c+8&quot;,&quot;&amp;lt;e2&quot;,&quot;e8&quot;,&quot;a4&quot;,&quot;b8&quot;,&quot;&amp;gt;c+4&quot;,&quot;&amp;lt;b8&quot;,&quot;a2&quot;,&quot;e2&quot;,&quot;p4&quot;,&quot;p4&quot;,&quot;p4&quot;,&quot;p4&quot;
FOR i = 1 TO LEN(lg$)
    READ p$
    display MID$(lg$, i, 1), p$
NEXT i
SCREEN 0
END

SUB display (c$, p$)
PLAY p$
CLS
PRINT c$
FOR i = 0 TO 10
  FOR j = 0 TO 10
    IF POINT(j, i) THEN LINE (j * 60 + 80, i * 25 + 10)-(j * 60 + 140, i * 25 + 40), 1, BF
  NEXT j
NEXT i
LOCATE 1, 1
PRINT &quot; &quot;

END SUB&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;다른분들의_발표작&quot;&gt;다른분들의 발표작&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://colus.egloos.com/3421372&quot; class=&quot;bare&quot;&gt;http://colus.egloos.com/3421372&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sqler.com/board_uxkorea/172190&quot; class=&quot;bare&quot;&gt;https://www.sqler.com/board_uxkorea/172190&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>방명록</title>
      <link>https://blog.benelog.net//904735.html</link>
      <pubDate>Thu, 8 Feb 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">904735.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;블로그 방명록이에요. 댓글로 달아주세요&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>나선형 배열 출력 문제 Spiral Array</title>
      <link>https://blog.benelog.net//901106.html</link>
      <pubDate>Wed, 7 Feb 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">901106.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2007년 회사 워크샵 갔었을 때 풀었던 문제와 답&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;문제&quot;&gt;문제&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;img/quiz/spiral-array.gif&quot; alt=&quot;image&quot; width=&quot;480&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;풀이&quot;&gt;풀이&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;정상혁&amp;amp;이상미 조의 풀이 답입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;public class MatrixMain {
    public static void main(String[] args) {
       Matrix mp = new Matrix(5,5);
       mp.locateNumbers();
       mp.print();
    }
}

public class Matrix {

    public static final int RIGHT = 0;
    public static final int DOWN = 1;
    public static final int LEFT = 2;
    public static final int UP  = 3;

    private static final int INIT_VALUE = -1;

    private int m = 0;
    private int n = 0;
    private int[][] matrix;

    public Matrix(int m, int n){
        this.m = m;
        this.n = n;
        matrix = new int[m][n];
    }

    public void print(){
        for (int i=0;i&amp;lt; m ;i++){
            for (int j=0;j&amp;lt;n;j++) System.out.print(matrix[i][j] + &quot;\t&quot;);
            System.out.println();
       }
    }

    public void locateNumbers() {
       init();
       int[] startPosition = new int[]{0,0};
       matrix[0][0]= 0;
       int direction = RIGHT;
       while(move(startPosition ,direction)) direction ++;
    }

    private void init() {
        for (int i=0;i&amp;lt; m ;i++){
            for (int j=0;j&amp;lt;n;j++) matrix[i][j] = INIT_VALUE;
        }
    }

    private boolean move(int[] position, int direction){
        int nowNumber = matrix[position[0]][position[1]];
        boolean moved = false;
        int[] nextPosition =getNextPosition(position,direction);
        while(isMovable(nextPosition[0],nextPosition[1])){
            moved = true;
            nowNumber++;
            position[0] = nextPosition[0];
            position[1] = nextPosition[1];
            matrix[position[0]][position[1]]= nowNumber;
            nextPosition = getNextPosition(position,direction);
        }
        return moved;
    }

    private int[] getNextPosition(int[] position,int direction){
        int x = position[0];
        int y = position[1];
        int nowDirection = direction%4;
        if (nowDirection == RIGHT) y++;
        else if (nowDirection == DOWN) x++;
        else if (nowDirection == LEFT) y--;
        else if (nowDirection == UP) x--;
        return new int[]{x,y};
    }

    private boolean isMovable(int x, int y){
        if (x&amp;gt;=m) return false;
        if (y&amp;gt;=n) return false;
        if (x&amp;lt;0) return false;
        if (y&amp;lt;0) return false;
        if (matrix[x][y]!= INIT_VALUE) return false;
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;결과&quot;&gt;결과&lt;/h3&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;prettyprint highlight&quot;&gt;&lt;code&gt;0   1   2   3   4

15  16  17  18  5

14  23  24  19  6

13  22  21  20  7

12  11  10  9   8&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이동하는 좌표를 사이즈가 2인 int배열에다 담았는데, 이걸 따로 클래스로 빼서 만들까 하다가 클래스를 더 늘리기 싫어서 그만뒀습니다.
소스에서 &lt;code&gt;position[0], position[1]&lt;/code&gt; 이라고 표시된 부분이 좌표를 나타내는 부분입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;main메서드에서  &lt;code&gt;Matrix mp = new Matrix(5,5);`&lt;/code&gt; 부분이 5*5 행렬을 만든다는 의미입니다.
사이즈를 바꿀려면 이 부분을 고치시거나, 사용자 입력에서 온 값을 연결시키면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>더골(THE GOAL)</title>
      <link>https://blog.benelog.net//900996.html</link>
      <pubDate>Wed, 7 Feb 2007 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">900996.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.yes24.com/Goods/FTGoodsView.aspx?goodsNo=244882&amp;amp;CategoryNumber=001001025008009&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;http://image.yes24.com/momo/TopCate04/MidCate10/391510.jpg&quot; alt=&quot;image&quot;&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(이미지는 yes24에서)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;더 골(The Goal)  , 제프 콕스 저, 김일운 역, 동양문고&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;회사 소모임의 워크샵에서 이 책을 주제로 한 발표가 있었습니다. 그 때 생각난 내용을 나중에 회사 커뮤니티 게시판에 적었는데, 그 내용의 일부를 옮겨써 봅니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;저의 사촌형이 이 책을 낸 출판사에 일하고 있어서, 저는 책을 공짜로 받았었습니다. 몇년전 제가 그 형 집에 놀러 갔을때 이책을 아마 제가 꼭 읽어야 할 것이라면서 그냥 저에게 주는 것이였습니다. 대충 넘겨보면서 내용을 훑어보니 공장에 대한 이야기라서 서비스업에 종사하는 저로써는 별 상관없는 내용으로만 보였습니다. 그래서 한동안 책장에 꽂아만 두고 있었죠.  책을 받고 한참 뒤에서야 잡지나 블로그 등을 통해 개발자가 읽을 책으로  이 책을 권하는 사람이 많은 것을 알게 되었고,  다시 찾아서 읽게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;소설의 내용은 공장장이 문제의 공장을 해결해 나가는 이야기입니다.  그 문제의 공장이 잘 돌아가지 않았던 것은  각각의 공정의 &lt;strong&gt;부분적인&lt;/strong&gt; 생산성에만 작업자의 목표가 맞춰져 있었기 때문이라고 정리하고 싶습니다. &quot;부분적인 최적화는 전체 최적화를 보장하지 않는다&quot;는 말도 학교 다닐때 들었는데, 그 예가 이 소설 내용에 있는 것 같네요.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;공장에서는 자동로봇을 도입하고 이를 자랑꺼리로 여겼는데, 그 로봇은 많은 부품을 찍어냈지만, 그 부품은 결국 재고만 더 쌓이게 만들어서 오히려 공장에 손해만 끼치는 결과를 낳았다는 내용이 있습니다. 그 로봇이 있는 공정에서는 부품의 생산양만 보고 높은 성과를 이루고 있다고 생각했겠죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;모두들 열심히 일하고 로봇도 쉬지 않고 있는 공장이 고객이 주문하는 생산량을 못 맞추자 공장장은 대학 때 교수로 부터 자문을 받아서 이를 해결해 나가기 시작합니다. 그 방법들은&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;병목이 일어나는 공정에 대해서 외주를 주거나 동원가능한 다른 기계를 이용해서 지원작업을 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;우선 순위가 급한 부품을 다른 표시를 해서 그 부품이 해당 공정에 들어왔을때는 가장 먼저 작업을 하도록 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;등입니다. 위에 로봇의 경우와는 반대로 최적화된 한번의 작업량 (예를 들면 붕어빵틀에 모든 구멍에 밀가루를 채우는 것 같이 해당공정의 효율이 극대화 되는 한번의 양)을 포기했을 때 오히려 공장의 전체 생산성이 더 좋아지는 예도 책에 나옵니다. 그리고 &quot;재고는 자산이 아닌 비용이다&quot;라는 개념도 기존 통념을 뒤엎는 것이였죠.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;전에 이 책을 다 읽고나서도 &quot;그래도 나와는 큰 관련 없는 공장이야기군&quot;하고 생각을 했었는데,  최근에 들어서야 IT업과 연관성을 생각해보게 되었습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;개발 프로젝트를 하면서 제가 경험한 일정관리는 프로그램의 목록을 엑셀시트나 이슈 트래커에다 나열해 놓고 그중에 몇개를 완성했느냐, 그것이 전체의 몇%냐로 평가를 받는 것이였습니다. 그 갯수가 적으면 다른 사람보다 생산성이 적은 것이 되고, 계획했던 갯수보다 적으면 일정을 못 맞춘것이 되는것이였습니다. 여기서 개발자가 충족해야할 목표는 그 갯수를 늘이는 것인데, 그러다보니 쉬운 프로그램, 고객이 별로 신경쓰지 않는 프로그램을 먼저 구현을 하게 되는 경향이 생기기 쉬워집니다. 그러다보면 오히려 정말 많이쓰이고, 고객이 민감해 하는 프로그램들은 나중에 급하게 졸속으로 구현되는 경우를 봐왔습니다. 이는 개발자가 평가받는 부분적인 목표(프로그램 갯수)와 프로젝트 성공의 목표(고객의 필요한 프로그램이 잘 돌아가서 검수를 받는것)이 상충되는것이 아닐까 합니다. 익스트림 프로그래밍 서적에서도 비슷한 이야기를 본것 같네요. 고객이 가장 우선시 하는것부터 먼저 구현을 하고 고객에게 피드백을 받고 다음 스토리로 넘어가야한다는 내용이였던것 같습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;그리고  프로젝트에서 &quot;고객검토&quot;가 병목인 경우가 많다는 생각도 듭니다. 한참동안 피드백이 없다가 프로젝트 종료시점에서야 요구사항을 쏟아내는 고객의 경험을 다른 분들도 겪었을것 같습니다. 병목을 보강하듯이 업무별 고객담당자를 적절히 분할해서 지정하거나, 개발팀 쪽에서도 고객검토을 촉진시킬수 있는 담당자를 두거나 하는 해결방안등이 생각이 납니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;이 책이 공장의 이야기인데도 개발자들사이에 많이 권해지는 이유를 연결시켜서 생각해봤습니다. 그 외에도 연관시킬만한  것이 있는지 궁금합니다.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>

  </channel> 
</rss>
