<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Rubbish Philosopher</title>
    <description>This statement is false.</description>
    <link>http://philosophical.one/</link>
    <atom:link href="http://philosophical.one/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>Tail Recursion in Python</title>
        <description>&lt;p&gt;요즘 &lt;a class=&quot;reference external&quot; href=&quot;https://leetcode.com/&quot;&gt;LeetCode&lt;/a&gt; 에서 하루에 하나씩 알고리즘 문제를
풀고 있는데,&lt;a class=&quot;footnote-reference&quot; href=&quot;#leet&quot; id=&quot;id1&quot;&gt;[1]&lt;/a&gt; 재귀 호출을 이용할 때가 많다. 특히 트리나 그래프를 &lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Depth-first_search&quot;&gt;깊이
우선 탐색(DFS)&lt;/a&gt;할 때 직접
&lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Stack_(abstract_data_type)&quot;&gt;스택&lt;/a&gt;에 값을
넣고 빼지 않아도 되기 때문에 편리하게 구현할 수 있다. 당연한 이야기겠지만, 내
코드에서 관리되는 스택이 아니라 시스템 스택을 사용하기 때문에 가능한 것이다.&lt;/p&gt;
&lt;p&gt;재귀 호출은 구현이 편리하긴 하지만 나름의 문제를 가지고 있다. 재귀 호출 스택의
깊이가 얕은 경우에는 어떻게 구현하든 별로 상관이 없는데, 깊이가 깊어지면 문제가
될 수도 있다.&lt;/p&gt;
&lt;p&gt;그럼 파이썬에서 가능한 호출 스택의 최대 깊이는 얼마일까?&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;recurse&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;name&quot;&gt;recurse&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;나와 비슷한 궁금증을 가진 사람의 블로그 포스트를 참고하여&lt;a class=&quot;footnote-reference&quot; href=&quot;#max-depth&quot; id=&quot;id3&quot;&gt;[6]&lt;/a&gt; 현재
시스템에서 가능한 가장 깊은 호출 스택의 깊이를 측정해보았다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
Traceback (most recent call last):
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 3, in recurse
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 3, in recurse
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 3, in recurse
  [Previous line repeated 992 more times]
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 2, in recurse
RecursionError: maximum recursion depth exceeded while calling a Python object
997
&lt;/pre&gt;
&lt;p&gt;포스트 작성자의 시스템과 마찬가지로 내 시스템에서도 997이 최대로 나온다.&lt;/p&gt;
&lt;p&gt;참고로 이 값은 &lt;tt class=&quot;docutils literal&quot;&gt;sys.setrecursionlimit()&lt;/tt&gt; 함수를 이용해서 오버라이드 할 수
있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#setrecursionlimit&quot; id=&quot;id4&quot;&gt;[7]&lt;/a&gt; 물론 무한대로 늘릴 수 있는건 아니다. 아주 큰 값을
넣고 실험해본 결과 다음과 같이 segmentation fault가 발생했다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
...
34936
34937
34938
[1]    73359 segmentation fault  python
&lt;/pre&gt;
&lt;p&gt;다시 말해서 &lt;tt class=&quot;docutils literal&quot;&gt;N&lt;/tt&gt;의 값이 충분히 크다면 마음 놓고 재귀 호출을 사용할 수 없다는
뜻이다. 해결책은 크게 두 가지다.&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;Iterative solution&lt;/li&gt;
&lt;li&gt;Tail recursion elimination&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;오늘은 두 번째 해결책에 대한 이야기를 해보고자 한다.&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;tail-recursion&quot;&gt;
&lt;h2&gt;Tail Recursion&lt;/h2&gt;
&lt;p&gt;검색을 해보니 한국어로는 '꼬리 재귀'라고 표현하는 것으로 보인다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#tail-recursion-ko&quot; id=&quot;id5&quot;&gt;[3]&lt;/a&gt; 개인적으로는 tail recursion이 더 익숙한 용어이긴 하지만,
글을 쓸 때 한/영 전환을 하는 것은 번거로운 일이기 때문에(?) 이 글에서는 꼬리
재귀로 표기하도록 하겠다.&lt;/p&gt;
&lt;p&gt;위키피디아는 꼬리 재귀를 다음과 같이 정의하고 있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#id18&quot; id=&quot;id6&quot;&gt;[2]&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
In computer science, a tail call is a subroutine call performed as the
final action of a procedure. If a tail call might lead to the same
subroutine being called again later in the call chain, the subroutine is
said to be tail-recursive, which is a special case of recursion. Tail
recursion (or tail-end recursion) is particularly useful, and often easy to
handle in implementations.&lt;/blockquote&gt;
&lt;p&gt;조금 더 간단히 이야기 하자면, 함수에서 마지막으로 호출하는 함수가 자기
자신이고, 재귀 호출이 끝난 뒤 추가적인 연산이 필요하지 않다면 꼬리 재귀라고 볼
수 있다. 재귀 호출 후 추가적인 연산이 필요하지 않다면 진짜로 함수를 호출하는 것
처럼 시스템 콜 스택에 이것저것 저장하지 않고 선형적으로 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;팩토리얼을 연산하는 파이썬 코드를 예제로 사용해보자.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;tt class=&quot;docutils literal&quot;&gt;factorial(n - 1)&lt;/tt&gt; 호출이 끝난 후 &lt;tt class=&quot;docutils literal&quot;&gt;n&lt;/tt&gt;의 값과 곱해주어야 하기 때문에, 다시
말해서, &lt;tt class=&quot;docutils literal&quot;&gt;factorial(n)&lt;/tt&gt;의 실행이 완료 되지 않은 상태에서 &lt;tt class=&quot;docutils literal&quot;&gt;factorial(n -
1)&lt;/tt&gt;를 호출하기 때문에 리턴 주소를 저장하기 위해서 시스템 콜 스택을 사용할 수
밖에 없다.&lt;/p&gt;
&lt;p&gt;조금 더 깊숙히 들여다보기 위해 &lt;a class=&quot;reference external&quot; href=&quot;https://opensource.com/article/18/4/introduction-python-bytecode&quot;&gt;파이썬 바이트 코드&lt;/a&gt;를
해부해보도록 하자. &lt;a class=&quot;reference external&quot; href=&quot;https://docs.python.org/3/library/dis.html&quot;&gt;&lt;tt class=&quot;docutils literal&quot;&gt;dis&lt;/tt&gt; 패키지&lt;/a&gt;를 이용하면 손쉽게 바이트 코드를 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; dis.dis(factorial)
2           0 LOAD_FAST                0 (n)
            2 LOAD_CONST               1 (0)
            4 COMPARE_OP               2 (==)
            6 POP_JUMP_IF_FALSE       12

3           8 LOAD_CONST               2 (1)
            10 RETURN_VALUE

5     &amp;gt;&amp;gt;   12 LOAD_FAST                0 (n)
            14 LOAD_GLOBAL              0 (factorial)
            16 LOAD_FAST                0 (n)
            18 LOAD_CONST               2 (1)
            20 BINARY_SUBTRACT
            22 CALL_FUNCTION            1
            24 BINARY_MULTIPLY
            26 RETURN_VALUE
            28 LOAD_CONST               0 (None)
            30 RETURN_VALUE
&lt;/pre&gt;
&lt;p&gt;여기서 주의 깊게 봐야 할 부분은 &lt;tt class=&quot;docutils literal&quot;&gt;factorial()&lt;/tt&gt; 함수를 호출하는 부분이다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
14 LOAD_GLOBAL              0 (factorial)
16 LOAD_FAST                0 (n)
18 LOAD_CONST               2 (1)
20 BINARY_SUBTRACT
22 CALL_FUNCTION            1
&lt;/pre&gt;
&lt;p&gt;평가 스택(evaluation stack)에 &lt;tt class=&quot;docutils literal&quot;&gt;n&lt;/tt&gt;과 &lt;tt class=&quot;docutils literal&quot;&gt;1&lt;/tt&gt;을 넣은 후 &lt;tt class=&quot;docutils literal&quot;&gt;BINARY_SUBTRACT&lt;/tt&gt;
명령어를 수행하면 평가 스택에서 값 두 개를 꺼내서 빼기 연산을 수행하고, 그
결과를 다시 평가 스택에 넣는다. 그런 다음 &lt;tt class=&quot;docutils literal&quot;&gt;CALL_FUNCTION&lt;/tt&gt; 명령어의
인자(&lt;tt class=&quot;docutils literal&quot;&gt;1&lt;/tt&gt;) 만큼 평가 스택에서 값을 꺼내고, 그 전에 넣어 놓았던 함수
이름(&lt;tt class=&quot;docutils literal&quot;&gt;factorial&lt;/tt&gt;)을 꺼내서 함수를 호출한다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
24 BINARY_MULTIPLY
26 RETURN_VALUE
&lt;/pre&gt;
&lt;p&gt;바이트 코드를 계속 이어서 보자면, &lt;tt class=&quot;docutils literal&quot;&gt;factorial()&lt;/tt&gt; 함수 호출이 끝나면 함수 실행
결과 값이 평가 스택에 저장되고, 곧이어 &lt;tt class=&quot;docutils literal&quot;&gt;BINARY_MULTIPLY&lt;/tt&gt; 명령어를 호출한다.
함수 호출 결과값과 &lt;tt class=&quot;docutils literal&quot;&gt;LOAD_GLOBAL (factorial)&lt;/tt&gt; 명령어 이전에 평가 스택에
넣어놨던 &lt;tt class=&quot;docutils literal&quot;&gt;n&lt;/tt&gt;을 꺼내서 곱한 후 그 결과를 다시 평가 스택에 넣는다.
&lt;tt class=&quot;docutils literal&quot;&gt;RETURN_VALUE&lt;/tt&gt; 명령어는 평가 스택에서 값을 하나 꺼내 현재 함수의
호출자(caller)에게 돌려준다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
return n * factorial(n - 1)
&lt;/pre&gt;
&lt;p&gt;이로써 위와 같은 파이썬 코드가 수행되는 과정을 간략하게 살펴보았는데, 핵심은
현재 함수(&lt;tt class=&quot;docutils literal&quot;&gt;factorial(n)&lt;/tt&gt;)에서 결과값을 반환하기 위해서는 현재 함수의 인자
값(&lt;tt class=&quot;docutils literal&quot;&gt;n&lt;/tt&gt;)을 평가 스택에 가지고 있다가 그 다음 호출 될 함수(&lt;tt class=&quot;docutils literal&quot;&gt;factorial(n -
1)&lt;/tt&gt;)의 결과 값과 함께 연산을 해야 한다는 점이다. 이렇게 되면 꼬리 재귀의 조건을
만족하지 못한다.&lt;/p&gt;
&lt;!-- TODO: Define a set of styles for this --&gt;
&lt;div style=&quot;margin: 1em 0 1.5em 0; padding: 1em; background: #f8ffff; color: rgba(0,0,0,.87); box-shadow: 0 0 0 1px #a9d5de inset,0 0 0 0 transparent; border-radius: 4px; font-size: 0.9em;&quot;&gt;
    &lt;h4 style=&quot;margin: 0.5em 0;&quot;&gt;토막 상식&lt;/h4&gt;
    &lt;div&gt;&lt;p&gt;함수의 최상위 블럭에 &lt;tt class=&quot;docutils literal&quot;&gt;return&lt;/tt&gt; 구문이 없을 경우 함수의 바이트 코드 맨 뒤쪽에는
항상 &lt;tt class=&quot;docutils literal&quot;&gt;None&lt;/tt&gt;을 반환하는 코드가 붙는다. 예를 들어서, 다음과 같은 코드의 경우
&lt;tt class=&quot;docutils literal&quot;&gt;return&lt;/tt&gt; 구문이 실행되지 않는 경우는 없겠지만, &lt;tt class=&quot;docutils literal&quot;&gt;return&lt;/tt&gt; 구문이 모두
&lt;tt class=&quot;docutils literal&quot;&gt;if&lt;/tt&gt;/&lt;tt class=&quot;docutils literal&quot;&gt;else&lt;/tt&gt; 조건문 안쪽에 있고, 최상위 블럭에는 &lt;tt class=&quot;docutils literal&quot;&gt;return&lt;/tt&gt; 구문이 존재하지
않는다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;x&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;바이트 코드의 끝 부분을 보면 다음과 같이 &lt;tt class=&quot;docutils literal&quot;&gt;None&lt;/tt&gt;을 반환하는 코드가 붙는다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; dis.dis(f)
..(중략)..
         18 RETURN_VALUE
         20 LOAD_CONST               0 (None)
         22 RETURN_VALUE
&lt;/pre&gt;
&lt;p&gt;반면, 다음과 같은 코드는 위 코드와 논리적으로 아무런 차이가 없지만, &lt;tt class=&quot;docutils literal&quot;&gt;return&lt;/tt&gt;
구문이 함수의 최상위 블럭에 존재하기 때문에 &lt;tt class=&quot;docutils literal&quot;&gt;None&lt;/tt&gt;을 반환하는 코드가
추가되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;x&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;따라서 다음과 같이 &lt;tt class=&quot;docutils literal&quot;&gt;return x + 1&lt;/tt&gt; 구문을 마지막으로 따로 추가되는 명령어는
없다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; dis.dis(g)
..(중략)..
4     &amp;gt;&amp;gt;   12 LOAD_FAST                0 (x)
            14 LOAD_CONST               2 (1)
            16 BINARY_ADD
            18 RETURN_VALUE
&lt;/pre&gt;
&lt;p&gt;다음과 같이 아무것도 하지 않는 함수라고 하더라도 &lt;tt class=&quot;docutils literal&quot;&gt;None&lt;/tt&gt;을 반환하도록
되어있다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;pass&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;바이트 코드는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; dis.dis(h)
1           0 LOAD_CONST               0 (None)
            2 RETURN_VALUE
&lt;/pre&gt;
&lt;p&gt;참고: CPython 이외의 인터프리터에서는 테스트해보지 않았다.&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;그럼 이 함수를 꼬리 재귀로 바꾸려면 어떻게 해야 할까. 재귀 호출을 하는 부분에서
추가적인 연산이 필요 없도록 만들면 된다. 코드를 살짝 수정하여 아래와 같이
바꾸어 볼 수 있을 것이다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;바이트 코드도 살펴보도록 하자.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
&amp;gt;&amp;gt;&amp;gt; dis.dis(factorial)
2           0 LOAD_FAST                0 (n)
            2 LOAD_CONST               1 (0)
            4 COMPARE_OP               2 (==)
            6 POP_JUMP_IF_FALSE       12

3           8 LOAD_FAST                1 (result)
            10 RETURN_VALUE

5     &amp;gt;&amp;gt;   12 LOAD_GLOBAL              0 (factorial)
            14 LOAD_FAST                0 (n)
            16 LOAD_CONST               2 (1)
            18 BINARY_SUBTRACT
            20 LOAD_FAST                0 (n)
            22 LOAD_FAST                1 (result)
            24 BINARY_MULTIPLY
            26 CALL_FUNCTION            2
            28 RETURN_VALUE
            30 LOAD_CONST               0 (None)
            32 RETURN_VALUE
&lt;/pre&gt;
&lt;p&gt;가장 핵심적인 차이점은 이것이다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
26 CALL_FUNCTION            2
28 RETURN_VALUE
&lt;/pre&gt;
&lt;p&gt;&lt;tt class=&quot;docutils literal&quot;&gt;factorial()&lt;/tt&gt; 함수를 재귀적으로 호출하긴 하지만, 결과값을 받아서 추가적인
연산을 하지 않고 바로 반환하도록 되어있다. 이로써 꼬리 재귀의 조건을 충족시킬
수 있게 되었다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;tail-recursion-elimination-tre&quot;&gt;
&lt;h2&gt;Tail Recursion Elimination (TRE)&lt;/h2&gt;
&lt;p&gt;위와 같이 꼬리 재귀 조건을 만족한다면 실제로 함수를 호출하지 않는
반복해(iterative solution) 코드로 변경할 수 있다. 이러한 과정을 tail recursion
elimination (TRE) 이라고 한다. 만약, 파이썬 바이트 코드 컴파일러가 TRE를 할 수
있다면 앞서 소개했던 꼬리 재귀 코드는 다음과 같이 변환될 것이다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;name builtin pseudo&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;
            &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;컴파일러가 충분히 똑똑하다면 조금 더 괜찮은 코드를 작성할 수 있을지도 모른다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;
        &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Scala와 같은 언어에서는 꼬리 재귀 최적화(tail recursion optimization)를
기본으로 제공하기도 하고,&lt;a class=&quot;footnote-reference&quot; href=&quot;#tail-recursion-in-scala&quot; id=&quot;id8&quot;&gt;[4]&lt;/a&gt; Haskell과 같은
언어에서는 함수 호출이 항상 새로운 콜 스택 프레임을 사용하지 않을 수도 있기
때문에&lt;a class=&quot;footnote-reference&quot; href=&quot;#tail-recursion-in-haskell&quot; id=&quot;id9&quot;&gt;[5]&lt;/a&gt; 마음놓고 재귀 호출을 사용할 수 있지만,
파이썬의 경우 아쉽게도 그런 호사는 누릴 수 없다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;home-brewing-tre&quot;&gt;
&lt;h2&gt;Home-Brewing TRE&lt;/h2&gt;
&lt;p&gt;없으면 만들어야지. 이것도 크게 두 가지 해결책이 있을 것 같다.&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;파이썬 인터프리터를 수정하기&lt;a class=&quot;footnote-reference&quot; href=&quot;#python-switch-statement&quot; id=&quot;id10&quot;&gt;[8]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;재귀 호출할 때 함수를 다른걸로 바꿔치기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;내 관점에서는 1번이 더 멋진 일이지만, 작업 분량과 난이도를 생각했을 때 2번이
조금 더 현실적인 대안이라고 생각했다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;파이썬은 런타임에 뭐든지 바꿀 수 있는 언어이기 때문에 위와 같이 재귀 호출이
일어나는 부분에서 &lt;tt class=&quot;docutils literal&quot;&gt;factorial()&lt;/tt&gt; 함수를 다른 것으로 바꾸어서 재귀 호출이 아닌
다른 일이 일어나도록 만들면 원하는 바를 이룰 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 역시 이런 생각은 내가 세계 최초로 한 것이 아니기 때문에 이미 누군가가 잘
만들어놓은 코드가 있었다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#tre&quot; id=&quot;id11&quot;&gt;[9]&lt;/a&gt; 원작자가 만든 코드를 내 입맛에 맞게 아주
조금만 수정해보았다.&lt;/p&gt;
&lt;p&gt;먼저, TRE를 하기 위해 필요한 몇가지 구성 요소들이 있다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;name class&quot;&gt;Recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name exception&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function magic&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name builtin pseudo&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;name builtin pseudo&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;
        &lt;span class=&quot;name builtin pseudo&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;


&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;recurse&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;Recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;tail_recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;name builtin pseudo&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;Recursion&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;
                &lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;wrapper&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;그리고 &lt;tt class=&quot;docutils literal&quot;&gt;factorial()&lt;/tt&gt; 함수는 다음과 같이 수정한다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;name decorator&quot;&gt;&amp;#64;tail_recursion&lt;/span&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword namespace&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;name namespace&quot;&gt;trlib&lt;/span&gt; &lt;span class=&quot;keyword namespace&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;recurse&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;factorial&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;기본적인 아이디어는 &lt;tt class=&quot;docutils literal&quot;&gt;factorial()&lt;/tt&gt; 함수를 실제로 재귀적으로 호출하는 대신,
내부적으로 다른 일이 일어나도록 만드는 것이다.&lt;/p&gt;
&lt;p&gt;재귀 호출이었다면 다음과 같이 &lt;tt class=&quot;docutils literal&quot;&gt;factorial()&lt;/tt&gt; 함수 호출의 흔적이 콜 스택에
차곡차곡 쌓였을텐데,&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
factorial(n=5, result=1)
  factorial(n=4, result=5)
    factorial(n=3, result=20)
      factorial(n=2, result=60)
        factorial(n=1, result=120)
          factorial(n=0, result=120)
&lt;/pre&gt;
&lt;p&gt;TRE 코드에서는 스택의 깊이가 깊어지지 않는다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
factorial(n=5, result=1)
factorial(n=4, result=5)
factorial(n=3, result=20)
factorial(n=2, result=60)
factorial(n=1, result=120)
factorial(n=0, result=120)
&lt;/pre&gt;
&lt;p&gt;실제로 큰 값을 가지고 (e.g., &lt;tt class=&quot;docutils literal&quot;&gt;n = 2000&lt;/tt&gt;) 테스트를 해보면 재귀 호출 코드의
경우 &lt;tt class=&quot;docutils literal&quot;&gt;RecursionError: maximum recursion depth exceeded in comparison&lt;/tt&gt;와
같은 오류 메시지가 발생하는 반면, TRE 코드는 아무 문제 없이 주어진 연산을
수행하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;dive-deep&quot;&gt;
&lt;h2&gt;Dive Deep&lt;/h2&gt;
&lt;p&gt;일단 돌아가게 만들어 놓긴 했는데, 성능은 어떨까? 파이썬 3.7 문서에서는 다음과
같이 명시하고 있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#python-exception-cost&quot; id=&quot;id12&quot;&gt;[10]&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
A try/except block is extremely efficient if no exceptions are raised.
Actually catching an exception is expensive.&lt;/blockquote&gt;
&lt;p&gt;하지만 우리는 재귀 함수의 종료 조건이 만족될 때를 제외하고는 실제로 예외를
캐치하고 있기 때문에 성능상 비싼 값을 치르고 있을 수도 있다. 그래서 얼마나
느린지 직접 테스트를 해보기로 했다. 테스트 코드는 &lt;a class=&quot;reference external&quot; href=&quot;https://gist.github.com/suminb/7118ffb2251b07701b4f8bb9dbd7f899&quot;&gt;Gist&lt;/a&gt;에
올려두었다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
recursive_code
0.305 ms/pass

tail_recursive_code
0.416 ms/pass

tail_recursion_eliminated_code
1.916 ms/pass
&lt;/pre&gt;
&lt;p&gt;일반적인 재귀 호출 코드와 꼬리 재귀(tail recursion) 호출 코드는 대동소이한
반면, TRE 코드는 여섯 배 가량 느린 것으로 나타났다(!) 성능을 개선하려면
아무래도 &lt;tt class=&quot;docutils literal&quot;&gt;try&lt;/tt&gt;/&lt;tt class=&quot;docutils literal&quot;&gt;except&lt;/tt&gt; 구문을 사용하지 않고 다른 방법으로 구현해야 할 것
같다.&lt;/p&gt;
&lt;p&gt;우리가 &lt;tt class=&quot;docutils literal&quot;&gt;try&lt;/tt&gt;/&lt;tt class=&quot;docutils literal&quot;&gt;except&lt;/tt&gt; 구문을 사용하는 이유는 신호를 전달하기 위함이다.
이번에 재귀 호출을 해야 하는지, 아니면 종료 조건이 만족되어 그냥 결과값을
반환하면 되는지 판단하고, 그 결과를 &lt;tt class=&quot;docutils literal&quot;&gt;tail_recursion()&lt;/tt&gt; 안쪽의 &lt;tt class=&quot;docutils literal&quot;&gt;wrapper()&lt;/tt&gt;
함수로 전달할 수 있으면 된다. 그래서 다음의 두 가지 방법을 시도해봤다.&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;take-one-globals&quot;&gt;
&lt;h3&gt;Take One: Globals&lt;/h3&gt;
&lt;p&gt;먼저, 전역 변수를 이용해서 신호를 전달하는 방식으로 코드를 조금 수정해보았다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;name&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name builtin&quot;&gt;globals&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;()&lt;/span&gt;


&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;recurse&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;name&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;literal string single&quot;&gt;'&amp;#64;caller_id'&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name builtin pseudo&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;tail_recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;name&quot;&gt;caller_id&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name variable magic&quot;&gt;__name__&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;name builtin pseudo&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;name&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;caller_id&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name builtin pseudo&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;name&quot;&gt;recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;caller_id&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;operator word&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;wrapper&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;여기서 &lt;tt class=&quot;docutils literal&quot;&gt;&amp;#64;caller_id&lt;/tt&gt;로 표시된 부분은 &lt;tt class=&quot;docutils literal&quot;&gt;recurse()&lt;/tt&gt; 함수를 호출하는
호출자(caller) 함수의 이름이 들어갈 자리이다. &lt;tt class=&quot;docutils literal&quot;&gt;inspect&lt;/tt&gt; 패키지를 이용하여
호출자 이름을 받아오는 방법이 있긴 하지만,&lt;a class=&quot;footnote-reference&quot; href=&quot;#caller-name&quot; id=&quot;id13&quot;&gt;[13]&lt;/a&gt; 사용할 수 없을
정도로 느리다. 시간을 재다가 너무 오래 걸려서 그냥 포기했다. 만약
&lt;tt class=&quot;docutils literal&quot;&gt;recurse()&lt;/tt&gt;에서 호출자 이름을 빠르게 알아낼 수 있는 방법이 없다면 이 방법은
범용적으로 사용하기는 어려울 것 같다. LeetCode 문제 풀어서 제출하는 정도의
용도로는 별 지장이 없겠지만.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
recursive_code
0.302 ms/pass

tail_recursive_code
0.413 ms/pass

tail_recursion_eliminated_code
1.441 ms/pass
&lt;/pre&gt;
&lt;p&gt;&lt;tt class=&quot;docutils literal&quot;&gt;try&lt;/tt&gt;/&lt;tt class=&quot;docutils literal&quot;&gt;except&lt;/tt&gt; 구문을 제거함으로써 25% 정도의 성능 향상을 도모할 수
있었지만, 충분히 만족스러운 수준은 아니었다. 재귀 호출 코드와 비교하여 여전히
다섯 배 가량 느리다. 게다가 예외 객체를 이용하는 코드와 비교하여 상당히
비직관적인 코드가 되었다는 것을 고려했을 때, 효용 대비 비용이 너무 큰
방법이라는 생각이 들었다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;take-two-coroutines&quot;&gt;
&lt;h3&gt;Take Two: Coroutines&lt;/h3&gt;
&lt;p&gt;예외 객체 대신 전역 변수를 사용하는 코드로 기대했던 만큼 성능 향상을 걷두지
못했기 때문에 &lt;a class=&quot;reference external&quot; href=&quot;https://docs.python.org/3/library/asyncio-task.html&quot;&gt;코루틴&lt;/a&gt;을 이용하는 방법도 생각해보았다. 단순하게 생각해서 재귀 호출 함수를 코루틴으로
만들면 어떤 식으로든 호출자(caller)와 피호출자(callee)가 신호를 주고받을 수
있지 않을까.&lt;/p&gt;
&lt;p&gt;StackOverflow의 어떤 답변은 코루틴을 다음과 같이 정의하고 있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#coroutine&quot; id=&quot;id15&quot;&gt;[14]&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
Coroutines are a general control structure whereby flow control is
cooperatively passed between two different routines without returning.&lt;/blockquote&gt;
&lt;p&gt;코루틴에 대한 학술적 정의와는 완벽하게 들어맞지 않을 수도 있지만, 지금 우리가
하고자 하는 작업의 맥락에서 가장 이해하기 쉬운 설명이라는 생각이 들었다. 우리가
필요한 부분은 두 함수가 신호를 주고 받는 장치이고, 코루틴이 그 부분을 해결해줄
수 있을 것 같아서 코루틴을 이용하여 TRE 코드를 작성해보기로 하였다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;keyword namespace&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;name namespace&quot;&gt;asyncio&lt;/span&gt;


&lt;span class=&quot;name&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name builtin pseudo&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation&quot;&gt;{}&lt;/span&gt;


&lt;span class=&quot;name&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;recurse&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name builtin pseudo&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;


&lt;span class=&quot;name&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;name builtin pseudo&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;name&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;ensure_future&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;name&quot;&gt;recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;task&lt;/span&gt;

        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;operator word&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;


&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;tail_recursion&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;name&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;get_event_loop&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;run_until_complete&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;wrapper&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;코루틴을 이용할 경우 원본 코드를 약간 수정해야 한다.&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;name decorator&quot;&gt;&amp;#64;tail_recursion&lt;/span&gt;
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;name function&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;keyword namespace&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;name namespace&quot;&gt;trlib&lt;/span&gt; &lt;span class=&quot;keyword namespace&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;recurse&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;factorial&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;literal number integer&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;재귀 종료 조건을 만족했을 때 위와 같이 &lt;tt class=&quot;docutils literal&quot;&gt;done()&lt;/tt&gt; 함수를 이용해서 결과값을
전달해야 한다. &lt;tt class=&quot;docutils literal&quot;&gt;done()&lt;/tt&gt; 함수를 거치지 않고 결과값을 전달하는 방법을 찾지
못했기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
recursive_code
0.303 ms/pass

tail_recursive_code
0.418 ms/pass

tail_recursion_eliminated_code
19.460 ms/pass
&lt;/pre&gt;
&lt;p&gt;아쉽게도 성능은 훨씬 더 안 좋아졌다. 어쩌면 더 좋은 구조로 개선할 수 있을지도
모른다. 어쨌든 전역변수를 사용하는 코드에 비해서 13배 이상 느리기 때문에
사용하지 않는 것이 좋겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;conclusion&quot;&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;파이썬으로 알고리즘 문제를 풀다가 느낀 불편함으로 인해 한참동안 야크 털을 깎은
것 같은데,&lt;a class=&quot;footnote-reference&quot; href=&quot;#yak-shaving&quot; id=&quot;id16&quot;&gt;[11]&lt;/a&gt; 나름 즐거운 경험이었다. 덕분에 어렴풋이 알고 있던
개념들을 조금 더 확고하게 익힐 수 있었고, 평소에 들여다 볼만한 계기가 없었던
파이썬 바이트 코드도 구경해 볼 수 있었다.&lt;/p&gt;
&lt;p&gt;TRE 코드를 통해 사실상 무제한으로 재귀호출을 할 수 있게 되었지만, 아쉽게도
실제로 사용할만한 성능을 끌어내지는 못했다. Dive Deep 섹션에서 제시한 대안
코드를 작성할 때 충분한 고민을 거치지 않아서 구조적인 결함이 있을 수도 있고,
아니면 그보다 더 근본적인 문제가 있을지도 모른다.&lt;/p&gt;
&lt;p&gt;성능 문제 이외에도 파이썬에서의 TRE에 대한 비판 의견도 있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#critiques-on-tre&quot; id=&quot;id17&quot;&gt;[12]&lt;/a&gt; TRE를 도입할 경우 스택 트레이스가 어려워질 뿐만 아니라
재귀 호출이 모든 프로그래밍의 기초가 되어서는 안 된다는 시각이다. 파이썬은 재귀
호출보다는 반복적(iterative) 해결책이 어울리는 언어이다. 나도 한가지 해결책으로
모든 문제를 해결하려는 태도를 지양하는 편이기 때문에 이런 시각에 대체적으로
동의한다.&lt;/p&gt;
&lt;p&gt;모든 문제를 재귀적으로 해결할 필요는 없다. 다만, &lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Dynamic_programming&quot;&gt;동적 프로그래밍(dynamic
programming)&lt;/a&gt;과 같은
방법으로 해결한 문제는 &lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Recurrence_relation&quot;&gt;점화식(recurrence relations)&lt;/a&gt;으로 표현되기 마련이다.
이런 경우에 재귀 호출을 사용한다면 수학식을 그대로 코드로 옮길 수 있기 때문에
편리하다.&lt;/p&gt;
&lt;p&gt;만약 다음에 또 이런 주제로 야크 털을 깎을 일이 있다면 파이썬 인터프리터를
개조해서 TRE를 지원하도록 만들어보는 것도 재밌을 것 같다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;footnotes&quot;&gt;
&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;leet&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id1&quot;&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/suminb/coding-exercise/tree/master/leetcode&quot;&gt;https://github.com/suminb/coding-exercise/tree/master/leetcode&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id18&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id6&quot;&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Tail_call&quot;&gt;https://en.wikipedia.org/wiki/Tail_call&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;tail-recursion-ko&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id5&quot;&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://ko.wikipedia.org/wiki/%EA%BC%AC%EB%A6%AC_%EC%9E%AC%EA%B7%80&quot;&gt;https://ko.wikipedia.org/wiki/%EA%BC%AC%EB%A6%AC_%EC%9E%AC%EA%B7%80&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;tail-recursion-in-scala&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id8&quot;&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.scala-exercises.org/scala_tutorial/tail_recursion&quot;&gt;https://www.scala-exercises.org/scala_tutorial/tail_recursion&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;tail-recursion-in-haskell&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id9&quot;&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://wiki.haskell.org/Tail_recursion&quot;&gt;https://wiki.haskell.org/Tail_recursion&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;max-depth&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id3&quot;&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://mattjegan.com/Chasing-Pythons-Recursion-Limit/&quot;&gt;https://mattjegan.com/Chasing-Pythons-Recursion-Limit/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;setrecursionlimit&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id4&quot;&gt;[7]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://docs.python.org/3/library/sys.html#sys.setrecursionlimit&quot;&gt;https://docs.python.org/3/library/sys.html#sys.setrecursionlimit&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;python-switch-statement&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id10&quot;&gt;[8]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.linkedin.com/in/ukysung/&quot;&gt;성우경&lt;/a&gt;님의 &lt;a class=&quot;reference external&quot; href=&quot;https://archive.pycon.kr/2018/program/49&quot;&gt;파이썬에 switch문 넣기: 새 구문을 만들면서 배우는 파이썬 내부&lt;/a&gt; 발표를 보고 파이썬 인터프리터를 입맞에 맞게 고쳐서 쓰는 일이 불가능한 일은 아니라는 용기를 얻었다.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;tre&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id11&quot;&gt;[9]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://chrispenner.ca/posts/python-tail-recursion&quot;&gt;https://chrispenner.ca/posts/python-tail-recursion&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;python-exception-cost&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id12&quot;&gt;[10]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://docs.python.org/3.7/faq/design.html#how-fast-are-exceptions&quot;&gt;https://docs.python.org/3.7/faq/design.html#how-fast-are-exceptions&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;yak-shaving&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id16&quot;&gt;[11]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.lesstif.com/pages/viewpage.action?pageId=29590364&quot;&gt;https://www.lesstif.com/pages/viewpage.action?pageId=29590364&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;critiques-on-tre&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id17&quot;&gt;[12]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html&quot;&gt;https://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;caller-name&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id13&quot;&gt;[13]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://stackoverflow.com/a/2654130&quot;&gt;https://stackoverflow.com/a/2654130&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;coroutine&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id15&quot;&gt;[14]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://stackoverflow.com/a/553745/1913623&quot;&gt;https://stackoverflow.com/a/553745/1913623&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

</description>
        <pubDate>Sun, 17 Mar 2019 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/tail-recursion-in-python/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/tail-recursion-in-python/</guid>
      </item>
    
      <item>
        <title>카카오페이 QR 코드 리버스 엔지니어링</title>
        <description>&lt;p&gt;소프트웨어 엔지니어라는 직업이 매력적인 이유는 엄청나게 많지만, 오늘은 그
