Jekyll2022-12-17T08:13:00+00:00http://widian.github.io//feed.xmlWidian’s Hobby Notes프로그래밍 중 생긴 문제상황에 대한 대처 및 번역 등록을 위한 블로그. 한국어로 검색하는 사람도 양질의 결과를 얻을 수 있는 날이 오기를 기대하면서...
surefire report 사용 중에 junit5 결과가 CI에서 안 보이는 경우2022-12-07T07:44:44+00:002022-12-07T07:44:44+00:00http://widian.github.io//blog/2022/12/07/surefire-report-%EC%82%AC%EC%9A%A9-%EC%A4%91%EC%97%90-junit5-%EA%B2%B0%EA%B3%BC%EA%B0%80-CI%EC%97%90%EC%84%9C-%EC%95%88-%EB%B3%B4%EC%9D%B4%EB%8A%94-%EA%B2%BD%EC%9A%B0<ul>
<li>Jenkins 등 CI에서 테스트 결과를 확인하기 위해 surefire report를 사용하는 경우가 있습니다. Jenkins Blueocean에서는 surefire report 플러그인으로 생성된 테스트 결과 xml 파일을 junit testResults에 post해주면 테스트 탭에서 수행된 테스트의 이름과 테스트 수행 결과를 확인할 수 있도록 도와줍니다.</li>
<li>그러나 구버전의 surefire report plugin을 사용하면, <code class="language-plaintext highlighter-rouge">mvn test</code> 를 실행하더라도 JUnit5 테스트가 실행되지 않는 경우가 발생합니다.</li>
<li>surefire 플러그인을 사용하고 있는 경우, 최소 2.19 버전을 사용하게 되면 Junit5 테스트에 대해서도 테스트가 정상적으로 동작합니다.</li>
</ul>
<h2 id="maven-pomxml-기준-플러그인-설정-추가">Maven pom.xml 기준 플러그인 설정 추가</h2>
<ul>
<li>아래와 같이 플러그인 버전을 올리고, dependency에 junit jupiter api 의존성을 추가해 줍니다.</li>
<li>junit-vintage-engine을 사용하고 있다면, vintage engine도 의존성에 추가해줍니다.</li>
<li>버전은 사용중인 junit jupiter 버전과 동일한 버전을 사용해야 합니다. (여기선 5.9.1 사용)</li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.apache.maven.plugins<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>maven-surefire-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.22.2<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><argLine></span>-Xms512m -Xmx1536m -Xss256k<span class="nt"></argLine></span>
<span class="nt"></configuration></span>
<span class="nt"><dependencies></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.junit.jupiter<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>junit-jupiter-api<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.9.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.junit.vintage<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>junit-vintage-engine<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.9.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"></dependencies></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<ul>
<li>surefire report plugin도 동일하게 변경해 줍니다.</li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.apache.maven.plugins<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>maven-surefire-report-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.22.2<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><outputDirectory></span>target/reports<span class="nt"></outputDirectory></span>
<span class="nt"></configuration></span>
<span class="nt"><dependencies></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.junit.jupiter<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>junit-jupiter-api<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.9.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.junit.vintage<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>junit-vintage-engine<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.9.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"></dependencies></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<h2 id="report-플러그인-설정-변경">Report 플러그인 설정 변경</h2>
<ul>
<li>만약 테스트 시에 xref 가 없다는 에러가 나온다면 아래 의존성을 reporting에 추가해줍니다.</li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.apache.maven.plugins<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>maven-jxr-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>3.3.0<span class="nt"></version></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<h2 id="실행">실행</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">mvn test surefire-report:report</code> 실행하여 surefire-report가 정상적으로 나오는지 확인합니다.</li>
</ul>Jenkins 등 CI에서 테스트 결과를 확인하기 위해 surefire report를 사용하는 경우가 있습니다. Jenkins Blueocean에서는 surefire report 플러그인으로 생성된 테스트 결과 xml 파일을 junit testResults에 post해주면 테스트 탭에서 수행된 테스트의 이름과 테스트 수행 결과를 확인할 수 있도록 도와줍니다. 그러나 구버전의 surefire report plugin을 사용하면, mvn test 를 실행하더라도 JUnit5 테스트가 실행되지 않는 경우가 발생합니다. surefire 플러그인을 사용하고 있는 경우, 최소 2.19 버전을 사용하게 되면 Junit5 테스트에 대해서도 테스트가 정상적으로 동작합니다.Terser plugin으로 license 주석 추출해서 라이센스 파일 생성하기2022-05-01T05:42:00+00:002022-05-01T05:42:00+00:00http://widian.github.io//javascript/2022/05/01/terser-plugin-license-comment<h2 id="terser-plugin-간단-설명">Terser Plugin 간단 설명</h2>
<ul>
<li>Terser Plugin은 Webpack에서 기존 Uglify Plugin이 수행하던 Minify(작게 하기) 모듈입니다.</li>
<li>Terser는 번들링하는 파일들을 작게 해줘서 번들링된 파일의 크기를 줄여주는 역할을 합니다.</li>
<li>Terser는 파일 크기를 작게 해주는 과정에서 불필요한 주석과 <code class="language-plaintext highlighter-rouge">console.log</code> 등 개발자 확인용의 코드를 제거해주는 역할을 합니다.</li>
<li>그렇지만, 주석중에는 올바른 오픈소스 사용을 위해 제거되지 말아야할 라이센스 주석들이 존재합니다.</li>
<li>Terser Plugin 에서는 extractComments 옵션을 사용해서 제거되지 말아야할 주석을 지정할 수 있습니다.</li>
</ul>
<h2 id="extractcomments-옵션-사용법">extractComments 옵션 사용법</h2>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">optimization</span><span class="p">:</span> <span class="p">{</span>
<span class="na">minimize</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">minimizer</span><span class="p">:</span> <span class="p">[</span>
<span class="k">new</span> <span class="nx">TerserPlugin</span><span class="p">({</span>
<span class="na">extractComments</span><span class="p">:</span> <span class="p">{</span>
<span class="na">condition</span><span class="p">:</span> <span class="sr">/^</span><span class="se">\*</span><span class="sr">*!|@preserve|@license|@cc_on/i</span><span class="p">,</span>
<span class="na">filename</span><span class="p">:</span> <span class="p">(</span><span class="nx">file</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// file은 bundling 결과의 파일 이름입니다.</span>
<span class="k">return</span> <span class="nx">file</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">\.(\w</span><span class="sr">+</span><span class="se">)(</span><span class="sr">$|</span><span class="se">\?)</span><span class="sr">/</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.$1.LICENSE.txt</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">banner</span><span class="p">:</span> <span class="p">(</span><span class="nx">licenseFile</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// banner는 번들링한 파일 상단에 만들어 질 주석입니다.</span>
<span class="k">return</span> <span class="s2">`License information can be found in </span><span class="p">${</span><span class="nx">licenseFile</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}),</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>
<ul>
<li>Terser Plugin 설정에 extractComments옵션을 넣습니다.</li>
<li>Object 대신 “all” 을 넣을 경우 모든 주석이 남습니다.</li>
<li>옵션 Object를 넣을 경우, condition, filename, banner 등의 옵션을 활용할 수 있습니다.</li>
<li>condition 은 주석에 특정 문구가 포함됐을 경우 추출합니다.</li>
<li>주로 @license, /*!, @preserve, @cc_on 을 사용하기 때문에 위와 같은 condition을 넣었습니다.</li>
<li>filename 은 String 을 넣거나 function을 넣을 수 있습니다.
<ul>
<li>String을 넣게 되면 모든 주석이 하나의 파일에 추출됩니다.</li>
<li>Function을 넣게 되면, 번들링된 파일마다 추출된 comment를 넣을 수 있습니다.</li>
<li>Function에 전달된 file은 번들링결과의 파일 이름입니다 (name, chunkhash등이 포함된).</li>
<li>여기서는 파일명을 그대로 남기고, 뒤에 .LICENSE.txt 를 추가해주도록 했습니다.</li>
</ul>
</li>
<li>banner는 번들링한 파일 상단에 만들어질 주석입니다. 라이센스 파일로 이동할 링크를 만들어 주면 좋습니다.</li>
</ul>
<h3 id="결과">결과</h3>
<ul>
<li>vendor-{chunkhash}.js.LICENSE.txt 파일이 만들어지고, vendor-{chunkhash}.js 상단에 <code class="language-plaintext highlighter-rouge">/* License information can be found in vendor-{chunkhash}.js.LICENSE.txt */</code> 주석이 남습니다.</li>
</ul>
<h2 id="banner-plugin과-조합하기">Banner Plugin과 조합하기.</h2>
<ul>
<li>webpack.BannerPlugin과 조합해서, 모든 라이센스 파일 상단에 남을 주석을 추가해줄 수 있습니다.</li>
<li>BannerPlugin으로 만들어지는 주석은 Minify 전에 남기 때문에, webpack.BannerPlugin 으로 상단에 condition으로 남는 주석을 추가해 준다면, 모든 LICENSE.txt 파일에 해당 주석이 들어가게 됩니다.</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nx">webpack</span><span class="p">.</span><span class="nx">BannerPlugin</span><span class="p">({</span>
<span class="na">banner</span><span class="p">:</span> <span class="p">(</span><span class="nx">yourVariable</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">`/*!
여기에 라이센스를 넣으세요.
*/`</span><span class="p">;</span>
<span class="p">},</span>
<span class="p">});</span>
</code></pre></div></div>
<h1 id="참고">참고</h1>
<ul>
<li>웹팩 배너 플러그인 https://webpack.js.org/plugins/banner-plugin/</li>
<li>웹팩 Terser Plugin https://webpack.js.org/plugins/terser-webpack-plugin/#root</li>
</ul>Terser Plugin 간단 설명 Terser Plugin은 Webpack에서 기존 Uglify Plugin이 수행하던 Minify(작게 하기) 모듈입니다. Terser는 번들링하는 파일들을 작게 해줘서 번들링된 파일의 크기를 줄여주는 역할을 합니다. Terser는 파일 크기를 작게 해주는 과정에서 불필요한 주석과 console.log 등 개발자 확인용의 코드를 제거해주는 역할을 합니다. 그렇지만, 주석중에는 올바른 오픈소스 사용을 위해 제거되지 말아야할 라이센스 주석들이 존재합니다. Terser Plugin 에서는 extractComments 옵션을 사용해서 제거되지 말아야할 주석을 지정할 수 있습니다.Mockito 사용중 Unnecessary Stubbing Exception 해소하기2021-08-15T18:10:00+00:002021-08-15T18:10:00+00:00http://widian.github.io//java/2021/08/15/mockito-%EC%82%AC%EC%9A%A9-%EC%A4%91-Unnecessary-Stubbing-Exception-%ED%95%B4%EC%86%8C%ED%95%98%EA%B8%B0<h1 id="mockito-사용중-unnecessary-stubbing-exception-해소하기">Mockito 사용중 Unnecessary Stubbing Exception 해소하기</h1>
<p>Spring Boot에서 JUnit5와 이에 포함된 Mockito Core 3.x 버전을 사용할 때 아래와 같이 <code class="language-plaintext highlighter-rouge">UnnecessaryStubbingException</code> 에러가 나면서 테스트코드가 실패하는 경우가 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
</code></pre></div></div>
<p>이는 mockito-core버전이 1.x일 때 없었던 Strictness(테스트코드의 엄격성)을 규정하기 위해 생긴 에러이며, mockito-core 2.x 버전에서 도입되었습니다.</p>
<p>테스트코드의 엄격성은 다음 요소들을 보장해 줍니다.</p>
<ul>
<li>테스트코드에서 사용되지 않는 stub (when/thenReturn)을 줄여줍니다.</li>
<li>테스트코드에서 불필요한 코드 중복을 없애주고 이를 통해 필요없는 테스트 코드 역시 줄여줍니다.</li>
<li>죽은 코드를 제거하면서 생기는 불필요한 테스트를 없애도록 도와줍니다.</li>
<li>이를 통해 디버깅 편의성과 생산 효율을 올려줍니다.</li>
</ul>
<p>테스트코드의 엄격성도입은 mockito의 다음 철학과 관련이 있습니다</p>
<blockquote>
<p>Use the right tools and get cleaner tests, faster.</p>
</blockquote>
<p>도입을 결정하게 된 <a href="https://github.com/mockito/mockito/issues/769">깃헙 이슈</a>와 해당 이슈에 레퍼런스된 <a href="https://www.linkedin.com/pulse/curious-how-get-even-cleaner-tests-new-mockito-features-faber/">Mockito 개발자의 철학에 대한 설명</a> , 그리고 stubbing 감지의 장점을 좀 더 디테일하게 설명한 <a href="http://blog.mockito.org/2017/01/clean-tests-produce-clean-code-strict.html">Mockito 블로그 글(Clean tests produce clean code - strict stubbing in Mockito)</a> 을 참고하기 바랍니다.</p>
<p>Mockito-core 3.x버전에서는 위에서 설명한 엄격성이 강제되었기 때문에, 불필요한 stubbing 코드를 제거해야 합니다. 해결방법은 3가지가 있습니다.</p>
<h3 id="1-mockito-core-버전을-1x나-2x로-내리기">1. Mockito-core 버전을 1.x나 2.x로 내리기</h3>
<ul>
<li>최악의 해법이지만, 수정되어야 하는 코드가 많을 경우 어쩔 수 없이 선택해야하는 경우의 수입니다.</li>
<li>2.x 에서는 엄격성 개념이 도입되었지만, 테스트에 아래와 같이 MockitoSettings를 지정해주면 엄격성을 우회해서 테스트할 수 있습니다.</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@MockitoSettings</span><span class="o">(</span><span class="n">strictness</span> <span class="o">=</span> <span class="nc">Strictness</span><span class="o">.</span><span class="na">WARN</span><span class="o">)</span>
<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="c1">// Test blah blah...</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="2-lenient-메서드를-앞에-추가하기">2. lenient() 메서드를 앞에 추가하기</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">doReturn</code>, <code class="language-plaintext highlighter-rouge">when</code> 등의 앞에 <code class="language-plaintext highlighter-rouge">lenient()</code>를 추가해서 해당 stubbing이 미사용될 수 있음을 표시합니다.</li>
<li>일반적으로, <code class="language-plaintext highlighter-rouge">@BeforeEach</code> 처럼 전체 테스트에 적용되어야 하는 stubbing이 일부 엣지 케이스에서 미사용될때 사용하는 것이 좋습니다.
<ul>
<li>남발하게 된다면, 과한 코드의 중복을 보게 되어 Mockito에서 추구하고자 한 테스트의 개선에 도움이 되지 않습니다.</li>
</ul>
</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// lenient()를 붙여서 mocking 결과를 항상 사용하진 않음을 표시합니다.</span>
<span class="n">lenient</span><span class="o">().</span><span class="na">doReturn</span><span class="o">(</span><span class="n">fixedClock</span><span class="o">.</span><span class="na">instant</span><span class="o">()).</span><span class="na">when</span><span class="o">(</span><span class="n">clock</span><span class="o">).</span><span class="na">instant</span><span class="o">();</span>
<span class="n">lenient</span><span class="o">().</span><span class="na">doReturn</span><span class="o">(</span><span class="n">fixedClock</span><span class="o">.</span><span class="na">getZone</span><span class="o">()).</span><span class="na">when</span><span class="o">(</span><span class="n">clock</span><span class="o">).</span><span class="na">getZone</span><span class="o">();</span>
</code></pre></div></div>
<h2 id="3-필요없는-stubbing을-제거하기">3. 필요없는 stubbing을 제거하기</h2>
<ul>
<li>말 그대로 필요없는 <code class="language-plaintext highlighter-rouge">when</code>, <code class="language-plaintext highlighter-rouge">doReturn</code>, <code class="language-plaintext highlighter-rouge">doThrow</code> 등을 제거합니다.</li>
<li>불필요한 테스트가 줄어들고, 중복 mocking이 줄어들기 때문에 나중에 코드를 수정하는 사람이 테스트코드를 이해하는데 편해집니다.</li>
</ul>Mockito 사용중 Unnecessary Stubbing Exception 해소하기협업에서 테스트코드는 왜 필요할까2020-12-27T11:17:00+00:002020-12-27T11:17:00+00:00http://widian.github.io//developer/2020/12/27/%ED%98%91%EC%97%85%EC%97%90%EC%84%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C<p>테스트 주도 개발 (TDD, Test Driven Development)에 대해 얘기를 하는 사람은 많았지만, 정확히 테스트가 왜 필요한지, 테스트를 썼을 때 좋은 점이 어떤 게 있는지에 대해 정리해 보려고 합니다. 혼자 개발할 때는 테스트 주도 개발과 테스트의 필요성에 대해 느끼지 못했지만, 협업을 하면서 테스트 코드가 만드는 즐거움에 대해 알게 되었고, 제가 생각하는 테스트의 장점은 크게 2가지로 나눌 수 있었습니다.</p>
<h2 id="외부환경을-분리해-두고-핵심로직을-먼저-개발할-수-있다">외부환경을 분리해 두고 핵심로직을 먼저 개발할 수 있다.</h2>
<ul>
<li>
<p>아무래도 협업해서 개발을 할 때는, 제가 만들어야 하는 기능이 다른 개발자가 만드는 기능과 함께 동작해야 기획서에 적혀있는 하나의 기능을 완성하는 경우가 있습니다. 크게는 API에 사용되어 유저 응답에 사용되는 기능과 관리툴 개발의 관계가 있을 수 있고, 작게는 어떤 한 데이터의 파이프라이닝 기능을 구현해야 할 때가 있습니다.</p>
</li>
<li>
<p>먼저 기능을 구현하기에 앞서서, 모듈간의 통신을 어떻게 해야할 지에 대해 정했다면, 그 후의 기능구현은 매우 간단합니다. 모듈간의 통신에 사용되는 데이터를 만드는 부분을 테스트 코드로 대신할 수 있습니다. 테스트 코드를 작성한다면 제가 구현하고자 하는 로직을 설명할 수 있습니다. 테스트의 시작 부분에서 어떤 요청이 들어오는 지를 정의할 수 있고, 테스트의 끝 부분에서 어떤 응답이 내려가는지에 대한 정의를 할 수 있습니다. 이렇게 먼저 개발하게 되면 미리 정의해 둔 데이터에 대한 명세에서 부족한 점이 어떤건지, 스펙에서 누락된 지점이 어디인지를 먼저 파악할 수 있어서 개발 시간과 커뮤니케이션 비용을 단축할 수 있습니다.</p>
</li>
<li>
<p>또한, 제가 개발해 오는 동안 겪었던 문제 상황에 대한 케이스들을 테스트케이스의 반복적인 작성에서 관찰할 수 있습니다. 다른 테스트 케이스를 짤 때 확인했던 것과 비슷한 지점에서 에러가 나고, 예외가 생길 지에 대한 확인을 할 수 있습니다. 특히 로직의 동작 확인을 빠른 시간 내에 확인할 수 있다는 점에서, 테스트 케이스 작성은 편리하게 어떤 예외가 발생할지에 대한 확인을 진행할 수 있게 도와줍니다.</p>
</li>
<li>
<p>마지막으로 다른 사람이 작성해 둔 테스트 데이터와 테스트 환경을 간섭하지 않고 로직에 집중할 수 있습니다. 이 부분은 테스트를 작성할 때도 조심해야 하는데 테스트의 장점은 아무래도 외부 환경에 영향없이 로직이 제대로 동작하는지 확인할 수 있다는 점에 있다고 생각하기 때문입니다. 만약 로직을 작성할 때 DB나 메시지큐 등 공동으로 사용해야하는 자원을 계속해서 수정해가면서 테스트해야 한다면, 아무래도 협업하는 사람들에게 예기치 못한 문제를 일으킬 가능성이 있습니다. 특히 DB의 테이블을 제거한다던가 컬럼을 제거하는 등 어플리케이션 기동에 필요한 정보를 수정할 경우 다른 사람들의 코딩 작업을 아예 막아버릴 수 있기 때문에 이런 부분을 수정할 때는 더 조심해야 합니다.</p>
</li>
</ul>
<h2 id="내가-개발해-둔-스펙의-기능명세서로-사용할-수-있다">내가 개발해 둔 스펙의 기능명세서로 사용할 수 있다.</h2>
<ul>
<li>
<p>테스트코드를 작성하고 나면, 테스트 코드는 테스트 대상이 되는 메소드에 들어오는 입력의 형태와, 원하는 출력의 형태가 적혀있는 코드가 완성됩니다. 이러한 테스트 코드는 나중에 다른 개발자가 내가 작성한 메소드를 수정할 일이 생겼을 때 코드를 이해하는 데에 도움이 됩니다. 특히 파라미터로 범용의 파라미터용 클래스가 들어올 때, 테스트 코드를 보고 파라미터 클래스 중 어떤 데이터만 사용되는지 확인하는데 크게 도움이 됩니다.</p>
</li>
<li>
<p>또한, 테스트 코드를 수정하고 실행해 보는 것으로 수정해야할 메소드가 어떻게 동작하는지 빠르게 파악할 수 있도록 도움을 줍니다. 간혹 테스트가 없는 경우 기능확인을 하기 위해 데이터를 입력하기 위한 툴도 확인하고, 기능을 확인하기 위해 앱을 켜야하기도 하고, 원하는 환경을 구성해줘야하는 경우가 있는데, 테스트 코드가 있을 경우 이런 부가 과정을 크게 줄여주는 역할을 합니다.</p>
</li>
<li>
<p>테스트 코드와 테스트 환경이 완성되어 있다는 것만으로도 해당 기능을 수정해야하는 사람에게 꽤 안정감을 줄 수 있습니다.</p>
</li>
</ul>
<h2 id="주의해야할-점">주의해야할 점</h2>
<ul>
<li>테스트코드가 공용환경의 자원을 사용하고 있으면 원치 않는 상황에서 테스트가 깨질 수 있습니다. 테스트코드를 사용할 때는 최대한 외부환경의 분리가 필요합니다.
<ul>
<li>예를 들어, 외부 API를 호출한다거나, DB등을 사용하는 경우에는 IntegrationTest로 따로 환경을 분리해두고, 로직을 테스트하는 부분은 Mock을 적극적으로 활용하는 것이 좋습니다.</li>
<li>너무 많은 Mock이 고민된다면, E2E 테스트의 도입을 고민해보는 것이 좋습니다.</li>
</ul>
</li>
<li>DB나 Redis등 외부에 존재하는 데이터를 사용해야 하는 테스트의 경우, 로컬에 환경을 구성할 수 있는지 먼저 확인하고, 부득이하게 데이터를 공용으로 사용해야 한다면 insert와 delete를 테스트에 포함해두는 것이 좋습니다.</li>
<li>MySQL등 롤백이 가능한 DB를 사용하고 있다면 테스트환경에서 구동할 때 커밋하지 않고 바로 롤백이 가능하도록 테스트를 구성해야 합니다.
<ul>
<li>만약 Private Key에서 Auto Increment를 사용할 경우, 테스트가 끝나고 나면 Auto Increment Index가 올라간 채로 돌아오지 않을 수 있습니다.</li>
<li>테스트할 때 Auto Increment인 Private Key를 바꾸지 않도록 임의의 숫자를 넣어서 테스트를 할 수 있도록 하거나, 아예 Private Key가 데이터에서 큰 의미를 가지지 않도록 개발하는 것이 좋을 수 있습니다.</li>
</ul>
</li>
</ul>테스트 주도 개발 (TDD, Test Driven Development)에 대해 얘기를 하는 사람은 많았지만, 정확히 테스트가 왜 필요한지, 테스트를 썼을 때 좋은 점이 어떤 게 있는지에 대해 정리해 보려고 합니다. 혼자 개발할 때는 테스트 주도 개발과 테스트의 필요성에 대해 느끼지 못했지만, 협업을 하면서 테스트 코드가 만드는 즐거움에 대해 알게 되었고, 제가 생각하는 테스트의 장점은 크게 2가지로 나눌 수 있었습니다.NodeJS로 zip파일의 압축 풀기2020-09-08T16:49:00+00:002020-09-08T16:49:00+00:00http://widian.github.io//node/2020/09/08/node%EB%A1%9C-zip%ED%8C%8C%EC%9D%BC%EC%9D%98-%EC%95%95%EC%B6%95-%ED%92%80%EA%B8%B0<h2 id="nodejs로-zip파일의-압축-풀기">NodeJS로 zip파일의 압축 풀기</h2>
<ul>
<li>Node에서 adm-zip을 이용해 Zip 파일의 압축을 푸는 내용에 대해 설명합니다.</li>
<li>보통 웹서버를 만드는 Node로는 zip파일 압축을 풀게 될 일이 거의 없지만, 가끔 간단한 스크립트를 Node로 작성하고 싶을 때 파일스트림을 이용해 zip파일의 압축을 풀어야 하는 순간이 옵니다.</li>
<li><a href="https://stackoverflow.com/questions/10308110/simplest-way-to-download-and-unzip-files-in-node-js-cross-platform">스택오버플로우 답변</a>에서 <code class="language-plaintext highlighter-rouge">adm-zip</code>을 추천하기에 해당 라이브러리를 사용해보았습니다.</li>
</ul>
<h2 id="adm-zip의-장점">adm-zip의 장점</h2>
<ul>
<li>adm-zip은 다른 unzip라이브러리와 다르게 다른 의존성을 가져오지 않고 순수 javascript로 제작된 unzip 라이브러리입니다.
<ul>
<li>새 오픈소스 라이브러리를 추가할 때 복잡한 라이선스에 대한 고민하지 않고 사용할 수 있습니다.</li>
</ul>
</li>
<li>압축해제 결과 파일을 memory로 올릴 필요 없이 disk, in-memory 버퍼에서 압축을 풀 수 있습니다.</li>
<li>압축할 파일을 disk에 바로 압축하거나 압축된 버퍼에 바로 압축합니다.</li>
<li>이미 압축된 zip파일에 새 파일을 바로 더하거나 수정/삭제할 수 있습니다.</li>
<li>filestream에 대한 이해 없이 바로 사용할 수 있습니다.</li>
</ul>
<h2 id="install">Install</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">yarn add --dev adm-zip</code> 으로 추가합니다.</li>
<li><code class="language-plaintext highlighter-rouge">const AdmZip = require('adm-zip')</code>으로 가져옵니다.</li>
</ul>
<h2 id="사용">사용</h2>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">AdmZip</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">adm-zip</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">fileName</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">./file.zip</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">zip</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AdmZip</span><span class="p">(</span><span class="nx">fileName</span><span class="p">);</span> <span class="c1">// file위치로 AdmZip 오브젝트를 생성합니다.</span>
<span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">./result/</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// 압축이 해제될 위치를 지정합니다.</span>
<span class="nx">zip</span><span class="p">.</span><span class="nx">extractAllTo</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="cm">/* 압축결과가 기존 파일을 overwrite 할지 */</span> <span class="kc">true</span><span class="p">);</span>
</code></pre></div></div>
<ul>
<li>간단하게 zip파일의 압축 해제를 할 수 있었습니다.</li>
</ul>
<h2 id="기타-정보">기타 정보</h2>
<ul>
<li>자세한 사항은 <a href="https://github.com/cthackers/adm-zip">adm-zip 공식 깃헙</a>에서 확인할 수 있습니다.</li>
</ul>NodeJS로 zip파일의 압축 풀기 Node에서 adm-zip을 이용해 Zip 파일의 압축을 푸는 내용에 대해 설명합니다. 보통 웹서버를 만드는 Node로는 zip파일 압축을 풀게 될 일이 거의 없지만, 가끔 간단한 스크립트를 Node로 작성하고 싶을 때 파일스트림을 이용해 zip파일의 압축을 풀어야 하는 순간이 옵니다. 스택오버플로우 답변에서 adm-zip을 추천하기에 해당 라이브러리를 사용해보았습니다.Java8에서 computeIfAbsent를 이용해 리스트 아이템을 키, 리스트로 된 맵으로 모으기2020-07-25T16:23:00+00:002020-07-25T16:23:00+00:00http://widian.github.io//java/2020/07/25/Java8%EC%97%90%EC%84%9C-computeIfAbsent%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%95%84%EC%9D%B4%ED%85%9C%EC%9D%84-%ED%82%A4,-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A1%9C-%EB%90%9C-%EB%A7%B5%EC%9C%BC%EB%A1%9C-%EB%AA%A8%EC%9C%BC%EA%B8%B0<h2 id="java8에서-computeifabsent를-이용해-리스트-아이템을-키-리스트로-된-맵으로-모으기">Java8에서 computeIfAbsent를 이용해 리스트 아이템을 키, 리스트로 된 맵으로 모으기</h2>
<p>뷰 관련 작업을 하다보면, Map이 아닌 key-value item의 List를 Map으로 모아줘야 할 일이 생길 때가 있습니다. 예를 들어, 게시판을 만들고 있을 때, 게시판의 글을 분류별이 아닌 시간 단위의 리스트로 불러온 다음, 게시판별로 분류를 해줘야하는 일이 있을 수 있습니다.</p>
<p>이럴 때는, post를 DB에서 리스트로 가져온 뒤, 서버에서 <code class="language-plaintext highlighter-rouge">Map<String, List<Post>></code> 와 같이 정리해주면 뷰에서 쉽게 작업할 수 있습니다. 원하는 결과는 Key가 boardName이고, Value가 Post의 리스트인 Map이 나와야 합니다.</p>
<p>아래와 같이 곁가지들을 쳐내고 필요한 부분만 떼서 lombok과 junit5를 활용해 간단한 테스트를 작성해 보았습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">자게</span> <span class="o">=</span> <span class="s">"자게"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">유게</span> <span class="o">=</span> <span class="s">"유게"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">핫게</span> <span class="o">=</span> <span class="s">"핫게"</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">test</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">></span> <span class="n">postList</span> <span class="o">=</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span>
<span class="nc">Post</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">boardName</span><span class="o">(</span><span class="n">자게</span><span class="o">).</span><span class="na">postId</span><span class="o">(</span><span class="mi">1</span><span class="o">).</span><span class="na">build</span><span class="o">(),</span>
<span class="nc">Post</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">boardName</span><span class="o">(</span><span class="n">자게</span><span class="o">).</span><span class="na">postId</span><span class="o">(</span><span class="mi">2</span><span class="o">).</span><span class="na">build</span><span class="o">(),</span>
<span class="nc">Post</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">boardName</span><span class="o">(</span><span class="n">유게</span><span class="o">).</span><span class="na">postId</span><span class="o">(</span><span class="mi">3</span><span class="o">).</span><span class="na">build</span><span class="o">(),</span>
<span class="nc">Post</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">boardName</span><span class="o">(</span><span class="n">유게</span><span class="o">).</span><span class="na">postId</span><span class="o">(</span><span class="mi">4</span><span class="o">).</span><span class="na">build</span><span class="o">(),</span>
<span class="nc">Post</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">boardName</span><span class="o">(</span><span class="n">유게</span><span class="o">).</span><span class="na">postId</span><span class="o">(</span><span class="mi">5</span><span class="o">).</span><span class="na">build</span><span class="o">(),</span>
<span class="nc">Post</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">boardName</span><span class="o">(</span><span class="n">핫게</span><span class="o">).</span><span class="na">postId</span><span class="o">(</span><span class="mi">6</span><span class="o">).</span><span class="na">build</span><span class="o">()</span>
<span class="o">);</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">>></span> <span class="n">result</span> <span class="o">=</span> <span class="n">arrange</span><span class="o">(</span><span class="n">postList</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">keySet</span><span class="o">().</span><span class="na">size</span><span class="o">(),</span> <span class="n">is</span><span class="o">(</span><span class="mi">3</span><span class="o">));</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">자게</span><span class="o">));</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">자게</span><span class="o">).</span><span class="na">size</span><span class="o">(),</span> <span class="n">is</span><span class="o">(</span><span class="mi">2</span><span class="o">));</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">유게</span><span class="o">));</span>
<span class="n">assertNotNull</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">핫게</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">>></span> <span class="nf">arrange</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">></span> <span class="n">postList</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">>></span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Getter</span>
<span class="nd">@Builder</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Post</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">boardName</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">postId</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="computeifabsent-없이-구현하기">computeIfAbsent 없이 구현하기</h4>
<ul>
<li>아직은 <code class="language-plaintext highlighter-rouge">arrange</code> 가 구현되지 않아서 테스트가 실패합니다. 먼저 <code class="language-plaintext highlighter-rouge">computeIfAbsent</code> 를 사용하지 않으면 아래와 같이 작성할 수 있습니다.</li>
<li>작성 후 테스트를 돌리면 성공하는 것을 확인할 수 있습니다.</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">>></span> <span class="nf">arrange</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">></span> <span class="n">postList</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">>></span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
<span class="n">postList</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">post</span> <span class="o">-></span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">post</span><span class="o">.</span><span class="na">getBoardName</span><span class="o">())</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">result</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">post</span><span class="o">.</span><span class="na">getBoardName</span><span class="o">(),</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>());</span>
<span class="o">}</span>
<span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">post</span><span class="o">.</span><span class="na">getBoardName</span><span class="o">()).</span><span class="na">add</span><span class="o">(</span><span class="n">post</span><span class="o">);</span>
<span class="o">});</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="computeifabsent를-사용해-간단하게-바꾸기">computeIfAbsent를 사용해 간단하게 바꾸기</h4>
<ul>
<li><code class="language-plaintext highlighter-rouge">computeIfAbsent</code>를 활용하면 if문을 간단하게 없앨 수 있습니다.</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">>></span> <span class="nf">arrange</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">></span> <span class="n">postList</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Post</span><span class="o">>></span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
<span class="n">postList</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">post</span> <span class="o">-></span> <span class="n">result</span><span class="o">.</span><span class="na">computeIfAbsent</span><span class="o">(</span><span class="n">post</span><span class="o">.</span><span class="na">getBoardName</span><span class="o">(),</span> <span class="n">k</span> <span class="o">-></span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>())</span>
<span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">post</span><span class="o">)</span>
<span class="o">);</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
<li>만약 <code class="language-plaintext highlighter-rouge">post.getBoardName()</code> 에 해당하는 키가 없다면
<ul>
<li>해당 키를 기반으로 <code class="language-plaintext highlighter-rouge">new ArrayList<>()</code> 를 만들어주고 put한다.</li>
</ul>
</li>
<li>있다면 바로 value를 리턴한다.</li>
<li>마지막으로 리턴 받은<code class="language-plaintext highlighter-rouge">ArrayList</code> 에 add(post)를 시행한다.</li>
</ul>
<p>if문이 없어져서 더 이해하기 쉬운 코드가 되었습니다^_^.</p>Java8에서 computeIfAbsent를 이용해 리스트 아이템을 키, 리스트로 된 맵으로 모으기core-js@2에서 core-js@3으로 마이그레이션2019-06-18T11:49:00+00:002019-06-18T11:49:00+00:00http://widian.github.io//javascript/2019/06/18/core-js@2%EC%97%90%EC%84%9C-core-js@3%EC%9C%BC%EB%A1%9C-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98<h2 id="배경">배경</h2>
<ul>
<li>core-js@2는 2017년 말에 지원이 종료되었습니다.
<ul>
<li>새로 추가되는 것들은 레거시 지원이고, 새로운 feature추가는 이루어지지 않고 있습니다.</li>
</ul>
</li>
<li>새로운 es 요소는 core-js@3에 추가되고 있습니다.</li>
<li>babel 7.4부터 core-js@3을 이용해 proposal 단계의 es 요소들도 활용 가능하도록 변경되는 중입니다.</li>
<li>자세한 변경사항은 https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md 에서 확인할 수 있습니다.</li>
</ul>
<h2 id="전환-노트">전환 노트</h2>
<ul>
<li>https://babeljs.io/blog/2019/03/19/7.4.0 페이지를 보고 따라하는 식으로 진행합니다.</li>
</ul>
<h3 id="babelrc-파일-수정">.babelrc 파일 수정</h3>
<ul>
<li><code class="language-plaintext highlighter-rouge">yarn add --dev core-js@3</code> 을 실행해서 core-js@3을 설치해 줍니다.</li>
<li><code class="language-plaintext highlighter-rouge">@babel/preset-env</code>를 사용하고 있다면, core-js@3 사용 옵션을 켜줘야 합니다.</li>
</ul>
<pre><code class="language-babelrc">presets: [
["@babel/preset-env", {
useBuiltIns: "entry",
corejs: 3,
}]
]
</code></pre>
<ul>
<li><code class="language-plaintext highlighter-rouge">@babel/plugin-transform-runtime</code> 을 사용하고 있다면, core-js@3 사용 옵션을 켜줘야 합니다.</li>
</ul>
<pre><code class="language-babelrc">plugins: [
["@babel/transform-runtime", {
corejs: 3,
}]
]
</code></pre>
<ul>
<li>그리고 <code class="language-plaintext highlighter-rouge">yarn remove @babel/runtime-corejs2 </code> 를 실행하고, <code class="language-plaintext highlighter-rouge">yarn add --dev @babel/runtime-corejs3</code> 를 실행해서 core-js@3 용 runtime-core를 설치해 줍니다.</li>
</ul>
<h3 id="파일-수정">파일 수정</h3>
<ul>
<li><code class="language-plaintext highlighter-rouge">@babel/polyfill</code>은 더이상 사용되지 않기 때문에, <code class="language-plaintext highlighter-rouge">yarn remove @babel/polyfill</code>을 사용해 줍니다. <code class="language-plaintext highlighter-rouge">@babel/polyfill</code> 에 <code class="language-plaintext highlighter-rouge">core-js@2</code>와 <code class="language-plaintext highlighter-rouge">core-js@3</code> 을 선택하는 옵션을 넣게 된다면, 양쪽 모두의 의존성을 가져야 하기 때문에 어쩔 수 없는 선택이었습니다.</li>
<li>파일 상단의 <code class="language-plaintext highlighter-rouge">import "@babel/polyfill";</code> 를 아래와 같이 바꿔줍니다.</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="dl">"</span><span class="s2">core-js/stable</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">regenerator-runtime/runtime</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">regenerator-runtime/runtime</code>는 <code class="language-plaintext highlighter-rouge">@babel/runtime-corejs3</code> 에 포함되어 있기 때문에 따로 설치해줄 필요는 없습니다.</li>
</ul>
<h3 id="js에서-window를-this로-쓰는-것을-수정하거나-제거">js에서 window를 this로 쓰는 것을 수정하거나 제거.</h3>
<ul>
<li>예를 들어</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">){</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">)}(</span><span class="k">this</span><span class="p">);</span>
</code></pre></div></div>
<ul>
<li>위와 같이 entry에서 this를 window처럼 쓰는 파일이 비정상동작합니다.
<ul>
<li>관련되는 코드가 거의 없어서 해당 코드를 리팩토링 하거나 제거했습니다.</li>
</ul>
</li>
</ul>배경JAVA ArrayList로 합병 정렬(Merge Sort)구현하기2019-03-17T06:41:00+00:002019-03-17T06:41:00+00:00http://widian.github.io//java/2019/03/17/%ED%95%A9%EB%B3%91%EC%A0%95%EB%A0%AC<h2 id="java로-합병-정렬merge-sort구현하기">JAVA로 합병 정렬(Merge Sort)구현하기.</h2>
<ul>
<li>인터넷에 JAVA로 합병 정렬 구현하기를 찾아봐서 꿀빨려고 했는데, 내가 검색해서 나온 결과는 전부 index랑 array를 쓰는 요즘 안 쓸거 같은 자바 코드로 짜여져 있는 코드밖에 없었다.</li>
<li>이직 및 취업준비를 하는데 도움이 되고자 ArrayList로 짠 합병정렬 코드를 velog에 올리겠습니다.</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.mycompany.app</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.ArrayList</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Arrays</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="cm">/**
* Merge Sort
*
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">App</span>
<span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">targetList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>(</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="mi">100</span><span class="o">,</span> <span class="mi">40</span><span class="o">,</span> <span class="mi">60</span><span class="o">,</span> <span class="mi">75</span><span class="o">,</span> <span class="mi">77</span><span class="o">,</span> <span class="mi">34</span><span class="o">,</span> <span class="mi">51</span><span class="o">,</span> <span class="mi">24</span><span class="o">,</span> <span class="mi">24</span><span class="o">,</span> <span class="mi">37</span><span class="o">,</span> <span class="mi">88</span><span class="o">));</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span> <span class="o">)</span>
<span class="o">{</span>
<span class="nc">App</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">App</span><span class="o">();</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">result</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">targetList</span><span class="o">);</span>
<span class="nc">App</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="n">targetList</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="o">}</span>
<span class="cm">/**
* int list를 받아서 한 줄로 프린트 합니다.
*
* @param result
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">print</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
<span class="n">result</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">forEach</span><span class="o">(</span><span class="n">item</span> <span class="o">-></span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%d "</span><span class="o">,</span> <span class="n">item</span><span class="o">)));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">();</span>
<span class="o">}</span>
<span class="cm">/**
* merge sort를 시행합니다.
*
* @param targetList sort할 target 입니다.
* @return
*/</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">sort</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">targetList</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 사이즈가 1보다 크다면</span>
<span class="k">if</span> <span class="o">(</span><span class="n">targetList</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">></span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 왼쪽 오른쪽을 merge 합니다.</span>
<span class="k">return</span> <span class="nf">merge</span><span class="o">(</span>
<span class="c1">// 왼쪽 / 오른쪽으로 배열을 나누고 다시 sort하도록 합니다.</span>
<span class="n">sort</span><span class="o">(</span><span class="n">targetList</span><span class="o">.</span><span class="na">subList</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">targetList</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">)),</span>
<span class="n">sort</span><span class="o">(</span><span class="n">targetList</span><span class="o">.</span><span class="na">subList</span><span class="o">(</span><span class="n">targetList</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">targetList</span><span class="o">.</span><span class="na">size</span><span class="o">()))</span>
<span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 사이즈가 1 이하라면 재귀가 종료됩니다.</span>
<span class="k">return</span> <span class="n">targetList</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="cm">/**
* 병합합니다.
*
* @param left 왼쪽 배열
* @param right 오른쪽 배열
* @return
*/</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">merge</span> <span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">left</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">right</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 결과가 될 임시 배열입니다.</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="kt">int</span> <span class="n">rightIdx</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="c1">// 왼쪽 배열을 순환하면서</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Integer</span> <span class="n">l</span> <span class="o">:</span> <span class="n">left</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// right를 끝까지 돌았는지 / right배열의 값이 l보다 작은지 확인하고,</span>
<span class="k">while</span> <span class="o">(</span><span class="n">right</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">></span> <span class="n">rightIdx</span> <span class="o">&&</span> <span class="n">l</span> <span class="o">></span> <span class="n">right</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">rightIdx</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// 작은 값을 결과에 넣습니다.</span>
<span class="n">result</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">right</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">rightIdx</span><span class="o">));</span>
<span class="n">rightIdx</span><span class="o">++;</span>
<span class="o">}</span>
<span class="c1">// left가 작다면 left element를 넣습니다.</span>
<span class="n">result</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">l</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 오른쪽 배열의 남은 숫자를 결과에 넣습니다.</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">rightIdx</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">right</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span> <span class="o">++)</span> <span class="o">{</span>
<span class="n">result</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">right</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">));</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이 글은 <a href="https://velog.io/@widian/JAVA%EB%A1%9C-%EB%B3%91%ED%95%A9-%EC%A0%95%EB%A0%ACMerge-Sort%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0.-21jtcjkoe2">velog 에도</a>작성되어있습니다.</p>JAVA로 합병 정렬(Merge Sort)구현하기. 인터넷에 JAVA로 합병 정렬 구현하기를 찾아봐서 꿀빨려고 했는데, 내가 검색해서 나온 결과는 전부 index랑 array를 쓰는 요즘 안 쓸거 같은 자바 코드로 짜여져 있는 코드밖에 없었다. 이직 및 취업준비를 하는데 도움이 되고자 ArrayList로 짠 합병정렬 코드를 velog에 올리겠습니다.브라우저에서 사용하는 javascript 모듈 기능2019-01-12T10:56:00+00:002019-01-12T10:56:00+00:00http://widian.github.io//javascript/2019/01/12/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-javascript-%EB%AA%A8%EB%93%88%EA%B8%B0%EB%8A%A5<h2 id="browser-native-js-module">Browser native js module</h2>
<ul>
<li>이 글은 2018 chrome dev summit 에서 소개된 Speed Essential 발표의 일부분(https://www.youtube.com/watch?v=reztLS3vomE)을 보고, 관련 문서(https://developers.google.com/web/fundamentals/primers/modules)를 번역하여 작성된 글입니다.</li>
<li>작성시점은 1월 12일입니다.</li>
<li>Module / nomodule의 지원 버전에 대해 확인하려면 <a href="https://jakearchibald.com/2017/es-modules-in-browsers/">jakearchibald의 블로그 글</a> 을 참고해 주시기 바랍니다.</li>
</ul>
<h2 id="js-모듈이란-무엇인가">JS 모듈이란 무엇인가?</h2>
<ul>
<li>JS 모듈(또는 ES Module, ECMAScript 모듈이라고 일컫는)은 자바스크립트의 중요한 새 기능, 또는 새 기능들의 통칭입니다. 이미 과거에 유저단계에서 구현된 많은 JS 모듈 시스템이 있었습니다, 예를들어 node js의 <a href="https://nodejs.org/docs/latest-v10.x/api/modules.html">CommonJS</a>, <a href="https://github.com/amdjs/amdjs-api/blob/master/AMD.md">AMD</a>, 그 외에 다른 걸 사용했을 지도 모릅니다. 어쨌든 모든 유저단계에서 구현된 모듈 시스템은 “코드뭉치를 import하고 export한다”는 공통점을 가지고 있습니다.</li>
<li>Javascript는 이제 모듈시스템을 표준화합니다. 자바스크립트 모듈은 단지 export예약어를 앞에 prefix로 붙여주는 것 만으로 const, function, 또는 다른 정의나 변수들을 export할 수 있습니다.</li>
<li>import 예약어를 사용하면 export예약어를 사용해 내보낸 것을 사용할 수 있습니다.</li>
<li>default 예약어를 사용하면 export한 변수/상수/함수 등을 import하는 곳에서 어떠한 이름으로든 사용할 수 있게 해줍니다.</li>
<li>모듈은 클래식 스크립트(모듈이 아닌 스크립트)비교해 다음과 같은 차이점을 갖고 있습니다.
<ul>
<li>모듈은 strict mode를 default로 사용합니다.</li>
<li>HTML style comment는 더 이상 사용할 수 없습니다.
<ul>
<li><code class="language-plaintext highlighter-rouge"><!--</code> 로 둘러싸인 주석을 의미합니다.</li>
</ul>
</li>
<li>모듈은 이제 lexical한 top level scope를 가집니다. 더이상 모듈 내에서 전역변수를 <code class="language-plaintext highlighter-rouge">var foo=42;</code> 와 같이 선언하더라도, <code class="language-plaintext highlighter-rouge">window.foo</code>로 접근할 수 없습니다.</li>
<li>import, export syntax는 오직 모듈 내에서만 사용 가능하고 클래식 스크립트 내에서는 사용할 수 없습니다.</li>
</ul>
</li>
<li>이러한 차이점 때문에, 같은 자바스크립트 코드더라도 module인지 클래식 스크립트인지에 따라 다르게 다뤄져야합니다. 이처럼 자바스크립트 런타임에는 어떤 스크립트가 모듈인지 알고 있어야합니다.</li>
</ul>
<h2 id="브라우저에서-js-모듈-사용하기">브라우저에서 JS 모듈 사용하기</h2>
<ul>
<li>브라우저에서 모듈 스크립트를 사용하기 위해, <code class="language-plaintext highlighter-rouge"><script> </code>태그 안에 type attribute에 module을 추가하는 것으로 사용할 수 있습니다.</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"main.mjs"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">nomodule</span> <span class="na">src=</span><span class="s">"legacy.js"</span><span class="nt">></script></span>
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">type="module"</code> 을 이해하는 브라우저는 <strong><code class="language-plaintext highlighter-rouge">nomodule</code>attribute를 가진 스크립트를 무시합니다.</strong>
<ul>
<li>(번역 주석 :) safari 10.1 에서는 nomodule을 ignore하지 않기 때문에 특수한 스크립트가 필요합니다 (<del>거지같은 사파리</del>). 일부 <a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10525830/">edge 16의 마이너 버전</a>, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1330900">파이어폭스 55의 마이너 버전</a> 에서도 지원하지 않습니다.
<ul>
<li>대응을 위해서는 다음 <a href="https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc">gist</a>를 참고해 주세요.</li>
</ul>
</li>
</ul>
</li>
<li>이 기능을 이용해, module기능을 지원하는 브라우저에서는 module 이전의 es6기능을 transpile하지 않고 사용할 수 있기 때문에 (arrow function이나 async-await 등), 성능상의 이점을 크게 가져갈 수 있습니다.
<ul>
<li><a href="https://codepen.io/samthor/pen/MmvdOM">module 이전에 브라우저별로 지원되던 ES6 기능</a></li>
</ul>
</li>
<li>만약 모듈 코드가 es7, 또는 그 이후의 코드가 포함되어있다면 <a href="https://philipwalton.com/articles/deploying-es2015-code-in-production-today/">Phlip Walton이 블로그에 설명한 글대로, module 이후의 자바스크립트만 transpile되도록 변경하는 것으로 큰 용량/성능 이득을 받는 방법이 있습니다</a>.</li>
<li>레거시 브라우저는 nomodule에 있는 payload만 가지게 될 것입니다.</li>
</ul>
<h4 id="브라우저별-모듈-클래식-스크립트의-차이점">브라우저별 모듈-클래식 스크립트의 차이점</h4>
<ul>
<li>위에서 설명한대로, 모듈과 클래식 스크립트는 동작방식도 조금 다릅니다. 브라우저별로도 조금 달라집니다.</li>
<li>예를들어, 모듈은 단 한번만 실행되지만, 클래식 스크립트는 DOM에 추가해서 load된 만큼 실행됩니다.</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script </span><span class="na">src=</span><span class="s">"classic.js"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"classic.js"</span><span class="nt">></script></span>
<span class="c"><!-- classic.js 는 2회 실행됩니다. --></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"module.mjs"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"module.mjs"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span><span class="nt">></span><span class="k">import</span> <span class="dl">'</span><span class="s1">./module.mjs</span><span class="dl">'</span><span class="p">;</span><span class="nt"></script></span>
<span class="c"><!-- module.mjs 단 한번만 실행됩니다. --></span>
</code></pre></div></div>
<ul>
<li>또한, 클래식 스크립트는 로드할 때 CORS 헤더에 관계 없이 항상 로드되는 스펙이었지만, module을 통해 mjs파일을 불러올 때는 <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin: *</code> 와 같이 CORS에 맞는 헤더가 필요합니다.</li>
<li>클래식 스크립트에서는 <code class="language-plaintext highlighter-rouge">defer</code> attribute등을 통해 <code class="language-plaintext highlighter-rouge">async</code> attribute가 붙은 스크립트의 다운로드가 막히지 않도록 할 수 없었지만, module에서는 가능합니다.</li>
</ul>
<h4 id="파일-확장자에-관련해">파일 확장자에 관련해</h4>
<ul>
<li>위에서는 모듈 자바스크립트를 사용하기 위해 <code class="language-plaintext highlighter-rouge">.mjs</code> 확장자를 사용하고 있는데, 사실 웹에서는 이 extension을 따로 구분하지 않고 일반적인 <a href="https://html.spec.whatwg.org/multipage/scripting.html#scriptingLanguages:javascript-mime-type">Javascript MIME type(<code class="language-plaintext highlighter-rouge">text/javascript</code>)로 제공하게 됩니다</a>.</li>
<li>브라우저는 오직 type attribute만을 이용해 모듈과 모듈이 아닌 스크립트를 구분합니다.</li>
<li>그럼에도, 구글에서는 <code class="language-plaintext highlighter-rouge">.mjs</code>확장자를 사용하길 권장하는데, 거기엔 두 가지 이유가 있습니다.
<ul>
<li>개발할 때 항상 코드 내용을 보지 않더라도, 열려는 파일이 모듈파일인지를 알고 사용할 수 있습니다.</li>
<li>nodejs의 실험적 모듈 기능 지원은 오직 <code class="language-plaintext highlighter-rouge">.mjs</code>파일에서만 동작합니다.</li>
</ul>
</li>
</ul>
<h4 id="사용하려는-모듈-특정하기">사용하려는 모듈 특정하기</h4>
<ul>
<li>모듈을 <code class="language-plaintext highlighter-rouge">import</code>할 때는 다음과 같이 사용합니다.</li>
</ul>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./lib.mjs</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li>아래와 같은 형태는 아직 지원되지 않습니다.</li>
</ul>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Not supported (yet):</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">jquery</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">lib.mjs</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">modules/lib.mjs</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li>아래와 같은 형태는 현재 지원됩니다.</li>
</ul>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Supported:</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./lib.mjs</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../lib.mjs</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">/modules/lib.mjs</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">shout</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">https://simple.example/modules/lib.mjs</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>
<h3 id="모듈은-기본적으로-지연deferred됩니다">모듈은 기본적으로 지연(Deferred)됩니다.</h3>
<ul>
<li>클래식 스크립트는 기본적으로 HTML 파서를 지연시킵니다.</li>
<li>모듈에서는 defer 어트리뷰트를 추가하는 것으로 스크립트 다운로드와 parsing을 병행하도록 할 수 있습니다.</li>
<li>기본적으로 모듈은 defer되기 때문에 모듈간의 의존관계에 따라 문제가 생기지 않습니다.</li>
</ul>
<h3 id="다른-모듈-기능">다른 모듈 기능</h3>
<h4 id="dynamic-import-동적-import">dynamic import (동적 import)</h4>
<ul>
<li>static import를 사용하려면, 모든 모듈이 다운로드 된 뒤에 코드가 execution됩니다. 유저 버튼 액션에서만 사용되는 모듈이 있다면, 유저 액션을 받아서 모듈을 로드하도록 하는 것이 전체 로드 시간을 줄이는 데 도움이 될 것입니다.</li>
</ul>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">module</span><span class="dl">"</span><span class="o">></span>
<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">moduleSpecifier</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">./lib.mjs</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="p">{</span><span class="nx">repeat</span><span class="p">,</span> <span class="nx">shout</span><span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="k">import</span><span class="p">(</span><span class="nx">moduleSpecifier</span><span class="p">);</span>
<span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// → 'hello hello'</span>
<span class="nx">shout</span><span class="p">(</span><span class="dl">'</span><span class="s1">Dynamic import in action</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// → 'DYNAMIC IMPORT IN ACTION!'</span>
<span class="p">})();</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span></code></pre></div></div>
<ul>
<li>static import와는 다르게, dynamic import는 regular script내에서도 사용될 수 있습니다. 이것은 기존 코드베이스에 모듈 기능을 점진적으로 추가시킬 때 유용하게 사용될 것입니다.
<ul>
<li>이에 대해서는 <a href="https://developers.google.com/web/updates/2017/11/dynamic-import">구글의 dynamic import에 관한 글</a> 을 참조해주세요.</li>
</ul>
</li>
</ul>
<h4 id="importmeta">import.meta</h4>
<ul>
<li>모듈과 관련된 새 기능으로,<code class="language-plaintext highlighter-rouge">import.meta</code> 가 있습니다. 현재 모듈에 대한 메타데이터를 제공하는 기능으로, ECMAScript에서 정의되지 않은 호스트 환경에 종속적인 기능을 사용하는 데에 정보를 제공해주는 기능입니다.</li>
<li>자세한 내용은 원본을 참고해주세요.</li>
</ul>
<h3 id="성능-권고">성능 권고</h3>
<h4 id="keep-bundling">Keep bundling</h4>
<ul>
<li>모듈로 나눠졌더라도 번들링을 해주세요
<ul>
<li>http/2 환경에서도 100개, 300개로 나눠진 모듈 환경을 테스트 했을때 병목현상이 발생했습니다.</li>
<li>https://docs.google.com/document/d/1ovo4PurT_1K4WFwN2MYmmgbLcr7v6DRQN67ESVA-wq0/pub</li>
</ul>
</li>
<li>모듈을 프로덕션에 배포하기 전에 번들러를 돌려서 모듈과 코드의 수를 줄이는 것이 퍼포먼스 측면에서도, minifying 측면에서도 이득입니다.</li>
</ul>
<h4 id="번들링하기-vs-번들링하지-않고-그대로-보내기의-trade-off">번들링하기 vs 번들링하지 않고 그대로 보내기의 trade-off</h4>
<ul>
<li>사실 웹 개발에서는 모든 것이 트레이드 오프 (뭔가를 포기하는 대신 뭔가를 받아야하는) 과정이기 때문에, 어떤 환경에서는 번들링하지 않는 것이 이득일 수도 있습니다.</li>
<li>대략 200kb정도의 코드일 경우는 번들링되지 않은 모듈을 사용하는 것으로 캐시 히트를 늘리는 것이 전체 번들링을 하는 것 보다는 이득이 될 수 있습니다.
<ul>
<li>그러나, 이 역시 유저 패턴에 따라 천차만별이기 때문에 데이터에 기반한 결정을 내리도록 하십시오.</li>
</ul>
</li>
</ul>
<h4 id="세분화된-모듈-사용하기">세분화된 모듈 사용하기</h4>
<ul>
<li>세분화된 모듈을 사용할 수록, 다른 코드에서 모듈을 참조할 때의 오버헤드가 작아지고, 번들러가 bundling을 할 때 필요 없는 코드를 쳐낼 때의 오버헤드도 줄어들게 됩니다.
<ul>
<li>사용하지 않는 번들은 브라우저에서도 모듈을 다운로드 하지 않기 때문입니다.</li>
</ul>
</li>
<li>추후 웹브라우저에서는 <a href="https://developers.google.com/web/fundamentals/primers/modules#web-packaging">네이티브 번들러</a>를 제공할 계획이기 때문에, 미래의 개발환경을 위해서도 도움이 될 것입니다.</li>
</ul>
<h4 id="모듈-preload하기">모듈 preload하기</h4>
<ul>
<li><a href="https://developers.google.com/web/updates/2017/12/modulepreload">모듈 preload</a>를 이용해 모듈 전달을 더 최적화할 수 있습니다.</li>
<li>모듈 preload는 의존성을 미리 로드해서 미리 컴파일 할 수 있도록 도와줍니다.</li>
<li>만약 모듈의 종속성 트리가 비대하다면, 브라우저는 모듈간의 종속성을 확인하기 위해 많은 http요청을 보내야합니다. 그러나 모듈을 preload하는 것으로 이러한 요청을 한번에 처리할 수 있습니다.</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><link</span> <span class="na">rel=</span><span class="s">"modulepreload"</span> <span class="na">href=</span><span class="s">"lib.mjs"</span><span class="nt">></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"modulepreload"</span> <span class="na">href=</span><span class="s">"main.mjs"</span><span class="nt">></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"main.mjs"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">nomodule</span> <span class="na">src=</span><span class="s">"fallback.js"</span><span class="nt">></script></span>
</code></pre></div></div>
<h4 id="http2-사용하기">http/2 사용하기</h4>
<ul>
<li>반드시 http/2 를 사용해야만 multiplexing을 사용할 수 있어서 모듈 사용의 이점을 최대화할 수 있습니다.</li>
<li>그러나 크롬팀에서 http/2 기능인 서버푸시를 사용해보았지만, 특별히 엄청난 기능향상을 경험할 수는 없었습니다.</li>
<li>http/2를 사용하는것은 필수적이지만, 서버 푸시는 아직 완벽한 해결책은 되지 않습니다 (not a silver bullet).</li>
</ul>
<h3 id="현재-웹-환경에서의-module-사용">현재 웹 환경에서의 module 사용</h3>
<ul>
<li><a href="https://www.chromestatus.com/metrics/feature/timeline/popularity/2062">크롬 통계</a> 에 따르면 아직 전체 웹 페이지의 0.08%정도밖에 사용하지 않고 있습니다.
<ul>
<li>1월 10일 기준 약 0.13%까지 올라오긴 했습니다.</li>
<li>이 기준은 dynamic import를 포함하지 않은 기준입니다.</li>
</ul>
</li>
</ul>
<h2 id="js-module의-다음은-무엇이-될까요">JS Module의 다음은 무엇이 될까요?</h2>
<ul>
<li>module resolution algorithm 개선
<ul>
<li>모듈간의 dependency 그래프에 따라 어떤 모듈을 사용해야할지 결정하는 알고리즘</li>
<li>현재는 O(n^2) 이지만, 새 알고리즘을 사용하는 것으로 O(n)까지 개선할 계획입니다.</li>
</ul>
</li>
<li>worklets and web workers
<ul>
<li>worklet을 이용해 모듈을 worklet에 포함해서 사용할 수 있도록 하는 내용입니다.</li>
<li>크롬 65에서 paint worklet 모듈이, 66에서는 audio worklet, 67에서는 layout worklet이 지원되었습니다.</li>
</ul>
</li>
<li>맵으로 module import하기.</li>
<li>브라우저 단에서 bundling 기능 지원</li>
<li>layered api
<ul>
<li>chrome dev summit에서 소개된 <a href="https://www.chromestatus.com/feature/5673195159945216">Virtual Scroller</a> (<a href="https://www.youtube.com/watch?v=UtD41bn6kJ0">Virtual scroller 영상</a>)를 html태그로 포함될 수 있도록 모듈 로딩 단계에서 기능을 지원해주는 내용입니다.</li>
</ul>
</li>
<li>아래와 같이 모듈로 virtual-scroller를 추가하면</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script
</span><span class="na">type=</span><span class="s">"module"</span>
<span class="na">src=</span><span class="s">"std:virtual-scroller|https://example.com/virtual-scroller.mjs"</span>
<span class="nt">></script></span>
</code></pre></div></div>
<ul>
<li>아래와 같이 custom virtual-scroller element를 사용할 수 있게 하는 것입니다.</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><virtual-scroller></span>
<span class="c"><!-- Content goes here. --></span>
<span class="nt"></virtual-scroller></span>
</code></pre></div></div>
<h2 id="개인적인-의견">개인적인 의견</h2>
<ul>
<li>결국 browser module기능을 사용하더라도, 번들링이나 transplie은 반드시 사용해야 합니다.</li>
<li>아직 webpack, parcel등을 사용하지 않는 레거시 프로젝트에서 번들링/transpile대신 해당 기능을 이용해 서비스를 전환할 계획이라면 원점으로 돌아가주세요.</li>
<li>module 사용전에 번들링과 babel을 이용한 transpile 적용이 선행되어야 한다는 점을 유념하셔야 합니다.
<ul>
<li>오히려 module기능 활용을 위해 legacy/non-legacy 두가지 형태의 bundling, transpile이 필요합니다.</li>
</ul>
</li>
<li>너무 낮은 버전 대상의 transpile때문에 성능이 낮아지는 경우를 제외한다면, 아직(2019년 1월 12일 기준)은 browser module 기능을 사용해야할 필요성은 적어 보입니다.</li>
</ul>Browser native js module루트권한 없이 포트 바인딩하기2018-12-28T03:00:00+00:002018-12-28T03:00:00+00:00http://widian.github.io//blog/2018/12/28/%EB%A3%A8%ED%8A%B8%EA%B6%8C%ED%95%9C-%EC%97%86%EC%9D%B4-%ED%8F%AC%ED%8A%B8-%EB%B0%94%EC%9D%B8%EB%94%A9%ED%95%98%EA%B8%B0<h3 id="nonrootportbinding">NonRootPortBinding</h3>
<ul>
<li>httpd, nginx 등의 웹서버 어플리케이션이 1024 미만의 포트바인딩을 루트권한 없이 해주는 일을 말합니다.
<ul>
<li>1024미만의 포트는 privileged port여서 root권한 없이는 사용할 수 없도록 제한되어있습니다.</li>
</ul>
</li>
<li>https://wiki.apache.org/httpd/NonRootPortBinding 의 글을 번역했습니다.</li>
</ul>
<h3 id="이게-왜-필요한가요">이게 왜 필요한가요?</h3>
<ul>
<li>웹서버 어플리케이션이 su 권한을 얻고 실행되었을 때, 어플리케이션의 보안허점을 통해 su 권한을 탈취할 가능성이 있습니다.
<ul>
<li>nginx에서는 <code class="language-plaintext highlighter-rouge">user</code> 설정을 추가할 수 있긴 합니다.</li>
</ul>
</li>
</ul>
<h3 id="어떻게-하나요">어떻게 하나요?</h3>
<ul>
<li>루트 권한을 얻은 상태에서, 다음 쉘 스크립트를 실행합니다.
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@myhost <span class="nv">$ </span>setcap <span class="nv">cap_net_bind_service</span><span class="o">=</span>+ep /usr/sbin/httpd
root@myhost <span class="nv">$ </span>setcap <span class="nv">cap_net_bind_service</span><span class="o">=</span>+ep /path/to/web/application/bin
</code></pre></div> </div>
</li>
<li>다음 스크립트로 권한이 정상적으로 부여되었는지 확인합니다.
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@myhost <span class="nv">$ </span>getcap /usr/sbin/httpd
/usr/sbin/httpd <span class="o">=</span> cap_net_bind_service+ep
</code></pre></div> </div>
</li>
<li>그 다음 유저 로컬 환경에서 웹서버 어플리케이션을 실행해 보는 것으로 정상동작하는지 확인합니다.</li>
</ul>
<h3 id="주의">주의</h3>
<ul>
<li>해당 옵션을 사용하게 되면, su권한이 없는 유저가 su권한이 필요한 포트에 해당 웹어플리케이션으로 웹 서버를 실행할 수 있게 됩니다.
<ul>
<li>chmod, chown 등으로 웹서버 어플리케이션의 권한을 제한해야 합니다.</li>
</ul>
</li>
</ul>
<h3 id="다른-방법">다른 방법</h3>
<ul>
<li>iptables를 이용한 NAT로 유저는 8080포트에 웹서버를 실행하고, linux는 80포트로 온 요청을 8080포트로 변환(translate)해줄 수 있습니다.
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@myhost <span class="nv">$ </span>iptables <span class="nt">-t</span> nat <span class="nt">-A</span> PREROUTING <span class="nt">-d</span> <ip> <span class="nt">-p</span> tcp <span class="nt">--dport</span> 80 <span class="nt">-m</span> addrtype <span class="nt">--dst-type</span> LOCAL <span class="nt">-j</span> DNAT <span class="nt">--to-destination</span> <ip>:8080
root@myhost <span class="nv">$ </span>iptables <span class="nt">-t</span> nat <span class="nt">-A</span> OUTPUT <span class="nt">-d</span> <ip> <span class="nt">-p</span> tcp <span class="nt">--dport</span> 80 <span class="nt">-m</span> addrtype <span class="nt">--dst-type</span> LOCAL <span class="nt">-j</span> DNAT <span class="nt">--to-destination</span> <ip>:8080
</code></pre></div> </div>
</li>
<li>ip에는 현재 ip를 넣어주시면 됩니다.</li>
</ul>
<h3 id="더-다른-방법">더 다른 방법.</h3>
<ul>
<li>공유기 등 NAT역할을 하는 라우터에 포트포워딩을 해줍니다.
<ul>
<li>위와 같은 방식이지만, 위에서는 서버가 직접 포트포워딩을 하는 것이 다릅니다.</li>
</ul>
</li>
</ul>
<h3 id="추가">추가</h3>
<ul>
<li>이 글은 <a href="https://velog.io/@widian/%EB%A3%A8%ED%8A%B8%EA%B6%8C%ED%95%9C-%EC%97%86%EC%9D%B4-%ED%8F%AC%ED%8A%B8-%EB%B0%94%EC%9D%B8%EB%94%A9%ED%95%98%EA%B8%B0">velog</a> 에도 포스트했습니다.</li>
</ul>NonRootPortBinding httpd, nginx 등의 웹서버 어플리케이션이 1024 미만의 포트바인딩을 루트권한 없이 해주는 일을 말합니다. 1024미만의 포트는 privileged port여서 root권한 없이는 사용할 수 없도록 제한되어있습니다. https://wiki.apache.org/httpd/NonRootPortBinding 의 글을 번역했습니다.