중에서도 '가내수공업이 가능한 점'을 꼽고 싶다. 필요한 것이 있으면 직접 만들 수도
있다는 말이다. 물론 제대로 된 무언가를 만들어 내기 위해서는 수많은 고민과 적지
않은 노력이 필요하긴 하다.&lt;/p&gt;
&lt;p&gt;오늘 여러분과 공유할 이야기는 취미 생활을 하면서 겪었던 문제를 해결하는 과정에
대한 이야기다. (그렇다, 일과 놀이의 경계가 불분명한 삶을 살고 있다.) 이 문제를
해결하기 위해서 굉장히 많은 고민을 하고 여러가지 시행착오를 겪은 것 같은데, 막상
글로 정리하고 보니 분량이 얼마 되지 않아서 &amp;quot;내가 뭘 했나&amp;quot; 싶다.&lt;/p&gt;
&lt;p&gt;이 이야기는 크게 네 가지 파트로 구성되어있다: 천원경매, 카카오페이, QR 코드
분석, 그리고 QR 코드 생성이다.&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;id1&quot;&gt;
&lt;h2&gt;천원경매&lt;/h2&gt;
&lt;p&gt;인테리어의 최고봉은 집안의 물건을 없애는 것이다. 아무리 미적 감각이 뛰어난
인테리어 전문가라고 해도 좁은 집에 수많은 잡동사니가 뒤엉켜 있는 상황을
정리하기는 쉽지 않다. 집안의 모습은 물건이 적을수록 깔끔해진다. 말처럼 쉽지는
않지만, 필요 없는 물건들을 과감하게 정리하는 것이 답이다. 아직 그것만큼 효과적인
인테리어 개선 방법을 찾지 못했다.&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;img alt=&quot;/attachments/2019/kakaopay-qrcode/dollar-auction.jpg&quot; src=&quot;/attachments/2019/kakaopay-qrcode/dollar-auction.jpg&quot; style=&quot;width: 80%;&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;그래서 사용하지 않는 물건을 발견할 때마다 &lt;a class=&quot;reference external&quot; href=&quot;https://1000won.auction&quot;&gt;천원경매&lt;/a&gt;에 내다 팔고 있다. 천원경매는 사내 장터에서 경매로 물건을 팔기 위해 직접 만든
서비스이다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#id12&quot; id=&quot;id3&quot;&gt;[3]&lt;/a&gt; 번개장터, 당근마켓, 중고나라 등 중고 거래 서비스가 이미 있긴
하지만, 상호 신뢰가 없는 사람들끼리 중고 거래를 하는 일이 결코 쉬운 일은 아니다.
하지만 회사 사람들끼리 거래를 한다면 소액의 물건을 가지고 서로 사기를 치는 일은
없을 것이라는 믿음이 있기 때문에 거래에 대한 부담감이 비교적 적다.&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;img alt=&quot;/attachments/2019/kakaopay-qrcode/jungonara.png&quot; src=&quot;/attachments/2019/kakaopay-qrcode/jungonara.png&quot; style=&quot;width: 240px;&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;중고 거래의 어려움&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;그리고 굳이 경매라는 판매 방식을 택한 이유는 중고 상품의 가격을 결정하는 일이
어렵기 때문이다. 내가 특정 가격을 제시했을 경우 팔리지 않을 수도 있고, 그렇게
되면 다시 적당히 가격을 내려서 다시 판매를 시도해야 한다. 물론 아주 저렴하게
내놓거나 무료로 올린다면 비교적 쉽게 처분할 수 있겠지만, 그래도 &amp;quot;일단 무료니까
받아놓고 생각해보자&amp;quot; 하는 사람보다는 해당 상품을 정말로 진지하게 원하는 사람이
가져갔으면 하는 바람이 있다. 자본주의 사회에서 가장 진지한 사람은 역시 돈을 가장
많이 내는 사람이다. 그런 면에서 경매라는 판매 방식은 내가 해결하고자 하는 문제를
모두 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;지금은 낙찰이 되었을 때 낙찰 금액과 입금 계좌 정보가 담긴 이메일 메시지가
자동으로 발송되도록 구성되어있다. 이메일을 받은 사람이 해당 계좌로 안내된 금액을
입금하면 판매자가 구매자에게 물건을 전달해주는 방식이다.&lt;/p&gt;
&lt;!-- (TODO: 예제 화면 보여주기) --&gt;
&lt;p&gt;아직까지는 그런 일이 생기지 않았지만, 금액을 입력하는 일은 사람이 하는
일이다보니 실수를 할 가능성이 언제나 존재한다. 예를 들어서, 30,500원을 송금해야
하는데 30,050원을 송금하는 경우가 생길 수도 있다. 반대로, 낙찰 금액보다 큰
금액을 실수로 송금할 가능성도 있다. 물론, 모르는 사람과의 거래가 아니라 사내
거래이기 때문에 언제든지 차액 정정 거래를 할 수 있겠지만, 이러한 실수의 여지를
남겨두지 않기 위해서는 인간의 개입을 최소화 하는 것이 최선책이라는 생각이
들었다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id4&quot;&gt;
&lt;h2&gt;카카오페이&lt;/h2&gt;
&lt;p&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.kakaopay.com/&quot;&gt;카카오페이&lt;/a&gt;는 송금, 인증, 청구서, 멤버십 관리
등을 편하게 해결할 수 있도록 도와주는 서비스이다. 나는 주로 친구들이나 직장
동료들끼리 밥값을 나눠 낼 때 사용한다.&lt;/p&gt;
&lt;p&gt;며칠 전, 서비스의 이런저런 부분들을 살펴보다가 송금을 요청하는 기능이 있다는
것을 우연히 발견했다. 상대방이 나에게 바로 송금할 수 있도록 QR 코드를
생성해준다. 원하는 금액도 넣을 수 있는데, 금액을 넣으면 QR 코드를 찍었을 때 송금
UI에 그 금액이 미리 입력되어서 나온다. 이 부분을 천원경매에 이용하면 어떨까 하는
생각이 들었다.&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;img alt=&quot;/attachments/2019/kakaopay-qrcode/sample1.png&quot; src=&quot;/attachments/2019/kakaopay-qrcode/sample1.png&quot; style=&quot;width: 320px;&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;카카오페이 송금 QR 코드&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;낙찰이 되었을 때 이메일로 무미건조하게 금액과 계좌번호를 텍스트로 표시하는 대신,
이메일 메시지에 QR 코드를 넣으면 편하게, 그리고 실수 없이 낙찰 대금을 송금할 수
있지 않을까 하는 생각이 들었다. 물론 카카오페이를 사용하지 않는 사람들도
있을 수 있으니 금액과 계좌번호는 여전히 표시를 해주어야 할 것이다.&lt;/p&gt;
&lt;p&gt;카카오페이 유저 아이디와 금액을 매개변수로 전달했을 때 송금 QR 코드를 생성해주는
기능이 있다면 큰 어려움 없이 내가 생각하는 기능을 구현할 수 있을 것 같았다.&lt;/p&gt;
&lt;p&gt;코딩 중에 최고는 안 코딩이다. 코드를 한 줄도 작성하지 않고 문제를 해결할 수
있다면 그게 최선의 해결책이라는 말이다. 그래서 카카오페이에서 개발자로 근무하고
있는 친구에게 슬쩍 물어봤다.&lt;/p&gt;
&lt;p&gt;&amp;quot;혹시 이 QR 코드를 생성해주는 API를 제공하는가?&amp;quot;&lt;/p&gt;
&lt;p&gt;아쉽게도 답변은 &amp;quot;제공하지 않는다.&amp;quot; 였다. 어쩔 수 없다. 없으면 만들어야지.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;qr&quot;&gt;
&lt;h2&gt;QR 코드 분석&lt;/h2&gt;
&lt;p&gt;카카오페이 송금 QR 코드는 크게 두 가지 타입이 있다.&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;유저 아이디만 나타내는 QR 코드&lt;/li&gt;
&lt;li&gt;유저 아이디와 함께 금액이 임베딩(embedding) 된 QR 코드&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1번 타입의 경우 스캔을 하면 돈을 보낼 사람의 이름과 함께 금액을 입력하는 UI가
나온다. 2번 타입은 금액이 미리 입력되어서 나온다.&lt;/p&gt;
&lt;p&gt;개인 정보 보호를 위해서 QR 코드를 블러 처리했다. 스캔을 하지 않고 눈으로만
보기에도 2번 타입이 조금 더 많은 정보를 담고 있다는 것을 알 수 있었다.&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;img alt=&quot;/attachments/2019/kakaopay-qrcode/sample2.png&quot; src=&quot;/attachments/2019/kakaopay-qrcode/sample2.png&quot; style=&quot;width: 640px;&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;타입 1 (왼쪽), 타입 2 (오른쪽)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;1번 타입을 만드는건 어렵지 않다. 역시, 개인 정보 보호를 위해서 유저 아이디를
&lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;0000...&lt;/span&gt;&lt;/tt&gt; 으로 치환했다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
https://qr.kakaopay.com/000000000000000000000000
&lt;/pre&gt;
&lt;p&gt;해당 URL로 접속하면 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;kakaopay://&lt;/span&gt;&lt;/tt&gt; URL로 리다이렉트 하는 자바스크립트 코드가
나온다. 곧바로 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;kakaopay://&lt;/span&gt;&lt;/tt&gt;로 보내지 않고 &lt;tt class=&quot;docutils literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;https://&lt;/span&gt;&lt;/tt&gt;로 보내는 이유는
아마도 카카오톡이 설치되있지 않을 경우 앱스토어로 보내주기 위함일 것이다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
kakaotalk://kakaopay/money/to/qr?qr_code=000000000000000000000000
&lt;/pre&gt;
&lt;p&gt;카카오톡이 설치된 모바일 폰에서 해당 URL을 열면 카카오페이 송금 UI가 바로
나타난다. 사실 여기까지만 해도 천원경매 사용자들이 카카오페이 메뉴를 열어서
판매자에게 송금하는 과정을 조금은 편하게 만들 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 내가 원하는건 2번 타입이다. 금액을 미리 입력해서 QR 코드를 발급할 수
있다면 사용자들의 실수를 방지할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;2번 타입 QR 코드에는 다음과 같은 값이 인코딩 되어있다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
kakaotalk://kakaopay/money/to/qr?qr_code=0000000000000000000000001f402302
&lt;/pre&gt;
&lt;p&gt;유저 아이디 뒷 부분에 무언가 추가적인 데이터(&lt;tt class=&quot;docutils literal&quot;&gt;1f402302&lt;/tt&gt;)가 붙어있다. 나는
1,000원을 입력했는데, 그런것 치고는 굉장히 많은 양의 정보가 들어가 있다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
kakaotalk://kakaopay/money/to/qr?uid=000000000000000000000000&amp;amp;amount=1000
&lt;/pre&gt;
&lt;p&gt;만약 이런 방식이었다면 일이 훨씬 수월했겠지만, 이 포스트에서 이야기 할 내용은
훨씬 짧아졌을 것이다. 어쩌면 아예 글을 쓰지 않았을지도 모른다.&lt;/p&gt;
&lt;p&gt;잠깐 이야기가 옆으로 샐 뻔 했는데, 가장 중대한 문제는 같은 금액을 입력하더라도
매번 조금씩 다른 QR 코드가 생성된다는 점이었다. 유저 아이디 부분은 동일했지만,
그 뒤에 붙는 금액 데이터가 조금씩 달라졌다. 이유는 잘 모르겠지만 난수를 사용하는
것 같이 보였다. 아마도 금액 데이터를 생(plain)으로 노출시키지 않기 위함이
아니었을까.&lt;/p&gt;
&lt;p&gt;하지만 암호화를 하지 않는 이상 특정한 규칙에 의해서 원본 데이터를 다른 데이터로
치환한 것에 불과하고, 어렵지 않게 규칙을 알아낼 수 있을 것 같았다.&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.cbclx01/bitshe.htm&quot;&gt;Bitwise shift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://hackernoon.com/xor-the-magical-bit-wise-operator-24d3012ed821&quot;&gt;Exclusive OR (XOR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte&quot;&gt;Bit (or byte) order reverse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래봤자 이 중 하나겠거니 하는 마음으로 조금 더 깊이 들여다보기로 했다.&lt;/p&gt;
&lt;p&gt;먼저, 금액을 1원으로 해서 바코드를 여러번 생성해봤다. 금액 데이터는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
81686
83780
86466
83840
89480
&lt;/pre&gt;
&lt;p&gt;이렇게 봐서는 뭐가 뭔지 하나도 모르겠다. 비트 단위로 표시를 해보면 어떤 패턴이
보이지 않을까?&lt;/p&gt;
&lt;pre class=&quot;code python literal-block&quot;&gt;
&lt;span class=&quot;operator&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;binary&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;literal string single&quot;&gt;'{:b}'&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;name&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;operator&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;name&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;literal number hex&quot;&gt;0x81686&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;literal string single&quot;&gt;'10000001011010000110'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;파이썬을 이용해서 16진수로 표시된 값을 바이너리 형식으로 표현해주는 한 줄 짜리
코드를 만들었다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
10000001011010000110
10000011011110000000
10000110010001100110
10000011100001000000
10001001010010000000
&lt;/pre&gt;
&lt;p&gt;가장 앞쪽 비트(most significant bit)가 1이라는 점 말고는 이렇다할 패턴이 보이지
않았다. 사실, 금액을 1로 잡으면 2진수, 10진수, 16진수 등 무엇으로 보든 1로
보이기 때문에 이런 패턴을 분석할 때 좋은 샘플은 아니다.&lt;/p&gt;
&lt;p&gt;이번에는 금액을 1씩 증가 시켜 가면서 금액 데이터가 어떻게 생성되는지 관찰해보기로 했다.&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;colwidths-given docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;25%&quot; /&gt;
&lt;col width=&quot;75%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;금액&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;QR 코드의 금액 데이터&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x86222&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;(금액 데이터가 생성되지 않았다. 사용자 실수이거나 버그인 것 같다.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x185920&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x202043&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x286900&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;여전히 잘 모르겠다. 사실, 이때 저 데이터들을 바이너리로 표현해보기만 했어도
패턴을 금방 알아낼 수 있었을 것이다. 이때에는 비트 순서나 바이트 순서가 뒤바뀐
것을 의심하면서 이런저런 가설을 세우고 확인하는 과정을 거치고 있었다.&lt;/p&gt;
&lt;p&gt;1원씩 증가시켜 가면서 만든 QR 코드를 분석하는 작업이 여의치 않아서 조금 더
패턴을 찾아보기 쉽게 2진수로 표현했을 때 1로만 구성된 숫자 몇가지를 샘플로
사용하기로 했다.&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;255 (2&lt;sup&gt;8&lt;/sup&gt; - 1)&lt;/li&gt;
&lt;li&gt;4,095 (2&lt;sup&gt;12&lt;/sup&gt; - 1)&lt;/li&gt;
&lt;li&gt;65,535 (2&lt;sup&gt;16&lt;/sup&gt; - 1)&lt;/li&gt;
&lt;li&gt;1,048,575 (2&lt;sup&gt;20&lt;/sup&gt; - 1)&lt;/li&gt;
&lt;li&gt;16,777,215 (2&lt;sup&gt;24&lt;/sup&gt; - 1)&lt;a class=&quot;footnote-reference&quot; href=&quot;#id10&quot; id=&quot;id6&quot;&gt;[1]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;카카오페이 UI에서 위의 금액을 일일히 넣어서 QR 코드를 하나씩 생성했다. 그다지
아름답지 못한 성격의 지루한 작업이었지만, 별다른 방법이 없었다.&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;colgroup&gt;
&lt;col width=&quot;33%&quot; /&gt;
&lt;col width=&quot;33%&quot; /&gt;
&lt;col width=&quot;33%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;금액&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;QR 코드의 금액 데이터&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;2진수 표현&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;255&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x7f83200&lt;/tt&gt;&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;111111110000011001000000000&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4,095&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x7ff87241&lt;/tt&gt;&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;1111111111110000111001001000001&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x7fff87321&lt;/tt&gt;&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;11111111111111110000111001100100001&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1,048,575&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;0x7ffff81305&lt;/tt&gt;&lt;/td&gt;
&lt;td&gt;&lt;tt class=&quot;docutils literal&quot;&gt;111111111111111111110000001001100000101&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이렇게 보니 패턴이 명확하게 보이기 시작했다. QR 코드의 금액 필드는 금액을 19칸
왼쪽으로 시프트 한 값에 무언가를 더한 값이었다. 금액 뒤에 붙은 데이터의 정체는
아직도 잘 모르겠다. 혹시 유효한 QR 코드인지 검사하는 에러 체킹 코드 같은 것이
아닐까 하는 생각도 했었는데, 아무렇게나 넣어도 작동이 되는 것으로 보아 그냥 랜덤
데이터인 것 같다.&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;img alt=&quot;/attachments/2019/kakaopay-qrcode/album.png&quot; src=&quot;/attachments/2019/kakaopay-qrcode/album.png&quot; style=&quot;width: 320px;&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;계속된 실험으로 인해 QR 코드로 가득 찬 사진 앨범&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;이 글에서는 규칙을 알아내는 과정을 아주 간단하게 요약해서 표현했지만, 이걸
알아내느라 두어시간 동안 굉장히 많은 삽질을 했었다. 이렇게 난독화 된 데이터가
주어졌을 때 보다 효과적으로 패턴을 알아내는 과학적인 방법을 예전에 학교 다닐 때
암호학 수업 시간에 들은 기억이 있는데&lt;a class=&quot;footnote-reference&quot; href=&quot;#id11&quot; id=&quot;id7&quot;&gt;[2]&lt;/a&gt;, 아쉽게도 기억이 잘 나지 않는다.
이번에는 운이 좋아서 큰 어려움 없이 규칙을 알아냈지만, 만약 다음번에 비슷한
문제에 봉착하게 되었는데 몇시간이 지나도 해결될 기미가 보이지 않는다면 그 부분을
다시 복기해봐야겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id8&quot;&gt;
&lt;h2&gt;QR 코드 생성&lt;/h2&gt;
&lt;p&gt;카카오페이가 송금 QR 코드를 만들어내는 방식을 알아냈으니, 이제는 내 코드로 송금
QR 코드를 만들어 낼 차례이다.&lt;/p&gt;
&lt;p&gt;앞서 이야기 했듯이 카카오페이의 송금 URL은 다음과 같이 이루어져있다.&lt;/p&gt;
&lt;pre class=&quot;code literal-block&quot;&gt;
kakaotalk://kakaopay/money/to/qr?qr_code=${uid}${scrambled_amount}
&lt;/pre&gt;
&lt;p&gt;사용자가 입력한 금액에 따라서 이 URL을 생성하고, 그것을 QR 코드로 만들면 되는
아주 간단한 작업이다. 나는 명령창 환경이 편하기 때문에 대부분의 작업은 vim
에디터를 띄워서 하는 편이지만, 화면에 무언가 보여줄 것이 있을 때에는 주피터
노트북을 이용해서 편하게 프로토타이핑을 할 수 있다. QR 코드 생성은 파이썬의
&lt;a class=&quot;reference external&quot; href=&quot;https://pypi.org/project/qrcode/&quot;&gt;&lt;tt class=&quot;docutils literal&quot;&gt;qrcode&lt;/tt&gt;&lt;/a&gt; 패키지의 도움을 받았다.&lt;/p&gt;
&lt;img alt=&quot;/attachments/2019/kakaopay-qrcode/qrcode-generation.png&quot; src=&quot;/attachments/2019/kakaopay-qrcode/qrcode-generation.png&quot; style=&quot;width: 80%;&quot; /&gt;
&lt;p&gt;그렇게 생성한 QR 코드를 폰에서 스캔 하면 다음과 같이 카카오페이 송금 화면이
뜬다. 코드에서 입력한 금액인 &lt;tt class=&quot;docutils literal&quot;&gt;35,050&lt;/tt&gt;원이 미리 입력되어서 송금 화면이 뜨는
것을 확인할 수 있었다.&lt;/p&gt;
&lt;img alt=&quot;/attachments/2019/kakaopay-qrcode/kakaopay-send.png&quot; src=&quot;/attachments/2019/kakaopay-qrcode/kakaopay-send.png&quot; style=&quot;width: 320px;&quot; /&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id9&quot;&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;아쉽게도 카카오페이에서 송금 QR 코드를 생성하는 API를 제공하지 않아 먼 길을
돌아왔지만, 비교적 큰 어려움 없이 QR 코드를 만들어내는 규칙을 파악할 수 있었고,
그 덕분에 원하는 기능을 만들 수 있었다. 물론, 이 기능이 천원경매 서비스에
들어가려면 아직 조금 더 작업해야 할 부분들이 남아있지만, 기본적인 기능을
구현하는데 필요한 사항들은 모두 마련한 상태라 큰 걱정은 없다. 조만간 천원경매
낙찰 안내 메시지에서 카카오페이 QR 코드를 볼 수 있을 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;notes&quot;&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id10&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id6&quot;&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;이 값을 넣었다가 카카오페이에서 송금 가능한 최대 금액이 2,000,000원이라는 사실도 알게 되었다.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id11&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id7&quot;&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.amazon.com/Introduction-Cryptography-Coding-Theory-2nd/dp/0131862391&quot;&gt;Introduction to Cryptography&lt;/a&gt; 책으로 공부했었다.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id12&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id3&quot;&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;천원경매 개발에 대한 자세한 이야기는 &lt;a class=&quot;reference external&quot; href=&quot;https://www.slideshare.net/suminb/pycon-2017-dollar-auction-78802984&quot;&gt;PyCon 2017 발표 자료&lt;/a&gt;에서 찾아볼 수 있다.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

</description>
        <pubDate>Fri, 22 Feb 2019 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/kakaopay-qrcode/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/kakaopay-qrcode/</guid>
      </item>
    
      <item>
        <title>개인 위키 프로젝트</title>
        <description>&lt;p&gt;“역사를 잊은 민족에게 미래는 없다” 라는 말이 있다. 사실 이 말의 출처는 불분명하다.&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; 하지만 이 말의 속 뜻이 “역사적 기록을 보존하고, 그것으로부터 교훈을 얻고 실수를 반복하지 않는 것이 중요하다” 라면 나는 그 말에 동의한다. 그건 국가 단위에서 뿐만 아니라 개인에게도 마찬가지다. 아주 잘 하고 있지는 못하지만, 그래도 시간과 자원이 허락하는 한 생활의 모든 부분에 있어서 문서화를 잘 하려고 노력하는 편이다. 체계적인 문서화가 어렵다면 단편적인 정보(이메일, 노트, 녹취록 등)라도 남기려고 한다.&lt;/p&gt;

&lt;p&gt;사실, 어떤 기술적 문제를 해결하는 절차를 적어놓은 기록이거나, 실험 결과와 도출해낸 결론에 대한 기록과 같은 것은 &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;, &lt;a href=&quot;https://stackoverflow.com&quot;&gt;StackOverflow&lt;/a&gt;, &lt;a href=&quot;https://www.wikipedia.org&quot;&gt;Wikipedia&lt;/a&gt; 등 전 세계 사람들이 볼 수 있는 곳에 공개할 수 있고, 실제로 그렇게 하고 있다. 다만, 일기장이나 각종 사건사고 기록과 같이 인터넷에 공개하기 어려운 기록들도 가지고 있기 때문에 이 둘은 명확하게 구분해서 관리할 필요가 있다.&lt;/p&gt;

&lt;p&gt;지금까지 비공개 기록들은 에버노트, 구글 독스, 드랍박스와 같은 서비스를 이용해서 관리해왔었다. 하지만 “이런 종류의 기록은 이곳에 보관해야 한다” 와 같은 체계적인 기준이 잡혀있지 않고, 오랜 기간 이런저런 서비스를 전전하다보니 기록이 파편화됨에 따라 관리하기가 점점 어려워졌다.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; 이렇게 하면 필요한 문서를 찾기 위해서 검색을 하려고 했을 때 여러 서비스에 다 검색을 하게 되는 경우가 생긴다.&lt;/p&gt;

&lt;p&gt;그럼 어떤 이유로 한가지 서비스에 만족하기 못하고 여러 서비스를 기웃거리게 되었는지 간단하게(?) 정리해보자.&lt;/p&gt;

&lt;h2 id=&quot;여러가지-노트-서비스앱-특징-분석&quot;&gt;여러가지 노트 서비스/앱 특징 분석&lt;/h2&gt;

&lt;h3 id=&quot;evernote&quot;&gt;Evernote&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/evernote.png&quot; alt=&quot;Evernote&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;학교 수업을 들으며 노트를 작성하기도 하고, 여행중 기록을 남기기 위해 사용했다.&lt;/p&gt;

&lt;h4 id=&quot;장점&quot;&gt;장점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Web, iOS, Android, Windows Phone, Mac OS X, Windows 등 다양한 플랫폼을 지원한다.&lt;/li&gt;
  &lt;li&gt;텍스트 뿐만 아니라 이미지, 동영상, 음성 녹음 등을 보관하기 편리하다. 특히 드래그앤드랍으로 이미지를 붙여넣고 다시 원본을 꺼내오는 작업이 수월하다.&lt;/li&gt;
  &lt;li&gt;통신이 되지 않을 때 (비행중이거나, 로밍이나 심카드 없이 외국 여행 중) 노트를 작성하기 편리하다.&lt;/li&gt;
  &lt;li&gt;검색 퀄리티가 나쁘지 않다. 한국어 검색 결과는 영어에 비해서는 질이 떨어지긴 하지만, 그래도 아주 못 쓸 정도는 아니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;단점&quot;&gt;단점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;(여러 장비에서 같은 문서를 다르게 수정했을 경우) 충돌 풀기 기능이 어설프다.
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/4129049/why-is-a-3-way-merge-advantageous-over-a-2-way-merge&quot;&gt;Three-way merge&lt;/a&gt;와 같은 방법을 사용해서 충돌을 풀 수 있는 경우에도 충돌을 자동으로 풀지 않고, 분기된 문서를 모두 보존해서 사용자가 수동으로 충돌을 풀게 만든다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.markdownguide.org/getting-starte&quot;&gt;Markdown&lt;/a&gt;, &lt;a href=&quot;http://docutils.sourceforge.net/rst.html&quot;&gt;RST&lt;/a&gt; 처럼 시맨틱 정보가 보장되는 포맷을 사용할 수 없다. WYSIWYG 에디터를 사용해야 한다.
    &lt;ul&gt;
      &lt;li&gt;위 문제는 에버노트를 백엔드로 사용하는 Markdown 에디터인 &lt;a href=&quot;https://marxi.co&quot;&gt;Marxico&lt;/a&gt;를 이용해서 해결할 수 있다. Marxico 자체는 매우 훌륭하다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;스타일이 지원되지 않는다.&lt;/strong&gt; 헤더 1, 헤더 2, 본문, 코드 등과 같은 시맨틱 스타일 지정 없이 글꼴 변경만으로 표현해야 한다. 만약 6개월 전에는 헤더 3을 bold Arial 14pt로 표기 했었지만, 오늘부터 헤더 3의 스타일을 bold Helvetica 12pt로 변경하고 싶다면 예전 노트를 샅샅히 뒤져서 예전 글꼴로 되어있던 부분을 새로운 글꼴로 변경해주어야 한다. 이 부분은 진짜 치명적인 단점이다. 다른 기능이 훌륭해서 돈을 내고 사용할까 고민했었지만, 결국 에버노트 자체를 사용하지 않기로 결심하게 된 결정적인 이유가 바로 이 부분이다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;참고-1&quot;&gt;참고 1&lt;/h4&gt;

&lt;p&gt;Markdown이라는 것을 처음 들어보는 독자들을 위해서 약간의 첨언을 하자면, 문서의 시맨틱을 표현할 수 있는 마크업 언어이다. 예를 들면, 우리가 워드프로세스에서 특정 텍스트를 선택해서 글꼴을 변경하듯이 “이 부분은 이탤릭 글꼴로 보여야 한다”를 정의하는 것이 아니라, &lt;code class=&quot;highlighter-rouge&quot;&gt;이 부분의 의미는 *구문 강조*이다&lt;/code&gt;를 정의하는 포맷이다. &lt;code class=&quot;highlighter-rouge&quot;&gt;*구문 강조*&lt;/code&gt;가 어떻게 보여야 하는지는 별도의 스타일시트를 통해서 정의한다. 잘 정의된 기본값을 그대로 사용해도 좋고, 필요한 경우에 스타일을 재정의(override) 할 수 있다.&lt;/p&gt;

&lt;p&gt;구문 강조 뿐만이 아니라 헤더, 하이퍼링크, 이미지, 불릿 포인트(bullet points), 인용, 코드 블럭, 테이블, 각주(footnotes) 등 문서를 작성하는데 필요한 여러가지 요소를 표현할 수 있다.&lt;/p&gt;

&lt;p&gt;그 덕분에 문서를 작성할 때에는 “어떻게 보일 것인가” 대신 “무슨 의미인가”에만 집중하면 된다. 번거롭게 마우스로 손을 옮길 필요도 없다. 어떻게 보일 것인지에 대해서는 글의 내용과 분리해서 생각할 수 있고, 일괄적으로 변경하기 용이하다. 또한, 각 구성 요소의 의미가 그대로 보존되기 때문에 텍스트를 처리하는 프로그램을 작성하기 훨씬 용이하다. 내용을 복사 &amp;amp; 붙여넣기 하다가 무언가 틀어질 염려도 없다.&lt;/p&gt;

&lt;p&gt;Markdown을 모든 기능을 설명하는 것은 이 글의 범주를 벗어나는 일이기 때문에 더 자세한 내용은 &lt;a href=&quot;http://commonmark.org/help/&quot;&gt;이 페이지&lt;/a&gt;를 참고하면 좋을 것 같다.&lt;/p&gt;

&lt;h4 id=&quot;참고-2&quot;&gt;참고 2&lt;/h4&gt;

&lt;p&gt;지금 여러분이 읽고 있는 이 글도 Markdown으로 작성되었다.&lt;/p&gt;

&lt;h3 id=&quot;google-docs&quot;&gt;Google Docs&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/google-docs.png&quot; alt=&quot;Google Docs&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;강력한 검색 기능에 이끌려 상당수의 기록을 여기에 남겼다. 대학원 시절 작성했던 클래스 노트의 상당수가 여기에 저장되어있다.&lt;/p&gt;

&lt;h4 id=&quot;장점-1&quot;&gt;장점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;에버노트와 다르게 스타일 지정은 된다.&lt;/li&gt;
  &lt;li&gt;구글 제품 답게 검색 성능은 타의 추종을 불허한다.
    &lt;ul&gt;
      &lt;li&gt;우리가 구글 웹 검색에서 기대하는 세계 최고의 검색 엔진을 내 개인 문서를 검색하는데 사용할 수 있다.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;텍스트로 입력하지 않은 내용도 검색해준다.&lt;/strong&gt; 예를 들어서, ‘pizza’로 검색을 할 경우 피자가 들어간 이미지를 보여준다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;여러명이 실시간으로 문서를 공동 편집할 때 매우 편리하다.&lt;/li&gt;
  &lt;li&gt;매우 똑똑한 리비전 관리를 해준다.&lt;/li&gt;
  &lt;li&gt;Docs, Spreadsheet 등 네이티브하게 지원되는 포맷을 사용할 경우 용량 할당량(quota)을 잡아먹지 않는다. 이론적으로는 무제한 용량처럼 사용할 수 있다.&lt;/li&gt;
  &lt;li&gt;크롬 확장 기능을 설치하면 오프라인 상태로도 사용 가능하다.&lt;/li&gt;
  &lt;li&gt;구글 아이디를 사용하는만큼 &lt;a href=&quot;https://en.wikipedia.org/wiki/Multi-factor_authentication&quot;&gt;MFA(Multi-Factor Authentication)&lt;/a&gt;를 사용할 수 있다.&lt;/li&gt;
  &lt;li&gt;문서 편집 도구인 독스(Docs) 뿐만 아니라 스프레드시트, 프리젠테이션, 폼(Form), 드로잉(Drawing) 등 매우 다양한 오피스 제품군을 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;단점-1&quot;&gt;단점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;시맨틱이 보장되는 문서 포맷은 지원되지 않는다. 이건 &lt;a href=&quot;https://stackedit.io&quot;&gt;StackEdit&lt;/a&gt;과 같은 도구를 통해 해결할 수 있다.&lt;/li&gt;
  &lt;li&gt;문서에 붙여넣은 이미지를 원본 그대로 꺼내는 작업이 매우 까다롭다.
    &lt;ul&gt;
      &lt;li&gt;이미지 복사는 구글 문서 안에서만 유효하다.&lt;/li&gt;
      &lt;li&gt;이미지를 바깥 세계로 가지고 나가려면 화면을 캡쳐하거나, 브라우저의 디버깅 도구를 사용하거나, 문서를 &lt;code class=&quot;highlighter-rouge&quot;&gt;.zip&lt;/code&gt;으로 내보내기 해서 꺼내야 한다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;UI가 전반적으로 무언가 굼뜬 느낌이 있다.
    &lt;ul&gt;
      &lt;li&gt;따라서 간단한 메모를 작성하기에는 적합하지 않을 수도 있다.&lt;/li&gt;
      &lt;li&gt;Docs를 사용하다보면 &lt;a href=&quot;https://code.visualstudio.com&quot;&gt;Visual Studio Code&lt;/a&gt; 또는 &lt;a href=&quot;https://www.vim.org/download.php&quot;&gt;Vim&lt;/a&gt; 같은 텍스트 에디터를 띄워서 빠르고 가볍게 문서를 편집할 수 있는 환경이 그리워진다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;dropbox&quot;&gt;Dropbox&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/dropbox.png&quot; alt=&quot;Dropbox&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;파일 관리와 공유에 대해서는 아마 절대 강자가 아닐까 한다. 한때에는 Dropbox와 비슷한 기능을 하면서 BitTorrent 프로토콜을 사용하는 &lt;a href=&quot;https://www.resilio.com&quot;&gt;Resilio (그 당시 이름은 BitTorrent Sync)&lt;/a&gt;를 사용하기도 했었지만, 리비전 관리 기능이나 다른 사람과 파일을 공유하고 불특정 다수에게 파일을 배포할 때 만큼은 Dropbox보다 편리한 도구를 보지 못했다.&lt;/p&gt;

&lt;h4 id=&quot;장점-2&quot;&gt;장점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;텍스트 문서 뿐만 아니라 모든 파일에 대한 &lt;a href=&quot;https://en.wikipedia.org/wiki/Revision_(writing)&quot;&gt;리비전&lt;/a&gt; 관리가 용이하다.&lt;/li&gt;
  &lt;li&gt;Markdown 형식으로 작성된 파일을 &lt;code class=&quot;highlighter-rouge&quot;&gt;.md&lt;/code&gt; 확장자를 붙여서 노트, 문서처럼 관리했는데, 다른 그 어떤 에디터, 웹 인터페이스, 모바일 앱과 비교해도 Visual Studio Code로 글을 작성할 때의 경험이 제일 훌륭했다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;단점-2&quot;&gt;단점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Unicode encoding conflict&lt;/li&gt;
  &lt;li&gt;Dropbox가 실행되는 환경의 파일과 디렉토리 구조를 그대로 따라가야 하다보니 하나의 문서가 여러개의 디렉토리에 속한것 처럼 보이게 만들 수 없다. Gmail에서는 태그를 이용해서 이런 방식으로 메시지를 정리하고 열람할 수 있게 해준다. Dropbox가 지원하는 모든 운영체제에서는 하나의 파일이 단 하나의 부모 디렉토리를 가진다.&lt;/li&gt;
  &lt;li&gt;검색 경험이 운영체제에 따라 달라질 수 있다.&lt;/li&gt;
  &lt;li&gt;모바일에서 문서를 바로 작성하기에는 불편하다.
    &lt;ul&gt;
      &lt;li&gt;사실 이건 Dropbox가 노트 앱이 아니라, 파일을 쉽게 관리하고 공유할 수 있도록 만드는 것이 본래의 목적이기 때문에 Dropbox의 단점이라고 이야기 할 수는 없다.&lt;/li&gt;
      &lt;li&gt;드랍박스에서 만든 문서 작성 도구인 &lt;a href=&quot;https://www.dropbox.com/paper&quot;&gt;Paper&lt;/a&gt;는 모바일에서도 쓰기 편리하다. 하지만 에버노트와 구글 독스가 가진 단점 몇 가지를 가지고 있다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;google-keep&quot;&gt;Google Keep&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/google-keep.png&quot; alt=&quot;Google Keep&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;원래는 간단한 기록을 남기기 위해 Trello를 사용했었지만, 구글에서 비슷한 제품을 만들었다고 해서 한번 써봤다.&lt;/p&gt;

&lt;h4 id=&quot;장점-3&quot;&gt;장점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;모바일에서 사용하기에 괜찮다.&lt;/li&gt;
  &lt;li&gt;구글 제품 답게 검색 성능이 좋고 협업 기능이 갖추어져있다.&lt;/li&gt;
  &lt;li&gt;Google Docs와 연동된다. Keep에서 간단하게 메모해놓은 것을 나중에 정식으로 문서로 작성하기 편리하게 되어있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;단점-3&quot;&gt;단점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;긴 문서를 작성하기에 적합한 도구는 아니다.&lt;/li&gt;
  &lt;li&gt;메모의 체계적인 분류가 어렵다. 메모 분류를 위해 지원하는 기능은 각 메모에 색깔을 부여할 수 있는 정도이다.&lt;/li&gt;
  &lt;li&gt;시맨틱이 보존되는 포맷을 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;trello&quot;&gt;Trello&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/trello.png&quot; alt=&quot;Trello&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;첫 직장에서 쓰게 되었는데, 제공하는 기능이 꽤 마음에 들어서 아직까지도 잘 사용하고 있다.&lt;/p&gt;

&lt;h4 id=&quot;장점-4&quot;&gt;장점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;시맨틱이 보존되는 포맷을 사용할 수 있다. (Markdown)&lt;/li&gt;
  &lt;li&gt;모바일에서 사용하기에 괜찮다.&lt;/li&gt;
  &lt;li&gt;협업 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;단점-4&quot;&gt;단점&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Keep과 마찬가지로 길이가 긴 문서를 관리하기 편하지는 않다.
    &lt;ul&gt;
      &lt;li&gt;사실 이건 제품 자체의 단점이라기보다는 ‘종합적인 기록 관리 도구로서’ 사용하기에 부족한 점이다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;한국어 검색 기능이 부족하다.
    &lt;ul&gt;
      &lt;li&gt;단순하게 띄어쓰기를 기준으로 구분해서 인덱싱 하는 듯. 예를 들어서, ‘농림’으로 검색하면 “옥수수가 남미 지역 가뭄으로 인해 6.0% 뛰면서 농림수산품이 0.2% 상승했다.” 와 같은 문장을 포함한 기록이 검색되지 않는다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;위키wiki&quot;&gt;위키(Wiki)&lt;/h2&gt;

&lt;p&gt;그러다가 문득 이런 생각이 들었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“&lt;a href=&quot;http://wikipedia.org&quot;&gt;위키피디아&lt;/a&gt;처럼 나의 모든 기록을 위키 형태로 관리하는건 어떨까?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;위키야말로 내가 원하는 거의 모든 조건을 만족하는 문서 관리 방법이 아닐까 한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;시맨틱이 보존되는 형식의 문서 작성&lt;/li&gt;
  &lt;li&gt;뷰어와 에디터의 분리&lt;/li&gt;
  &lt;li&gt;검색&lt;/li&gt;
  &lt;li&gt;리비전 관리&lt;/li&gt;
  &lt;li&gt;멀티미디어 첨부&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그래서 여러가지 위키 서비스와 설치형 제품들을 알아보았지만, 딱히 “이거다!” 하는 느낌을 주는건 찾지 못했다. 새로운 위키 서비스를 발견해서 테스트 목적으로 문서 몇 개를 만들다보면 무언가 중요한게 하나씩 빠져있는 느낌을 받았다. 예를 들면, 어떤 서비스는 클립보드에 있는 이미지를 붙여넣는 기능이 지원되지 않고 이미지 파일을 올려야 한다거나, 다른 서비스에서는 WYSIWYG 에디터만 제공하기 때문에 시맨틱이 보존되는 문서를 작성하기 어려웠다. 그럴 때마다 금방 포기하고 다시 구글 독스로 돌아갔었다.&lt;/p&gt;

&lt;p&gt;그렇게 여러가지 위키 서비스를 조금씩 기웃거리다가, 얼마 전에 회사에서 아주 잘 사용하고 있는 &lt;a href=&quot;https://gitlab.com&quot;&gt;깃랩(GitLab)&lt;/a&gt;이 떠올랐다. 깃랩은 사실 위키 서비스라기보다는 소스코드 버전 관리, 이슈 트래커, 위키 등을 합쳐놓은 소프트웨어 프로젝트 관리 도구이다. 하지만 꽤 괜찮은 위키 기능이 제공되기 때문에 위키 기능만 사용하는 것도 어쩌면 괜찮은 방법이 아닐까 하는 생각이 들었다. (왜 진작 이 생각을 하지 못했지!)&lt;/p&gt;

&lt;h2 id=&quot;깃랩gitlab&quot;&gt;깃랩(GitLab)&lt;/h2&gt;

&lt;p&gt;깃랩은 크게 두 가지 형태로 사용할 수 있는데, 서비스형과 설치형이다. 서비스형은 깃랩에서 호스팅 해주는 서비스를 사용하는 것이고 (&lt;a href=&quot;https://www.investopedia.com/terms/s/software-as-a-service-saas.asp&quot;&gt;SaaS&lt;/a&gt;), 설치형은 사용자가 직접 본인의 서버에 깃랩 소프트웨어를 설치해서 사용하는 것이다. 깃랩에서 호스트 해주는 서비스를 이용하면 서버 운영에 대한 아무런 고민을 하지 않을 수 있어서 좋지만, 이왕 깃랩을 도입하는 김에 위에서 언급했던 서비스들을 사용하면서 항상 마음 한켠에 부담으로 자리잡았던 사생활 보호 문제와 기록에 대한 소유권 문제를 해결하고 싶다는 생각이 들었다. 누군가가 나의 데이터를 보관해준다면, 특히 무료로 보관해준다면 어떤 식으로든 대가를 치르게 되어있다.&lt;/p&gt;

&lt;p&gt;설치형 깃랩을 사용해보기로 마음 먹었다. 하지만 처음부터 깃랩 서버를 구축하기보다는 도커 이미지를 받아서 운영을 해본 후 별 문제가 없을 때 본격적으로 깃랩 서버를 구축하는 것도 괜찮겠다 싶어서 일단 도커 이미지를 받았다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker get gitlab/gitlab-ee
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그리고 나서 다음과 같이 실행하면 된다. 처음 서비스를 띄우는데 의외로 오래 걸린다. 1분 정도 걸린 듯.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run --detach \
    --hostname localhost \
    --publish 8443:443 --publish 8080:80 --publish 2222:22 \
    --name gitlab \
    --restart always \
    --volume $HOME/gitlab/config:/etc/gitlab \
    --volume $HOME/gitlab/logs:/var/log/gitlab \
    --volume $HOME/gitlab/data:/var/opt/gitlab \
    gitlab/gitlab-ee:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이런식으로 깃랩 서비스를 운영하는 한, 홈 디렉토리에 있는 &lt;code class=&quot;highlighter-rouge&quot;&gt;gitlab&lt;/code&gt; 디렉토리만 잘 보관하면 모든 데이터가 안전하게 보관된다.&lt;/p&gt;

&lt;h3 id=&quot;무엇을-기록하는가&quot;&gt;무엇을 기록하는가?&lt;/h3&gt;

&lt;p&gt;도커를 이용해서 깃랩 서비스를 띄운 다음 여러가지 기록들을 위키에 기록하기 시작했다. 2주 정도 꾸준히 작성하다보니 어느새 이런 대략적인 문서의 분류들이 생겼다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/toc.png&quot; alt=&quot;Table of Contents&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;지금은 413건의 문서가 있지만, 앞으로 계속 늘어날 예정이다. 여행하면서 기록한 내용들, 각종 사건사고 기록, 문득 떠오른 앱/서비스 아이디어들, 사람들에 대한 기록 등 수많은 종류의 기록이 있다. 특히 예전에 작성된 일기를 모두 위키로 옮겨온다면 문서 수가 몇배로 늘어날 것이다.&lt;/p&gt;

&lt;p&gt;살다보면 이런저런 일을 겪기 마련인데, 그때마다 일의 진행상황을 기록해두면 나중에 비슷한 일이 생겼을 때 참고하기 좋다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/rent-renewal.png&quot; alt=&quot;임대차계약 갱신&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;임대차 계약을 갱신하면서 생긴 문제와 해결 과정을 기록해두기도 하고,&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/job-interview.png&quot; alt=&quot;Job interview with YouTube&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;면접 질문에 제대로 답하지 못했던 일도 기록해놓으면 언젠가 다 쓸모가 있다.&lt;/p&gt;

&lt;h3 id=&quot;마음에-드는-기능-1-drag--drop&quot;&gt;마음에 드는 기능 1: Drag &amp;amp; Drop&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/dnd1.png&quot; alt=&quot;Drag and drop&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;이미지를 비롯한 파일을 첨부하고자 할 때 이렇게 파일을 편집창으로 끌어다 놓으면 알아서 업로드가 되고,&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/dnd2.png&quot; alt=&quot;Drag and drop&quot; style=&quot;max-width:75%; margin:auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;이렇게 자동으로 마크업이 완성된다. 매우 편리한 기능이다.&lt;/p&gt;

&lt;h3 id=&quot;마음에-드는-기능-2-하이퍼링크&quot;&gt;마음에 드는 기능 2: 하이퍼링크&lt;/h3&gt;

&lt;p&gt;위키로 문서를 작성할 때의 큰 장점 중 하나는 바로 위키 페이지간 상호 링크가 가능하다는 점이 아닐까 한다. 예를 들어서, 어떤 문서를 작성하다가 비트코인에 대한 이야기가 나오면 예전에 작성해둔 ‘비트코인’ 항목으로 링크를 걸 수 있다. 물론 외부 링크도 가능하다. 웹을 웹답게 사용할 수 있어서 좋다.&lt;/p&gt;

&lt;h3 id=&quot;마음에-드는-기능-3-wiki-as-a-git-repository&quot;&gt;마음에 드는 기능 3: Wiki as a Git repository&lt;/h3&gt;

&lt;p&gt;그리고 깃랩에서는 위키를 Git 저장소 형태로 제공해준다. 다시 말해서 깃랩에서 제공하는 웹 에디터를 사용하지 않고, 원하는 에디터를 사용해서 자유롭게 위키를 편집할 수 있다는 뜻이다. 또한, 통신이 되지 않거나 위키 서버에 접속할 수 없을 때에도 위키 문서를 수정해 두었다가 나중에 위키 서버에 접속할 수 있게 되었을 때 변경된 내용을 푸시할 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;아직-결정하지-못한-문제들&quot;&gt;아직 결정하지 못한 문제들&lt;/h2&gt;

&lt;p&gt;지금은 베타 테스트 느낌으로 발을 반 쯤만 담구어서 사용하고 있는데, 본격적으로 사용하려면 결정하고 해결해야 할 문제들이 남아있다.&lt;/p&gt;

&lt;h3 id=&quot;기존-기록-가져오기&quot;&gt;기존 기록 가져오기&lt;/h3&gt;

&lt;p&gt;일기장 같은 경우 수백건의 기록이 구글 독스에 저장되어있는데, 이것을 하나씩 손으로 옮기기는 어려울 것 같으니 자동으로 가져올 수 있는 스크립트를 작성해야 할 것 같다.&lt;/p&gt;

&lt;h3 id=&quot;외부-접속과-https&quot;&gt;외부 접속과 HTTPS&lt;/h3&gt;

&lt;p&gt;지금은 특정 호스트에서만 접속할 수 있고, 외부 세계(인터넷)와는 (거의) 단절되어있다. 이건 장점이자 단점인데, 장점은 여러가지 보안 문제에 대해서 비교적 안전하다는 것이고, 단점은 이동중이거나 다른 환경에 있을 때 접속할 수 없다는 점이다.&lt;/p&gt;

&lt;p&gt;만약 외부 세계에서 연결할 수 있도록 만들고자 한다면 HTTPS를 필수적으로 사용해야 할 것이다. Self-signed 인증서를 이용해서 암호화된 연결을 제공할 수도 있고&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, &lt;a href=&quot;https://www.cloudflare.com&quot;&gt;Cloudflare&lt;/a&gt;와 같은 서비스를 이용해서 프록시에서 암호화된 연결을 제공하는 방법도 있다. 다만, 후자의 경우 Cloudflare의 프록시 서버와 나의 위키 서버 간에는 평문 통신을 하게 되므로 전자와 같은 방법이 더 나을 것이라고 생각한다.&lt;/p&gt;

&lt;h3 id=&quot;백업&quot;&gt;백업&lt;/h3&gt;

&lt;p&gt;호스트 되는 서비스를 이용한다면 특별히 백업에 신경쓸 필요가 없겠지만, 직접 서버를 운영하는만큼 백업에 특별히 주의를 기울여야 한다. 명령어를 잘못 치거나, 깃랩 서버가 보관된 장소에 불이 나거나, 천재지변 또는 외계인의 침략과 같은 이유로 십몇년간의 기록이 순식간에 사라져버린다면 그것만큼 허무한 일도 없을 것이다. 주기적으로, 그리고 최소 두 개 이상의 지리적으로 분리된 저장소에 자동으로 백업을 하는 정책을 수립해야 한다. &lt;code class=&quot;highlighter-rouge&quot;&gt;gitlab&lt;/code&gt; 디렉토리를 암호화 해서 AWS Glacier 버지니아 리전에 백업하고, Azure 독일 리전에도 백업해 놓는다면 아주 적은 비용으로 안전하게 데이터를 보관할 수 있을 것이다.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h3 id=&quot;모바일에서의-작성&quot;&gt;모바일에서의 작성&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2018/personal-wiki/gitlab-mobile.png&quot; alt=&quot;GitLab in mobile Safari&quot; style=&quot;max-width:240px; float:right;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;웹 인터페이스를 이용하는만큼 모바일 웹브라우저로 모든 기능을 사용할 수 있기는 하다. 다만, 깃랩 자체가 모바일에서 문서를 적극적으로 작성하는 부분에 있어서 편의성을 고려하고 만든 것이 아니라 본격적인 문서 작성을 하려면 데스크탑의 도움이 필요하다. 아니, 그 이 전에 작은 모바일 기기에서 사소하지 않은 문서 작성을 하는 것이 가능한 일(possibility)이기는 하지만, 적절한 일(plausability)인지는 조금 더 고민을 해 보아야 할 것 같다.&lt;/p&gt;

&lt;p&gt;당분간은 모바일에서 Google Keep 또는 Trello를 이용해서 간단히 기록하고, 나중에 위키로 옮겨 적는 작업을 하게 될 것이다.&lt;/p&gt;

&lt;h3 id=&quot;라이센스-비용&quot;&gt;라이센스 비용&lt;/h3&gt;

&lt;p&gt;지금 엔터프라이즈 버전을 사용하고 있긴 하지만, 라이센스 비용을 지불하지 않았기 때문에 유료 기능이 비활성화 되어있다. &lt;a href=&quot;https://www.elastic.co&quot;&gt;Elasticsearch&lt;/a&gt;를 연동하는 등 여러가지 고급 기능을 사용하려면 라이센스 비용을 지불해야 하는데, 이게 과연 그만한 가치가 있는지 따져볼 필요가 있다.&lt;/p&gt;

&lt;p&gt;Starter의 경우 월 $4 수준이기 때문에 큰 부담 없이 사용할 수 있지만, Ultimate의 경우 월 $99 수준이라 개인적으로 사용하기엔 조금 부담이 될 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;

&lt;p&gt;근 10년간 개인적인 기록을 정리하면서 고민했던 내용과, 최근 2주간 개인 위키를 사용한 소감을 간단하게(?) 정리해보았다. 위키로 기록을 관리하게 되면서 기존에 사용하던 도구에서 느꼈던 다양한 불편함을 해소하고, 사생활 보호 문제와 명확한 소유권 보장 등 부가적인 장점을 취할 수 있게 되었다. 하지만 아직 본격적으로 사용한다기보다는 시험 운용에 가깝다. 안정적으로, 그리고 장기적으로 개인 위키를 운영하기 위해서 앞으로 해결해야 할 일들이 많이 있겠지만, 왠지 즐거운 일이 될 것 같은 느낌이 든다.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.google.co.kr/search?q=%EC%97%AD%EC%82%AC%EB%A5%BC+%EC%9E%8A%EC%9D%80+%EB%AF%BC%EC%A1%B1%EC%97%90%EA%B2%8C+%EB%AF%B8%EB%9E%98%EB%8A%94+%EC%97%86%EB%8B%A4&quot;&gt;“역사를 잊은 민족에게 미래는 없다” 에 대한 구글 검색 결과&lt;/a&gt; &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;보안의 관점에서는 어쩌면 좋은 일일지도 모른다. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://danieleagle.com/2017/01/gitlab-ce-with-https-using-docker/&quot;&gt;https://danieleagle.com/2017/01/gitlab-ce-with-https-using-docker/&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;백업 정책을 수립할 때 리전별 스토리지 가격, 리전간 장애 상관관계 등을 고려해서 실제로 어떤 리전에 백업 데이터를 보관할 것인지 결정하게 될 것 같다. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Wed, 25 Apr 2018 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/personal-wiki/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/personal-wiki/</guid>
      </item>
    
      <item>
        <title>레이니스트 핀테크 체험 - 어니스트펀드</title>
        <description>&lt;!--
NOTE: 이 글은 청중들이 채권, 금리, 연평균 수익률, P2P 대출 등 기본적인 금융 용어들을 숙지하고 있다는 전제 하에 작성되었습니다.
--&gt;

&lt;blockquote&gt;
  &lt;p&gt;은행은 대출 이자가 저렴하지만 문턱이 매우 높다. 은행에서 대출 승인이 나지 않는다고 해서 반드시 대출금 상환 능력이나 의지가 부족하다고 보기는 어렵다. 하지만 은행에서 돈을 빌리지 못한 사람들은 어쩔 수 없이 제2금융권으로 발걸음을 돌리는데, 이곳은 대출 이자가 매우 비싸다. 왜 한국에는 대출 금리의 중간 지대가 없을까?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;몇년 전에 친구와 맥주를 마시면서 이야기 했던 내용이다.&lt;/p&gt;

&lt;p&gt;누군가는 이러한 의문을 제기하는 수준에서 멈추었지만, 다른 누군가는 거기서 멈추지 않고 그 문제를 해결하기 위해 직접 팔을 걷어붙였다. 얼마 지나지 않아 이러한 ‘중간 지대’를 메꾸어주는 서비스들이 하나둘씩 생겨나기 시작했고, 그 중 하나가 바로 어니스트펀드였다.&lt;/p&gt;

&lt;h2 id=&quot;어니스트펀드&quot;&gt;어니스트펀드&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.honest-fund.com&quot;&gt;어니스트펀드&lt;/a&gt;는 P2P 신용 대출 중개 서비스이다. 대출을 받고자 하는 사람들이 어니스트펀드를 통해 대출 신청을 하면 어니스트펀드의 대출 심사 절차를 통해 대출 금리와 한도를 산정하고 대출이 실행된다. P2P 대출 서비스마다 고유한 신용 평가 모델을 가지고 있는데, 이 신용 평가 모델이 얼마나 정확하게 부도 확률을 예측해서 적절한 금리와 한도를 산정해주는지가 곧 경쟁력이다. 지나치게 보수적으로 접근한다면 소수의 사람들에게만 대출을 해주게 되어 수익을 극대화 하지 못하게 되고, 그렇다고 너무 느슨하게 한다면 부도 채권이 걷잡을 수 없이 불어날 가능성이 있기 때문이다. 미국의 어떤 업체들은 더욱 정교한 신용 평가를 위해 대출 신청 사실을 대출자의 배우자가 알고 있는지 묻기도 하고&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, 트위터와 같이 모두에게 공개된 소셜 미디어 데이터를 분석하기도 한다.&lt;/p&gt;

&lt;p&gt;또한, 대출금은 어니스트펀드가 직접 마련하는 것이 아니라 다수의 투자자들이 조금씩 출자하여 마련하게 된다. 쉽게 말해서 어니스트펀드는 대출을 받고자 하는 사람들과 투자를 하고자 하는 사람들을 이어주는 서비스이다. 대출을 받고자 하는 사람들은 저축은행이나 캐피탈회사와 같은 제2금융권이나 사채 이자보다 훨씬 낮은 금리로 돈을 빌릴 수 있고, 투자를 하고자 하는 사람들은 은행 금리보다 월등하게 높은 이자 수익을 올릴 수 있다.&lt;/p&gt;

&lt;p&gt;어니스트펀드를 이용하는 고객이라면 아마도 투자자 혹은 대출자 중 하나일 것이다. 어니스트펀드 설립 초창기부터 지금까지 계속 투자를 했던 투자자의 관점에서 서비스를 다각도로 리뷰하는 시간을 가져보겠다.&lt;/p&gt;

&lt;h2 id=&quot;포트폴리오-투자&quot;&gt;포트폴리오 투자&lt;/h2&gt;
&lt;p&gt;어니스트펀드가 다른 P2P 대출 서비스와 차별화 되는 점을 하나 꼽자면 바로 ‘포트폴리오 투자 상품’이다. 개별 채권에 투자하는 것이 아니라 채권 묶음에 투자하는 방식이다. 보통 50개에서 100개 정도 되는 채권들을 묶어서 매달 일정 금액의 원금과 이자를 지급하는 18개월짜리 채권을 만들어낸다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2017/honest-fund/2017-03-31-00-21-05.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;예를 들어서, 이번달 초에 열린 포트폴리오 14호는 채권을 101개 묶어서 연평균수익률 11.13%를 제공하는 상품이다. 증권거래소에서 거래되는 일반적인 채권은 만기 때 약정된 원금과 이자를 한꺼번에 받게 되지만, 어니스트 포트폴리오 채권은 그것과는 달리 매달 원금과 이자를 조금씩 받는 구조이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2017/honest-fund/payment-schedules.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;처음 투자하는 사람 입장에서는 연평균 11.13%라는 말을 들으면 천만원을 투자했을 때 18개월 후 약 1,170만원을 돌려받을 것이라고 기대할 수도 있는데 (10,000,000 * 1.1113 * 18/12 = 11,695,000), 여기서 주의깊게 봐야 할 부분은 ‘매달 일정 금액을 돌려받는다’는 사실이다. 첫 달에는 투자금 전체에 대한 한달치 이자와 원금 일부를 돌려받는다. 그 다음달에는 원금에서 첫 달에 상환된 원금 일부를 제외한 금액에 대한 한달치 이자와 원금 일부를 돌려받는다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2017/honest-fund/2017-03-31-00-26-27.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;그렇게 해서 위의 그래프와 같은 모양으로 18개월에 걸쳐서 투자자에게 원금과 이자가 상환되는 구조이다. 전통적인 채권이나 부동산 담보 P2P 채권과는 다르게 P2P 신용 대출 채권은 일반적으로 매달 일정한 원리금을 상환하는 &lt;a href=&quot;http://wisenomics.com/loan/173/&quot;&gt;원리금균등상환&lt;/a&gt; 방식이다. 하지만 포트폴리오 채권의 상환 방식을 살펴보면 원금일시상환, 원금균등상환, 원리금균등상환 중 그 어디에도 속하지 않는다. 상환 기간이 제각각인 P2P 채권들을 여러개 묶다 보니 다소 독특한 모양이 나왔을 것이라고 생각된다.&lt;/p&gt;

&lt;h2 id=&quot;분산-투자&quot;&gt;분산 투자&lt;/h2&gt;
&lt;p&gt;이렇게 여러개의 채권을 묶어서 자동으로 분산 투자를 할 수 있도록 도와주는 점은 상당히 매력적이다. 대출자는 대부분 개인이거나 소규모 자영업자인데, 주식 시장에 상장된 회사와는 다르게 대출자의 재무 상황에 대해서 상세히 알기 어려운 부분이 있기 때문에 투자자 입장에서 개별 채권의 연체, 부도 확률을 가늠하는 것은 상당히 어려운 일이다. 게다가 고액 자산가가 아닌 이상 월급을 쪼개서 여러개의 채권에 분산 투자 하는 경우 각 개별 채권에 투자하는 금액이 소액이기 때문에 개별 채권의 위험을 파악하는데 많은 시간과 노력을 들이기에는 수지타산이 맞지 않는다. 그렇기 때문에 개별 채권을 잘 고르는 것 보다는 충분히 많은 숫자의 채권을 골고루 매입하는 편이 더 유리할 수 있다.&lt;/p&gt;

&lt;p&gt;또한, 최소 10만원부터 최대 1억까지 만원 단위로 투자가 가능하기 때문에 자금을 매우 유연하게 운용할 수 있다. 물론 8퍼센트와 같은 다른 P2P 대출 서비스에서도 비슷한 방식으로 분산 투자가 가능하지만, 개별 채권에 직접 투자 하는 방식이라 채권별 상환일과 만기일이 제각각이고, 투자 금액의 최소 단위(granularity) 또한 업체에 따라서 5만원에서 100만원으로 다양하기 때문에 소액을 가지고 효과적으로 분산 투자 하기 어려운 경우가 많다. 어니스트펀드에서 처럼 적은 금액으로 다양한 상품에 분산 투자를 할 수 있는 기회는 그렇게 많지 않다.&lt;/p&gt;

&lt;p&gt;어니스트펀드를 비롯하여 P2P 채권에 투자된 원금은 은행 예금과는 달리 예금자보호법에 의하여 보호되지 않기 때문에 투자 결과의 변동성(variance)을 줄이기 위해서 반드시 분산 투자를 해야 한다. 한 사람에게 큰 금액을 빌려주었을 경우엔 그 사람이 돈을 갚지 않을 경우 빌려준 돈을 전부 잃어버리게 된다. 하지만 여러 사람에게 적은 금액을 빌려주었을 경우 모든 사람이 돈을 갚지 않을 확률은 한 사람이 돈을 갚지 않을 확률보다 훨씬 낮고, 몇몇 사람이 돈을 갚지 않아서 발생한 손실분을 다른 사람들이 지불한 이자로 메울 수도 있다.&lt;/p&gt;

&lt;p&gt;하지만 무조건 여러개의 상품에 나눠서 투자하는 것이 전부가 아니다. 투자의 대상이 되는 기초 자산들간의 상관관계가 낮아야 한다. 상관관계가 높다면 분산 투자를 하는 의미가 희석된다. 예를 들어서, 전국 각지의 신축 주택 담보 P2P 대출 채권에 투자했는데, 기준 금리 인상이나 주택 과잉 공급, 인구 감소 등 외부 요인에 의해서 부동산 가격이 하락한다면 투자한 모든 기초 자산의 가치에 영향을 주기 때문에 원금 손실 위험이 커질 수 있다.&lt;/p&gt;

&lt;p&gt;반면, 개인 신용 대출의 경우 각 대출자들간의 경제 상황이 제각각이기 때문에 상대적으로 상관관계가 낮다고 볼 수 있다. 어니스트펀드를 통해서 돈을 빌려간 사람들 중에는 평범한 회사원, 공무원 뿐만 아니라 빵집 사장님도 있을 수 있고, 스시집 주방장도 있을 수 있고, 잘 나가는 연예인의 스타일리스트가 있을 수도 있다. 직업과 수입원, 연령과 지역, 가족 구성, 주거 형태도 다양하다. 따라서 특정한 사건이 발생하였을 때 그 사건이 모든 기초 자산에 영향을 줄 가능성이 상대적으로 적어진다. 예를 들어서, 조류 독감이 발생해서 전국 치킨집의 경영 상황이 악화된다면 치킨집 사장님들은 대출금 상환에 어려움을 겪을 수도 있겠지만, 구청에서 일하는 사무직 공무원은 별다른 영향을 받지 않고 대출금을 차곡차곡 갚아 나갈 수 있다.&lt;/p&gt;

&lt;p&gt;어니스트펀드 포트폴리오 투자 상품은 분명 여러가지 장점을 지닌 매력적인 상품이지만, 한 달에 단 한 번의 투자 기회가 있는 것은 아쉬움으로 남는다. 특히 시간이 갈수록 참여자가 많아지고 있어서 모집 금액이 다 채워질 때 까지 걸리는 시간이 점점 줄어들고 있다. 포트폴리오 상품 1호가 막 나왔을 때에는 모집 금액이 6억원으로 14호의 모집 금액 15억원과 비교하여 절반도 안 되는 수준이었지만 모집 금액을 채울 때 까지 며칠이 걸렸었다. 하지만 이제는 불과 몇시간만에 15억원을 채울 정도이다. 좋은 투자 기회를 잡으려면 부지런히 움직여야 한다.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/attachments/2017/honest-fund/dashboard.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;꾸준히 투자해놓으면 매달 제 2의 월급을 받는 기분을 누릴 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;연체-채권-관리&quot;&gt;연체 채권 관리&lt;/h2&gt;
&lt;p&gt;세상에는 다양한 사람들이 있고, 각자의 사정이 모두 다르기 때문에 어니스트펀드에서 아무리 정교한 신용 평가 모델을 만들어서 열심히 대출 심사를 한다고 해도 일정 수준의 연체나 부도는 필연적으로 발생하기 마련이다. 실제로 어니스트펀드 포트폴리오 1호부터 투자를 시작하여 현재 15호까지 총 1,196건의 채권에 투자를 했고 이 중 52건의 연체가 발생하였다. 그 중 연체일이 90일이 넘어 ‘채무 불이행’으로 분류된 것은 19건이고, 개인회생절차로 인해 매각된 채권이 11건이다.&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;연체가 발생했다고 해도 중간에 회수가 되는 경우도 있으므로 위의 수치만 가지고 연체율을 고려한 실질 수익률을 측정하는 것은 어렵다. 정확한 실질 수익률은 만기가 되어봐야 알 수 있다. 포트폴리오 1호 채권의 세전 연평균 수익률은 10%였지만 포트폴리오를 구성하는 채권들 중 일부에서 손실이 발생하여 실제 세전 수익률은 8%에 약간 못 미쳤다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2017/honest-fund/2017-03-31-00-02-29.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;아래와 같이 연체가 발생한 채권을 한 눈에 볼 수 있는 인터페이스를 제공한다는 점은 마음에 든다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2017/honest-fund/defaults.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;각 연체건별로 자세한 상황을 안내해준다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2017/honest-fund/2017-03-31-00-03-10.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;물론 가장 좋은 케이스는 연체가 발생하지 않는 상황이겠지만, 연체가 발생하더라도 이렇게 일 처리 과정과 데이터를 투명하게 공개하는 점은 긍정적으로 평가하고 싶다.&lt;/p&gt;

&lt;h2 id=&quot;채권-거래와-현금화&quot;&gt;채권 거래와 현금화&lt;/h2&gt;
&lt;p&gt;P2P 투자가 주식, 채권, 펀드 등 전통적인 투자 수단과 비교하여 가지는 단점이 있다면 환매가 어렵다는 점이다. 주식이나 펀드는 내가 원하는 시점에 현금화 하는 것이 가능하지만, P2P 채권은 그렇지 못하다. 사실 이 부분은 어니스트펀드 뿐만의 문제가 아니라 P2P 채권 모두의 문제이기도 하다.&lt;/p&gt;

&lt;p&gt;환매가 불가능하기 때문에 나중에 갑자기 현금이 필요할 때 문제가 되지 않도록 ‘진짜 여유 자금’만 가지고 투자하고 있다. 만약 환매가 조금 더 자유로웠다면 지금보다 더 적극적으로 투자 규모를 늘릴 수 있지 않았을까 하는 생각이 든다.&lt;/p&gt;

&lt;p&gt;채권 환매 얘기가 나와서 생각났는데, 2015년 말에 ‘8퍼센트’에서 공개한 ‘&lt;a href=&quot;http://www.newstomato.com/ReadNews.aspx?no=609622&quot;&gt;서울채권거래소&lt;/a&gt;‘라는 서비스가 있었다. 만기가 도래하지 않은 채권을 투자자들끼리 자유롭게 사고팔 수 있는 플랫폼이다. 하지만 무슨 이유에서인지 얼마 지나지 않아 서비스를 종료하게 되었고, 그 이후로는 아직 비슷한 시도를 하는 서비스를 보지 못했다.&lt;/p&gt;

&lt;p&gt;만약 P2P 채권거래소가 단일 업체가 개발하고 유지하기 어려운 성격의 서비스라면 P2P 금융 업체들끼리 컨소시엄을 구성하여 채권거래소 서비스를 만들어보는건 어떨까 하는 생각이 들었다. 채권의 환매 가능성을 열어둔다면 투자자들의 적극적인 참여를 이끌어낼 가능성이 있고, 궁극적으로는 P2P 금융 시장을 더 크게 성장시킬 수 있는 발판을 마련할 수 있지 않을까.&lt;/p&gt;

&lt;h2 id=&quot;법적-제도적-보호-장치&quot;&gt;법적, 제도적 보호 장치&lt;/h2&gt;
&lt;p&gt;P2P 투자가 전통적인 투자 수단과 비교하여 가지는 약점이 하나 더 있다. P2P 대출 업체가 파산하거나, 보안 사고가 생겨서 고객의 자금을 탈취당하는 등 극한 상황이 발생할 경우 투자금을 보호해줄 법적, 제도적 장치가 마련되어있지 않다는 점이다. 미국의 경우 은행이 파산하는 경우에도 해당 은행이 가지고 있던 채권은 파산법원의 감독 하에 다른 금융 기관이 매입하기 때문에 투자자의 자금을 대부분 보전할 수 있다.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; 한국의 경우엔 어떻게 되는지 설명해놓은 신뢰성 있는 문서를 찾지 못했지만, 은행업 자체의 성격을 고려했을 때 미국과 크게 다르지 않을 것으로 예상된다. 비슷한 예로, 증권사가 파산하는 경우에도 투자자들의 주식은 &lt;a href=&quot;http://www.ksd.or.kr&quot;&gt;한국예탁결제원&lt;/a&gt;에 안전하게 보관되기 때문에 다른 증권사로 갈아타야 하는 불편함은 있더라도 주식 자체가 증발하지는 않는다.&lt;/p&gt;

&lt;p&gt;만약 어니스트펀드가 파산한다면 내가 투자했던 돈은 어떻게 될까? 아쉽게도 이 부분에 대한 명확한 답을 찾지는 못했다. 어니스트펀드 뿐만 아니라 다른 P2P 대출 업체도 이 질문에서 자유롭지 못할 것이라고 생각한다. 이 부분에 대해서는 P2P 대출 업체들이 힘을 모아서 관련 법령을 제정하도록 입법기관을 설득하거나, 한국예탁결제원과 같은 역할을 하는 제도적 장치를 마련하는 노력을 해야 할 부분이라고 보여진다.&lt;/p&gt;

&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;
&lt;p&gt;어니스트펀드의 본질은 대출을 받고자 하는 사람들과 투자를 하고자 하는 사람들을 이어주는 P2P 금융 서비스이다. 상환 능력과 의지가 있음에도 불구하고 여러가지 사정으로 인해 은행에서 대출을 받기 어려운 사람들에게는 합리적인 대출 금리를 제공하고, 저금리 시대에 마땅한 투자처를 찾지 못한 투자자들에게는 은행 이자보다 훨씬 높은 투자 수익률을 올릴 수 있는 좋은 기회가 될 것이라고 생각한다. 또한, 어니스트펀드는 단순히 투자자와 대출자를 연결해주는 것을 넘어 ‘포트폴리오 투자’라는 편리하고 강력한 투자 상품을 제공함으로써 한국 P2P 금융 서비스 수준을 한 단계 올려놓았다.&lt;/p&gt;

&lt;p&gt;조금 전에 P2P 투자의 단점에 대해 다소 부정적인 어감으로 이야기 하긴 했지만, P2P 투자가 매력적인 투자처임을 부정하는 것은 아니다. 다만, 나와 같은 투자자들이 P2P 채권 투자에 동반되는 위험을 정확히 인지하고 투자를 시작했으면 하는 바람이 있다. 높은 수익률에는 필연적으로 높은 위험이 따른다는 점, 그리고 변동성을 줄이기 위해서는 반드시 적정한 수준의 분산 투자가 필수적이라는 부분을 다시 한 번 강조하면서 이 글을 마친다.&lt;/p&gt;

&lt;!--
표준화된 신용평가 모델 없음
'부도'의 기준이 업체마다 상이할 수 있음
총 투자 채권 수 1,076건
연체 채권 수 총 41건
90일 이상, 채무 불이행 21건
평균 예상 수익률 10.96%
https://www.honest-fund.com/mypage/investor/products/730/bonds/579
--&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;안 재만. &lt;em&gt;&lt;a href=&quot;http://biz.chosun.com/site/data/html_dir/2015/06/09/2015060902804.html&quot;&gt;[신용평가의 진화] 빅데이터의 묘기 “대출받는 걸 와이프가 알고 있나요?”&lt;/a&gt;&lt;/em&gt;. 조선비즈, 10 June 2015. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;장기연체 개인회생채권에 대한 어니스트펀드의 설명: 120일 이상 장기연체 개인회생채권은 당사정책에 의해 미상환원금의 50%의 가액으로 채권을 매각하며, 이에 따라 본 채권은 미상환원금의 50%의 가액으로 매각되었음. 매각대금은 4월 포트폴리오 상환일에 같이 지급될 예정. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;“Consumer Information.” &lt;em&gt;&lt;a href=&quot;http://www.consumer.ftc.gov/articles/0191-if-your-mortgage-lender-or-servicer-closing-or-bankruptcy&quot;&gt;If Your Mortgage Lender or Servicer Is Closing or in Bankruptcy&lt;/a&gt;&lt;/em&gt;, Federal Trade Comission, Dec. 2007. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 31 Mar 2017 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/honest-fund/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/honest-fund/</guid>
      </item>
    
      <item>
        <title>이상한 호주 여행기 - 비용 정산</title>
        <description>&lt;p&gt;NOTE: 여행중 남긴 기록들을 토대로 열심히 여행기를 작성하고 있는데, 사실 이 글이 여행기의 마지막편이다. 마지막편을 가장 먼저 공개하게 되어서 유감이지만 완성도가 낮은 글을 공개하기는 곤란하다고 판단했다. 지금 나의 친구들과 동료들이 열심히 리뷰해주고 있으니, 리뷰 코멘트를 반영해서 완성되는대로 한편씩 차례대로 공개할 생각이다.&lt;/p&gt;

&lt;h2 id=&quot;사건의-개요&quot;&gt;사건의 개요&lt;/h2&gt;

&lt;p&gt;회사의 든든한 지원을 등에 업고 즐거운 여행을 다녀온것 까지는 좋았으나, 복잡한 비용 정산 문제가 남아있었다.&lt;/p&gt;

&lt;p&gt;부연 설명을 하나 덧붙이자면, 1인당 주어진 GEP 예산은 300만원이었다. 이것으로 항공권, 현지 교통비, 숙박, 식사 등 여행에 필요한 전반적인 비용들을 해결할 수 있다. 주어진 예산만 가지고도 여행을 하는데 부족함이 없었겠지만, 개인 돈을 조금 보태서 쓴 덕분에 훨씬 풍요로운 생활을 누릴 수 있었다.&lt;/p&gt;

&lt;p&gt;GEP 예산 지원을 받기 위해서는 여행중에 사용한 비용을 법인카드로 결제 하고 그 내역을 상신하면 된다. 비용은 한달 단위로 정산한다. 9월 말에 사용한 비용은 여행중에 VPN을 통해 회사 인트라넷에 접속해서 직접 상신했고, 호주팀 사람들과 공동으로 사용한 비용은 공용 법인카드로 처리하고 담당자분께서 대신 결재를 올려주셨다. 이제 10월 초에 사용한 비용만 정산하면 된다.&lt;/p&gt;

&lt;p&gt;9월에 사용한 비용과 호주팀 사람들과 공동으로 사용한 비용을 제외하고 나에게 남은 예산은 약 90만원 정도였다. 이 글에서는 자세히 설명하지 않을 여러가지 복잡한 사정에 의해 그 예산의 일부는 우리 팀원이 사용한 결제 내역을 처리하는데 사용하고, 그분이 그 비용을 나한테 현금으로 보내주셨다. 아직 결재를 올리지 못한 법인카드 사용 내역은 29건, 총 1,324,996원이었다. 이중에 GEP 예산으로 처리할 수 있는 비용이 903,910원, 내 개인 돈으로 해결해야 할 돈이 421,086원이었다. 우리 팀원이 나한테 보내준 돈이 590,943원이었으니 내가 결재를 올릴 수 있는 금액은 정확히 312,967원이었다. 다시 말해서 그 29건의 거래 내역 중 적당한 내역들을 골라서 최대한 312,967원에 가깝게, 하지만 그 금액을 넘기지는 않게 조합해서 결재를 올리면 되는 아주 간단한(?) 문제였다.&lt;/p&gt;

&lt;h3 id=&quot;아직-결재를-올리지-못한-내역들&quot;&gt;아직 결재를 올리지 못한 내역들&lt;/h3&gt;

&lt;p&gt;전체 거래 내역을 일일이 들여다보지 않아도 포스트의 내용을 이해하는데 전혀 지장이 없긴 하지만, 궁금증이 많은 독자들을 위해서 전체 내역을 기록해두었다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;사용 내역&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;금액(KRW)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;AYR TRAVEL CENTRE AYR AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;16,199&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HJ MACKAY MACKAY AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10,906&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HJ ROSS RIVER TOWNSVILLE AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;3,382&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;TRAVEL RESERVATION AU SYDNEY AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;84,725&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;TRAVEL RESERVATION KOR PARIS FRA&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;71,294&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;JIMMYS BURGER &amp;amp; CO CAIRNS AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;36,372&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AROI BANGKOK THAI RE HERMIT PARK AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;21,256&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;WW PETROL 2268 HERMIT PAR AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;18,555&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MATILDA MARYBOROUGH SINNAMON PARK AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;21,636&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;COLES EXPRESS 1764 MACKAY AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;19,425&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CALTEX BOYNE RIVER BENARABY AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;18,688&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SUBWAY MACKAY - NEBO WEST MACKAY AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10,482&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Lillys Bistro 5277108 GIN GIN AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;7,613&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;COLES 4564 BRISBANE AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;74,453&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AIRBNB * AIRBNB.COM GBR&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;58,176&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sydney Opera House Tru Sydney AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;50,942&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SECURE PARKING ASTOR T SPRING HILL AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;26,950&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7-ELEVEN 4174 GAVEN AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;26,582&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HAKATAYA RAMEN BR SBANE AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;9,589&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;KFC NO 2 PORT MACQRIE PORT MACQUARI AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;9,520&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AIRBNB * AIRBNB.COM GBR&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;210,198&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;JAMIES ITALIAN BY JA SYDNEY AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;50,818&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7-ELEVEN 2240 LAMBTON AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;36,992&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;FRIENDLY GROCER PYRM PYRMONT AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;17,732&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;NOMONIE PTY LTD WICKHAM AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;16,953&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;UBER AU OCT05 CRMVT HELP.UBER.C AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;9,971&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;COLES EXPRESS 1698 ULTIMO AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;8,031&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;TRANSPORT FOR NSW SYDNEY AUS&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5,247&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HERTZ AUSTRALIA P/L&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;372,309&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;렌터카(372,309원)는 이미 최대치를 넘겼으니 올리지 말고, 그 다음으로 큰 금액인 에어비앤비(210,198원) 내역을 올리면 10만원 정도가 남으니까 10만원 이하 결제 내역 중에서 다음으로 큰게 뭐였더라… 아니지, &lt;a href=&quot;https://en.wikipedia.org/wiki/Greedy_algorithm&quot;&gt;그리디 알고리즘(greedy algorithm)&lt;/a&gt;으로는 전역적 최적해(global optimum)를 찾지 못할 가능성이 높다. 생각이 여기까지 미치자 프로그래머의 고질병인 &lt;a href=&quot;https://www.lesstif.com/pages/viewpage.action?pageId=29590364&quot;&gt;야크 털 깎기&lt;/a&gt;가 발현되었다.&lt;/p&gt;

&lt;h2 id=&quot;문제-해결&quot;&gt;문제 해결&lt;/h2&gt;
&lt;p&gt;가만있자, 이거 어디선가 많이 본 문제인데? 컴퓨터공학을 전공한 사람이라면 내가 무슨 얘기를 할 것인지 벌써 눈치 챘을 것이라고 생각한다. 그렇다. 최적화 문제의 일종인 배낭 문제와 매우 흡사하다.&lt;/p&gt;

&lt;h3 id=&quot;배낭-문제-knapsack-problem&quot;&gt;배낭 문제 (Knapsack Problem)&lt;/h3&gt;

&lt;p&gt;배낭 문제란, 일정 가치와 무게가 있는 짐들을 배낭에 넣을 때, 가치의 합이 최대가 되도록 짐을 고르는 방법을 찾는 문제이다.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; 예를 들어서, 다음과 같은 물건들이 있을 때 최대 5kg 한도 내에서 배낭 안에 들어가는 물건들의 가치가 최대가 되는 조합을 찾는 상황을 가정해보자.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;가격($)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;무게(kg)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;(가격, 무게)&lt;/code&gt; 순서쌍으로 표현했을 때 답은 &lt;script type=&quot;math/tex&quot;&gt;\{(2, 1), (10, 4)\}&lt;/script&gt;가 될 것이다. 두 물건의 무게의 합은 5kg이고, 가격의 합은 $12이다. 이것보다 더 높은 가격의 합을 만들기 위해서는 5kg을 초과할 수 밖에 없기 때문에 이 조합이 최적해이다. 문제를 수학식으로 표현하자면 다음과 같다.&lt;/p&gt;

&lt;script type=&quot;math/tex; mode=display&quot;&gt;\text{maximize } \sum_{i=1}^n v_i x_i \\
\text{subject to } \sum_{i=1}^n w_i x_i \leq W \text{ and } x_i \in \{0,1\}&lt;/script&gt;

&lt;p&gt;여기서 $v$는 가격을 의미하고, $w$는 무게를, $x$는 해당 물건의 포함 유무를, 마지막으로 $W$는 최대 무게를 의미한다.&lt;/p&gt;

&lt;h3 id=&quot;변형된-배낭-문제&quot;&gt;변형된 배낭 문제&lt;/h3&gt;

&lt;p&gt;전통적인 배낭 문제에서는 물건들의 무게의 합을 일정 값 이하로 유지하면서 가격의 합을 최대화 하는 것이 목표였다면, 내가 해결하고자 하는 문제에서는 무게라는 개념은 빠져있고 정해진 최대치 내에서 가격의 합을 최대화 하는 문제이다.&lt;/p&gt;

&lt;p&gt;수학식으로는 다음과 같이 표현할 수 있다.&lt;/p&gt;

&lt;script type=&quot;math/tex; mode=display&quot;&gt;\underset{v \in X}{\operatorname{arg\,max}}
\sum v \text{ subject to } \sum v \leq W&lt;/script&gt;

&lt;p&gt;우리의 배낭 $X$는 다음과 같이 정의할 수 있다.&lt;/p&gt;

&lt;script type=&quot;math/tex; mode=display&quot;&gt;X = \{v_1, v_2, \cdots, v_n\}&lt;/script&gt;

&lt;p&gt;물론 배낭은 비유적인 표현일 뿐이다. $X$는 아직까지 결재를 올리지 않은 법인카드 사용 내역들의 집합이고, $v_i$는 각각의 거래당 결제 금액이다.&lt;/p&gt;

&lt;p&gt;그리고, $m[i, v]$를 $i$번째까지의 결제 내역의 일부 또는 전부를 합쳐서 만들 수 있는 $v$ 이하의 최대 금액으로 정의하자. 조금 더 격식을 차려 표현하자면 다음과 같다.&lt;/p&gt;

&lt;script type=&quot;math/tex; mode=display&quot;&gt;% &lt;![CDATA[
m[i, v] = \left\{
  \begin{array}{ll}
    0 &amp; \text{ if } i = 0 \text{ (empty) } \\
    m[i-1, v] &amp; \text{ if } v_i &gt; v \\
    \max \left\{
      \begin{array}{l}
        m[i-1, v] \\
        m[i-1, v - v_i] + v_i
      \end{array}
    \right. &amp; \text{otherwise}
  \end{array}
\right. %]]&gt;&lt;/script&gt;

&lt;h3 id=&quot;파이썬-코드로-최적의-해-구하기&quot;&gt;파이썬 코드로 최적의 해 구하기&lt;/h3&gt;

&lt;p&gt;식까지 세웠으니 코드로 옮기는 것은 그다지 어려운 일이 아니다. 재귀호출을 이용하여 다음과 같이 아주 간단하게 구현할 수 있다. 함수 호출에 필요한 시스템 스택은 크기가 제한되어있기 때문에 $n$이 클 경우 이 방법은 적합하지 않을 수도 있지만, 29개의 거래 내역을 가지고 문제를 푸는데 딱히 걱정해야 할만한 사항은 아니라서 그냥 편한 방법으로 구현하기로 했다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def m(i, limit, values):
    if i &amp;lt; 0:
        return 0
    else:
        curr = values[i]
        if curr &amp;gt; limit:
            return m(i - 1, limit, values)
        else:
            left = m(i - 1, limit, values)
            right = m(i - 1, limit - curr, values) + curr
    
            return right if right &amp;gt; left else left
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;수학식에서는 $v_1$이 첫번째 거래 내역의 금액을 의미하는 표기였지만, 파이썬 코드에서는 &lt;code class=&quot;highlighter-rouge&quot;&gt;v[0]&lt;/code&gt;이 리스트의 첫번째 원소가 된다. 따라서 물건을 하나도 사용하지 않고 만들 수 있는 최대 금액인 $m[0, v]$는 &lt;code class=&quot;highlighter-rouge&quot;&gt;m[-1, V]&lt;/code&gt;로 표현된다.&lt;/p&gt;

&lt;p&gt;다음과 같이 거래 금액을 리스트로 만들고&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;values = [
    16199, 10906, 3382, 84725, 71294, 36372, 21256, 18555, 21636, 19425, 18688,
    10482, 7613, 74453, 58176, 50942, 26950, 26582, 9589, 9520, 210198, 50818,
    36992, 17732, 16953, 9971, 8031, 5247, 372309]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;조금 전에 정의했던 &lt;code class=&quot;highlighter-rouge&quot;&gt;m()&lt;/code&gt; 함수를 이용해서 최적해를 구할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;v = m(len(values) - 1, 312967, values)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;하지만 &lt;code class=&quot;highlighter-rouge&quot;&gt;m()&lt;/code&gt;은 각 거래 금액의 합산으로 예산 범위 내에서 만들 수 있는 최대값만 구할 뿐, 어떤 내역을 취했고, 어떤 내역을 버렸는지는 알 수 없다.&lt;/p&gt;

&lt;h3 id=&quot;어떤-거래-내역을-취했는지-알아내기&quot;&gt;어떤 거래 내역을 취했는지 알아내기&lt;/h3&gt;

&lt;p&gt;조금 전에 세웠던 식을 다시 리뷰해보자.&lt;/p&gt;

&lt;script type=&quot;math/tex; mode=display&quot;&gt;% &lt;![CDATA[
\max \left\{
    \begin{array}{l}
        m[i-1, v] &amp; \text{(1)} \\
        m[i-1, v - v_i] + v_i &amp; \text{(2)}
    \end{array}
\right. %]]&gt;&lt;/script&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;(1)&lt;/code&gt;은 현재 내역을 버리는 것, &lt;code class=&quot;highlighter-rouge&quot;&gt;(2)&lt;/code&gt;는 현재 내역을 취하는 경우이다. &lt;code class=&quot;highlighter-rouge&quot;&gt;(2)&lt;/code&gt;를 선택할 경우에 이것을 기록해두는 작업이 필요하다. 아까 작성했던 &lt;code class=&quot;highlighter-rouge&quot;&gt;m()&lt;/code&gt; 함수를 조금 고쳐봤다. 차이점이 있다면 &lt;code class=&quot;highlighter-rouge&quot;&gt;(2)&lt;/code&gt;의 경우가 나올때마다 &lt;code class=&quot;highlighter-rouge&quot;&gt;(i, limit)&lt;/code&gt;을 기록해놓는다는 것이고, 이것을 위해 &lt;code class=&quot;highlighter-rouge&quot;&gt;taken&lt;/code&gt;이라는 인자가 추가됐다는 점이다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def m(i, limit, values, taken):
    if i &amp;lt; 0:
        return 0
    else:
        curr = values[i]
        if curr &amp;gt; limit:
            return m(i - 1, limit, values, taken)
        else:
            left = m(i - 1, limit, values, taken)
            right = m(i - 1, limit - curr, values, taken) + curr
    
            if right &amp;gt; left:
                taken[(i, limit)] = 1
                return right
            else:
                return left
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;코드에서 볼 수 있듯이 &lt;code class=&quot;highlighter-rouge&quot;&gt;m()&lt;/code&gt; 함수는 여전히 &lt;code class=&quot;highlighter-rouge&quot;&gt;values&lt;/code&gt;의 원소들을 조합하여 &lt;code class=&quot;highlighter-rouge&quot;&gt;limit&lt;/code&gt; 안에서 만들 수 있는 최대값을 반환한다. 어떤 원소들을 택했는지 알아내려면 &lt;code class=&quot;highlighter-rouge&quot;&gt;taken&lt;/code&gt;을 역추적해봐야 한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def track_solutions(n, limit, values, taken):
    k = limit
    for i in range(n, -1, -1):
        if (i, k) in taken:
            yield i
            k -= values[i]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이렇게 최종 결과로부터 하나씩 거슬러 올라가면서 추적할 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;중간-계산-결과-저장하기&quot;&gt;중간 계산 결과 저장하기&lt;/h3&gt;

&lt;p&gt;처음 코드를 작성할 때에는 원소 여섯개짜리 샘플 데이터 셋을 가지고 테스트 하면서 작업했었는데, 29개의 레코드를 다 넣으니 CPU 사용량이 100%로 올라간 상태가 꽤 오래 지속되었다. 그럴만도 한 것이, 위의 배낭 문제에서 하위 문제(subproblem)의 계산 결과값을 처음부터 다시 계산할 경우 알고리즘의 시간복잡도는 지수 함수(exponential function)인 $O(2^n)$가 된다.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; 여기서 $n$은 리스트 원소의 개수인데, 원소의 수가 적을 때는 다항 함수(polynomial function)의 시간복잡도를 가지는 알고리즘과 비교하여 실제 수행 시간상의 의미 있는 차이를 발견하기 어렵지만, 원소의 개수가 많아질수록 급격하게 헬게이트가 펼쳐진다.&lt;/p&gt;

&lt;p&gt;그렇지만 우리가 배낭 문제를 푸는데 사용한 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%8F%99%EC%A0%81_%EA%B3%84%ED%9A%8D%EB%B2%95&quot;&gt;동적 계획법(dynamic programming)&lt;/a&gt;이라는 기법의 아름다움은 이미 풀었던 하위 문제를 다시 풀 필요가 없다는 것이다. 하위 문제를 풀때마다 그 결과를 저장해놓고 그 다음 문제에서 그걸 이용하면 계산 시간을 획기적으로 줄일 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def m(i, limit, values, taken, cache):
    if i &amp;lt; 0:
        return 0
    else:
        curr = values[i]
        key = (i, limit)

        try:
            return cache[key]
        except KeyError:
            pass

        if curr &amp;gt; limit:
            value = m(i - 1, limit, values, taken, cache)
        else:
            left = m(i - 1, limit, values, taken, cache)
            right = m(i - 1, limit - curr, values, taken, cache) + curr

            if right &amp;gt; left:
                taken[key] = 1
                value = right
            else:
                value = left

        cache[key] = value
        return value
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이렇게 해서 1분 27초쯤 걸리던 작업이&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜  time python knapsack.py 
...
python knapsack.py  87.26s user 1.86s system 96% cpu 1:32.39 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;18초 미만으로 줄어들었다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜  time python knapsack.py 
...
python knapsack.py  17.56s user 0.65s system 96% cpu 18.898 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이것보다 더 빠르게 만들 수도 있겠지만, 파이썬의 생산성 덕분에 아낄 수 있었던 내 시간을 고려해서 너그럽게 봐주도록 하자.&lt;/p&gt;

&lt;p&gt;전체 소스코드는 &lt;a href=&quot;https://gist.github.com/suminb/e9b255dc1afdf3b586a43ce2a28e960a&quot;&gt;Gist에 공개&lt;/a&gt;해놓았다.&lt;/p&gt;

&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;

&lt;p&gt;나의 프로그램은 아래와 같이 아름다운 결과를 도출해냈고,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜  python knapsack.py
Sum of taken values = 312967
Taken records = [9520, 58176, 74453, 7613, 18688, 19425, 21636, 21256, 71294, 10906]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;프로그램이 찾아준 최적해에 따라 1원 단위까지 딱 맞춰서 비용정산을 마칠 수 있었다.&lt;/p&gt;

&lt;h4 id=&quot;덧붙이는-말&quot;&gt;덧붙이는 말&lt;/h4&gt;

&lt;p&gt;그리디 알고리즘으로 풀면 312원 차이가 생기고, 손으로 풀어도 몇백원 내지는 몇천원 정도의 차이가 생길 수 있다. 이정도의 푼돈을 아끼려고 이렇게 야크 털을 깎았다고 생각하면 매우 바보같은 짓이라고 생각될 수도 있다. 하지만 나는 다음 두 가지의 큰 가치를 얻었으니 매우 만족한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;문제 해결의 즐거움&lt;/li&gt;
  &lt;li&gt;공개한 소스코드를 이용해 누군가의 시간을 아낄 수 있는 가능성&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;주말이나 연휴에 심심해서 할 일이 없다면 이걸 서비스로 만들어서 공개해보는것도 괜찮을것 같다는 생각이 들었다.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;이 부분은 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B0%B0%EB%82%AD_%EB%AC%B8%EC%A0%9C&quot;&gt;위키피디아의 ‘배낭 문제’ 항목 한국어판&lt;/a&gt;을 인용했다. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;그런 일이 일어날 가능성은 희박하지만, 시간적 여유가 된다면 어떤 과정을 통해 이런 결론을 도출했는지 간략하게 설명해봐도 좋을 것 같다. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 25 Oct 2016 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/roadtrip-in-australia-financial-settlement/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/roadtrip-in-australia-financial-settlement/</guid>
      </item>
    
      <item>
        <title>이상한 호주 여행기 - 둘째 날 (상)</title>
        <description>&lt;div class=&quot;section&quot; id=&quot;id1&quot;&gt;
&lt;h2&gt;시드니에서 맞이하는 아침&lt;/h2&gt;
&lt;p&gt;비행기 좌석이 불편해서 그런지 깊이 잠들지 못하고 중간에 여러번 깼다. 실내 공기가 건조하니 자꾸 물을 찾게 되고, 그러다보니 화장실에 여러번 가게 되고. 그렇게 자다 깨기를 여러번. 어느새 착륙이 두 시간 앞으로 다가왔고, 두번째 기내식 서비스가 시작되었다. 비행기에 가만히 앉아서 하는 일도 없는데 왜 이렇게 배가 고플까, 가만히 있어도 배가 고픈데 쉴새없이 돌아다니는 승무원들은 얼마나 배가 고플까 하는 생각을 하면서 순식간에 눈앞의 밥을 먹어치웠다.&lt;/p&gt;
&lt;p&gt;식사를 끝내고 프로페셔널하게 멍때리기를 하다보니 귀가 멍멍한 느낌이 들었다. 고도를 낮추면서 기내 압력이 높아지고 있었던 것이다. 그제서야 인천공항에서 느꼈던 감정보다 한단계 격상된 설렘을 느낄 수 있었다. 내가 평생 한번도 경험하지 못한 미지의 땅, 호주에 왔구나. 도착지 공항의 날씨가 좋아서 별다른 문제 없이 착륙할 수 있었다.&lt;/p&gt;
&lt;p&gt;시드니 공항에서의 첫 인상은 미국 대도시의 공항과 비슷했다. 어떻게 보면 굉장히 평범했다. 인천공항처럼 새집 증후군(?)이 채 가시지 않은 새 공항도 아니었고, 라스베가스 공항처럼 비행기에서 내리자마자 번쩍번쩍한 슬롯머신들이 나를 반겨주는 특이한 공항도 아니었다. 그래도 시드니 공항에서 보는 하늘은 한없이 맑고 푸르렀다.&lt;/p&gt;
&lt;p&gt;기내식을 모두 챙겨먹었음에도 불구하고 또 배가 고파서 일단 밥부터 먹기로 했다. 뱃속에 기생충이나 외계인이 살고 있는게 틀림없다. 보안구역을 빠져나오자마자 가장 먼저 눈에 띄는 맥도날드에 가서 밥을 먹었다. 식사를 하면서 인터넷에 연결하려고 했는데, 와이파이 상황이 매우 좋지 않았다. 공항에서 무료로 제공하는 와이파이를 이용하려고 했지만 너무 느렸다. DHCP 서버로부터 IP 주소를 할당받은 후 웹 인터페이스를 통해 사용자 등록을 하는 과정이 있는데 (아마도 MAC 주소를 등록하는 과정이었을 것이다), 그 과정을 여러번 시도했음에도 불구하고 한번도 성공하지 못했다. 다행히 맥도날드에서 제공하는 와이파이를 사용할 수 있었지만, 이건 30분 시간 제한이 있었다. 한국이 확실히 이런 면에서는 압도적으로 우위를 점하고 있다는 사실을 다시 한 번 깨달았다.&lt;/p&gt;
&lt;p&gt;짐을 가볍게 싼 덕분에 위탁 수화물로 보내지 않고 모두 기내 수화물로 가지고 올 수가 있었다. 수화물 찾는 곳에서 기다릴 필요 없이 바로 바깥으로 빠져나올 수 있었던 것은 좋았지만, 본격적으로 여행을 시작하기 전에 현지에서 마련할 것들이 몇가지 있었다.&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;id2&quot;&gt;
&lt;h3&gt;선불 심카드&lt;/h3&gt;
&lt;p&gt;시드니 공항에 내리면 호주의 주요 통신회사인 &lt;a class=&quot;reference external&quot; href=&quot;http://www.optus.com.au&quot;&gt;Optus&lt;/a&gt;와 &lt;a class=&quot;reference external&quot; href=&quot;http://www.vodafone.com.au&quot;&gt;Vodafone&lt;/a&gt;의 부스를 볼 수 있다. &lt;a class=&quot;reference external&quot; href=&quot;https://www.telstra.com.au&quot;&gt;Telstra&lt;/a&gt;는 없었다. 두 회사의 선불 심카드 가격 조건을 비교해보니 Vodafone 쪽이 더 매력적이었다. 게다가 할인 행사를 진행중이라 AU$35에 굉장한 조건을 제공했다.&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;원래 가격은 AU$50 (약 43,000원)&lt;/li&gt;
&lt;li&gt;데이터 8.5GB&lt;/li&gt;
&lt;li&gt;호주 내 통화 및 문자 무제한&lt;/li&gt;
&lt;li&gt;한국을 포함한 12개의 국가로의 통화 1,000분&lt;/li&gt;
&lt;li&gt;그 이외의 국가로의 통화 50분&lt;/li&gt;
&lt;li&gt;유효 기간은 28일&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SKT보다 훨씬 좋은데...?&lt;/p&gt;
&lt;p&gt;한국에서는 약정 없이 &lt;a class=&quot;reference external&quot; href=&quot;https://goo.gl/dvtjuV&quot;&gt;band 데이터 2.2GB&lt;/a&gt; 요금제를 쓰고 있는데, 부가세를 포함한 월 정액이 46,200원이고 다음과 같은 조건을 제공한다.&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;데이터 2.2GB&lt;/li&gt;
&lt;li&gt;한국 내 통화 및 문자 무제한&lt;/li&gt;
&lt;li&gt;영상통화, 부가음성통화 50분&lt;/li&gt;
&lt;li&gt;사용량 제한(quota)은 매달 첫날 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가족 할인을 받아서 30% 할인된 가격에 사용하고는 있지만 할인된 가격을 고려해도 Vodafone쪽이 훨씬 더 넉넉한 혜택을 제공한다.&lt;/p&gt;
&lt;p&gt;이렇게 놓고 비교를 해보니 SKT의 비싼 가격에 대한 분노가 은근슬쩍 고개를 들었다. SKT의 재무제표를 보면 2015년 기준 영업이익이 1조 7080억원, 가입자가 2863만명으로 나와있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#skt-ir&quot; id=&quot;id3&quot;&gt;[1]&lt;/a&gt; 단순 계산으로 가입자 한 명당 연간 약 6만원 정도의 영업이익을 내고 있다는 소리이다. 영업이익률이 무려 9.97%이다. 반면, Vodafone의 경우 고객당 연간 25파운드의 영업이익을 내고 있는데&lt;a class=&quot;footnote-reference&quot; href=&quot;#vodafone-ir&quot; id=&quot;id4&quot;&gt;[2]&lt;/a&gt;, 이는 SKT의 절반 수준이다. 인구가 한국의 절반인 호주에도 메이저 통신사가 세 개는 있는데, 한국에는 더 많은 통신사가 있어야 업체들간 경쟁을 통해 가격이 내려가지 않을까 하는 생각이 든다. 물론 Vadofone은 호주 뿐만이 아니라 뉴질랜드, 유럽의 여러 국가들, 아프리카 일부 지역, 인도 등지에서 사업을 하는 거대한 다국적 기업이고, 고객들이 사용하고 있는 통신망의 품질, 각 국가별 소득 수준이 천차만별이기 때문에 SKT와의 단순 비교가 어려운 것은 사실이다. 하지만 나는 SKT가 가격을 내리거나 같은 가격에 더 넉넉한 용량을 제공할 여지가 충분하다고 생각한다. 다만, 사실상 독과점 구조인 한국의 이동통신시장 덕분에 그렇게 할 이유가 없을 뿐.&lt;/p&gt;
&lt;p&gt;하지만 나는 SKT의 고객이기도 하지만 동시에 주주이기도 하다. 매년 배당금과 시세 차익으로 벌어들이는 돈으로 통신비의 일부를 충당하고 있다. SKT가 가격을 내리거나 혜택을 강화할 경우 영업이익률이 떨어질 가능성이 높고, 그렇게 되면 배당금이 줄어들거나 주가가 떨어질 수도 있기 때문에 주주로서는 타격을 입게 된다. 그렇기 때문에 어느 장단에 맞추어 춤을 춰야 할지 잘 모르겠다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#id31&quot; id=&quot;id5&quot;&gt;[10]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;나의 분노 때문에 이야기가 잠시 지구 반대편에 있는 SKT 이야기로 빠졌는데, 다시 시드니 공항의 Vadafone 부스로 돌아오자. 직원분이 매우 친절하게 안내해주셨지만, 처음 접하는 호주 영어라 그런지 중간중간 잘 못 알아듣는 부분이 생겼다. 이 여행, 과연 무사히 마칠 수 있을까.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id6&quot;&gt;
&lt;h3&gt;렌터카&lt;/h3&gt;
&lt;p&gt;현지에서의 통신수단을 마련했으니 이제 이동수단을 마련할 차례다. 공항에 있는 &lt;a class=&quot;reference external&quot; href=&quot;http://hertz.com&quot;&gt;Hertz&lt;/a&gt;의 부스에 가서 예약한 차를 찾으러 왔다고 얘기했다. 심카드를 구입할때와 마찬가지로 직원이 매우 친절하고 상세하게 계약 내용을 안내해주고, 추가 보험 조건 같은걸 안내해주셨는데, 중간에 잘 알아듣지 못한 부분이 있어서 몇번 다시 물어보게 되었다. (끄응...)&lt;/p&gt;
&lt;p&gt;카운터에 카탈로그가 한 장 펼쳐져있었는데, 이용 가능한 모델 중에 &lt;a class=&quot;reference external&quot; href=&quot;https://www.google.co.kr/search?q=ford+mustang&amp;amp;tbm=isch&quot;&gt;머스탱&lt;/a&gt;이 눈에 들어왔다. 혹시 돈을 더 내고 머스탱으로 바꿀 수 있냐고 물어보니 직원이 무전기로 어디론가 연락을 했다. 잠시 후에 답변이 왔는데, 머스탱은 타이어에 문제가 있어서 정비중이라고 했다. 아쉽지만 어쩔 수 없지. 사실 자동변속기 달린 머스탱이 운전하기에 그렇게까지  재미있는 차는 아니다. 물론 내가 타봤던 3년 전 모델과 현재 모델은 다를 수도 있겠지만, 이 부분에 있어서 극적인 변화가 있었을 것이라고는 생각하기 어렵다. 카탈로그를 둘러보다가 &lt;a class=&quot;reference external&quot; href=&quot;https://www.google.co.kr/search?q=vw+golf&amp;amp;tbm=isch&quot;&gt;골프&lt;/a&gt;도 눈에 띄어서 혹시 골프를 대여하는 것은 가능한지 물어봤더니 골프는 다섯 대 밖에 없어서 이미 다 예약 되었다는 답변이 돌아왔다. 그래서 그냥 원래 예약한대로 준중형 세단을 대여하기로 했다.&lt;/p&gt;
&lt;p&gt;대부분의 렌터카는 예약을 할 때 자동차의 클래스만 지정할 수 있고, 카운터에 가서 예약된 차를 찾을 때 실제로 어떤 차를 받게 될지 결정된다. 예를 들면, '중형 세단'을 예약했다면 예약된 차를 찾을 때 까지는 그게 현대 쏘나타가 될지, 토요타 캠리가 될지, 그것도 아니면 닛산 알티마가 될지 알 수 없다는 것이다. 차를 하루이틀만 쓰려고 빌리는 것도 아니고, 짧다면 짧지만 긴 기간이라고도 볼 수 있는 12일동안 쓰려고 빌리는건데 자동차 모델을 선택할 수 없다는 점은 아쉬움으로 다가왔다.&lt;/p&gt;
&lt;p&gt;물론 렌터카 회사의 입장도 이해가 안 되는건 아니다. 예정 시간보다 늦게 자동차를 반납하는 고객이 있을 수 있고, 예상치 못한 고장이나 사고로 정비를 받아야 하는 경우도 있을 수 있기 때문에 고객들에게 특정 모델을 제공할 것을 약속하는 것 보다는 특정 클래스에 속한 모델을 제공할 것을 약속하는 편이 회전률을 높이고 재고 비용을 줄이는데 더 유리할 것이라고 생각된다.&lt;/p&gt;
&lt;p&gt;시드니 공항의 Hertz와는 대조적으로 &lt;a class=&quot;reference external&quot; href=&quot;https://www.alamo.com&quot;&gt;Alamo&lt;/a&gt; 로스엔젤레스 공항 지점은 주차장에 가서 고객이 직접 마음에 드는 차를 골라서 가져갈 수 있게 되어있는데, 매우 신선한 경험으로 다가왔던 기억이 있다. 중형 세단을 예약했다면 카운터에 가서 직원의 안내를 받은 후 주차장에 가서 마음에 드는 중형 세단 중 아무거나 골라 타고 갈 수 있다는 말이다. 그쪽 동네는 워낙 규모도 크고 고객이 많기 때문에 가능한 일이 아닌가 하는 생각이 들었다. 그정도 까지는 아니더라도 중장기로 자동차를 대여하거나, 추가 금액을 지불하는 고객에게 자동차 선택권을 주었으면 하는 바람이 있다.&lt;/p&gt;
&lt;figure style=&quot;width:400px;&quot;&gt;
  &lt;a href=&quot;/attachments/2016/roadtrip-to-australia/day2/toyota-corolla.jpg&quot;&gt;
    &lt;img src=&quot;/attachments/2016/roadtrip-to-australia/day2/toyota-corolla.jpg&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;2016 Toyota Corolla&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;그렇게 해서 나에게는 빨간색 토요타 코롤라가 주어졌다. 토요타 팬들에게는 미안한 말이지만,  갈수록 디자인이 퇴보하는 것 같다는 느낌을 지울 수 없었다. 특히 뒷모습은 2008년형 코롤라가 더 낫다는 생각이 들었다. &amp;quot;아, 이렇게 자동차 꼰대가 되어가는건가&amp;quot; 하는 생각을 하면서 당당하게 차 문을 열었는데 조수석이었다. 아 맞다, 여기는 통행 방향이 반대였지. 다시 반대쪽으로 가서 운전석에 앉으니 가속 페달과 브레이크 페달을 제외한 모든것이 반대였다. 방향지시등 스위치가 오른쪽, 와이퍼 스위치가 왼쪽이다. 물론 변속기 레버도 왼쪽에 달려있다. 난생 처음 운전석에 앉았을 때의 설렘이 어렴풋이 기억났다.&lt;/p&gt;
&lt;p&gt;차량을 대여할 때에는 출발하기 전에 자동차에 이상이 없는지 확인하는게 좋다. 자동차에 흠집이나 파손된 부분이 있다면 계약서에 명시 되어있는지 확인하고, 명시되어있지 않다면 직원을 불러서 이야기를 하는게 좋다. 일단 렌터카 회사의 주차장을 벗어나면 자동차의 파손이나 결함이 내 과실이 아니라는 것을 증명하기 훨씬 까다로워진다. 파손된 부위를 사진으로 찍어놓으면 추후에 분쟁이 생겼을 때 큰 도움이 될 수도 있다. 다만, 몇 cm 이내의 짧은 긁힘, 고속 주행중 작은 돌이나 모래 알갱이가 튀어서 생긴 상처(paint chips), &lt;a class=&quot;reference external&quot; href=&quot;https://namu.wiki/w/%EB%AC%B8%EC%BD%95&quot;&gt;문콕&lt;/a&gt; 상처, 범퍼 밑부분 긁힘 등은 일상적인 사용에 의한 마모(normal wear and tear)로 간주되기 때문에 크게 신경쓰지 않아도 된다.&lt;/p&gt;
&lt;p&gt;기계적 결함이 있는지 확인해보는것도 좋은데, 많은 경우에 Hertz와 같이 세계적인 렌터카 회사에서 운용하는 차량들은 출고된지 2-3년 이내의 새차이기도 하고, 정기적으로 소모품을 교환해주기 때문에&lt;a class=&quot;footnote-reference&quot; href=&quot;#rental-cars&quot; id=&quot;id7&quot;&gt;[5]&lt;/a&gt; 중대한 문제가 생길 여지가 많지는 않다. 다년간 (미국의) Hertz를 비롯하여 여러 회사의 렌터카를 빌려서 수만킬로미터 이상을 주행해봤지만, 타이어를 제외한 부분에서 문제가 생긴적은 단 한번도 없었다.&lt;/p&gt;
&lt;p&gt;2013년쯤에 대여했던 차량의 타이어 중 하나에 미세한 구멍이 있어서 공기압이 서서히 낮아지는 문제가 있었던 적이 있었다. 차를 받아올 때에는 멀쩡했지만, 하루가 지나니 한쪽 타이어가 눈에 띌 정도로 주저앉아 있었다. 하지만 이런 문제가 발생하는 경우는 매우 드문데다가, 렌터카 정비사들이 타이어 공기압을 잘 맞춰서 내보내기도 하고, 요즘 출고되는 차량들은 대부분 타이어 공기압 모니터링 시스템(TPMS)이 장착되어있기 때문에 압력에 대해서는 크게 걱정하지 않아도 된다. 타이어의 노면 접촉면에 편마모가 있는지, 측면 벽(sidewall)에 눈에 띌만한 상처가 있는지 정도만 확인해봐도 충분할듯 하다.&lt;/p&gt;
&lt;p&gt;사실 이번 여행은 자동차 여행인만큼 좋은 차를 빌려서 다니는 것에 대한 꿈이 있었다. 하지만 곰곰히 생각해보니 이번이 생애 마지막 여행도 아니고 노후 준비도 해야 하기에 자동차에 필요 이상으로 큰 돈을 지출하는건 자제하기로 했다.&lt;/p&gt;
&lt;figure style=&quot;width:400px;&quot;&gt;
  &lt;a href=&quot;https://en.wikipedia.org/wiki/Porsche_Boxster/Cayman#/media/File:2016-03-01_Geneva_Motor_Show_1342.JPG&quot;&gt;
    &lt;img src=&quot;/attachments/2016/roadtrip-to-australia/day2/porsche-boxter.jpg&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;사실은 이런 차를 빌리고 싶었다.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;iphone-7&quot;&gt;
&lt;h3&gt;iPhone 7&lt;/h3&gt;
&lt;p&gt;이동수단까지 확보했으니 이제 &lt;a class=&quot;reference external&quot; href=&quot;http://apple.com&quot;&gt;캘리포니아의 과일 회사&lt;/a&gt;가 운영하는 가게를 찾아갈 차례이다.&lt;/p&gt;
&lt;p&gt;혹시나 하는 기대를 품고 애플 웹사이트에서 아이폰 7 블랙을 장바구니에 담은 다음 시드니에 있는 모든 애플 스토어의 재고 상태를 알아봤지만 역시나 모두 품절이었다. 특히 이번에 새로 나온 &lt;a class=&quot;reference external&quot; href=&quot;http://www.businessinsider.com/apple-iphone-7-jet-black-vs-black-photos-2016-9/#this-is-the-47-inch-iphone-7-in-matte-black-1&quot;&gt;젯블랙(Jet Black)&lt;/a&gt; 색상은 3-5주나 기다려야 받아볼 수 있다고 했다.&lt;/p&gt;
&lt;p&gt;그래서 그냥 구경이라도 해볼까 하는 마음으로 공항 근처의 애플 스토어를 무작정 찾아갔다. 조용히 아이폰이랑 맥북이나 구경하고 가려고 했었는데, 직원이 친절하게 인사를 해주어서 &amp;quot;혹시 아이폰 7 재고 있나요?&amp;quot; 라고 물어보았다. 내가 원하는 색상과 저장 용량을 묻더니 &amp;quot;잠시만요&amp;quot; 하고 손에 들고 있는 단말기에서 뭔가를 찾아보고는 &amp;quot;네! 마침 딱 한개 남은게 있네요&amp;quot; 라고 미소 띈 얼굴로 대답해주었다.&lt;/p&gt;
&lt;p&gt;더이상 고민할 필요가 없었다. 바로 신용카드를 꺼내 구매했다. 결제를 도와주는 직원이 내가 가지고 있는 신한 Simple+ 카드의 디자인을 꽤나 마음에 들어했다.&lt;/p&gt;
&lt;figure style=&quot;width:400px;&quot;&gt;
  &lt;img src=&quot;/attachments/2016/roadtrip-to-australia/day2/shinhan-card.jpg&quot;&gt;
  &lt;figcaption&gt;이 카드를 정말 마음에 들어했다.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;직원의 꼬임에 넘어가서 케이스도 같이 구매했다. 일단 써보고 마음에 안 들면 14일 내에만 가져오면 교환 또는 환불 해주겠다는 말에 마음이 흔들렸다. 지금까지는 1,500원짜리 케이스를 쓰다가 4만원이 넘는 정품 케이스를 써보니 확실히 좋긴 좋더라. 가격에 비례해서 29배 좋지는 않겠지만, 적어도 몇배 이상의 만족감을 준다고 말할 수 있을 정도이다.&lt;/p&gt;
&lt;p&gt;사실 아이폰이든 안드로이드 폰이든 요즘 나오는 폰은 기본적으로 매우 잘 만들었기 때문에 신제품을 구입한다고 해서 처음 아이폰을 만져봤을 때의 감동을 느끼기는 어렵다. 분명히 거의 모든 면에서 작년 모델보다 좋아졌지만 퀀텀 점프 수준은 아니다. 빨라진 프로세서, 넉넉한 저장 공간, 햅틱 피드백, 방수 기능, 향상된 카메라 등 모두 마음에 들지만 가장 마음에 드는 부분은 따로 있었다. 바로 사진을 찍을 때 소리가 나지 않는다는 점이다.&lt;/p&gt;
&lt;p&gt;사실 나는 나의 아홉번째 폰인 아이폰 6s를 제외하고는 사진을 찍을 때 소리가 나는 폰을 단 한번도 써본적이 없다. 2000년대 초반에 한국에서 구입했던 폰은 사진을 찍을 때 소리가 나지 않았고, 그 이후로는 미국에 거주했기 때문에 셔터음이 나는 폰을 경험해볼 기회가 없었다. 아이폰 6s는 작년에 일본으로 여행갔던 친구한테 부탁해서 대리 구매를 했었는데, 그때까지만 해도 카메라 셔터음을 강제하는 국가는 한국이 전세계에서 유일하다고 알고 있었다. 일본에서 구입한 폰은 괜찮을 것이라 생각했지만 그것은 나의 착각이었다.&lt;/p&gt;
&lt;p&gt;처음에는 &amp;quot;그래, 사람들이 얼마나 몰카를 찍어댔으면 이렇게까지 했겠어&amp;quot; 라는 생각이었지만, 지내다보니 불편한 점이 한두가지가 아니었다. 컨퍼런스 발표장에서 사진을 찍을 때에는 옆사람에게 소리가 들리지 않도록 스피커를 손가락으로 덮어야 했다. 아이폰 6s 까지는 모노 스피커였기 때문에 아래쪽 스피커만 막으면 소리가 많이 줄어들지만, 아이폰 7부터는 스테레오 스피커이기 때문에 아래쪽과 위쪽 스피커를 모두 막아야 한다. 아이폰은 사진 뿐만이 아니라 스크린샷을 찍을 때에도 소리가 나게 만들어놓는 바람에 신경쓸 것이 하나 더 늘었다. 지하철에서 웹서핑을 하다가 재밌는 것을 발견해서 스크린샷을 찍을 때 혹시나 카메라 렌즈가 앞에 서있는 사람의 신체를 향하고 있지는 않은지 신경을 곤두세워야 했다. 한번은 지하철 역에서 걸어가다가 주머니에서 폰을 꺼내는데 뭘 잘못 눌렀는지 갑자기 카메라가 켜지더니 의도하지 않게 사진이 찍혔다. 다행히 주변이 시끄러워서 찰칵 소리가 잘 들리지 않았고, 사진도 알아볼 수 없을 정도로 흐릿하게 나와서 큰 문제가 되지 않았었다. 만약 그때 우연히 내 앞에 걸어가던 여성분의 허벅지와 엉덩이가 찍혔다면, 그리고 주변 사람들이 그 장면을 목격했다면 나는 지금쯤 어떻게 되었을까 하고 상상하면 섬뜩하다.&lt;/p&gt;
&lt;p&gt;물론 몰카가 한국에서 심각한 사회 문제라는 점은 잘 알고 있다. 대중교통 뿐만이 아니라 워터파크 탈의실이나 화장실 같은 곳에서 찍은 몰카가 인터넷을 통해 배포되는 사건이 심심치 않게 일어나는것 또한 문제이다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#id21&quot; id=&quot;id11&quot;&gt;[3]&lt;/a&gt;&lt;a class=&quot;footnote-reference&quot; href=&quot;#id23&quot; id=&quot;id12&quot;&gt;[4]&lt;/a&gt; 그래서 정책을 만드는 사람 입장에서는 이렇게 셔터음을 강제해서라도 문제를 해결해보려는 충동이 들 수도 있다는 점도 이해한다. 단기간에 사법 체계를 바꾸거나, 경찰력을 증원해서 지하철에 배치하기는 어려울 수도 있으니 단기적 미봉책으로서 셔터음을 강제할 수도 있다고 생각한다. 하지만 이러한 미봉책으로 문제를 근본적으로 해결할 수 있다고 믿으면 곤란하다. 선의의 피해자가 발생할 수도 있고, 수요가 있는 한 방법은 문제가 되지 않기 때문에 셔터음 강제 정책의 실효성에 대해서도 강한 의구심을 가지게 만든다. 심지어 스마트폰에 카메라 모듈을 탑재를 금지하는 법안이 발효된다고 하더라도 사람들은 몰카를 찍을 방법을 어떻게 해서든 마련할 것이다.&lt;/p&gt;
&lt;p&gt;이 문제를 해결하기 위해서는 장기적으로 사회 체계를 바꾸어 나가야 한다. 부정행위, 특히 성범죄와 같이 다른 사람의 삶에 커다란 피해를 주는 행위에 대해 혹독한 대가를 치르는 사회가 되어야 한다. 지금같은 솜방망이 처벌로는 아무것도 달라지지 않는다고 생각한다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#id25&quot; id=&quot;id13&quot;&gt;[6]&lt;/a&gt;&lt;a class=&quot;footnote-reference&quot; href=&quot;#id27&quot; id=&quot;id14&quot;&gt;[7]&lt;/a&gt;&lt;a class=&quot;footnote-reference&quot; href=&quot;#sbs&quot; id=&quot;id15&quot;&gt;[8]&lt;/a&gt; 물론 이러한 주장에 힘을 실어주기 위해서는 성범죄 처벌 강도가 높은 나라일수록 성범죄 발생 비율이 낮다는 사실을 뒷받침할만한 자료가 있어야 하지만, 각국의 성범죄 통계 방법의 차이, 수사기관에 보고되지 않은 범죄 등으로 인해 현재 나와있는 자료만으로 단순 비교하기는 어렵기 때문에&lt;a class=&quot;footnote-reference&quot; href=&quot;#id30&quot; id=&quot;id16&quot;&gt;[9]&lt;/a&gt; 이 주제에 대한 제대로 된 연구는 다음 기회로 미루어두었다.&lt;/p&gt;
&lt;p&gt;아무튼 다시 소리가 나지 않는 폰으로 돌아올 수 있어서 매우 만족스럽다. 사진을 찍을때마다 우렁찬 셔터음이 나는 나의 아이폰 6s에게 편지를 쓴다면 이런 말을 해주고 싶다. &amp;quot;함께 해서 더러웠고 다시는 만나지 말자.&amp;quot;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id18&quot;&gt;
&lt;h3&gt;생필품&lt;/h3&gt;
&lt;p&gt;아이폰때문에 생각의 가지가 좀 멀리까지 뻗어 나갔었는데, 다시 여행 얘기로 돌아올 때이다. 사실, 별 생각 없이 애플 스토어 하나만 보고 온 쇼핑몰인데 안내 표지판을 보니 식료품점을 비롯해서 매우 다양한 상점들이 있었다. Coles라는 이름의 식료품점이 있어서 안쪽으로 들어가봤다. 넓은 공간에 다양한 농산물과 여러가지 포장식품들이 즐비했다. 내가 관심 있게 보는 품목들의 가격도 (한국과 비교하여) 대체적으로 저렴한 편이었다. 미국의 마트에서 느껴볼 수 있는 풍요로움을 여기서 다시 느껴보는구나.&lt;/p&gt;
&lt;img alt=&quot;/attachments/2016/roadtrip-to-australia/day2/coles-broadway.jpg&quot; src=&quot;/attachments/2016/roadtrip-to-australia/day2/coles-broadway.jpg&quot; /&gt;
&lt;p&gt;이곳에서 생수, 키친타월, 물티슈 등을 여행에 필요한 잡다한 물건들을 구매했다. 여기서 영어 팁 한가지. 미국 사람들은 식료품점을 &lt;em&gt;grocery&lt;/em&gt; 라고 하지만, 호주 사람들은 &lt;em&gt;supermarket&lt;/em&gt; 이라고 한다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id19&quot;&gt;
&lt;h3&gt;스마트폰 마운트&lt;/h3&gt;
&lt;a class=&quot;reference external image-reference&quot; href=&quot;/attachments/2016/roadtrip-to-australia/day2/belkin.png&quot;&gt;&lt;img alt=&quot;/attachments/2016/roadtrip-to-australia/day2/belkin.png&quot; class=&quot;float-right&quot; src=&quot;/attachments/2016/roadtrip-to-australia/day2/belkin.png&quot; style=&quot;width: 240px;&quot; /&gt;&lt;/a&gt;
&lt;p&gt;내가 빌리 자동차에는 네비게이션이 장착되어있지 않다. 렌터카 업체에서 네비게이션 장비를 대여할 수도 있지만, 무료이면서 훨씬 똑똑한 구글맵을 이용하기로 했다. 하지만 손에 폰을 들고 운전하는건 위험하기도 하고 일부 지역에서는 법적으로 금지된 사항이기도 해서 스마트폰 마운트를 하나 구입했다. 다음번에 차를 빌릴 때에는 세상이 조금 더 발전되어 있어서 코롤라 같은 보급형 자동차에도 속도와 엔진 회전수와 같은 전반적인 상태를 표시해주고 길 안내를 해줄 수 있는 &lt;a class=&quot;reference external&quot; href=&quot;http://www.carscoops.com/2011/11/bmw-colorful-head-up-display-technology.html&quot;&gt;헤드-업 디스플레이(HUD)&lt;/a&gt;가 기본으로 장착되어있었으면 좋겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id20&quot;&gt;
&lt;h2&gt;여행 준비 완료&lt;/h2&gt;
&lt;p&gt;이제 정말로 본격적인 여행을 떠날 준비가 되었다. 여행하는 내내 마음껏 사용할 수 있는 통신수단과 교통수단을 마련했고, 가방을 가볍게 하기 위해서 한국에서 가져오지 않은 생필품들도 모두 구했다. 현지에서의 여행 준비를 마치고 나니 벌써 점심시간이었다.&lt;/p&gt;
&lt;p&gt;(하) 편에 이어서 계속...&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;references&quot;&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;skt-ir&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id3&quot;&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;http://www.sktelecom.co.kr/sktelecom/ir/ir02_04.jsp&quot;&gt;http://www.sktelecom.co.kr/sktelecom/ir/ir02_04.jsp&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;vodafone-ir&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id4&quot;&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;http://www.vodafone.com/content/annualreport/annualreport15/assets/pdf/financials.pdf&quot;&gt;http://www.vodafone.com/content/annualreport/annualreport15/assets/pdf/financials.pdf&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id21&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id11&quot;&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;김 양수. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.nocutnews.co.kr/news/4532745&quot;&gt;워터파크 몰카 사주男 4년6월·촬영女 3년6월…실형&lt;/a&gt;.&amp;quot; 노컷뉴스, 14 Jan. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id23&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id12&quot;&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;박 용하. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://news.khan.co.kr/kh_news/khan_art_view.html?artid=201610021145001&amp;amp;code=940301#csidx6109ddb653239cdb4b92270653b40ce&quot;&gt;여성스텝 '화장실 몰카' 찍은 유명 뮤지컬 배우, 징역 5월&lt;/a&gt;.&amp;quot; 경향신문, 2 Oct. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;rental-cars&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id7&quot;&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Cole, Craig. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.autoguide.com/auto-news/2013/03/should-i-buy-a-used-car-that-was-a-rental.html&quot;&gt;Under the Hood: Should I Buy a Rental Car?&lt;/a&gt;&amp;quot; AutoGuide, 20 Mar. 2013.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id25&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id13&quot;&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;김 동철. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.yonhapnews.co.kr/bulletin/2016/06/23/0200000000akr20160623135200055.html&quot;&gt;검찰 '다리찍은 몰카범 징역 가혹' 이색 항소... 법원, 벌금으로&lt;/a&gt;.&amp;quot; 연합뉴스, 23 June 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id27&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id14&quot;&gt;[7]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;임 우재. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.hyundaenews.com/sub_read.html?uid=19078&quot;&gt;제주 몰카 동영상 촬영 유포자 징역 2년 선고&lt;/a&gt;.&amp;quot; 현대뉴스, 주간현대, 8 Jan. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;sbs&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id15&quot;&gt;[8]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;SBS 뉴미디어부. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://news.sbs.co.kr/news/endPage.do?news_id=N1003240530&amp;amp;plink=COPYPASTE&amp;amp;cooper=SBSNEWSEND&quot;&gt;여성 승객 104명 몰카 촬영한 택시기사 징역 1년&lt;/a&gt;.&amp;quot; SBS 뉴스, 29 Oct. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id30&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id16&quot;&gt;[9]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;이 훈동. &amp;quot;성폭력범죄에 대한 유럽 각국의 형량 및 형집행 실태.&amp;quot; 한국외국어대학교 법학전문대학원.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;notes&quot;&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;id31&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id5&quot;&gt;[10]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;아직은 SKT로 인해 벌어들이는 돈보다 SKT에 지불하는 돈이 훨씬 더 많으니 당분간은 고객의 입장에서 생각하는 것이 합리적인 판단이라 생각된다.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

</description>
        <pubDate>Sun, 25 Sep 2016 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/roadtrip-in-australia-day2-1/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/roadtrip-in-australia-day2-1/</guid>
      </item>
    
      <item>
        <title>이상한 호주 여행기 - 첫째 날</title>
        <description>&lt;div class=&quot;section&quot; id=&quot;id1&quot;&gt;
&lt;h2&gt;출발!&lt;/h2&gt;
&lt;p&gt;9월 24일, 드디어 출발이다. 오후 8시 비행기라 인천 공항에 6시 반쯤에 도착하기 위해 5시 15분에 출발하는 공항버스에 올라탔다. 집에서 공항까지의 거리와 고속도로 구간의 비율을 고려했을 때 한시간 정도면 충분하다고 생각했다. 하지만 오늘이 토요일이라는 사실을 깜빡 하고 있었다. 금요일과 토요일은 일주일 중 가장 막히는 날이다.물론 평일 출퇴근 시간이 토요일 오후 다섯시쯤보다는 더 막힐 수도 있다. 하지만 평일에는 출퇴근 시간만 피하면 그럭저럭 다닐만한 경우도 많지만, 토요일엔 시간대에 관계 없이 항상 길이 막히는 경험을 할 수 있다. 버스에서 한참을 자다가 일어났는데도 아직 고속도로에서 느릿느릿 움직이고 있었다. 시침은 어느새 한바퀴 이상을 돌아 6시 반을 가리키고 있었는데, 지도를 보고 사태의 심각성을 깨달았다. 아직 공항까지 반도 못 간 것이었다.&lt;/p&gt;
&lt;img alt=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/The_Scream.jpg/377px-The_Scream.jpg&quot; class=&quot;float-right&quot; src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/The_Scream.jpg/377px-The_Scream.jpg&quot; style=&quot;width: 200px;&quot; /&gt;
&lt;p&gt;순간적으로 여러가지 생각들이 머리속을 스쳐 지나갔다. 이번 비행기가 오늘 시드니로 가는 마지막 비행기일텐데, 내일 아침 비행기가 있으려나? 직항은 하루에 한 번이었나? 렌터카는 예약한 시간에 찾아가지 않으면 자동으로 예약이 취소 될텐데 그건 일단 항공편부터 해결하고 나서 전화해서 물어봐야겠다. 출발 날짜가 하루 늦어지면 케언스 도착 날짜도 하루 늦어질텐데, 호주팀 동료들한테도 연락해야겠다. 아니다, 하루 운전 할당량을 늘리면 어떻게든 제시간에 도착할 수 있지 않을까? 이런 생각이 들었다. 그 상황에서 내가 할 수 있는 것이 없으니 모든 것을 운에 맡기는 수밖에 없었다. 기사 아저씨 힘내요! 하루에도 몇번씩 인천공항까지 왔다갔다 하시는 전문가니까 분명히 도로 상황이 허락하는 한 최적의 경로로 나를 국제선 터미널까지 데려다주실거야.&lt;/p&gt;
&lt;p&gt;다행히도 내가 잠에서 깨어났을 때 지나고 있던 부분이 정체가 거의 끝나가는 구간이라 교통 흐름이 조금씩 좋아지더니 금방 최고 속도를 낼 수 있을만큼 풀렸다. 쏜살같이 달려 국제선 터미널에 도착했다. 버스에서 내렸을 때가 7시쯤이었다. 탑승 게이트가 열릴때 까지 딱 30분 남았다. 내리자마자 버스 정류장 근처에 있는 E 카운터에서부터 아시아나항공이 있는 L 카운터까지 단숨에 달려갔다. 평소에 유산소 운동을 꾸준히 해두길 잘 했다는 생각이 들었다. 다행히도 아시아나항공 카운터에는 대기인원이 한명도 없었다. 탑승권을 받고 보안 검색대 대기줄 쪽을 살펴보았는데, 거기도 비교적 한산한 편이었다.&lt;/p&gt;
&lt;p&gt;문득 예전에 구글 검색으로 찾아봤던 보안검색대를 빨리 빠져나가기 위한 여러가지 스킬들이 생각났다. 스킬이라고 해봤자 대단한건 아니고, 간편한 복장으로 여행을 하고, 금지 품목을 가방에 넣어서 가져오지 않도록 주의하고, 랩탑과 태블릿을 재빠르게 꺼낼 수 있도록 가방을 꾸리고, 똑같은 길이의 대기줄이라도 영유아를 동반한 가족보다는 서류 가방을 들고 있는 양복 입은 사람들 뒤에 줄을 서는 것 정도이다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#eaglecreek&quot; id=&quot;id2&quot;&gt;[1]&lt;/a&gt; 어떤 사람들은 제일 빨리 줄어들 것 같은 줄을 고르기 위해서 일종의 인종 프로파일링(racial profiling)을 제안하기도 하는데, 여기에 대한 나의 감상이나 자세한 방법은 지역에 따라 도덕성 논란이 있을 수도 있기 때문에 이 글에 기록하지는 않겠다. 하지만 내 생각에 이런 것들은 대부분의 경우 근소한 차이를 만들 수 있을 뿐이고, 가장 중요한 것은 공항에 충분히 일찍 오는 것이다.&lt;/p&gt;
&lt;p&gt;어찌 되었든 공항이 한산해서 탑승 시작 시간까지 약간의 여유가 생겼다. 덕분에 샌드위치로 간단하게 저녁 식사를 해결하고 검색대로 들어갔다. 그렇게 해서 무사히 비행기를 탈 수 있었다. 출발 전부터 이렇게 심장이 쫄깃해지는 스릴을 맛보게 될줄은 몰랐다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id3&quot;&gt;
&lt;h2&gt;비행기 안에서&lt;/h2&gt;
&lt;a class=&quot;reference external image-reference&quot; href=&quot;/attachments/2016/roadtrip-to-australia/day1/icn-to-syd.png&quot;&gt;&lt;img alt=&quot;/attachments/2016/roadtrip-to-australia/day1/icn-to-syd.png&quot; class=&quot;float-right&quot; src=&quot;/attachments/2016/roadtrip-to-australia/day1/icn-to-syd.png&quot; style=&quot;width: 240px;&quot; /&gt;&lt;/a&gt;
&lt;p&gt;인천공항과 시드니공항간의 거리는 8,300km, 비행 시간은 10시간 정도로 꽤 먼 편이지만&lt;a class=&quot;footnote-reference&quot; href=&quot;#icn-syd&quot; id=&quot;id4&quot;&gt;[15]&lt;/a&gt;, 경도상의 이동 거리보단 위도상의 이동 거리가 훨씬 길기 때문에 시차가 거의 없어서 좋았다. &lt;a class=&quot;reference external&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%BC%EA%B4%91_%EC%A0%88%EC%95%BD_%EC%8B%9C%EA%B0%84%EC%A0%9C&quot;&gt;일광 시간 졀약제(daylight saving time)&lt;/a&gt;를 시행하는 동안에는 시차가 1시간, 그 이외의 기간에는 2시간이다. 사실 이정도면 명절 연휴 기간이 끝나고 일상으로 돌아오면서 겪는 시차(?)보다 미미한 수준이기 때문에 사실상 시차가 없다고 봐도 무방하다. 만약 미국 여행기를 쓰고 있었다면 자연스럽게 시차 적응하는 몇가지 팁을 써놓을 수도 있었겠지만, 호주 여행기에 그런 얘기를 넣을 필요는 없을 것 같다.&lt;/p&gt;
&lt;p&gt;시차가 거의 없다고는 해도 비행 시간이 무려 10시간이 넘는다. 낮 시간에 비행을 한다면 하루를 꼬박 비행기 안에서 보내게 되고, 밤 시간에 비행을 한다고 해도 비좁은 이코노미석에서는 편하게 잠을 자기 어렵다. 장거리 국제선을 탈때마다 &amp;quot;다음번에는 돈을 조금(?) 더 들여서라도 비즈니스석에 타야지&amp;quot; 라는 생각을 하곤 하는데, 아직까지 그런 일은 일어나지 않고 있다. 월급쟁이로 살아가는 이상 비약적인 수입 향상을 기대하기 어렵기도 하고, 비즈니스석과 일등석 가격이 워낙 비싼 탓도 있다. 아시아나항공의 경우 인천-시드니를 왕복한다면 비즈니스석은 400만원 근처, 일등석은 700만원 정도를 지불해야 한다. 이코노미석의 요금이 100만원 정도인 것과는 매우 대조적이다.&lt;/p&gt;
&lt;p&gt;높은 수준의 서비스를 받으며 편하게 누워서 갈 수 있는 것도 좋겠지만, 사실 나는 그정도의 비용을 지불한다면 편안함보다는 비행시간 단축을 원할 것 같다. 아쉽게도 지금은 역사속으로 자취를 감추었지만, 2003년까지 운항했던 초음속 여객기 &lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Concorde&quot;&gt;콩코드&lt;/a&gt;라면 인천에서 시드니까지 네시간 정도면 충분했을 것이다. 순항속도가 마하 2.03 (약 2,179km/h) 에 이르기 때문이다. 현재 대륙간 장거리 비행에 널리 이용되고 있는 &lt;a class=&quot;reference external&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%B3%B4%EC%9E%89_747&quot;&gt;보잉 747&lt;/a&gt;의 두 배를 훌쩍 넘는 속도이다. 네시간이면 서울에서 부산까지 고속버스를 타고 가는 것보다도 더 적은 시간이다.&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;a class=&quot;reference external image-reference&quot; href=&quot;https://en.wikipedia.org/wiki/Concorde#/media/File:Concordev1.0.png&quot;&gt;&lt;img alt=&quot;By Julien.scavini - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=18401888&quot; src=&quot;/attachments/2016/roadtrip-to-australia/day1/concorde.png&quot; style=&quot;width: 60%;&quot; /&gt;&lt;/a&gt;
&lt;p class=&quot;caption&quot;&gt;By Julien.scavini - Own work, CC BY-SA 3.0, &lt;a class=&quot;reference external&quot; href=&quot;https://commons.wikimedia.org/w/index.php?curid=18401888&quot;&gt;https://commons.wikimedia.org/w/index.php?curid=18401888&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;그렇다면 27년간 멀쩡하게 운항하던 콩코드는 왜 퇴역했을까. 초음속 비행으로 인해 발생하는 소음 문제와 환경 문제, 그리고 2000년 7월에 있었던 있었던 에어프랑스 4590편의 추락 사고&lt;a class=&quot;footnote-reference&quot; href=&quot;#air-france-4590&quot; id=&quot;id7&quot;&gt;[5]&lt;/a&gt; 등 여러가지 이유가 있을 수 있다. 콩코드 노선이 수익을 내고 있었음에도 불구하고 정치적인 이유로 중단 되었다고 얘기하는 사람도 있지만&lt;a class=&quot;footnote-reference&quot; href=&quot;#bbc&quot; id=&quot;id8&quot;&gt;[4]&lt;/a&gt;, 콩코드 퇴역의 주된 이유는 경제적인 이유로 알려져있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#independent&quot; id=&quot;id9&quot;&gt;[2]&lt;/a&gt;&lt;a class=&quot;footnote-reference&quot; href=&quot;#economist&quot; id=&quot;id10&quot;&gt;[3]&lt;/a&gt;&lt;sup&gt;[more sources needed]&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;콩코드의 노선은 두 가지였다. 파리-뉴욕 노선과 런던-뉴욕 노선. 파리-뉴욕 노선은 하루에 한 번, 런던-뉴욕 노선은 하루에 두 번 비행했다. 이 노선들의 왕복 항공권 가격은 그당시 금액으로 $10,000이 훌쩍 넘었기 때문에&lt;a class=&quot;footnote-reference&quot; href=&quot;#mailonline&quot; id=&quot;id11&quot;&gt;[13]&lt;/a&gt; 콩코드의 존폐는 미국과 유럽 고객의 경제적 능력에 달려있다고 추측해볼 수 있다. 하지만 2000년에 시작된 &lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Early_2000s_recession&quot;&gt;세계 경제 위기&lt;/a&gt;, 2001년 &lt;a class=&quot;reference external&quot; href=&quot;https://ko.wikipedia.org/wiki/9%C2%B711_%ED%85%8C%EB%9F%AC&quot;&gt;9-11 테러&lt;/a&gt;와 2003년 &lt;a class=&quot;reference external&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B4%EB%9D%BC%ED%81%AC_%EC%A0%84%EC%9F%81&quot;&gt;이라크 전쟁&lt;/a&gt;이라는 악재가 겹치면서 미국 경제는 몇년째 내리막을 걷고 있었다. 침체된 경제가 콩코드 승객의 감소를 야기했다는 것을 증명해줄만한 근거 자료는 찾을 수 없었지만, 실제로 2000년 이후 승객 수가 감소했다는 것을 말해주는 자료는 찾을 수 있었다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#bbc-concorde-grounded&quot; id=&quot;id12&quot;&gt;[14]&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;a class=&quot;reference external image-reference&quot; href=&quot;/attachments/2016/roadtrip-to-australia/day1/sp500.png&quot;&gt;&lt;img alt=&quot;/attachments/2016/roadtrip-to-australia/day1/sp500.png&quot; src=&quot;/attachments/2016/roadtrip-to-australia/day1/sp500.png&quot; style=&quot;width: 60%;&quot; /&gt;&lt;/a&gt;
&lt;p class=&quot;caption&quot;&gt;S&amp;amp;P500&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;또한, 콩코드의 낮은 연비도 한몫 한다. &lt;sup&gt;[아래 표 참조]&lt;/sup&gt; 콩코드가 비행할 때 승객 한명당 사용하는 연료의 양이 이렇게 많은 이유는 항공기의 구조상 승객을 많이 태울 수 없고, 일반적인 여객기보다 두 배 이상 빠른 속도로 비행하기 때문이다. 450석 이상을 배치할 수 있는 보잉 747과 달리 콩코드는 좌석 수가 최대 128석에 불과하다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#concorde-wiki&quot; id=&quot;id16&quot;&gt;[7]&lt;/a&gt; 그리고 공기저항은 속도의 제곱에 비례하기 때문에 다른 항공기보다 두 배 이상 빠르다는 것은 공기저항을 네 배 이상으로 받는다는 뜻이고&lt;a class=&quot;footnote-reference&quot; href=&quot;#drag&quot; id=&quot;id17&quot;&gt;[9]&lt;/a&gt;, 그것을 극복하기 위해 그만큼 더 많은 연료를 소모할 수 밖에 없다는 것을 의미한다.&lt;/p&gt;
&lt;table border=&quot;1&quot; class=&quot;docutils&quot;&gt;
&lt;caption&gt;콩코드와 보잉 747-400의 승객당 연비 비교&lt;a class=&quot;footnote-reference&quot; href=&quot;#concorde-fuel-economy&quot; id=&quot;id18&quot;&gt;[8]&lt;/a&gt;&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width=&quot;51%&quot; /&gt;
&lt;col width=&quot;19%&quot; /&gt;
&lt;col width=&quot;30%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead valign=&quot;bottom&quot;&gt;
&lt;tr&gt;&lt;th class=&quot;head&quot;&gt;Aircraft&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;Concorde&lt;/th&gt;
&lt;th class=&quot;head&quot;&gt;Beoing 747-400&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td&gt;passenger miles/US gallon&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;passenger km/L&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;매우 아쉬운 일이긴 하지만, 자본주의 사회에서 돈을 벌지 못하는 서비스가 살아남을 길이란 신기루 같은 존재이다. 사실, 수백명을 한꺼번에 이동시키는 거대 교통수단의 승객당 연비가 자동차의 연비와 비교 된다는 것은 부끄러운 일이다. 게다가 14mpg (6.0km/L) 정도의 연비면 자동차 중에서도 극악무도한 연비를 자랑하는 페라리 FF와 비슷한 수준이다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#ferrari-ff-fuel-economy&quot; id=&quot;id19&quot;&gt;[10]&lt;/a&gt; 공중에 돈을 뿌리고 다닌다는 표현이 적절할 것 같다. 이쯤 되면 콩코드 노선의 운영 주체인 에어프랑스와 영국항공(British Airways) 입장에서는 서비스를 중단하는 것 이외에는 별다른 대안을 내놓기 어려웠을 것이라 짐작된다.&lt;/p&gt;
&lt;p&gt;하지만 실망하기엔 아직 이르다. 미국에서 군사 목적이 아닌 민간 항공기의 초음속 비행에 대한 논의가 활발하게 진행되고 있기 때문이다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#forbes&quot; id=&quot;id20&quot;&gt;[6]&lt;/a&gt; 미 항공우주국(NASA)에서 초음속 여객기 개발 얘기가 나오고 있고&lt;a class=&quot;footnote-reference&quot; href=&quot;#nasa-supersonic-flights&quot; id=&quot;id21&quot;&gt;[11]&lt;/a&gt;, Boom이라는 스타트업에서도 2017년 말 첫 비행을 목표로 초음속 여객기를 개발하고 있다고 하니&lt;a class=&quot;footnote-reference&quot; href=&quot;#boom&quot; id=&quot;id22&quot;&gt;[12]&lt;/a&gt; 생각보다 이른 시일 내에 나의 꿈이 이루어 질 수 있을 것이라는 기대를 걸어볼만 하다. 여기서 '나의 꿈'이란 속도 뿐만이 아니라 경제성까지 포함한다. 인천에서 시드니까지 초음속 비행으로 네 시간만에 갈 수 있다고 해도 만약 왕복 항공권 가격이 천만원이 넘어간다면 내가 그 노선을 이용할 가능성은 희박하다. '비싸지만 한번쯤은 해볼만한 경험' 정도의 가격으로 등장했으면 하는 작은 소망이 있다.&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;id23&quot;&gt;
&lt;h3&gt;기내식&lt;/h3&gt;
&lt;p&gt;비행기에서 읽으려고 '프로페셔널의 조건'이라는 책을 가져왔는데, 막상 자리에 앉으니 책을 읽고 싶은 마음이 들지 않았다. 그래서 기내 엔터테인먼트 시스템에서 제공하는 수도쿠 게임을 하고 있었는데, 이륙 후 약 두시간 정도가 지나자 기내식 서비스가 시작되었다. 기내식은 받자마자 먹는데 정신이 팔려서 사진으로 남기지는 못했다.&lt;/p&gt;
&lt;p&gt;아시아나항공과 대한항공의 기내식은 두세가지 다른 종류의 식사를 섞어놓은 느낌이다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#no-complaint&quot; id=&quot;id24&quot;&gt;[17]&lt;/a&gt; 이코노미석의 경우 기내식 메뉴 선택권이 매우 제한적이기 때문에 승객이 기내식을 매우 불만족스럽게 평가할 위험을 헤징(hedging)하기 위해서 그런 것이 아닐까 하는 생각이 들었다. 예를 들어서 메인 요리로 나온 비빔밥이 입맛에 맞지 않으면 사이드메뉴로 나온 빵에 버터를 발라서 먹거나 수박 샐러드를 먹으면서 허기를 채우라는 의미일수도 있다. 만약 이렇게 비빔밥과는 전혀 조화로워보이지 않는 뜬금없는 사이드메뉴조차 제공되지 않은 상태에서 메인 요리가 입맛에 맞지 않을 경우 승객은 기내식을 포기하고 쫄쫄 굶을 가능성이 있기 때문에 그러한 상황을 미연에 방지하고자 함이 아닐까. 나중에 &lt;a class=&quot;reference external&quot; href=&quot;http://quora.com&quot;&gt;Quora&lt;/a&gt;같은 곳에 이런 의문에 대한 질문을 올려봐도 재밌을 것 같다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id25&quot;&gt;
&lt;h3&gt;호주에서의 운전 예습&lt;/h3&gt;
&lt;p&gt;식사를 마치고 &lt;a class=&quot;reference external&quot; href=&quot;http://wikitravel.org/en/Driving_in_Australia&quot;&gt;호주에서의 운전에 대한 설명글&lt;/a&gt;을 읽어보았다. 21세기답게 태평양 상공에서 900km/h로 비행하는 중에도 와이파이가 돼서 자유롭게 웹서핑을 할 수 있어야 할 것 같지만, 아쉽게도 아시아나항공은 기내 와이파이 서비스를 제공하지 않아서 그렇게 하지는 못했다. 그대신 어제 띄워놨던 웹브라우저 탭을 보고 있었다. 와이파이는 안 되지만 기내에서 지상으로 음성 전화를 할 수 있는 서비스를 제공한다. 하지만 요금이 무려 분당 $12.50이라고 하니 누가 이걸 마음놓고 이용할 수 있을지는 잘 모르겠다. 분당 130 단어 정도를 말할 수 있다고 가정했을 때&lt;a class=&quot;footnote-reference&quot; href=&quot;#speech-speed&quot; id=&quot;id27&quot;&gt;[18]&lt;/a&gt; 한 단어를 말할때마다 평균적으로 100원씩 나간다고 보면 된다.&lt;/p&gt;
&lt;p&gt;와이파이 때문에 이야기가 잠시 다른 곳으로 샐 뻔 했는데, 다시 호주에서의 운전 얘기로 돌아와보자. 위에 링크한 설명글에서는 호주에서의 운전에 대해 꽤 많은 부분을 안내해주었지만 모든 내용을 다 언급할 필요는 없고, 그 중에서 몇가지만 리뷰해보겠다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most Australians live on or near the eastern and south-east coasts. Roads within and between the cities and towns in these areas are sealed (paved) and well maintained, (중략). There are usually plenty of well marked rest areas on major highways, though these are usually very basic and do not always have toilet facilities.&lt;/p&gt;
&lt;p&gt;대부분의 호주인들은 동쪽과 남동쪽 해안가 혹은 그 근처에 거주한다. 이 지역의 시내 도로 및 도시간 도로는 포장된 도로이고 잘 관리되고 있다. 일반적으로 주요 고속도로(major highways)에는 잘 표시된 졸음 쉼터(rest areas)가 있다. 하지만 이들은 대부분 매우 기본적인 시설만 갖추고 있으며 화장실이 없을 수도 있다.&lt;/p&gt;
&lt;p&gt;In more remote areas (known as the &amp;quot;Outback&amp;quot;) motorists may travel for hundreds of kilometres between towns or road houses without opportunities to refuel, get water, refreshments, or use toilets. In these areas, even on major highways, you will have to plan your trip, including fuel and food stops. Off the major inter-city highways, road conditions can be difficult in remote areas. Many roads are unsealed (gravel or sandy) and often poorly maintained.&lt;/p&gt;
&lt;p&gt;'아웃백'으로 알려진 외딴 지역에서는 중간에 연료, 물, 간단한 음식물을 얻을 수 있고 화장실을 이용할 수 있는 마을이나 휴게소가 없이 수백킬로미터씩 이동을 해야 하는 경우도 있다. 이러한 지역에서는 주요 고속도로라고 해도 식량과 연료를 조달하기 위해 여행 계획을 잘 세울 필요가 있다. (역자 주: 일반적으로 '주요 고속도로'에는 이러한 시설들이 잘 갖추어져있다.) 도시간 주요 고속도로를 벗어나면 도로 상황이 열악할 수도 있다. 자갈이나 모래로 뒤덮인 비포장 도로가 많으며 잘 관리되지 않는 경우가 많다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 부분을 읽고 여행 계획을 약간 수정하였다. 원래는 해안 도로를 따라서 북쪽으로 올라갔다가 다시 시드니로 돌아오는 길에는 내륙 도로를 통해서 남쪽으로 내려올 생각도 있었는데, 혼자 여행하는데다가 위성전화나 발전기 같은 장비가 없기 때문에 만약의 상황에 대비하기 어려울 것이라고 판단했다. 통신이 안 되는 지역에서 연료가 바닥난 상태로 멈춰있는데 몇시간째 아무도 지나가지 않는다면, 혹은 몇시간만에 처음으로 나타난 차량의 운전자가 나쁜 의도를 가지고 있었다면 나의 인생이 매우 피폐해질 수도 있다. 난생 처음 가보는 곳에서의 모험은 매우 신나는 일이지만 그것보다는 안전이 우선이다.&lt;/p&gt;
&lt;div class=&quot;figure&quot;&gt;
&lt;a class=&quot;reference external image-reference&quot; href=&quot;/attachments/2016/roadtrip-to-australia/day1/palm-cove-to-sydney.png&quot;&gt;&lt;img alt=&quot;/attachments/2016/roadtrip-to-australia/day1/palm-cove-to-sydney.png&quot; src=&quot;/attachments/2016/roadtrip-to-australia/day1/palm-cove-to-sydney.png&quot; style=&quot;width: 320px;&quot; /&gt;&lt;/a&gt;
&lt;p class=&quot;caption&quot;&gt;이렇게 내륙 도로를 따라서 내려올 경우 해안 도로와 비교하여 약 190km (2시간)을 절약할 수 있지만, 통신 커버리지와 도로 상태가 어떨지 모르기 때문에 호주 내륙 모험은 다음번으로 미뤄두기로 하였다.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;다음번에 또 호주에서 자동차 여행을 하게 된다면 여행 파트너도 데리고 오고, 사륜구동 SUV도 빌리고, 위성전화도 하나 마련해서 아웃백 탐험을 해보는 것도 재밌을 것 같다는 생각이 들었다. 위성 전화는 아마존에서 US$250-1,000 정도면 하나 구할 수 있다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#amazon-satellite-phone&quot; id=&quot;id28&quot;&gt;[19]&lt;/a&gt; 통신 비용은 분당 $1-2 정도로 꽤 비싼 편이지만&lt;a class=&quot;footnote-reference&quot; href=&quot;#iridium-prepaid-airtime&quot; id=&quot;id29&quot;&gt;[20]&lt;/a&gt; 비상용으로 사용하기엔 나쁘지 않다.&lt;/p&gt;
&lt;p&gt;여행 경로에 대한 계획이 조금 더 확실해졌으니 나의 큰 관심 분야중 하나인 과속 처벌 기준에 대해서도 알아봤다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Exceeding the speed limit by 10km/h or so will usually result in you being sent a fine notice of around $200 (and demerit points if driving on an Australian licence). Exceeding the speed limit by more than 30km/h can result in a court appearance and possible criminal conviction.&lt;/p&gt;
&lt;p&gt;일반적으로 10km/h 이상 제한 속도를 초과한 경우엔 약 $200의 벌금이 부과된다. 호주 면허증 소지자의 경우 벌점도 부과된다. 제한 속도를 30km/h 이상 초과하는 경우엔 법정에 출두해야 할수도 있으며, 형사상 유죄 판결이 내려질 수도 있다.&lt;/p&gt;
&lt;p&gt;Speed cameras are used in all states and territories of Australia, with some states using hidden cameras, others preferring highly visible ones. (중략) These mobile cameras operate in all speed zones (suburban side streets to freeways/highways) and in some instances in both directions. Fixed overhead speed cameras are on some highways/freeways usually under overhead bridges or sign gantrys.&lt;/p&gt;
&lt;p&gt;과속 단속 카메라는 호주의 모든 주에서 사용된다. 어떤 주는 카메라를 숨겨놓기도 하지만 어떤 주는 카메라를 잘 보이게 해놓는 것을 선호한다. (중략) 이러한 이동식 카메라는 교외 지역의 작은 도로에서부터 고속도로까지 모든 지역에서 사용되며 어떤 경우에는 양방향 모두 감시하기도 한다. 고정식 카메라는 주로 고속도로 위를 지나가는 교량 밑이나 표지판 지지대에 설치되어있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;확실히 한국보다는 범법행위에 대한 대가가 혹독하구나. 한국은 소득 수준에 비해서 교통법규 위반 벌금이 저렴하기도 하고, 과속 단속은 경찰이 직접 하는 경우가 없고 대부분 자동화된 카메라에 의해서 이루어지기 때문에 마음놓고 과속할 수 있는 환경을 제공한다.&lt;/p&gt;
&lt;a class=&quot;reference external image-reference&quot; href=&quot;https://en.wikipedia.org/wiki/Police_vehicles_in_the_United_States_and_Canada#/media/File:2006_Michigan_State_Police_Dodge_Charger_1.jpg&quot;&gt;&lt;img alt=&quot;/attachments/2016/roadtrip-to-australia/day1/dodge-charger.jpg&quot; class=&quot;float-right&quot; src=&quot;/attachments/2016/roadtrip-to-australia/day1/dodge-charger.jpg&quot; style=&quot;width: 320px;&quot; /&gt;&lt;/a&gt;
&lt;p&gt;예를 들어서, 미국의 경찰들은 도로 곳곳에 숨어있다가 과속이나 전용차로 위반, 신호 위반, 난폭운전 등 도로교통법을 위반하는 운전자가 있으면 바로 경광등을 켜고 위법 행위를 한 차량 뒤에 바짝 따라붙는다. 경찰차가 숨어있을만한 공간이 없다고 해서 안심할 수는 없다. 모터싸이클 경찰관이 스피드건을 들고 있는 모습도 어렵지 않게 목격할 수 있기 때문이다. 경찰의 정지 명령에 불복하고 계속 도망다니면 뒤에 따라붙는 경찰차 숫자가 하나둘씩 늘어나다가 어느샌가 헬리콥터까지 뜬다. 그쯤 되면 뉴스에 대문짝만하게 보도될 수도 있다. &lt;a class=&quot;reference external&quot; href=&quot;https://www.youtube.com/watch?v=ceqeNDYUFKE&quot;&gt;이렇게&lt;/a&gt; 말이다. 그리고 Chevrolet Impala, Dodge Charger, Ford Explorer 등 300-400마력을 넘나드는 고성능 차량들이 대부분이기 때문에 경찰차를 따돌리고 도망가기가 쉽지 않다. 또한, 공차 중량이 1,800kg이 넘는 대형차들인데다가 차량 앞쪽에 &lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Bullbar&quot;&gt;불바(bullbar)&lt;/a&gt;를 달아놓았기 때문에 몸싸움에서도 절대적으로 유리하다. 누군가가 지켜보고 있기 때문에 법을 준수한다는 것이 바람직한 명분인지는 잘 모르겠지만, 어찌 되었든 이런 경찰들 덕분에 도로교통법을 준수하기 위한 노력을 해야 하는 이유가 하나 더 생긴 것이다.&lt;/p&gt;
&lt;p&gt;하지만 한국의 경우 도로 여건상 경찰차가 숨어있을만한 곳이 별로 없어서 그런지 한국에서 운전을 시작한지 2년이 되어가는 지금까지도 경찰차가 직접 도로교통법 위반 차량을 검거하는 모습을 단 한 번도 보지 못했다. 미국에 있을 때에는 내가 살고 있던 애리조나 주를 비롯해서 내가 일주일 이상 머물렀던 모든 주에서 경찰차가 경광등을 켜고 누군가를 붙잡아 세우는 모습을 한 번 이상 목격한 것과는 상반되는 경험이다.&lt;/p&gt;
&lt;p&gt;개인적인 경험을 기반으로 얘기하자면 한국에서는 미국에서 운전하던것 만큼 제한속도를 준수하는데 집중하지 않아도 괜찮았다. 물론 나와 다른 사람의 안전을 위해서 제한속도는 항상 준수하는 것이 좋다. 다만, 도로에 설치된 과속 단속 카메라는 표지판으로 미리 예고를 해주기도 하고 네비게이션이 위치를 알려주기 때문에 단속에 걸릴 일이 거의 없다. 심지어 일부 운전자들은 한번에 한두개의 차선만 감시할 수 있는 단속 카메라의 특성을 이용해서 카메라가 보고 있는 차선을 피해서 제한속도를 훨씬 상회하는 속도로 통과하기도 한다. 게다가 경찰차와 나란히 달리고 있는 상황에서 주변 차량들이 실선에서 차선 변경을 하거나 제한속도보다 빨리 달리는데도 요지부동인 경찰차를 보면서 한국 경찰들의 도로교통법 위반 단속 의지에 대한 의문을 품게 되었다.&lt;/p&gt;
&lt;p&gt;하지만 지금 읽고 있는 글에 따르면 호주에서는 경찰관 뿐만이 아니라 무인 카메라까지 동원해서 단속을 하기 때문에 특별히 주의를 기울일 필요가 있다는 생각이 들었다. 미국에서도 무인 카메라로 과속 및 신호 위반 단속을 하지만, 애리조나 주의 경우 무인 카메라 운영을 경찰이 아닌 경찰과 계약된 사설 업체에서 하는데다가 미국 법이 조금 특이하기 때문에 경찰관이 아닌 카메라에 단속 당했을 경우에는 벌금을 합법적으로 내지 않을 방법이 있다. 하지만 여기에 대한 내용은 이 글의 주제에서 벗어나기 때문에 나중에 기회가 된다면 따로 글을 써보는 것도 좋을 것 같다.&lt;/p&gt;
&lt;p&gt;마지막으로 한국의 수도권 지역에서는 좀처럼 경험하기 힘든 부분에 대한 설명이 눈길을 끌었다. 로드킬에 대한 내용이었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you are driving on Australia's open roads you may see dead animals on the side of the road. The fact is, quickly swerving or braking heavily could cause a much more serious accident. Dusk and sunrise are times to be on the alert through the Australian bush, as well as regions where you will encounter water sources like rivers and reservoirs, or the plains surrounding mountain ranges.&lt;/p&gt;
&lt;p&gt;호주의 탁 트인 길을 달리다보면 길가에서 죽은 동물을 볼 수 있다. 이때 갑자기 방향을 틀거나 브레이크를 세게 밟을 경우 더 큰 사고로 이어질 수도 있다. 해질녘과 동틀녘에는 숲을 지날 때에나, 강과 저수지같은 수원지, 그리고 산맥을 둘러싼 평야(?)를 지날 때 특히 주의를 기울여야 한다.&lt;/p&gt;
&lt;p&gt;If you come across multiple tyre marks on the road, this could suggest that animals regularly use this part of the road as a crossing, so just be a little more aware, and also, using the high beam head lights at night, will make it harder for an animal to find an appropriate escape route, so practice flicking them off for animals as well as for on coming traffic.&lt;/p&gt;
&lt;p&gt;도로에서 다수의 타이어 자국을 발견했다면 동물들이 도로를 건너기 위해 그 지점에 자주 출현한다는 의미일 수 있기 때문에 주의를 기울여야 한다. 야간에 상향등을 사용하는 것은 동물들이 적절한 탈출로를 찾는 것을 어렵게 만들기 때문에 (역자 주: 주행 중 동물을 발견한다면) 동물들을 위해서, 그리고 마주오는 차량들을 위해서 상향등을 끄는 연습을 해두는 것이 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;다행스럽게도 아직까지 차로 동물을 쳐본적은 없지만, 만약 그런 일이 일어난다면 나에게도, 동물에게도, 차에게도 매우 좋지 않은 경험이 될테니 주의할 필요가 있어보인다. 천천히 글을 읽다보니 어느새 잘 시간이 되었다.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;id31&quot;&gt;
&lt;h3&gt;기내 응급 환자 발생&lt;/h3&gt;
&lt;p&gt;쿨쿨 자고 있는데 내 앞자리에 부부로 보이는 승객 두 명과 통로쪽에 서있는 승무원이 무언가 심각한 주제에 대해 얘기하는 소리가 들렸다. 승무원이 어디론가 가더니 잠시 후에 기내 방송이 들려왔다. 기내에 응급 환자가 발생했으니 승객 중에 의사가 있으면 승무원에게 얘기해달라는 내용이었다. 다행히도 승객중에 의사가 있어서 잠시 후에 의사 두 명이 와서 환자에게 이런저런 질문을 하기 시작했다. 커다란 주사기라든가 제세동기 같은 무서운(?) 장비는 보이지 않았다. 분위기를 보니 심각한 문제는 아닌 것 같았다. 얼마 후 그 의사들은 다시 각자의 자리로 돌아갔다.&lt;/p&gt;
&lt;p&gt;그러고보면 의사는 참 멋진 직업이다. 39,000피트 상공에서 발생한 응급 환자에게 도움을 줄 수 있으니 말이다. 그에 비하면 항공기 엔지니어나 소프트웨어 개발자는 아무짝에도 쓸모가 없다.&lt;a class=&quot;footnote-reference&quot; href=&quot;#just-kidding&quot; id=&quot;id32&quot;&gt;[16]&lt;/a&gt; &amp;quot;3번 엔진에 문제가 발생했습니다. &lt;a class=&quot;reference external&quot; href=&quot;http://www.ge.com/kr/&quot;&gt;GE&lt;/a&gt;에서 일하는 엔지니어가 있다면 승무원에게 말씀해주십시오.&amp;quot; 아마 안 될거야...ㅇ&amp;lt;-&amp;lt;&lt;/p&gt;
&lt;p&gt;이런저런 잡생각을 하다가 문득 이런 생각도 들었다. 기내에서 응급 환자가 발생했는데 외부 의료 기관의 도움이 필요한 상황이라면 어떻게 될까? 아마도 가장 가까운 공항에 비상 착륙을 할 것이다. 그렇지만 이것으로 인해 발생한 비용은 항공사가 전부 부담하게 되는건지 궁금해졌다. 예를 들어서, 이륙한지 얼마 지나지 않아서 착륙을 해야 한다면 착륙시 최대 무게를 맞추기 위해 상당량의 연료를 공중에 버려야 할 수도 있다. 그렇게 되면 목적지까지 비행을 재개하기 위해서 연료를 재구매 해야한다. 또한, 어떤 사정으로 인해 곧바로 이륙할 수 있는 상황이 아니라 승객들을 근처 호텔에서 하룻밤 재워야 한다면 숙박비와 교통비만 해도 꽤 큰 금액이 될 것이다. 혹시 이런것에 대비하여 항공사가 이용할 수 있는 보험 상품이 있을까? 그리고 비상 착륙으로 인해 지연된 일정 때문에 누군가가 일생일대의 취직 면접 일정을 놓치거나 부모님의 임종을 지키지 못하는 상황이 발생했다면 그 사람은 어디에 가서 누구에게 보상받을 수 있을까? 아마도 자연재해로 인한 연착과 비슷하게 아무데서도 보상받을 수 없을 것이라 예상된다. 오해의 여지가 있을까봐 사족을 하나 달자면, 응급 환자가 발생했을 때 비상 착륙 없이 목적지까지의 비행을 강행해야 한다고 주장하고 싶은건 아니다. 당연히 환자를 살리는게 최우선이다.&lt;/p&gt;
&lt;p&gt;피곤한 상태에서 이런저런 복잡한 생각을 했더니 더 피곤해졌다. 눈을 떴을 때 아침식사가 제공되고 있었으면 좋겠다는 생각을 하면서 다시 눈을 감았다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;references&quot;&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;eaglecreek&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id2&quot;&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Unknown. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.eaglecreek.com/blog/8-expert-tsa-tips-get-through-airport-security-faster&quot;&gt;8 Expert TSA Tips To Get Through Airport Security Faster&lt;/a&gt;.&amp;quot; Eagle Creek. N.p., 28 July 2011. Web. 05 Nov. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;independent&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id9&quot;&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Woodman, Peter. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.independent.co.uk/news/uk/home-news/end-of-an-era-concorde-is-retired-114575.html&quot;&gt;End of an Era - Concorde Is Retired&lt;/a&gt;.&amp;quot; The Independent. Independent Digital News and Media, 10 Apr. 2013. Web. 05 Nov. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;economist&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id10&quot;&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Unknown. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.economist.com/node/2142593&quot;&gt;After Concorde&lt;/a&gt;.&amp;quot; The Economist. The Economist Newspaper, 18 Oct. 2003. Web. 05 Nov. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;bbc&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id8&quot;&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Westcott, Richard. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.bbc.com/news/business-24629451&quot;&gt;Could Concorde Ever Fly Again? No, Says British Airways&lt;/a&gt;.&amp;quot; BBC News. BBC, 24 Oct. 2013. Web. 05 Nov. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;air-france-4590&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id7&quot;&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.youtube.com/watch?v=ByB6wO3F30Y&quot;&gt;https://www.youtube.com/watch?v=ByB6wO3F30Y&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;forbes&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id20&quot;&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Meyer, Jared. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.forbes.com/sites/jaredmeyer/2016/10/28/supersonic-flight-make-america-boom-again&quot;&gt;Supersonic Flight: Make America Boom Again&lt;/a&gt;.&amp;quot; Forbes. Forbes Magazine, 28 Oct. 2016. Web. 05 Nov. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;concorde-wiki&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id16&quot;&gt;[7]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Concorde&quot;&gt;https://en.wikipedia.org/wiki/Concorde&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;concorde-fuel-economy&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id18&quot;&gt;[8]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Supersonic_transport&quot;&gt;https://en.wikipedia.org/wiki/Supersonic_transport&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;drag&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id17&quot;&gt;[9]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://en.wikipedia.org/wiki/Drag_equation&quot;&gt;https://en.wikipedia.org/wiki/Drag_equation&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;ferrari-ff-fuel-economy&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id19&quot;&gt;[10]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.fueleconomy.gov/feg/Find.do?action=sbs&amp;amp;id=36960&quot;&gt;https://www.fueleconomy.gov/feg/Find.do?action=sbs&amp;amp;id=36960&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;nasa-supersonic-flights&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id21&quot;&gt;[11]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Harrington, J.D., and Kathy Barnstorff. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.nasa.gov/press-release/nasa-begins-work-to-build-a-quieter-supersonic-passenger-jet&quot;&gt;NASA Begins Work to Build a Quieter Supersonic Passenger Jet&lt;/a&gt;.&amp;quot; NASA. NASA, 5 May 2016. Web. 05 Nov. 2016.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;boom&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id22&quot;&gt;[12]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;http://boomsupersonic.com&quot;&gt;http://boomsupersonic.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;mailonline&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id11&quot;&gt;[13]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Unknown. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;http://www.dailymail.co.uk/news/article-196994/concorde-tickets-snapped-up.html&quot;&gt;Concorde Tickets Snapped Up&lt;/a&gt;.&amp;quot; Mail Online, 22 Sept. 2003.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;bbc-concorde-grounded&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id12&quot;&gt;[14]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Unknown. &amp;quot;&lt;a class=&quot;reference external&quot; href=&quot;news.bbc.co.uk/2/hi/uk_news/2934257.stm&quot;&gt;Concorde Grounded for Good&lt;/a&gt;.&amp;quot; BBC News, BBC, 10 Apr. 2003.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;notes&quot;&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;icn-syd&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id4&quot;&gt;[15]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.world-airport-codes.com/distance/?a1=icn&amp;amp;a2=syd&quot;&gt;Distance between ICN and SYD&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;just-kidding&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id32&quot;&gt;[16]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;농담이다.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;no-complaint&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id24&quot;&gt;[17]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;딱히 불평을 하고 싶은건 아니다. 나는 항상 배가 고프기 때문에 아무리 뜬금없는 메뉴들이 섞여 나와도 웬만하면 골고루 다 먹는 편이다.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;speech-speed&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id27&quot;&gt;[18]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.quora.com/Speeches-For-the-average-person-speaking-at-a-normal-pace-what-is-the-typical-number-of-words-they-can-say-in-one-minute&quot;&gt;Speeches: For the average person speaking at a normal pace, what is the typical number of words they can say in one minute?&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;amazon-satellite-phone&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id28&quot;&gt;[19]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://www.amazon.com/s/ref=a9_sc_1?keywords=satellite+phone&quot;&gt;https://www.amazon.com/s/ref=a9_sc_1?keywords=satellite+phone&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;docutils footnote&quot; frame=&quot;void&quot; id=&quot;iridium-prepaid-airtime&quot; rules=&quot;none&quot;&gt;
&lt;colgroup&gt;&lt;col class=&quot;label&quot; /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign=&quot;top&quot;&gt;
&lt;tr&gt;&lt;td class=&quot;label&quot;&gt;&lt;a class=&quot;fn-backref&quot; href=&quot;#id29&quot;&gt;[20]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class=&quot;reference external&quot; href=&quot;http://www.satphonestore.com/airtime/iridium-airtime.html&quot;&gt;http://www.satphonestore.com/airtime/iridium-airtime.html&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

</description>
        <pubDate>Sat, 24 Sep 2016 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/roadtrip-in-australia-day1/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/roadtrip-in-australia-day1/</guid>
      </item>
    
      <item>
        <title>이상한 호주 여행기 - 출발 전날</title>
        <description>&lt;p&gt;2016년 9월 25일부터 10월 6일까지 총 12일간 호주 여행을 다녀왔다. 시드니 공항에 내려서 팜 코브까지 총 5,500km를 달리면서 8개의 도시를 거쳐가는 여행이었다. 여행 그 자체로도 충분히 만족스러웠지만, 시간이 지남에 따라 희미해지는 기억 속에만 남겨두기 아까워 여행에서 보고 느꼈던 것들을 글로 기록해보고자 한다.&lt;/p&gt;

&lt;h2 id=&quot;where-it-all-started&quot;&gt;Where It All Started&lt;/h2&gt;
&lt;p&gt;사실 이 얘기는 여행 중에 만났던 사람들과의 대화 중 나왔던 단골 주제 중 하나였다. 내가 어떤 계기로 이 여행을 시작하게 되었는지 설명하려면 이야기는 작년, 그러니까 2015년 &lt;a href=&quot;https://ndc.nexon.com&quot;&gt;넥슨 개발자 컨퍼런스(NDC)&lt;/a&gt;로 거슬러 올라간다.&lt;/p&gt;

&lt;p&gt;작년 NDC에서 &lt;a href=&quot;http://www.slideshare.net/suminb/durango-opencl&quot;&gt;&amp;lt;야생의 땅: 듀랑고&amp;gt;의 식물 생태계를 담당하는 21세기 정원사의 OpenCL 경험담&lt;/a&gt;이라는 주제로 발표를 했었는데, 정말 감사하게도 청중들이 나의 발표를 높게 평가해주신 덕분에 우수발표상을 수상하게 되었다. 상품은 세가지였다. 유리로 만들어진 상패, 다른 수상자들과 함께 하는 넥슨 대표이사와의 점심식사, 그리고 Global Experience Program (GEP) 참가권이 주어진다.&lt;/p&gt;

&lt;p&gt;GEP가 무엇인지에 대한 공식 설명은 “다양성과 도전정신, 그리고 창의적 자극을 경험할 수 있는 글로벌 문화체험 프로그램” 이지만&lt;sup id=&quot;fnref:gep&quot;&gt;&lt;a href=&quot;#fn:gep&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, 간단하게 말해서 매년 회사에서 선발된 직원들에게 해외 여행을 할 수 있도록 일정한 여행비와 휴가를 제공하는 제도이다. 일반적으로는 지원서를 정성들여 써 내고 심사를 거쳐 높은 경쟁률을 뚫고 GEP 참가자로 선발 되는 것이지만, 나는 NDC 수상 덕분에 프리 패스를 얻은 것이었다. 작년에는 목적지와 여행 일정이 정해져 있었지만, 올해에는 참가자들이 각자 가고 싶은 곳을 선택할 수 있었다.&lt;/p&gt;

&lt;p&gt;하지만 “두 명 이상 같은 팀이 되어야 하고, 같은 팀끼리는 3일 이상 동행해야 한다”는 조건이 붙는다. 그래서 올해 초에 각자 가고 싶은 목적지와 여행 테마 등을 주제로 5분씩 짧게 발표하여 다른 사람들을 설득해서 두 명 이상의 팀을 만드는 시간을 가졌었다. 안타깝게도 나는 그때 우리 팀에서 개발하고 있는 게임 테스트 일정 때문에 여행 테마를 고민할 여유가 없었기 때문에 그냥 다른 사람들의 발표를 듣고 마음에 드는 목적지를 고르기로 했다. 중국 여행, 스페인 여행, 스위스 도보 여행, 아프리카 커피 여행 등 다양하고 흥미로운 여행 주제들이 나왔지만, 그중에서도 나는 호주 여행에 마음이 갔다.&lt;/p&gt;

&lt;p&gt;여행 팀을 구성하는데에도 여러가지 에피소드가 있었지만, 결론만 간단히 얘기하자면 다섯 명으로 구성된 호주팀, 다섯 명으로 구성된 스페인팀, 그리고 두 명으로 구성된 하와이팀이 만들어졌다.&lt;/p&gt;

&lt;h2 id=&quot;여행-계획&quot;&gt;여행 계획&lt;/h2&gt;
&lt;figure class=&quot;float-right&quot; style=&quot;width:50%; max-width:480px;&quot;&gt;
  &lt;img src=&quot;/attachments/2016/roadtrip-to-australia/prep/wikia-screenshot.png&quot; /&gt;
  &lt;figcaption&gt;이렇게 위키 페이지를 만들어서 다같이 여행 계획을 정리하기도 했다.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;이제 각 팀별로 세부 일정과 여행 계획을 세울 차례였다. 우리 팀은 주로 ‘넥다’에서 만나서 이야기를 했다. 사내 카페 이름이 ‘넥슨 다방’인데 넥슨 사람들은 이것을 줄여서 ‘넥다’라고 부른다. 넥다에서 신나는 여행 계획이 시작되었다. &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8B%9C%EB%93%9C%EB%8B%88&quot;&gt;시드니&lt;/a&gt;, &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%A9%9C%EB%B2%84%EB%A5%B8&quot;&gt;멜버른&lt;/a&gt;, &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%95%A0%EB%93%A4%EB%A0%88%EC%9D%B4%EB%93%9C&quot;&gt;애들레이드&lt;/a&gt;, &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B8%8C%EB%A6%AC%EC%A6%88%EB%B2%88&quot;&gt;브리즈번&lt;/a&gt; 등 동남부 대도시를 여행할까, 아니면 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%8D%BC%EC%8A%A4_(%EC%9B%A8%EC%8A%A4%ED%84%B4%EC%98%A4%EC%8A%A4%ED%8A%B8%EB%A0%88%EC%9D%BC%EB%A6%AC%EC%95%84_%EC%A3%BC)&quot;&gt;퍼스&lt;/a&gt;같은 서부 도시 근처를 여행할까, 자동차를 빌려서 이동할까 아니면 비행기 타고 이동할까 등 수많은 토론거리가 오고 갔다. 다행히도 우리 팀원 중에 호주 여행 경험이 있는 분이 한 분 계셔서 대략적인 이야기를 전해들을 수 있었다. 각 도시의 분위기가 어떻고, 어떤 볼거리가 있는지 등에 대한 이야기였다.&lt;/p&gt;

&lt;p&gt;팀원이 다섯 명이나 되다 보니까 일정을 정하는 일도 꽤 많은 조율이 필요했다. 나를 제외하고는 전부 가정이 있는 분들이라 추석 연휴를 끼고 여행을 가는 것은 매우 어려워보였다. 그래서 추석 연휴가 끝난 다음주 혹은 그 다음주 쯤에 여행을 시작하는 것으로 대략적인 계획을 잡았다.&lt;/p&gt;

&lt;!--
해지의 코멘트

그런썰좀 풀어주게나 ㅋㅋ
호주에 대한 환상이낰ㅋ 가서 뭘 보고 경험하게 될지 기대하는 것들에 대해사 ㅋㅋ
뭔가 호주를 그 먼거리를 차로 다니면서 보고싶었던 것들 ㅋㅋ
여행 전에만 느낄 수 있는 그런 감정들 ㅋㅋ
그런걸 좀 봐야 막 나도 두근두근 설레면서 &quot;실제론 뭘 봤을까- 어땠을까-!!&quot; 막 기대하면서 보게 된다공ㅋㅋ

왜 호주를 선택했는지 ㅎㅎ 이유정도 ㅎㅎ 발표가 넘 흥미진진했다던가 등등?ㅋㅋ
--&gt;

&lt;p&gt;그렇게 수많은 대화가 오가면서 느꼈던건 각자 원하는 여행 스타일이 참 많이 다르구나 하는 것이었다. 나는 처음부터 자동차를 타고 이동하는 것을 제안했었는데 어떤 사람들은 계속 한 곳에 머무르고 싶어하기도 하고, 다른 사람들은 비행기를 타고 이동하는 것을 선호하는것 같았다. 굳이 처음부터 끝까지 같이 여행을 할 필요는 없으니 GEP에서 요구하는 최소한의 동행 조건만 만족시키고, 나머지 시간은 혼자 여행을 하는 것도 괜찮을 것 같다는 생각이 들었다.&lt;/p&gt;

&lt;p&gt;여러번의 미팅 끝에 케언스(Cairns)라는 동북부 휴양 도시에서 2박 3일동안 머물며 스쿠버 다이빙, 스노클링과 같은 수상스포츠 활동을 하는 것으로 여행 주제가 정해졌다. 그리고 각자 여행 스타일이 다르기에 2박 3일동안은 케언스에서 같이 활동을 하되, 그 이외의 시간에는 각자 하고싶은대로 자유 여행을 하기로 합의했다.&lt;/p&gt;

&lt;p&gt;교통편을 알아보니 팜 코브는 인구 1,200명 정도의 조그마한 마을이라 공항이 없고, 그 근처 도시 케언스는 인천공항에서 바로 갈 수 있는 직항 항공편이 없었다.&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; 시드니같은 대도시만 직항 노선이 있었는데, 그렇게 되면 일단 인천공항에서 시드니로 이동한 후 다시 국내선을 타고 케언스까지 이동해야 했었다. 시드니에서 케언스까지 비행기로 세시간 정도면 가는 거리겠지만 자동차로는 주행시간만 최소 30시간 이상이 걸리는 거리였다. 그래서 앞뒤로 휴가를 조금 더 붙여서 자동차로 여행을 하는 방향으로 가닥을 잡았다.&lt;/p&gt;

&lt;h3 id=&quot;자동차-여행&quot;&gt;자동차 여행&lt;/h3&gt;

&lt;p&gt;나는 자동차 여행을 좋아한다. 어렸을때부터 자동차를 좋아하기도 했었고, 미국의 중소도시에서 20대 시절의 대부분을 보냈기 때문에 20세부터 운전을 했었다. 다양한 기후와 환경에서 수천킬로미터씩 장거리 여행을 했던 경험도 꽤 있고, 심지어는 자동차를 타고 미국을 시계방향으로 한바퀴 도는 약간 정신나간 여행도 했었다. 조금 더 엄밀히 따져보자면 자동차를 타고 이동하는 과정에서 얻는 경험을 즐기는 편이라고 볼 수 있다. 낯선 곳의 멋진 풍경, 내 차의 거울이 안 보일 정도의 칠흑같은 어둠, 여행중에 만난 사람들과 이야기를 나누는 경험, 새로운 음식, 도로라는 완전히 개방된 공공장소에서 자동차가 제공하는 사적 공간이 주는 묘한 안정감. 그래서 그런지 한국으로 돌아온 후에도 자동차 여행을 종종 하고 있다. 500km 이하의 단거리 자동차 여행은 기분 내키면 별다른 준비 없이 혼자서 훌쩍 떠나기도 한다. 주말에 바다가 보고 싶으면 서해안으로, 산으로 가고 싶으면 강원도로 가는 식이다. &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EA%B2%BD%EB%B6%80%EA%B3%A0%EC%86%8D%EB%8F%84%EB%A1%9C&quot;&gt;경부고속도로&lt;/a&gt;, &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%84%9C%EC%9A%B8%EC%99%B8%EA%B3%BD%EC%88%9C%ED%99%98%EA%B3%A0%EC%86%8D%EB%8F%84%EB%A1%9C&quot;&gt;서울외곽순환고속도로&lt;/a&gt;, &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%98%81%EB%8F%99%EA%B3%A0%EC%86%8D%EB%8F%84%EB%A1%9C&quot;&gt;영동고속도로&lt;/a&gt; 등 고속도로 접근성이 좋은 지역에 살고 있기 때문에 충청도든 강원도든 한시간 반 정도면 충분하다.&lt;/p&gt;

&lt;h3 id=&quot;역대-최고의-여행&quot;&gt;역대 최고의 여행&lt;/h3&gt;

&lt;p&gt;내가 자동차 여행을 좋아하게 된 계기는 아마도 지금껏 다녔던 여행 중에 최고의 여행이 자동차 여행이라서 그런 것이 아닐까 하는 생각이 든다.&lt;/p&gt;

&lt;p&gt;이야기는 2011년 여름으로 거슬러 올라간다. 미국에서 학부를 졸업하고 로봇 인공지능 연구실에서 일을 하다가 문득 “아, 내가 대학을 졸업했지만 아는 것이 아무것도 없구나” 하는 것을 깨달았다. 그래서 조금 더 학문에 정진하고자 대학원에 진학하기로 마음 먹었었는데, 일을 그만 두고 첫 학기가 시작될 때 까지 약 3개월의 공백 기간이 생긴 것이었다. 일 하면서 모아둔 돈도 조금 있는데다가 시간마저 넘쳐나니까 마구 놀기 시작했다. 무슨 생각이었는지는 모르겠는데, 문득 자동차 여행을 하고 싶다는 생각이 들었다.&lt;/p&gt;

&lt;p&gt;약 2주 정도 여행을 할 계획이었는데, 이왕 여행 하는거 친구들 얼굴도 좀 볼겸 서부 도시에 거주하고 있는 친구들에게 연락을 하기 시작했다. “내가 여행을 계획중인데, 몇월 몇일 쯤에 너희 집을 지나갈 것 같아. 시간 괜찮으면 같이 놀자. 그리고 나 하룻밤만 재워줘.” 어떻게 보면 갑작스럽고 무례한 부탁일수도 있는데 다들 흔쾌히 승락했다. 정말 고마운 친구들이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2016/roadtrip-to-australia/prep/us-westcoast.png&quot; style=&quot;max-width:60%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;그렇게 해서 이렇게 다섯개의 도시를 거쳐가는 여행 계획이 완성되었다. 주행 거리만 4,100km가 넘는 대장정이었다.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;자동차 여행의 묘미는 중간에 거쳐가는 곳에서 누구를 만나게 될지, 어디서 얼마나 오래 머물게 될지 모른다는 것이다. 아니, 조금 더 정확하게 말하면 비행기나 기차처럼 정해진 일정과 경로를 따라 움직이는 것이 아니기 때문에 여행을 하다가 마음에 드는 곳이 있으면 조금 더 오래 머무를 수도 있고, 중간에 자유롭게 여행 경로를 재설정 할 수 있다는 점이 큰 매력으로 다가왔다. 실제로 솔트레이크시티(Salt Lake City)에서는 예정보다 하루 더 머무르기도 했고, 그렇게 하루 더 머무르게 된 날 새로 만난 친구와는 아직도 연락을 하고 지낸다.&lt;/p&gt;

&lt;p&gt;여러 친구들에게 민폐를 끼친 덕분에 여행 기간동안 숙박비로 단 1달러도 쓰지 않았다. 하지만 아쉽게도 라스베가스에 거주하는 친구가 없어서 예외적으로 호텔에서 숙박을 했었다. 하지만 여행 전 몇달동안 주말마다 친구들을 집으로 초대해서 맥주를 마시며 블랙잭을 연습했던 덕분일까, 이곳에서는 블랙잭으로 돈을 벌어서 호텔비와 식사비를 모두 해결하고 홀가분하게 떠날 수 있었다.&lt;/p&gt;

&lt;p&gt;그 이후에도 꽤 많은 여행을 다녔지만 아직까지는 이렇게 혼자 훌쩍 떠났던 미국 서부 여행이 최고의 여행으로 남아있다.&lt;/p&gt;

&lt;h3 id=&quot;호주에서의-자동차-여행&quot;&gt;호주에서의 자동차 여행&lt;/h3&gt;

&lt;p&gt;끝없이 펼쳐진 광활한 대지, 깨끗한 자연 환경, 해안가를 따라 달리는 고속도로. GEP 참가자들의 여행 계획 발표를 듣고 내가 호주를 고른 이유는 자동차 여행을 하기에 최적의 조건을 갖추고 있기 때문이었다. 그런 곳에서의 자동차 여행이라니! 정말 멋진 여행이 될 것 같다는 생각이 들었다. 어쩌면 호주에서의 여행이 내 마음 속의 최고의 여행 자리를 탈환할 수 있지도 않을까 하는 기대감도 생겼다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://maps.google.com&quot;&gt;구글 지도&lt;/a&gt;를 펼쳐놓고 보니 호주의 땅은 정말 넓었다. 시드니에서 팜 코브까지의 거리를 보니 약 2,700km 정도였다. 왕복 거리가 대략 5,300km 였다. “미국에선 이것보다 더한 짓도 했었는데 이정도야 별것 아니지”와 같은 안일한 생각으로 여행의 현실성에 대한 고민을 진지하게 해보지 않았다.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/attachments/2016/roadtrip-to-australia/prep/route-plans.png&quot; style=&quot;max-width:80%&quot; /&gt;
  &lt;figcaption&gt;호주 땅의 광활함을 표현하기 위해 똑같은 스케일로 표시된 한반도 지도를 옆에 놓고 비교해보았다.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;여행 일정에 맞추어 항공권을 예매하고 렌터카&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; 예약을 해놓은 다음에는 GEP에 대해서 한동안 생각을 하지 않고 있었다.&lt;/p&gt;

&lt;h2 id=&quot;슬슬-다가오는-출발일&quot;&gt;슬슬 다가오는 출발일&lt;/h2&gt;
&lt;p&gt;처음에 자동차 여행을 생각해냈을 때에는 별 생각이 없었지만 출발 날짜가 가까워지고 세부 계획을 고민하면서 슬슬 걱정이 되기 시작했다. “차량 통행 방향과 운전석 위치가 반대인데 별 문제는 없을까”, “중간에 거쳐가는 도시는 어떻게 정하지”, “구글 맵스로 보니까 도시간 거리가 꽤 되는 구간도 있는데 통신이 안 되는 구간은 없을까.” 와 같은 걱정이 들기 시작했다.&lt;/p&gt;

&lt;p&gt;통신 문제에 대한 걱정은 한국의 높은 통신 커버리지에 익숙한 사람들이 보기엔 조금 과해보인다고 생각할 수도 있다. 하지만 지구상엔 다양한 환경이 존재한다. 예를 들어서, 미국 텍사스주의 엘 파소(El Paso)에서 샌 안토니오(San Antonio)까지 거리가 887km 정도, 그러니까 약 7시간 반 거리인데 그 중간에 인구 10만이 넘는 도시가 없다. 중간에 보이는 소도시 몇개를 제외하고는 거의 빈땅이라고 봐도 무방한데, 그래서 그런지 곳곳에 통신이 안 되는 지역이 있다. 멕시코 국경 근처를 지날 땐 미국 통신사 대신 멕시코 통신사가 잡히기도 한다. 뭐라도 잡히면 그나마 다행인데 완전한 통신 불능 상태에서 연료가 바닥나거나 차에 문제가 생기면 매우 난처해지기 때문에 이러한 지역을 지날 때에는 특별히 주의를 기울일 필요가 있다.&lt;/p&gt;

&lt;h3 id=&quot;호주에서의-운전&quot;&gt;호주에서의 운전&lt;/h3&gt;

&lt;p&gt;지도와 위성사진을 살펴보는것으로는 조금 부족다하는 느낌이 들어서 &lt;a href=&quot;http://wikitravel.org/en/Driving_in_Australia&quot;&gt;호주에서의 운전에 대해 설명해놓은 글&lt;/a&gt;을 하나 찾아서 읽어봤다. 꽤 자세하게 설명해 놓아서 대략적인 내용을 파악하는데 도움이 되었다.&lt;/p&gt;

&lt;p&gt;사실 내가 가장 궁금했던건 호주의 도로교통법이 한국과 같은 화이트리스트 방식인지, 아님 미국과 같은 블랙리스트 방식인지였다. 예를 들어서, 한국은 좌회전 신호인 경우 또는 비보호 좌회전 표지가 있는 곳에서만 좌회전을 할 수 있지만, 미국의 경우 좌회전 금지 표지가 있지 않는 한 직진 신호에서 비보호 좌회전을 할 수 있다. 물론 이때 사고가 나면 좌회전 차의 과실이다. 유턴도 마찬가지다. 한국은 유턴 허용 표시가 있는 곳에서만 유턴을 해야 하지만, 미국은 유턴 금지 표시가 없으면 유턴을 해도 된다.&lt;/p&gt;

&lt;p&gt;하지만 글만 읽어보고 이러한 것을 총체적으로 파악하기에는 한계가 있었다. 직접 가서 표지판이나 도로 시설물 같은걸 보고, 로컬 드라이버들이 어떻게 운전하는지 관찰을 해봐야 알 수 있을 것 같다.&lt;/p&gt;

&lt;h3 id=&quot;호주-영어&quot;&gt;호주 영어&lt;/h3&gt;

&lt;p&gt;운전도 걱정이지만 언어도 걱정이었다. 미국에서 대학을 졸업하고 일을 했던 경험 덕분에 영어로 의사소통을 하는데 별다른 불편함이 없긴 하지만, 영어가 모국어가 아닌데다가 호주 영어 경험이 전혀 없기 때문에 혹시나 여행 중 어려움이 생기지는 않을까 하는 걱정이 있었다. 하지만 지금은 전 세계가 하나로 연결되는 21세기가 아닌가. 유튜브에서 호주 영어를 미리 체험해보기로 했다.&lt;/p&gt;

&lt;iframe src=&quot;https://www.youtube.com/embed/xuRrp83jCuQ&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; style=&quot;display:block; margin:auto; width:560px; max-width:100%; height:315px;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;미국 영어보다는 영국 영어에 더 가깝지만 영국 영어와는 다르다. 사용하는 단어도 미묘하게 다르다.&lt;/p&gt;

&lt;iframe src=&quot;https://www.youtube.com/embed/dG0v9tZStAk&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; style=&quot;display:block; margin:auto; width:560px; max-width:100%; height:315px;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;단어를 &lt;em&gt;호주 스타일로&lt;/em&gt; 짧게 줄여 쓰고 미국 영어와는 다르게 단어 끝의 굴러가는 R 발음을 안 하는 것이 포인트이다.&lt;/p&gt;

&lt;p&gt;휴, 어떤 느낌인지는 알겠지만 자신은 없다. 하지만 부산 사람이 서울말 알아듣는것처럼 호주 사람들도 내가 미국 엑센트로 얘기 해도 잘 알아듣겠지. 반대로 내가 그 사람들 말을 잘 못 알아들으면 다시 물어보면 되니까 어떻게든 되겠지.&lt;/p&gt;

&lt;h3 id=&quot;숙박&quot;&gt;숙박&lt;/h3&gt;

&lt;p&gt;숙박은 &lt;a href=&quot;http://airbnb.com&quot;&gt;에어비앤비&lt;/a&gt;를 통해 해결하기로 했다. 하지만 여행 당일 도로 사정이 어떨지, 내 체력 상태가 어떨지 전혀 짐작할 수 없어서 미리 숙소 예약을 하지는 못했다. 길이 막혀서 예상보다 이동 시간이 훨씬 오래 걸릴 수도 있고, 운전을 하다가 너무 졸려서 원래 계획했던만큼 이동하지 못할 수도 있다. 상황을 봐가며 당일에 숙소를 구하는 편이 나을 것이라는 생각이 들었다. 에어비앤비에서 적절한 숙소를 구하지 못하면 모텔이나 호텔에서 숙박을 해도 되고, 그것마저 여의치 않을 경우에는 차에서 자도 되기 때문에 이 부분에 있어서 큰 걱정은 없었다.&lt;/p&gt;

&lt;h2 id=&quot;출발-전-준비물&quot;&gt;출발 전 준비물&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;가방은 가볍게, 지갑은 무겁게&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;나의 여행 철학(?)이다. 꼭 필요한것만 챙겨가고 웬만한건 현지에서 조달함으로써 짐을 줄이는 것을 선호한다. 그리고 없으면 없는대로 잘 사는 경우가 많다. 여행을 하다보면 인간이 삶을 영위하는데 필요한게 의외로 그렇게 많지 않다는 것을 깨닫게 된다.&lt;/p&gt;

&lt;figure style=&quot;width:60%; max-width:480px;&quot;&gt;
  &lt;img src=&quot;/attachments/2016/roadtrip-to-australia/prep/light-baggage.jpg&quot; /&gt;
  &lt;figcaption&gt;이 가방과 노트북 배낭 하나만 챙겨갔다. 월마트에서 $40 주고 구입한 이 하늘색 가방은 2007년부터 나와 함께 했던 유서 깊은 가방이다.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;물론 현지에서 조달하기 어려운것들은 미리 준비해서 잘 챙겨가는게 좋다.&lt;/p&gt;

&lt;h3 id=&quot;여권과-운전면허증&quot;&gt;여권과 운전면허증&lt;/h3&gt;

&lt;p&gt;여권은 예전에 가지고 있던 것이 만료되어서 올해 초 홍콩 여행 전에 만들어뒀다. 그리고 여권을 만들 때 국제운전면허증 발급을 같이 신청할 수 있다. 발급 비용은 8천원이고 유효기간은 발급일로부터 1년이다. 한가지 주의할 점은 원래의 운전면허증을 국제운전면허증과 함께 가지고 다녀야만 그 효력을 인정받을 수 있다는 점이다.&lt;/p&gt;

&lt;h3 id=&quot;옷&quot;&gt;옷&lt;/h3&gt;

&lt;p&gt;사실 여행을 떠날 때 옷은 계륵과 같은 존재이다. 여행 중 입을 옷을 모두 챙겨가자니 무거운데다 부피를 많이 차지하고, 현지에서 구하자니 시간이 오래 걸릴것 같고. 가져가도 문제, 안 가져가도 문제다. 다행히 현지 날씨가 그렇게까지 춥지 않아서 부피가 크고 무거운 겨울옷을 챙겨갈 필요는 없었다. 여행 코스 중 최남단인 시드니의 기온은 13-20도 (습도는 20-30%), 최북단인 팜 코브의 기온은 23-31도 (습도는 60-70%) 정도로 온화한 편이었다. 한국의 봄, 여름 복장을 챙겨가면 적당하다고 판단했다. 그렇다고 해도 12일 분량을 모두 챙겨가긴 어려우니 랑데뷰 포인트인 팜 코브에서 빨래를 할 생각으로 6일 분량만 챙겨갔다.&lt;/p&gt;

&lt;h3 id=&quot;세면도구&quot;&gt;세면도구&lt;/h3&gt;

&lt;p&gt;현지에서 구입해도 괜찮지만 나는 &lt;a href=&quot;http://labs.suminb.com/s/coding-expedition&quot;&gt;원정코딩&lt;/a&gt;할 때 가져가는 세면도구 킷(kit)이 있기 때문에 그냥 그걸 그대로 들고 가기로 했다. 구성품은 칫솔, 치약, 세안제, 바디워시, 샴푸, 로션, 헤어왁스, 헤어스프레이, 전동면도기이다. 물론 모두 휴대용으로 제작된 소형 제품들이다.&lt;/p&gt;

&lt;h3 id=&quot;액션캠&quot;&gt;액션캠&lt;/h3&gt;

&lt;p&gt;케언스에서 스쿠버 다이빙을 할 계획이기 때문에 액션캠도 하나 챙겨가기로 했다. 다행히도 아버지가 &lt;a href=&quot;https://gopro.com/update/hero3&quot;&gt;고프로 액션캠&lt;/a&gt;을 하나 가지고 계셔서 그걸 빌려서 사용하기로 했다. 헤드 마운트, 체스트 마운트, 클립 마운트, 가방, 듀얼 충전기 등 다양한 주변기기들을 구매했다. 고프로를 반납하면서 같이 돌려드릴 생각이다.&lt;/p&gt;

&lt;h3 id=&quot;전자기기&quot;&gt;전자기기&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2016/roadtrip-to-australia/prep/wall-socket.jpg&quot; class=&quot;float-right&quot; style=&quot;width:200px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;호주의 가정용 전원은 110V @50Hz 이다. 전원 플러그는 이렇게 생겼다. 한국에서 가져갈 전자기기들을 사용할 수 있도록 &lt;a href=&quot;http://www.coupang.com/vp/products/2572702?itemId=11888551&amp;amp;itemsCount=36&amp;amp;rank=1&quot;&gt;여행용 전원 어댑터&lt;/a&gt;를 하나 구매했다. USB 포트가 있는 모델을 구입했더라면 더 좋았을 것 같다. 대부분의 전자기기들은 100-240V @50-60Hz 의 교류 전원에서 작동될 수 있도록 만들어져있기 때문에 플러그 모양만 바꿔주면 정상적으로 동작하지만, 간혹 50Hz 에서 동작하지 않는 기기가 있을 수도 있으니 미리 확인하고 가져가면 좋을 것 같다.&lt;/p&gt;

&lt;h2 id=&quot;도착-후-현지에서-챙길것들&quot;&gt;도착 후 현지에서 챙길것들&lt;/h2&gt;
&lt;p&gt;미리 준비해가면 좋은 것들도 있지만, 무게나 부피 때문에 가져가기 번거롭거나 현지에서 구하는게 더 편하고 좋은 것들도 있다. 선불 심카드, 생수와 물티슈 같은 생필품들은 미리 챙겨가지 않고 현지 공항과 마트에서 조달할 생각이다.&lt;/p&gt;

&lt;h2 id=&quot;두근두근&quot;&gt;두근두근&lt;/h2&gt;
&lt;p&gt;이렇게 해서 여행 준비가 모두 끝났다. 멀게만 느껴졌던 호주 여행 날짜가 눈 깜짝할 사이에 코앞으로 다가왔다. &lt;!-- 생각을 해보니 이번 여행 계획은 휴가를 매우 방만하게(?) 사용하는 여행이다. 금요일 오후에 출발하는 대신 토요일 오후에 출발하기 때문에 토요일 하루를 온전히 까먹기도 하고, 돌아올 때에는 목요일 아침 비행기라 하루종일 비행기 안에서 보낸다. --&gt; 내일의 즐거운 여행을 기약하며 잠자리에 들었다.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:gep&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;http://company.nexon.com/careers/hr_system/welfare/welfare.aspx&quot;&gt;넥슨 인사제도&lt;/a&gt; &lt;a href=&quot;#fnref:gep&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;올해 말에 진에어에서 직항 노선을 하나 편성한다고 한다. 관련 기사 &lt;a href=&quot;http://www.ajunews.com/view/20160609103350369&quot;&gt;[단독] 진에어, 국적사 최초 12월 ‘호주 케언즈’ 신규취항 나선다&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;주행거리가 이것의 2.5배쯤 되는 자동차 여행도 해봤는데, 이 글의 주제에서 벗어나기 때문에 다음에 기회가 있다면 따로 글을 써보는게 좋을것 같다. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;사실 나는 대여한 차량을 표현하는 말로 ‘rental car’라는 표현이 익숙하지만, &lt;a href=&quot;http://world.kbs.co.kr/korean/program/program_koreanlanguage_detail.htm?No=1737&quot;&gt;올바른 외래어 표기법이 ‘렌터카’&lt;/a&gt;이기 때문에 이 글에서는 ‘렌터카’로 통일한다. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 23 Sep 2016 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/roadtrip-in-australia-prep/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/roadtrip-in-australia-prep/</guid>
      </item>
    
      <item>
        <title>스포카 코드 난독화 컨테스트 2016</title>
        <description>&lt;p&gt;&lt;a href=&quot;http://www.spoqa.com&quot;&gt;스포카&lt;/a&gt;의 작년 코딩 대회 주제가 주어진 일을 가장 짧은 코드로 해내는 사람이 우승하는 ‘코드 골프’였다면 올해는 코드를 최대한 알아보기 어렵게 만드는 ‘난독화’이다. &lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2016-obfuscation-contest&quot;&gt;난독화 대회 페이지&lt;/a&gt;에도 나와있긴 하지만, 제출할 코드의 조건은 다음과 같다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;계산기 프로그램을 작성하세요. 프로그램은 아래의 조건들을 만족하여야 합니다.&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;프로그램 파일 명은 calc.py 로 설정합니다.&lt;/li&gt;
    &lt;li&gt;커맨드 라인의 인자를 통해 입력을 전달하고 계산 결과가 출력되어야 합니다.&lt;/li&gt;
    &lt;li&gt;사칙연산이 지원되어야 합니다.
      &lt;ul&gt;
        &lt;li&gt;연산자 우선순위는 일반적인 사칙연산의 규칙을 따릅니다. 곱셈과 나눗셈이 우선으로 평가되어야 하며, 같은 우선 순위 내의 연산은 순서대로 평가되어야 합니다.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;괄호로 연산의 우선순위를 지정할 수 있어야 합니다.
      &lt;ul&gt;
        &lt;li&gt;소수점 연산이 지원되어야 합니다. 나눗셈 결과가 떨어지지 않는 경우에는 결과가 실수로 출력되어야 합니다.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;소수점 아래가 있는 경우에는 최소 3자리 이상 출력되어야 합니다.&lt;/li&gt;
    &lt;li&gt;잘못된 식이 입력되면 0이 아닌 값이 반환값으로 설정되고 프로그램이 종료되어야 합니다. 에러 메시지를 출력하는 것은 자유입니다.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;채점 기준에 독특한 조항이 하나 보였다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;문제 자체는 제출을 위한 최소 조건이며, 얼마나 독창적인 방법으로 코드를 작성했는가가 주된 평가 기준입니다. 따라서 채점은 전적으로 평가자의 주관적인 판단에 근거합니다. 자세한 평가 기준은 수상작 발표 때 공개하도록 하겠습니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;코드 골프는 ‘가장 짧은 코드’라는 매우 객관적 기준이 있었지만 이번 대회는 조금 다르다. 그래서 나는 ‘예술성’에 사활을 걸어보기로 하였다.&lt;/p&gt;

&lt;h2 id=&quot;난독화-목표&quot;&gt;난독화 목표&lt;/h2&gt;
&lt;p&gt;어떤 일을 진행하든 목표를 정하고 진행하면 방향을 잃지 않고 가는데 큰 도움이 된다. 이번 일도 마찬가지로 몇가지 대략적인 목표를 정하고 진행하기로 했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;eval&lt;/code&gt; 및 &lt;code class=&quot;highlighter-rouge&quot;&gt;exec&lt;/code&gt; 함수를 직접적으로 사용하지 않기&lt;/li&gt;
  &lt;li&gt;모든 문자열 리터럴을 제거하기&lt;/li&gt;
  &lt;li&gt;코드에 최소한의 글로벌 함수만 노출시키기&lt;/li&gt;
  &lt;li&gt;코드에 최소한의 파이썬 키워드만 노출시키기&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://flake8.pycqa.org/en/latest/&quot;&gt;Flake8&lt;/a&gt; 테스트 통과 (그 어떤 상황에서도 멋을 잃지 말자!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;파이썬 표준 라이브러리만 사용해서 사칙연산 파서를 직접 작성하는 것이 그렇게
어려운 일은 아니지만, 그렇다고 몇분만에 뚝딱 만들어낼 정도로 단순한 문제 또한
아니다. 어느정도 시간을 들여서 해법을 고민하고 실제로 코딩해보고 디버깅 하면서
만들어 나가야 하는 성격의 것이다. 정직한
&lt;a href=&quot;https://github.com/yeonghoey&quot;&gt;@김영호&lt;/a&gt;님은 스택을 이용해서 사칙 연산 어휘
분석기(lexical analyzer)와 해석기(parser)를 직접 구현했지만, 나는 조금
양아치(?)같은 방식으로 해결하기로 했다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import subprocess
import sys


def parse(expr):
    stmt = 'print({})'.format(expr)
    p = subprocess.Popen(['python3', '-c', stmt],
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    r, _ = p.communicate()
    return r.decode('utf-8').strip()


if __name__ == '__main__':
    x = parse(sys.argv[1])
    try:
        float(x)
    except ValueError:
        sys.exit(1)
    else:
        print(x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그렇다. 입력받은 수식을 다른 파이썬 인터프리터에 넘겨서 결과값을 출력하도록 하는 프로그램이다. (…) 사실, &lt;code class=&quot;highlighter-rouge&quot;&gt;eval&lt;/code&gt; 나 &lt;code class=&quot;highlighter-rouge&quot;&gt;exec&lt;/code&gt; 함수를 이용하는것과 별 차이가 없긴 하지만, 그렇다고 대놓고 그렇게 하다간 &lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2015-codegolf/pull/35/files&quot;&gt;작년처럼 부정행위로 간주될까봐&lt;/a&gt; 조금은 걱정되기도 했고, 수식 파서보다는 난독화에 조금 더 집중해보고 싶은 마음이 컸다. 어쨌든 “계산기 프로그램을 작성하세요” 라고 했지, “사칙연산 파서를 직접 만드세요” 라는 말은 없었으니까 나는 결백해!&lt;/p&gt;

&lt;h3 id=&quot;동적-임포트import&quot;&gt;동적 임포트(import)&lt;/h3&gt;

&lt;p&gt;파이썬의 최대 장점이자 단점 중 하나는 실행 시간(run-time)에 모든 것을 바꿀 수 있다는 것이다. 패키지 임포트도 예외가 아니다.&lt;/p&gt;

&lt;p&gt;아래와 같은 임포트 명령어를&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import subprocess
import sys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;다음과 같이 &lt;code class=&quot;highlighter-rouge&quot;&gt;__import__&lt;/code&gt; 함수를 통해서 해결할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;subprocess, sys = map(__import__, ['subprocess', 'sys'])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;getattr-함수&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;getattr&lt;/code&gt; 함수&lt;/h3&gt;

&lt;p&gt;최소한의 글로벌 함수만 노출시키기 위해서 선택한 방법이 &lt;code class=&quot;highlighter-rouge&quot;&gt;getattr&lt;/code&gt; 함수를 이용하는 것이었다.&lt;/p&gt;

&lt;p&gt;다음과 같은 코드를&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;r, _ = p.communicate()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이렇게 바꿀 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;r, _ = getattr(p, 'communicate')()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;조금 더 복잡한 예제를 보여주자면,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;p = subprocess.Popen(['python3', '-c', stmt],
                     stdout=subprocess.PIPE)
r, _ = p.communicate()
return r.decode('utf-8').strip()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;위와 같은 코드는 다음과 같이 바꿀 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;popen = getattr(subprocess, 'Popen')
args = ['python3', '-c', getattr('print({})', 'format')(expr)]
r, _ = getattr(
    popen(args, stdout=getattr(subprocess, 'PIPE')),
    'communicate')()
x = getattr(getattr(result, 'decode')('utf-8'), 'strip')()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;키워드-인자를-딕셔너리로-변환&quot;&gt;키워드 인자를 딕셔너리로 변환&lt;/h4&gt;

&lt;p&gt;여기서 키워드 인자인 &lt;code class=&quot;highlighter-rouge&quot;&gt;stdout&lt;/code&gt;을 코드에 노출시키지 않으려려면 이 또한 문자열 리터럴로 바꾸어줄 필요가 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;popen(args, stdout=getattr(subprocess, 'PIPE'))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이 부분을 다음과 같이 바꿔준다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kwargs = {'stdout': getattr(subprocess, 'PIPE')}
popen(args, **kwargs)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;글로벌-빌트인&quot;&gt;글로벌 빌트인&lt;/h3&gt;

&lt;p&gt;글로벌 함수인 &lt;code class=&quot;highlighter-rouge&quot;&gt;float&lt;/code&gt;은 다음과 같이 호출할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;globals()['__builtins__'].float(x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;위의 &lt;code class=&quot;highlighter-rouge&quot;&gt;getattr&lt;/code&gt; 예제와 함께 응용하면,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;getattr(globals()['__builtins__'], 'float')(x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이렇게 하는 이유는 모든 함수 호출을 문자열 리터럴로 바꾸어서 최소한의 글로벌 함수만 노출시키고, 나중에 문자열 리터럴을 난독화 하기 위함이다.&lt;/p&gt;

&lt;h3 id=&quot;문자열-리터럴-제거&quot;&gt;문자열 리터럴 제거&lt;/h3&gt;

&lt;p&gt;문자열 리터럴을 제거하는 방법에는 여러가지가 있겠지만, 어떤 능력자가 &lt;a href=&quot;https://benkurtovic.com/2014/06/01/obfuscating-hello-world.html&quot;&gt;눈이 뱅글뱅글 돌아가는 난독화 코드&lt;/a&gt;를 만들어 놓은게 있어서, 이걸 참고하기로 했다.&lt;/p&gt;

&lt;p&gt;먼저, 다음과 같이 문자열을 정수로 인코딩 하는 함수를 만든다. 제출할 코드에는 포함시키지 않고, 별도의 파일에 넣었다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def encode_str(s):
    codes = [ord(c) for c in s]
    return sum(codes[i] * 256 ** i for i in range(len(codes)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그럼 &lt;code class=&quot;highlighter-rouge&quot;&gt;float&lt;/code&gt;이나 &lt;code class=&quot;highlighter-rouge&quot;&gt;__builtins__&lt;/code&gt; 같은 문자열들은 다음과 같이 정수로 인코딩할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; encode_str('float')
499850898534

&amp;gt;&amp;gt;&amp;gt; encode_str('__builtins__')
29516468994777367089179156319
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;지금에서야 든 생각인데, &lt;code class=&quot;highlighter-rouge&quot;&gt;__builtins__&lt;/code&gt;처럼 엄청나게 큰 정수 값으로 인코딩 되는 긴 문자열을 다음과 같이 잘게 쪼개서 인코딩 했다면 난독화 된 코드의 길이를 줄일 수 있지 않았을까 하는 생각도 든다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; encode_str('__buil'), encode_str('tins__')
(119200196747103, 104863563147636)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;조금 더 잘게 쪼갤 수도 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; encode_str('__bu'), encode_str('ilti'), encode_str('ns__')
(1969381215, 1769237609, 1600090990)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;하지만, 나는 너무 게을렀고 이렇게까지 하지는 않았다. 덕분에 최종 코드 길이가 조금(?) 길어지긴 했지만 큰 문제라고 생각하지는 않는다. 오히려 ‘난독화’라는 관점에서 보면 더 좋을 수도 있다.&lt;/p&gt;

&lt;p&gt;이렇게 인코딩 된 문자열을 다시 원래대로 되돌리는 함수가 필요하다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(lambda _, __, ___: _(_, __, ___))(
    lambda _, __, ___:
        chr(___ % __) + _(_, __, ___ // __) if ___ else
        chr(32)[1:],
    256,
    encoded)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;자세한 설명은 이 코드를 작성한 &lt;a href=&quot;https://benkurtovic.com/2014/06/01/obfuscating-hello-world.html&quot;&gt;Kurtovic씨의 포스트&lt;/a&gt;를 참고하도록 하자.&lt;/p&gt;

&lt;p&gt;한가지 설명을 덧붙이자면, 원본 코드에는 다음과 같은 부분이 있는데,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(lambda: _).func_code.co_lnotab,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;파이썬 2.7에서는 다음과 같이 빈 문자열 &lt;code class=&quot;highlighter-rouge&quot;&gt;''&lt;/code&gt;을 내어주지만,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; (lambda: _).func_code.co_lnotab
''
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;파이썬 3.5에서는 다음과 같이 빈 바이트 문자열 &lt;code class=&quot;highlighter-rouge&quot;&gt;b''&lt;/code&gt;을 반환한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; (lambda: _).__code__.co_lnotab                                                                                
b''
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이걸 다시 문자열로 변환하는 과정에서 최소 한 개 이상의 함수를 호출해야 하기 때문에 나는 빈 문자열을 다음과 같이 해결하기로 했다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; chr(32)[1:]
''
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;1을-제외한-모든-숫자-리터럴-제거&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;을 제외한 모든 숫자 리터럴 제거&lt;/h3&gt;

&lt;p&gt;문자열 리터럴을 제거하는데에서 그칠 수도 있었겠지만, 이왕 하는 김에 한단계 더 나아가보기로 했다. 컴퓨터에서는 모든 정보가 &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;과 &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;로 표현된다는 점에 착안해서 모든 숫자 리터럴을 &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;만 사용해서 표현하기로 했다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;사이에 끼어있는 &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;은 다음과 같이 묵시적으로 표현될 수 있고 (&lt;code class=&quot;highlighter-rouge&quot;&gt;0b1001&lt;/code&gt;),&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(1 &amp;lt;&amp;lt; 3) | 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;은 다음과 같이 표현될 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 &amp;gt;&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;을 제외한 다른 숫자 리터럴은 사용하지 않기로 했기 때문에 &lt;code class=&quot;highlighter-rouge&quot;&gt;1 &amp;lt;&amp;lt; 3&lt;/code&gt;은 다음과 같이 바꾸도록 한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;물론 다음과 같이 할 수도 있었겠지만, 나는 위의 표현이 조금 더 예술적(?)이라고 생각한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 &amp;lt;&amp;lt; (1 + 1 + 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제 기본 재료가 마련되었으니, 모든 정수를 위와 같이 표현할 수 있는 함수를 만들어보자. 먼저, 원하는 개수만큼 &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;을 쉬프트 해주는 함수를 만들었다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def gen_shift(n):
    return '({})'.format(' &amp;lt;&amp;lt; '.join(['1'] * (n + 1)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;하지만, Flake8 기준을 통과하기 위해서는 한줄당 80자 기준을 만족시켜야 하기 때문에 &lt;code class=&quot;highlighter-rouge&quot;&gt;n&lt;/code&gt;이 충분히 크다면 줄바꿈을 해주어야 한다. 쉬프트 연산을 13번 하면 표현식의 문자열 길이가 68자가 된다. 거기에 들여쓰기 4칸, 앞에 붙을 수도 있는 &lt;code class=&quot;highlighter-rouge&quot;&gt;+&lt;/code&gt; 연산자 등을 고려하여 쉬프트 연산을 13번 했다면 다음 줄로 넘어가도록 만들었다. 다소 지저분하지만, 최종 결과물을 깔끔하게 유지하기 위해 참고 넘어가기로 한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def gen_shift(n, threshold=13):
    buf = []
    while n &amp;gt; threshold:
        buf.append(' &amp;lt;&amp;lt; '.join(['1'] * (threshold + 1)))
        buf.append('\n &amp;lt;&amp;lt; ')
        n -= threshold + 1
    buf.append(' &amp;lt;&amp;lt; '.join(['1'] * (n + 1)))
    return '({})'.format(''.join(buf))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;단 한번만 쉬프트 한다면,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; gen_shift(1)
'(1 &amp;lt;&amp;lt; 1)'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;14번 쉬프트 한다면,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; gen_shift(14)
'(1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1\n &amp;lt;&amp;lt; 1)'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;결과적으로 &lt;code class=&quot;highlighter-rouge&quot;&gt;gen_shift(n)&lt;/code&gt;에서 생성된 표현식을 평가하면 &lt;script type=&quot;math/tex&quot;&gt;2^n&lt;/script&gt; 과 같아야 한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; [eval(gen_shift(i)) == 2 ** i for i in range(15)]
[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제 이것을 기반으로 정수를 쉬프트 연산식으로 인코딩 하는 함수를 만들 차례다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def encode_num(n, prefix='', suffix='\n'):
    buf, offset = [], 0
    while n &amp;gt; 0:
        if n &amp;amp; 1 != 0:
            buf.append(gen_shift(offset))
        n = n &amp;gt;&amp;gt; 1
        offset += 1
    return (suffix + prefix + '+ ').join(buf)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;결과를 살펴보자면, 정수 &lt;code class=&quot;highlighter-rouge&quot;&gt;5&lt;/code&gt;는 다음과 같이 변환된다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(encode_num(5))
(1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;마찬가지로 정수 &lt;code class=&quot;highlighter-rouge&quot;&gt;123&lt;/code&gt;은 다음과 같이 변환된다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(encode_num(123))
(1)
+ (1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제 예술의 경지에 점점 다가가는듯한 모습이다.&lt;/p&gt;

&lt;h3 id=&quot;putting-everything-together&quot;&gt;Putting Everything Together&lt;/h3&gt;

&lt;p&gt;최대한 줄이고 줄여서 단 세개의 글로벌 함수만 코드에 노출할 수 있었다. 더 줄일 수 있었으면 좋겠지만, 지금 단계에선 내 능력 밖의 일인 것 같다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_, __, C = getattr, globals, chr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그리고 글로벌 함수에 접근할 수 있는 함수를 만들었다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;T = lambda x: _(__()['__builtins__'], x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이렇게 해서 다음과 같이 글로벌 함수를 호출할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;T('float')('1.0')
T('map')(T('__import__'), ['subprocess', 'sys'])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;위에서 설명했던 문자열을 정수로 인코딩 하는 함수를 다음과 같이 정의했다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;B = 256
J, L = (lambda _, __, ___: _(_, __, ___)), \
    lambda _, __, ___: \
        C(___ % __) + _(_, __, ___ // __) if ___ else \
        C(1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)[1:]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그러면 다음과 같이 정수로 인코딩 된 문자열을 디코딩 할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; J(L, B, 1986490977)
'argv'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;지금 생각해보면 다음과 같이 정의해서 &lt;code class=&quot;highlighter-rouge&quot;&gt;F(499850898534)&lt;/code&gt;처럼 간편하게 쓸 수도 있었을텐데 하는 아쉬움이 남는다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;F = lambda _: (lambda _, __, ___: _(_, __, ___))(
    (lambda _, __, ___: \
        C(___ % __) + _(_, __, ___ // __) if ___ else \
        C(1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)[1:]), \
        1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1, _
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;어찌됐든 아래의 코드를&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;X, Y = T('map')(T('__import__'), ['subprocess', 'sys'])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;다음과 같이 바꿀 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;X, Y = T(
    J(L, B, 7364973),
)(T(
    J(L, B, 450385647451203110002527),
), [
    J(L, B, 545200826904043625543027),
    J(L, B, 7567731),
])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;한꺼번에 변환하기 쉽도록 코드 중간에 있는 정수 리터럴들을 한 곳에 모으자.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;I = [
    7364973,
    450385647451203110002527,
    545200826904043625543027,
    7567731,
]
X, Y = T(
    J(L, B, I[0]),
)(T(
    J(L, B, I[1]),
), [
    J(L, B, I[2]),
    J(L, B, I[3]),
])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제, 숫자 리터럴을 아름답게 변환해줄 차례이다. 모두 포함하기엔 코드가 너무 길어서 &lt;code class=&quot;highlighter-rouge&quot;&gt;7364973&lt;/code&gt;의 경우만 살펴보도록 하겠다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(encode_num(7364973))
(1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1
 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1
 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1
 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
+ (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1
 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그리고 배열의 인덱스도 같은 방식으로 변환해준다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;X, Y = T(
    J(L, B, I[1 &amp;gt;&amp;gt; 1]),
)(T(
    J(L, B, I[1]),
), [
    J(L, B, I[1 &amp;lt;&amp;lt; 1]),
    J(L, B, I[1 + (1 &amp;lt;&amp;lt; 1)]),
])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;just-fing-around&quot;&gt;Just F***ing Around&lt;/h3&gt;

&lt;p&gt;거의 다 끝났다. 여기서 멈추기엔 무언가 아쉬워서 조금만 더 이상한 짓을 해보기로 했다. 다음과 같이 간단한 표현식의 경우 오른쪽 공간이 많이 남는다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;J(L, B, I[1 &amp;gt;&amp;gt; 1])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;의미 없는 동작을 하는 코드로 빈 칸을 채워주자.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;J(L, B, I[1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt; &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1&lt;/code&gt; 처럼 왼쪽으로 한칸 쉬프트 한 후에 다시 오른쪽으로 쉬프트 하는 코드도 괜찮고 &lt;code class=&quot;highlighter-rouge&quot;&gt;* 1&lt;/code&gt; 처럼 항등원(identity element)을 곱해주거나, &lt;code class=&quot;highlighter-rouge&quot;&gt;// 1&lt;/code&gt; 처럼 정수형 나눗셈 연산도 좋다.&lt;/p&gt;

&lt;h2 id=&quot;tips--tricks&quot;&gt;Tips &amp;amp; Tricks&lt;/h2&gt;

&lt;p&gt;난독화 작업이라는 것이 아무래도 코드를 읽기 어렵게 만드는 과정이다보니 내 꾀에 내가 당하는 경우가 생길 수 있다. 그리고 위에서 설명한 난독화 과정을 거칠수록 코드를 읽는 난이도가 극악무도하게 올라가기 때문에 무언가를 바꿀때마다 기존의 기능들이 제대로 작동하는지 엄격하게 검증할 필요가 있다. 그래서 나는 코드를 변경할때마다 다음과 같은 명령어를 수행하면서 코드가 제대로 동작하는지 확인하고 넘어갔다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;flake8 calc.py &amp;amp;&amp;amp; python3 test.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;맺음말&quot;&gt;맺음말&lt;/h2&gt;
&lt;p&gt;그렇게 해서 &lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2016-obfuscation-contest/pull/10/files&quot;&gt;사람의 눈과 마음으로는 도저히 이해할 수도, 고칠 수도 없는 코드&lt;/a&gt;가 탄생했다. 일부만 발췌해보자면,&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;T(J(L, B, I[(1 &amp;lt;&amp;lt; 1) + (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 * 1 * 1)]))(
    T(J(L, B, I[(1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1) + (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)]))(
        _(_(R, J(L, B, I[(1 &amp;lt;&amp;lt; 1) + (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1) + (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 // 1)]))(
            J(L, B, I[(1) + (1 &amp;lt;&amp;lt; 1) + (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1) + (1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1)])),
            J(L, B, I[(1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 &amp;lt;&amp;lt; 1 &amp;gt;&amp;gt; 1 // 1)]))()))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;얼마나 아름다운가(!)&lt;/p&gt;

&lt;p&gt;어떻게 하면 더 좋은 코드를 만들까 하는 고민은 많이 하지만, 어떻게 하면 더 ㅂㅅ같은 코드를 만들까 하는 고민은 평소에 거의 해볼 기회가 없다. 이번 대회를 통해 매우 색다른 즐거움을 향유할 수 있는 기회를 제공해주신 스포카 여러분들께 감사드린다.&lt;/p&gt;

&lt;p&gt;그리고 다른 사람들이 작성한 흥미로운 코드도 있으니 한번 살펴보는걸 추천드린다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2016-obfuscation-contest/pull/16/files&quot;&gt;나는 예술 점수만 노린다&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2016-obfuscation-contest/pull/14/files&quot;&gt;누가 코드에 똥을 쌌어&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2016-obfuscation-contest/pull/13/files&quot;&gt;ㄱㅈㄱㅎㄲㅎㄷ&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2016-obfuscation-contest/pull/8/files&quot;&gt;정직한 프로그래머의 파서&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/spoqa/spoqa-pycon-2016-obfuscation-contest/pulls&quot;&gt;더 보기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Mon, 15 Aug 2016 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/spoqa-pycon-2016-obfuscation-contest/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/spoqa-pycon-2016-obfuscation-contest/</guid>
      </item>
    
      <item>
        <title>8퍼센트 투자 후기</title>
        <description>&lt;p&gt;오랫동안 묵혀두었던 글을 이제서야 다시 꺼내서 쓴다. 글을 최초로 작성했던 시점과
비교하여 &lt;a href=&quot;http://8percent.kr&quot;&gt;8퍼센트&lt;/a&gt;의 서비스가 달라진 부분이 많아서 꽤 많은
부분을 고쳐야 했다.&lt;/p&gt;

&lt;p&gt;나는 2015년 4월 8일부터 8퍼센트 채권에 투자하기 시작했다. 금액이 크지 않아서 큰
의미가 없을 수도 있겠지만, 이율이나 상환 조건 등이 상이한 35개의 채권에
투자하면서 느꼈던 점을 글로 옮겨보고자 한다.&lt;/p&gt;

&lt;h2 id=&quot;8퍼센트&quot;&gt;8퍼센트?&lt;/h2&gt;
&lt;p&gt;8퍼센트는 쉽게 얘기해서 사람들끼리 돈을 빌리고 빌려줄 수 있는 서비스이다. 돈이
필요한 사람은 일정한 서류를 갖추어서 대출 신청을 하고, 심사를 통과 하면 8퍼센트
웹사이트에 해당 대출건이 하나의 ‘채권’으로 공시된다. 돈을 빌려가는 채무자는
개인일수도 있고, 사업자일수도 있다. 사업자의 경우 &lt;a href=&quot;http://www.socar.kr&quot;&gt;쏘카&lt;/a&gt;와
같이 어느정도 규모가 있는 업체인 경우도 있지만, 대부분의 경우엔 맥주집, 치킨집
같은 소규모 자영업자이다. 채권이 공시 되면 여러명의 투자자들이 소액을 출자하여
해당 채권을 매입하는 형태이다. 대출이 실행 되면 약정된 날짜와 이자율에 따라
매달 원리금이 상환된다.&lt;/p&gt;

&lt;div class=&quot;image-caption&quot;&gt;
  &lt;img src=&quot;/attachments/2016/8percent/workflow.png&quot; alt=&quot;8퍼센트 대출 실행 과정&quot; /&gt;
  &lt;div&gt;8퍼센트 웹사이트에서 갈무리 한 이미지&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;예전부터 가지고 있던 의문 중 하나가 “왜 한국에는 신용 대출 금리의 중간지대가
없을까”” 였다. 제 1금융권이라 일컫는 은행권은 연 3-4% 정도의 비교적 낮은 이율로
신용 대출을 해주는 반면 심사 기준이 매우 까다롭다. 의사, 변호사와 같은 공인
자격증의 보호를 받는 전문직이나 정규직 봉급생활자를 제외하고는 사실상 은행 돈을
빌리기 어렵다. 은행권에서 대출을 받지 못하면 그 다음으로 가는 곳이 제
2금융권인데, 이곳은 연 금리가 15-20% 수준이다. 대부업은 30% 가 넘는다. 소득
수준과 형태, 위험 등급을 고려했을 때 분명 그 중간층에 해당하는 사람들이
있을텐데 이들을 위한 금융 서비스가 없는 점은 매우 기이해보였다. 8퍼센트가 이
중간지대를 메꾸어주는 서비스이다.&lt;/p&gt;

&lt;h3 id=&quot;대출금-상환&quot;&gt;대출금 상환&lt;/h3&gt;

&lt;p&gt;8퍼센트에 올라오는 채권들을 보면 만기일시상환이나 일정기간 예치 후
원리금균등상환 같은 조건들도 있지만, 대부분은 원리금균등상환 조건이다.
원리금균등상환은 쉽게 얘기해서 매달 상환하는 금액이 동일하다는 의미이다. 매달
균일한 원금과 그에 상응하는 이자를 상환하는 원금균등상환 방식과는 다르게 매달
상환금이 일정해야 하므로 초기에는 상환금에서 이자가 차지하는 비중이 높았다가
시간이 흐를수록 이자가 차지하는 비중이 적어진다는 특징이 있다. 원금균등상환
방식에 비해서 초기에는 월상환금이 조금 더 적지만, 최종적으로 지불하게 되는 이자
비용의 총 합은 더 크다.&lt;sup id=&quot;fnref:loan_calc&quot;&gt;&lt;a href=&quot;#fn:loan_calc&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; 아래는 8퍼센트 채권 중 하나를 예로 든
것인데, 매달 상환금이 8,838원으로 동일하지만, 상환금에서 이자가 차지하는 비중이
점점 줄어들면서 원금이 차지하는 비중이 늘어나는 것을 볼 수 있다. 실제 입금액이
이와 다른 것은 세금 때문이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2016/8percent/payments.png&quot; alt=&quot;8퍼센트 상환&quot; /&gt;&lt;/p&gt;

&lt;p&gt;그리고 만기일시상환 조건이 아닌 이상 이자 금액의 총합이 공시된 이자율보다
적어보일 수 있다. 예를 들어서, 100,000원을 연 10%로 12개월동안 원리금균등상환
방식으로 빌려주었을 때 최종적으로 받게 되는 세전 이자 금액은 10,000원보다
적을수밖에 없다. 이것은 원금을 12개월동안 빌려준 것이 아니라, 원금의 약 1/12을
12개월간, 원금의 약 1/12을 11개월간, …, 원금의 약 1/12을 1개월간 빌려주었기
때문이다.&lt;/p&gt;

&lt;h3 id=&quot;이자소득세&quot;&gt;이자소득세&lt;/h3&gt;

&lt;p&gt;8퍼센트에서 발생하는 이자소득은 비영업대금에 대한 이자소득으로 간주되어 25%에
주민세 2.5%를 가산한 27.5%의 세율이 적용된다. 일반적인 금융상품의 이자소득세가
15.4%인 것에 비하면 꽤 높은 편이다. 예를 들어서, 8.66%의 명목 금리를 제공하는
채권이 있다면, 세후에 내가 실제로 받게 될 이율은 6.28%인 셈이다. 세무당국에 내
돈을 강탈당한 느낌을 지울 수 없지만, 어쨌든 은행 예금보다는 훨씬 높은 금리를
제공한다.&lt;/p&gt;

&lt;h3 id=&quot;예치금-계좌&quot;&gt;예치금 계좌&lt;/h3&gt;

&lt;p&gt;과거에는 내 명의의 계좌를 통해 직접 거래를 했었지만, 올해부터 8퍼센트에서
제공하는 가상계좌를 통해서 거래하는 것이 의무화 되었다. 이것을 ‘예치금
계좌’라고 하는데, ‘8퍼센트홍길동’과 같이 회원의 이름이 붙은 가상계좌이다. 이
예치금 계좌에 입금을 해야 투자를 할 수 있고 상환금도 이 예치금 계좌로 입금된다.
예전에는 수동으로 상환금을 고객의 계좌에 넣어줬었는데&lt;sup id=&quot;fnref:manual_deposit&quot;&gt;&lt;a href=&quot;#fn:manual_deposit&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, 이
부분을 자동화 하기 위한 조치가 아니었을까 하는 생각이 든다. 또한, 8퍼센트
입장에서는 고객 명의가 아닌 8퍼센트 법인 명의로 된 계좌에 하루라도 돈을 더
가지고 있을수록 소정의 이자 소득을 노려볼 수 있지 않을까 한다. 물론 이 부분은
순전히 나의 추측이므로 타당성에 대한 판단은 독자에게 맡기겠다.&lt;/p&gt;

&lt;h3 id=&quot;원금-보장-여부&quot;&gt;원금 보장 여부&lt;/h3&gt;

&lt;p&gt;원금은 보장되지 않는다. 내가 보기엔 8퍼센트에서 이러한 위험 요소를 적극적으로
안내하고 있기 때문에 큰 문제가 될 것 같지는 않다. 또한, 개별 채권에 투자금
상한선을 설정해서 분산 투자를 유도하고 있고, 안심펀드라는 제도를 통해 투자자를
보호하기 위해 노력하고 있는 점은 높이 사줄만 하다. 각 채권들의 위험 요인들간
상관계수가 낮다면 분산 투자가 위험을 낮추는데 효과가 있을 것으로 기대된다.&lt;/p&gt;

&lt;h3 id=&quot;안심펀드&quot;&gt;안심펀드&lt;/h3&gt;

&lt;p&gt;안심펀드는 3천만원 이하 채권에 한해 손실이 날 경우 투자 원금의 50%까지 보호받을
수 있는 제도이다.&lt;sup id=&quot;fnref:insurance&quot;&gt;&lt;a href=&quot;#fn:insurance&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; 첫번째 상환금에서 ‘안심료’라는 이름으로 소정의
수수료를 차감하는데, 이를 모아두었다가 부도가 발생했을 때 일정 금액을
투자자들에게 돌려주는 방식으로 운영되는 것으로 추정된다. 한가지 주의해야 할
점은 원금의 50%를 지급 보장하는 상품이 아니라, 원금의 최대 50%까지만 보호해주는
제도라는 점이다. 아래 그림과 같이 원금 일부가 상환된 상황에서 부도가 발생한다면
원금의 50%인 500,000원을 투자자에게 지급하는 것이 아니라, &lt;code class=&quot;highlighter-rouge&quot;&gt;(원금의 50%) -
(기상환 원리금)&lt;/code&gt; 만큼의 금액을 지급한다는 말이다. (기상환 원리금이 원금의 50%
이상일 경우엔 안심펀드가 아무 소용이 없는건가…?)&lt;/p&gt;

&lt;div class=&quot;image-caption&quot;&gt;
  &lt;img src=&quot;/attachments/2016/8percent/insurance.png&quot; alt=&quot;안심펀드&quot; /&gt;
  &lt;div&gt;&lt;a href=&quot;https://8percent.kr/safe_fund/&quot;&gt;8퍼센트 웹사이트&lt;/a&gt;에서 갈무리 한 이미지&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;또한, 보호금은 안심펀드 총 잔액의 50% 이내에서 지급된다는 조항이 있는데, 그런
일이 발생할 확률은 굉장히 작겠지만 안심펀드 기금이 바닥을 드러낸다면 안심펀드가
채권 부도 발생시 완충작용을 해주지 못할 가능성도 있다.&lt;/p&gt;

&lt;p&gt;한가지 아쉬운 점은 안심펀드가 선택이 아닌 의무라는 점이다. 충분히 분산 투자를
하고 있기 때문에 안심펀드가 불필요하다고 느끼는 투자자도 있을 것이고,
무엇보다도 안심펀드의 복잡한 규칙이 내가 미래에 만들지도 모르는 8퍼센트 채권
포트폴리오 분석용 시뮬레이션 제작에 걸림돌이 될 것이 분명하다.&lt;/p&gt;

&lt;!--
어떤 채권은 안심펀드 적용, 어떤 채권은 미적용. 어떤 기준으로 이것이 결정되는지 잘 모르겠다.
--&gt;

&lt;h3 id=&quot;채무자가-돈을-갚지-않을-경우&quot;&gt;채무자가 돈을 갚지 않을 경우&lt;/h3&gt;

&lt;p&gt;처음 2개월동안은 8퍼센트 자체적으로 채권추심을 진행하고, 그 다음에는 외부
채권추심업체에 의뢰하여 추심업무를 이행하게 된다. 6개월 이상 연체될 경우에는
미회수 금액이 대손처리 되어 원금 손실이 발생할 수 있다.&lt;sup id=&quot;fnref:FAQ&quot;&gt;&lt;a href=&quot;#fn:FAQ&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;채권-위험도-평가&quot;&gt;채권 위험도 평가&lt;/h2&gt;

&lt;p&gt;그렇다면 얼굴도 모르는 이들을 어떻게 믿고 돈을 빌려줄 수 있을까.&lt;/p&gt;

&lt;p&gt;데이터가 충분히 쌓여있어서 8퍼센트에서 제시한 부도 확률과 실제 부도 확률이
얼마나 차이가 나는지 검증해볼 수 있고 정말 좋겠지만, 아쉽게도 그렇게 하기엔
아직 데이터가 부족해보인다. 나중에 데이터가 충분히 쌓여서 8퍼센트의 예측불량률
모델이 비교적 정확하다는 확신이 생기면, 그것을 토대로 여러가지 채권에 분산 투자
했을 때 연체와 부도를 고려한 최종 기대 수익이 어느정도 되는지 시뮬레이션을 해볼
수도 있을 것 같다. 어찌되었든 채권의 위험도를 판단할 때 일정 부분은 8퍼센트를
믿고, 다른 부분은 연역적 추리에 의존하기로 했다.&lt;/p&gt;

&lt;p&gt;내가 주의 깊게 보는 부분은 소상공인의 경우 월매출이고, 개인의 경우 월소득 대비
부채 상환액, 소득 대비 부채 비율(LTI)과 월가처분소득이다. 빌려간 돈을 갚는 일이
현실적으로 가능한지의 여부를 살피는 것이다. 특히 월매출과 월소득 대비 부채
상환액은 주의해서 살표보아야 할 유동성 지표이다. 물론 월상환액이
월가처분소득보다 높은 경우 8퍼센트 심사팀에서 애초에 대출 승인을 해주지
않겠지만, 한번쯤 채무자의 입장이 되어서 생각해보는 것이다. 예를 들어서,
월소득이 200만원인데 생활비로 100만원, 기존 대출 상환금으로 83만원을 내고 있는
사람이 있다면 이 사람한테 실직이나 질병 등 예기치 못한 일이 생겼을 때 빌려간
돈을 제대로 갚을 수 있을까 하는 것을 한번쯤은 고민해볼만 하다.&lt;/p&gt;

&lt;p&gt;금액 뿐만 아니라 돈을 빌려가는 목적도 고려한다. 실례를 하나 들자면, 연 소득이
2,160만원이고 현재 직장에서 근무 기간이 0.5년인데 중고차를 구입하고자 600만원
대출 신청을 한 사람이 있었다. 나는 해당 채권에 투자하지 않았다. 수치상으로 봤을
때 그 사람이 자동차를 구입하고 유지하는 것은 경제적으로 올바르지 않은 선택이기
때문이다. 미국의 중소도시나 강원도쯤 되면 몰라도 한국의 경우 대부분의 인구가
대중교통이 잘 갖추어진 인구밀집지역에 거주하고 있기 때문에, 그정도 소득
수준이라면 자동차를 구입하지 않는 것이 올바른 선택이라고 생각한다. 물론 업무상
필요에 의해서 구입하는 경우일수도 있었겠지만, 그것은 공개된 정보만 가지고는
판단할 수 없었다. 그리고 꼭 필요해서 구입하는 경우라고 해도 소득이 낮으면
상환금, 보험료, 연료비, 수리비를 감당하느라 허리가 휜다. 이건 경험담이다.
8퍼센트에서 채권을 공시할 때 도시 단위 정도로 채무자의 대략적인 거주 지역을
알려준다면 채권의 위험도를 평가하는데 도움이 될 것 같다는 생각이 든다.&lt;/p&gt;

&lt;p&gt;대출 목적이 ‘타기관 대출 상환’이라면 기존의 금융기관에서 채무자가 해당 금액을
상환할 수 있는 능력이 있는 것으로 판단을 한 경우이기 때문에&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;, 최초 대출
시점으로부터 8퍼센트 대출 신청 시점까지 채무자의 경제 상황에 큰 변화가 없었다면
상환 능력에 대한 걱정이 상당 부분 해소 될 것이라 생각한다. 이 부분은 대출
신청자의 현재 직장 근속 년수로 어느정도 파악이 가능하지만, 기존 대출 내역을
대출 건별로 상세한 내역을 보여주는 것이 아니라 금융권별 총 대출 금액만 보여주기
때문에 신청자의 채무 상황을 제대로 파악하기 어렵다는 아쉬움이 있다.&lt;/p&gt;

&lt;p&gt;하지만 개인 대출의 경우 채무자의 신원을 알 수 있는 어떠한 정보도 제공되지 않기
때문에 소득이나 대출 목적 등의 정보는 전적으로 8퍼센트 심사팀의 역량을 신뢰해야
한다. 대출 신청자의 직장, 업종, 근무 형태, 근무 기간, 연소득 등은 대출 신청을
할 때 제출한 서류를 기초로 공시하는 것으로 추정되고, 연체 이력이나 신용 점수
등은 기존의 신용 평가 기관의 기록에 따른 것으로 보인다. 소득이나 기존 대출
상황은 서류상으로 대부분 확인이 가능하지만, 8퍼센트 측에서 대출 목적의 진위
여부를 확인하는 장치를 마련하고 있는지는 잘 모르겠다. 예를 들면 타 기관 대출
상환 목적으로 8퍼센트에서 돈을 빌려놓고 기존의 대출을 상환하지 않는 부정행위를
방지할 수 있는 절차나 제도 같은 것 말이다.&lt;/p&gt;

&lt;p&gt;투자를 시작한지 10개월이 지난 지금, 아직까지 내가 투자했던 채권에서는 연체나
부도가 발생하지 않았다. 상환이 완료된 채권도 다섯개나 된다. 8퍼센트 전체적으로
봤을 때에는 부도는 아직 발생하지 않았고, 연체는 총 8건이 발생했다.&lt;sup id=&quot;fnref:statistics&quot;&gt;&lt;a href=&quot;#fn:statistics&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h3 id=&quot;어디까지-믿을-수-있을까&quot;&gt;어디까지 믿을 수 있을까&lt;/h3&gt;

&lt;p&gt;영화 &lt;a href=&quot;http://www.imdb.com/title/tt1596363/&quot;&gt;The Big Short&lt;/a&gt;를 보면 헤지 펀드 매니저인 마크 바움(Mark Baum)이
주택담보대출 중개인과 실제 주택 소유자들을 만나러 다니면서 주택담보대출 심사가
얼마나 엉망으로 이루어지는지 깨닫는 장면, 그리고 라스베가스에서 열린 금융
포럼(American Securitization Forum)에서 부채담보부증권(collateralized debt
obligation; CDO) 매니저와 대화를 나누면서 CDO와 그 파생상품이 얼마나 심각한
시한폭탄인지 깨닫는 장면이 나온다. 사무실에 앉아서 숫자만 들여다보고는 파악하기
어려운 것들이었다.&lt;/p&gt;

&lt;p&gt;지금까지 숫자로 본 8퍼센트는 괜찮았다. 하지만, 본격적으로 투자 규모를
늘려나가기 전에 8퍼센트에서 직접 대출을 받아보아서 어떤 방식으로 심사가
이루어지는지, 8퍼센트 심사팀에서 대출 신청자가 제공한 정보를 제대로 검증 하는지
살펴볼 필요가 있다는 생각이 든다. 실사(實査)는 중요하다.&lt;/p&gt;

&lt;div class=&quot;image-caption&quot;&gt;
  &lt;img src=&quot;/attachments/2016/8percent/the-big-short.jpg&quot; alt=&quot;The Big Short&quot; /&gt;
  &lt;div&gt;
    &lt;a href=&quot;https://nerdgeist.com/2016/01/27/the-big-short-2015-film-review/&quot;&gt;
      The Big Short (2015) Film Review by NERDGEISTGUESTWRITERDONAL
    &lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;앞으로의-계획&quot;&gt;앞으로의 계획&lt;/h3&gt;

&lt;p&gt;일단 8퍼센트에서 제시한 예측불량률이 정확하다는 충분한 확신이 생기면,
예측불량률과 기타 수치화 가능한 지표들을 토대로 적절한 분산투자 수익률 분석
모델을 만들어볼 계획이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/attachments/2016/8percent/default-rate.png&quot; alt=&quot;8퍼센트 예측불량률&quot; /&gt;&lt;/p&gt;

&lt;p&gt;투자하는 채권의 개수가 많아지면 지금처럼 사람들이 어떤 목적으로 돈을
빌려가는지, 상환이 어려워질만한 요인은 없는지 하나하나 수동으로 분석하는 일은
불가능해진다. 개별 채권의 위험을 신뢰도 있게 분석하는 모델이 필요하며, 보유한
채권의 일부가 부도 나더라도 기대 수익률을 맞출 수 있도록 운영되어야 한다.&lt;/p&gt;

&lt;!--
어니스트 펀드의 [포트폴리오 채권 투자
상품](https://www.honest-fund.com/products/portfolio/3)처럼 여러가지 채권을
묶어서 제공해도 좋을 것 같다.
--&gt;

&lt;h2 id=&quot;아쉬운-점&quot;&gt;아쉬운 점&lt;/h2&gt;

&lt;p&gt;8퍼센트가 매력적인 투자처임에는 분명하지만 아직까지는 부족한 점이 여럿 보인다.&lt;/p&gt;

&lt;p&gt;예금, 주식, 채권, 펀드, 외화 등 다른 금융자산과는 다르게 환매가 어렵다는 단점이
있다. 예전에는 환매가 원천적으로 불가능했지만, 최근에
&lt;a href=&quot;http://sbx.or.kr/&quot;&gt;서울채권거래소&lt;/a&gt;라는 서비스가 생겨서 이 문제는 점진적으로
해소가 될 것으로 보인다. 하지만 아직까지는 매물이 많지 않은 것으로 보아 정기
예금이나 펀드처럼 원하는 시점에 자유롭게 환매하는 것은 어려울 것으로 예상된다.&lt;/p&gt;

&lt;p&gt;또한, 예금이나 주식과는 다르게 8퍼센트 채권은 담보로 인정받을 수 없다는
문제점도 있다. 예를 들어서, 대부분의 돈을 예금 계좌에 넣어 두었는데 급하게
목돈이 필요한 경우 예금을 담보로 잡고&lt;sup id=&quot;fnref:pledge&quot;&gt;&lt;a href=&quot;#fn:pledge&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; 일정한 이자를
지불하면&lt;sup id=&quot;fnref:mortgage&quot;&gt;&lt;a href=&quot;#fn:mortgage&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; 예금액의 95%까지 대출을 받을 수 있다. 주식은 95%까지는
아니더라도 보유하고 있는 주식 종목의 전일 종가 등 일정한 기준의 50-70%정도를
대출 받을 수 있다. 8퍼센트 채권은 내가 알고 있는 선에서는 아직까지 담보 대출이
되는 금융 상품이 없다.&lt;/p&gt;

&lt;p&gt;이러한 이유로 8퍼센트에 투자할 때에는 &lt;strong&gt;진짜 여유 자금&lt;/strong&gt;을 가지고 투자해야
한다. 그래야 유동성에 문제가 생기지 않는다.&lt;/p&gt;

&lt;p&gt;금융 서비스로서의 특징 이외에 기술적인 면에서도 아쉬운 점이 몇 있는데, 그 중
하나는 API를 제공하지 않는다는 점이다. 8퍼센트의 벤치마크 모델이라고 볼 수 있는
&lt;a href=&quot;https://www.lendingclub.com&quot;&gt;렌딩클럽&lt;/a&gt;&lt;sup id=&quot;fnref:ppss&quot;&gt;&lt;a href=&quot;#fn:ppss&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;의 경우 &lt;a href=&quot;https://www.lendingclub.com/developers/lc-api.action&quot;&gt;API 를
제공&lt;/a&gt;하여 사용자들이
렌딩클럽의 서비스를 무한대로 확장할 수 있는 가능성을 열어두었다. API가
제공된다면 나의 투자 성향에 맞는 채권을 자동으로 선별해서 예치금 계좌에 있는
잔고만큼 알아서 투자하는 프로그램을 만들 수도 있을 것이고, 이것을 확장하여 다른
사람들도 사용할 수 있도록 서비스화 하는 것도 가능할 것 같다. 8퍼센트의 경우
내가 작년에 API를 제공해달라고 이메일을 보냈을 때 그러한 계획이 없다는 답변을
받았었고 아직까지도 별다른 변화가 없는 것으로 보아 이쪽에는 크게 신경을 쓰지
않는 것 같다.&lt;/p&gt;

&lt;p&gt;API 를 통한 다양한 상호작용까지는 아니더라도, 나의 투자 내역이라던가 예치금
거래 내역 등 데이터라도 받아올 수 있는 방법이 있었으면 좋겠는데 그러한 것도
보이지 않는다. 데이터를 CSV (혹은 다른 적당한 형식)으로 내려받을 수 있도록
해달라고 요청해볼 생각이다. 이것마저 안 된다면 투자 규모가 커짐에 따라 관리
비용이 너무 커져서 일정 금액 이상은 투자하기 어려울 것 같다. 장부를 전부
수동으로 작성할 수는 없다.&lt;/p&gt;

&lt;p&gt;몇달 전에는 채권 공시 시간인 오후 1시만 되면 사람들이 구름같이 몰려와서
웹사이트가 먹통이 되는 현상이 종종 발생했었는데, 이 글을 쓰는 지금(2016년
2월)은 그러한 문제들이 대부분 해결된 듯 보인다.&lt;/p&gt;

&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;

&lt;p&gt;예금과 적금 금리가 물가상승률보다 낮아지면서&lt;sup&gt;[citation needed]&lt;/sup&gt;
원금보장이라는 말은 한낱 공염불에 불과한 말이 되어버렸다. 이제는 여유자금을
조금 더 적극적으로 운용하지 않으면 가만히 앉아서 힘들게 번 돈의 가치를 까먹는
세상이 된 것이다. 이런 상황에서 물가상승률을 한참 상회하는 금리를 제공하는
8퍼센트는 주식이나 펀드처럼 매력적인 투자처가 될 가능성이 충분하다고 생각한다.
하지만 앞서 언급했던 단점들 때문에 개인적으로 8퍼센트를 주된 투자 수단으로 삼는
것은 당분간 보류하고 현수준의 투자 규모를 유지할 생각이다.&lt;sup id=&quot;fnref:ceiling&quot;&gt;&lt;a href=&quot;#fn:ceiling&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;8퍼센트는 짧은 기간동안 놀라운 속도로 성장했다. 2014년 11월에 창업하여&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;
1년이 조금 넘는 기간동안 운영하면서 오늘(2016년 2월 2일) 기준으로 총
12,970,090,000원의 대출이 이루어졌다. 굉장한 성과다. 하지만 길에서 아무나
붙잡고 8퍼센트를 아는지 물어보면 아마도 대부분의 사람은 모른다고 대답을 할
것이다. 8퍼센트라는 단일 서비스 뿐만 아니라 P2P 금융이라는 개념 자체가 아직은
찻잔 속의 태풍 같은 느낌이지만, P2P 금융 시장이 계속 성장해서 더욱 재미있고
다양한 서비스들이 나왔으면 하는 바람이 있다.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:loan_calc&quot;&gt;
      &lt;p&gt;무슨말인지 잘 모르겠다면 &lt;a href=&quot;http://best79.com/loan/&quot;&gt;대출이자 계산기&lt;/a&gt;를 이용해서 직접 계산해보고 비교해보자. &lt;a href=&quot;#fnref:loan_calc&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:manual_deposit&quot;&gt;
      &lt;p&gt;예전에는 은행 거래 내역에 ‘타행PC’로 나왔었는데, 이것으로 추측하건대 상환금 입금을 수동으로 처리했던 것으로 보인다. &lt;a href=&quot;#fnref:manual_deposit&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:insurance&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://8percent.kr/safe_fund/&quot;&gt;https://8percent.kr/safe_fund/&lt;/a&gt; &lt;a href=&quot;#fnref:insurance&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:FAQ&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://8percent.kr/board/faq/&quot;&gt;https://8percent.kr/board/faq/&lt;/a&gt; &lt;a href=&quot;#fnref:FAQ&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;하지만 미국의 서브프라임 모기지론 사태 같은 것을 보면 모든 금융기관이 일을 제대로 하는 것은 아니라는 생각이 든다. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:statistics&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://8percent.kr/info/introduction/statistics/&quot;&gt;https://8percent.kr/info/introduction/statistics/&lt;/a&gt; &lt;a href=&quot;#fnref:statistics&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:pledge&quot;&gt;
      &lt;p&gt;담보로 제공한 예금 계좌에 대하여 은행이 질권 설정을 한다. &lt;a href=&quot;#fnref:pledge&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:mortgage&quot;&gt;
      &lt;p&gt;신한은행의 경우 예금담보대출 금리는 예금 금리에 1.25%P 가 더해진 이율이다. 예를 들어서 예금 금리가 2.00%라면 해당 예금을 담보로 한 대출 금리는 3.25%가 된다. &lt;a href=&quot;#fnref:mortgage&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ppss&quot;&gt;
      &lt;p&gt;리, 승환. “&lt;a href=&quot;http://ppss.kr/archives/56683&quot;&gt;‘8퍼센트’ 이효진 대표 인터뷰.&lt;/a&gt;” ㅍㅍㅅㅅ. N.p., 17 Sept. 2015. Web. 02 Feb. 2016. &lt;a href=&quot;#fnref:ppss&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ceiling&quot;&gt;
      &lt;p&gt;추가적으로, 채권은 주식과 다르게 수익률 면에서 천장은 있지만 바닥은 없는 금융 상품이라 채권 비중을 높이는 것이 올바른 선택인지도 잘 모르겠다. &lt;a href=&quot;#fnref:ceiling&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;최, 영진. “&lt;a href=&quot;https://jmagazine.joins.com/forbes/view/307981&quot;&gt;P2P 대출서비스 선보인 이효진 8퍼센트 대표.&lt;/a&gt;” 중앙시사매거진. 포브스코리아, 13 Aug. 2015. Web. 2 Feb. 2016. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 02 Feb 2016 00:00:00 +0900</pubDate>
        <link>http://philosophical.one/posts/8percent/</link>
        <guid isPermaLink="true">http://philosophical.one/posts/8percent/</guid>
      </item>
    
  </channel>
</rss>
