devcken.io

Thoughts, stories and ideas.

My advice on studying algorithms

이 글은 Buck Shlegeris의 My advice on studying algorithms을 번역한 글입니다. 심한 오역이나 어색한 부분이 있을 수도 있습니다.

Buck Shlegeris

2016년 8월 14일

소프트웨어 엔지니어링 인터뷰에서는 종종 화이트보드 알고리즘 문제가 나오곤 합니다. 이 글은 그러한 알고리즘 문제를 공부하는 방법에 대한 저의 충고입니다(이 주제에 대해 제가 자격이 있다는 것을 말씀드리고 싶습니다. 저는 Google과 Apple을 포함한 많은 화이트보드 인터뷰에 통과했습니다. 프로그래머들이 이러한 알고리즘 인터뷰를 준비하도록 하는 것이 제 일의 일부입니다. 저는 매우 넓고 다양한 배경지식을 가지고 프로그래머들에 대한 200회 이상의 기술 면접을 진행해왔습니다).

Triplebyte 직원이 아닌 제 스스로가 이 포스트를 작성하고 있습니다.

인터뷰에서는 알고리즘 이외의 다른 주제도 다룹니다. 그러한 주제의 연구를 위한 유용한 정보를 담은 Triplebyte의 블로그 포스트도 있습니다. 저는 본질적으로 그 포스트의 두번째 섹션에 대한 보충 설명으로 이 글을 쓰려고 합니다.

배경: 회사들은 왜 알고리즘을 물어볼까요?

실제 삶에서, 프로그래머들이 이진 탐색 트리나 그래프 탐색 알고리즘을 구현하는 일은 없습니다. 그러면 회사들은 왜 그에 대한 질문을 그렇게나 많이하는 걸까요?

이 질문에 대해서는 내적 그리고 외적 해석이 모두 존재합니다. "회사가 알고리즘 문제를 묻는 것이 왜 유용한가"가 하나의 질문이고, "회사가 알고리즘 문제를 내기로 결정한 실제 원인에 대한 메커니즘은 무엇인가"가 또 다른 질문입니다.

저는 알고리즘 문제를 내는 이유에 대해 설명하는 것으로 시작해, 그 유행에 대해 좀 더 비꼬는 설명으로 나아가려고 합니다.

우선, 많은 프로그램들이 아주 기본적인 것을 해내지 못합니다. 예를 들면, Customer 객체 리스트가 존재하고, 각각의 객체가 Purchase 객체의 배열을 가지고 있을 때, 지난 주에 가장 많이 소비한 5명의 고객 이름을 얻고자 한다고 해보겠습니다. 추측컨대, 아마도 전문 프로그래머 중 50%는 이 문제를 30분 내에 풀지 못할 겁니다. 실수로 그러한 사람을 고용하고 싶지는 않을 겁니다.

조금 덜 비관적으로 말하자면, 프로그래밍 업무에 대해 누군가와 인터뷰할 때, 한번에 머리 속에 많은 세부사항들을 담고 있어야 하는 어렵고 복잡한 문제들을 풀 때 그들이 얼마나 잘 해낼 수 있는지 알아내려고 할 겁니다. 실제 삶에서, 몇 주씩 걸릴 만큼 큰 프로젝트를 수행하고, 소프트웨어의 서로 다른 많은 부분들에 대해 한번에 생각해야 하기 때문에, 헷갈리고 복잡한 문제가 생겨납니다. 그러나 인터뷰에서는 일반적으로 여러분이 프로그래밍 문제에 깊게 파고들 만한 충분한 시간이 주어지지 않습니다. 그래서, 규모가 있어 어려운 문제를 묻기보다는, 짧고 어려운 문제를 물어보게 됩니다.

어떻게 하면 설명하기 간단하면서도 복잡하지만 짧은 코딩 문제를 찾아낼 수 있을까요? 제가 생각하기에 알고리즘이 제격입니다. 알고리즘은 대부분의 모든 소프트웨어 엔지니어가 아는 컴퓨터 과학의 가장 복잡한 영역이며, 설명하기 쉽고 구현하기 까다로운 문제들이 많습니다.

이제부터 좀 더 냉소적인 생각을 말해보겠습니다.

인터뷰 프로세스는 어색하고 성가십니다. 엔지니어 팀은 모두 팀의 기술 인터뷰를 통과한 사람들로 구성됩니다. 그래서 모두 그들의 인터뷰 과정이 소프트웨어 엔지니어링 능력을 측정하는데 있어 굉장히 정확하다고 믿고 생각하도록 자기 합리화합니다. 그러므로 알고리즘 인터뷰 문화가 사내에 생기고 나면, 그것을 바꾸기는 어렵습니다.

모두가 알듯, 10년 전 Google에는 굉장한 팀이 있었고, 그 당시 인터뷰에서 알고리즘 문제를 냈습니다. (최고의 지원자들을 Google에 뺏기는 일이 많기에) 대부분의 회사들은 자신들이 Google이 아니라는 점에 대해 약간 불안해 합니다. 그래서 Google의 인터뷰 프로세스를 모방하려고 합니다.

최악의 경우에 알고리즘 인터뷰는 기괴하고 못살게 구는 과정으로 변질될 수 있습니다. 때때로 회사들은 어떤 무작위 수수께끼가 굉장한 지원자를 찾기 위한 비법 양념이라고 완전히 맹신하게 되며, 여러분은 그에 대한 그들의 마음을 결코 바꿀 수 없습니다.

개괄적으로 말하면, 저는 인터뷰에서 전통적인 어려운 알고리즘 문제를 내지 않습니다. 최악의 경우에 알고리즘 문제는 극히 나쁜 인터뷰 문제입니다. 제가 특히 싫어하는 문제에 관한 근거에 대해 쓴 적이 있습니다. 알고리즘 문제는 좀 더 난해하고 굉장한 통찰력을 요할 때 특히 좋지 않습니다(알고리즘 인터뷰 프로세스를 구축하고자 한다면, 주저 말고 이메일을 주세요. 이러한 문제가 없는 문제를 내는 방법에 대한 좀 더 자세한 의견을 드릴 수 있습니다).

공부 방법

편집: 이에 대한 Haseeb Qureshi의 블로그 포스트를 막 다시 읽었습니다. 그 글에 동의하며 좀 더 자세하게 설명하고 있다고 생각합니다. "General Study Strategy"와 "Programming Interview Study Guide" 섹션을 읽어보세요.

알고리즘 문제를 푸는데 두 가지 기술이 필요하다고 생각합니다. 먼저, 모든 고전 알고리즘과 데이터 구조 자료를 알아야 합니다. 두번째로, 압박 속에서 화이트보드 에 알고리즘 로직을 빠르게 만들 수 있어야 합니다. 이 주제들에 대해서 각각 알아보려고 합니다.

규범적인 알고리즘 자료

회사가 당신을 테스트하기 위한 일관되고 괜찮은 핵심 알고리즘 지식이 있습니다. 많은 좋은 프로그래머들이 이 리스트에 존재하지 않는 자료에 의존하는 문제를 모르며 그런 문제를 낼 경우 좋은 프로그래머들이 실패하게 될 것이므로 회사는 그러한 문제를 묻지 않으려 할 겁니다.

다음은 여러분이 알아야 하는 데이터 구조 자료 목록입니다:

  • 리스트 구조: 배열, 동적 배열, 연결 리스트
  • 세트와 맵 구조: 해시 맵, 이진 탐색 트리, 힙

각각의 데이터 구조에 대해, 모든 필수 메서드가 구현된 방법과 런타임에 대해 알아야 합니다(리스트의 필수 메서드에는 set, get, pushAtEnd, popAtEnd, insertByIndex, removeByIndex가 있습니다. 세트의 필수 메서드에는 insert, remove, contains?가 있습니다). 데이터 구조의 구현을 사용하는 코드를 작성할 줄 알아야 합니다. 예를 들어, 이진 탐색 트리를 가져와 트리에서 x에 가장 가까운 값을 탐색하는 getNearestElementTo(x)를 구현할 수 있어야 합니다.

이에 대한 다른 참고 사항은 다음과 같습니다:

  • 여러분의 이진 탐색 트리 구현이 균형을 위한 로직을 필요한다는 것을 알아야 하지만, 세부 사항은 몰라도 괜찮습니다(선택 자료: 자가 균형 이진 탐색 트리 구현을 빠르게 배우려면 트립에 대해 알아보세요. 레드블랙 트리가 동작하는 방식을 이해하려면, 대신 좌편향 레드블랙 트리 혹은 2-3-4 트리에 대해 알아보세요).
  • 아마도 큐를 두 개의 스택으로 구현할 수 있다는 점을 알아둬야 할 겁니다.

다음의 모든 알고리즘을 구현할 수 있어야 합니다:

  • 그래프 알고리즘: 너비 우선 탐색, 깊이 우선 탐색, 다익스트라(Dijkstra) 알고리즘
  • 한 가자 빠른 정렬 알고리즘: 병합 정렬 혹은 퀵 정렬
  • 배열에 대한 이진 탐색. 이 알고리즘은 올바르게 구현하기 까다롭지만, 대충 이해하더라도 코드를 작성해 볼 가치는 있습니다.

Big O 표기법에 익숙해져야 합니다.

이 모든 걸 어떻게 배워야 할까요? 제가 즐겨찾는 자료는 Skiena의 알고리즘 디자인 메뉴얼입니다. 위에서 말한 모든 자료를 2-6 챕터에서 다루고 있습니다. 집필 스타일이 정말 매혹적이며 제가 생각하기에 가장 중요한 자료에 초점을 맞추고 있어 좋아합니다. 이 책은 인터넷에서 자유롭게 이용할 수 있습니다. 이 책의 한 가지 단점은 책의 코드 예제가 C로 되어 있어 C를 읽을 수 없는 프로그래머들이 보기에는 어렵다는 점입니다. 이 책을 읽는다면, 1-6 챕터와 12 챕터는 꼭 읽을만한 가치가 있다고 생각됩니다. 이 책에서는 인터뷰에 나올 가능성이 극히 작은 자료를 아주 약간 다루지만, 불필요한 자료가 아주 핵심적인 것들을 돋보이게 하는 좋은 일을 한다고 생각합니다.

간결한 설명을 원한다면, Cracking the Coding Interview와 InterviewCake.com의 설명이 좋다고 생각합니다.

제 생각에는 Skiena의 책이 극도로 딱딱하고 다소 현학적인 유명한 CLRS 텍스트북보다 더 좋습니다.

여기에 그래프 알고리즘에 대한 약간의 정리를 해두었는데 유용한 리소스가 될 겁니다.

규범적인 알고리즘 스킬

앞서 인터뷰에 필수인 핵심 지식에 대해 알아봤습니다. 다음으로 학습을 위해 제가 좋아하는 리소스와 함께 테스트한 다른 종류의 프로그래밍 스킬에 대해서 알아보겠습니다.

Cracking the Coding Interview는 그 모든 것을 위한 대단히 유용한 리로스입니다. 여기에 이 책에 대해 몇 가지 메모를 해두었습니다.

다음은 알고리즘 인터뷰 문제에 대한 가장 흔한 중점 요소들입니다:

  • 동적 프로그래밍: Skiena의 책 챕터 8을 읽거나 이 주제에 대한 Cracking the Coding Interview의 챕터를 읽고 학습하세요.
  • 재귀: 이 주제에 대한 굉장한 챕터가 Cracking the Coding Interview에 있습니다.
  • 유명한 데이터 구조를 선회하기. CtCI에는 이 주제에 대해서 데이터 구조 각각에 대한 좋은 문제가 있습니다. 예를 들어, BST에 대해, CtCI의 트리 챕터를 참고 가능합니다.
  • 문제에 대한 빠른 데이터 구조를 구성하기. 이런 종류의 문제에 대한 예제가 여기 있습니다.

제가 하고자 하는 주된 조언은 Cracking the Coding Interview에 있는 문제들을 풀어보라는 것입니다. 제가 가장 중요하다고 생각했던 문제들에 관해 노트에 나열해두었습니다.

다음은 이 문제들을 공부하는 데 필요한 대략적인 참고사항입니다: 풀이를 읽어 "컨닝"을 하는 것도 괜찮다고 생각합니다. 인터뷰 연습 문제 풀이를 완전히 포기하는 것보다는 직접 풀지 않고 해답을 읽는 것이 더 낫다고 생각합니다.

알고리즘 인터뷰 성공에 관한 비기술적인 측면

실제 사람이 여러분에게 알고리즘 문제를 묻도록 하여, 압박 속에서 문제에 답하는 연습을 해볼 가치가 있습니다. 이에 대한 좀 더 많은 조언은 Triplebyte 블로그 포스트, 특히 2, 3 그리고 7번 항목을 참고하시기 바랍니다.

알고리즘과 데이터 구조에 대한 심화 학습

일자리를 얻기 위한 경제적 목적이 아니라, 스스로가 알고리즘과 데이터 구조에 심취해 있다고 가정해보겠습니다. 좀 더 배우려면 어떻게 해야 할까요?

좀 더 배우기에 가장 쉬운 것은 제가 위해서 설명했던 핵심 데이터 구조 명부에는 없는 것 중 상대적으로 간단한 데이터 구조일 겁니다. 예를 들면, 트립, 스킵 리스트, augmented BST 그리고 서로소 집합(disjoint set) 데이터 구조는 모두 이해하기에 상당히 쉽고 모두 정말 멋지다고 생각합니다.

이해하기 좀 더 어렵지만 노력을 들일 가치가 있는 데이터 구조에 대한 주제도 있습니다. 예를 들어, 이 슬라이드에서는 BTree와 2-3-4 트리를 설명하고 있습니다.

좀 더 흥미로운 데이터 구조 자료에 대한 리소스 중 제가 좋아하는 것은 다음과 같습니다:

  • Skiena 책의 챕터 12와 그 이후의 챕터들
  • 놀라운 스탠포드 클래스인 CS166. 이 슬라이드들은 놀라우며 읽기 좋습니다. 저는 몇 가지 문제 세트 풀이를 즐겼습니다. 더 많은 데이터 구조의 재미를 위해 클래스의 프로젝트 아이디어 핸드아웃을 추천합니다.
  • 저는 여기서 했던 특정 데이터 구조 문제에 관한 일들이 꽤나 자랑스럽니다. 실제로 열심히 하지는 않았지만 아마추어 활동으로 즐겼던 것입니다. 그 문제에 대한 저의 해법은 고급 데이터 구조에서 나온 몇 가지 아이디어를 수반하며 그것이 꽤나 훌륭하다고 생각합니다.

리소스

The brain-changing benefits of exercise on TED

TED 영상을 보면서 이런 에너지를 받은 적이 없다. 심지어, 내가 TED 영상을 엄청 많이 본 건 아니지만, 이런 형식의 발표는 처음이다. 세상에, 프레젠테이션 페이지도 2페이지였던거 같은데...

이름으로 보아 일본계 미국인인 것 같은데, 정말 멋진 분이다. 알고 보니 엄청 유명한 분인듯?

아무튼, 발표 말미에 청중 모두를 일으켜세워 운동을 시킨 발표자는 처음인 것 같다. 🤣

운동하자, 운동!

Intuition, Insight

개발자에게 있어 중요한 능력은 수도 없이 많다. 문제 해결 능력, 합리적 사고력, 수학적 해석 능력, 습득력, 관련 기술 지식 등등, 대표적인 지식 노동자 중 하나라고 할 수 있다.

지식 노동자에게 필요한 능력에는 이외에도 무시할 수 없는 두 가지가 더 있는데, 바로 직관력과 통찰력이다.

직관력의 사전적 의미는,

[명사] 판단이나 추리 따위의 사유 작용을 거치지 아니하고 대상을 직접적으로 파악할 수 있는 능력.

또 통찰력의 사전적 의미는,

사물이나 현상을 통찰하는 능력.

통찰력의 경우 그 의미에 통찰이라는 단어가 또 들어가 좀 애매한데, 말하자면 '사물의 관찰력으로 꿰뚫어 보는 능력'이라 할 수 있다.

여기서 두 단어가 뜻하는 바가 비슷하면서도 오묘하게 다른데, 직관력은 겉으로 보기 힘든 어떤 사물의 면모를 볼 수 있는 힘을 말하며, 통찰력은 어떤 사물이나 현상에 대해서 포괄적, 다각적으로 볼 수 있는 힘을 말한다.

직관력과 통찰력이 있다면 개발자에게 어떤 점이 좋을까?

이 두 능력의 공통적인 부분은 바로 '해보지 않고도 알 수 있다'라는 것에 있다. 해보지 않고도 할 수 있다니 그 얼마나 대단한 능력인가? 그런데 그러한 능력을 얻기 위해서는 어떤 조건이 따른다.

어떤 일이나 사물에 대해 어느 정도 능통해야 한다. 어떠한 경험도 없이 직관과 통찰을 얻기란, 박혁거세처럼 알에서 태어나는 정도의 탄생 신화로 태어난 사람만 가능하지 않을까?

직관과 통찰은 그냥 생겨나는 것이 아니라 어떤 일이나 사물에 숙련되고, 또 다른 여러 가지 것들을 융합하여 생겨날 수 있다.

그런데 이 좋은 능력에도 문제가 있다. 그 중 하나가 측정하기 매우 어렵다는 것이다. 직관과 통찰은 사실 우리 주변에서 많이 일어나지만 그것으로 어떤 성과 또는 효과가 일어났는지 우리는 알아차리기 어렵다.

또 다른 문제는 바로 직관과 통찰의 오용과 남용이다. 내가 이 포스트를 통해 말하고자 하는 부분은 여기에 있다.

직관과 통찰은 맞고 틀리고의 문제라기 보다는, 어떤 정답을 얻어내기 위한 방법에 도달하기 위해 사용하는 지름길 같은 것이다. 물론, 직관과 통찰로 어떤 문제가 단번에 해결되는 경우도 아주 간혹 있지만, 아주 운이 좋은 경우라고 봐야 한다.

또, 직관력과 통찰력이 아주 높은 사람이라면, 그러한 행운의 빈도가 높긴 하겠지만, 그들의 그러한 능력만으로 일이 해결되는 경우는 아마 거의 없을 것이다.

주변에서 자신의 직관력 내지 통찰력을 맹신하는 개발자들을 꽤 보아왔다. 나도 가끔 느끼는 이러한 능력에 감탄(?)하곤 하지만, 사실 돌이켜보면 그 두 가지 능력으로 일이 말끔히 해결된 적은 없는 거 같다. 그렇다고 보기보다는 답을 얻어내는 과정을 좀 더 빠르고 쉽게 만들어주는 느낌이 강하다.

직관과 통찰이 가지는 문제점 중 하나는 그것이 주는 기쁨에 있다. 그것으로 어떤 문제가 해결된 것처럼 보일 때, 또는 해결될 것처럼 보일 때 우리는 어떤 카타르시스를 느끼는 것 같다.

사실 개발자는 이성적 존재여야 한다. 좀 더 명확하게 얘기하자면 과학자여야 한다. 과학자란 어떤 존재인가? 어떤 가설을 세우고 그 가설이 맞는지를 검증하기 위해 남들이 인정할 정도의 실험을 수도 없이 실행해 그 가설이 진리임을 알아내기 위한 사람 아닌가?

개발자도 그와 아주 비슷한 일은 한다. 어떤 문제에 대한 문제 해결 방법을 세우고 그 방법이 맞는지 검증하기 위해 테스트 코드를 작성해 그 방법이 옳다는 것을 증명해내는 존재다.

이 때 직관력과 통찰력은 아주 중요한 역할을 하는데, 그 중 몇 가지를 예로 들어보겠다.

첫번째, 문제 해결 방법, 즉 알고리즘을 만들 때 그 역할을 수행한다. '아 이 문제는 이렇게 하면 되겠다'라는 생각이 떠오르는 이유는 경험에서 나오는 것인데, 여기서 더 나아가 '이렇게 하면 되겠구나?'라는 생각이 든다면 그것은 직관력이나 통찰력에 의한 것일 가능성이 크다. 그리고 이때는 그야말로 문제를 파악할 수 있는 능력이 있어야 하므로 직관력이 좀 더 필요할 거 같다.

통찰력도 필요한데, 만약 문제 해결 방법이 쉽사리 떠오르지 않을때이다. '어떤 다른 측면은 없나?', '더 나은 방법은?' 등을 고민하는 순간 필요하다.

두번째로, 테스트 코드를 작성할 때 직관력과 통찰력이 필요하다. 90년대 말, 2000년대 초에 접어들면서 TDD에 대한 필요성이 대두되기 시작했다. 물론 우리 나라에는 그보다 훨씬 늦은 2000년대 말에 소개되기 시작했고 2010년대 중반에 들어서야 조금 자리 잡기 시작한거 같다. 개발자들은 테스트 코드 작성 자체에는 그리 힘들어하지 않는다. 물론, 테스트할 대량의 데이터를 만들고 그것을 대입해 프로덕션 레벨의 코드를 만든다는 것이 고난의 길이다. 하지만 진짜 힘들어하는 것은 '바로 무엇을 테스트할 것인가?', 즉 테스트 케이스 수립이다.

알고리즘 문제를 풀때는 테스트 케이스가 분명한 경우가 거의 대부분이다. 로직 자체에 대한 경우는 물론이고, 대부분 반복의 횟수, 경계값, 실행 속도, 메모리 사용량 등 정형화된 테스트 케이스를 통과시키면 되는 것들이 대부분이다.

그런데, 우리가 흔히 작성하는 코드들은 그러한 것들은 물론이고 테스트 케이스가 애매한 것들이 많다. 한번에 딱 떠오르지 않기도 하고, 너무 큰 덩어리라 어떻게 테스트해야할지 감이 오지 않는 경우도 많다. 물론, TDD에 숙련되면 이러한 것들이 좀 더 쉬워지기는 하겠지만, 이 때 발휘되어야 하는 통찰력의 힘을 무시할 수는 없다.

통찰력은 어떤 상황을 다각적으로 바라볼 수 있는 능력을 말하는데, 테스트 케이스 작성에 그 능력이 필요하다. 해결해야 하는 문제 그리고 그 방법, 그 방법을 검증하기 위한 것이 테스트 케이스인데, 그 상황을 다각적으로 볼 수 있어야 풍부한 테스트 케이스 작성이 이루어지고 그래야 확실한 증명이 된다.

그런데 이러한 직관력과 통찰력을 잘못 사용하는 경우가 많다. 사실 이 포스트 쓰는 나도 그런 경우가 많은데, 나의 직관력 또는 통찰력을 맹신하는 것이다.

머리 속으로 생각해보고 '우와 이거야!'라고 생각하는 것까지는 괜찮다. 당연히 그러한 과정이 필요하고 그 때 필요한 능력이 그 녀석들이니까! 그런데, 문제는 이 이후부터 발생한다. 그렇게 얻어낸 방법에 대해서 '증명'하지 않는다는 것이다. 그냥 믿어버린다. 물론 모든 경우를 테스트하기란 현실적으로 어려움이 있다. 하지만, 직관과 통찰로 믿었던 방법들이 계속해서 문제를 일으키고 있다면 자신의 맹신을 경계해야 한다.

개발자가 자신이 생각한 방법, 즉 직관이나 통찰을 이렇게 맹신하는 이유는 소위 말하는 '귀차니즘'의 영역에 있는거 같다. 물론 어떤 문제는 테스트할 엄두도 나지 않는 경우가 있다. 하지만 그 문제를 더 작은 하위 문제들로 쪼개서 하위 문제만이라도 검증하는 과정이 필요하다. 물론 해보지 않아서 그 방법을 모르기 때문일 수도 있지만, 사실 거기에는 '귀차니즘'이라는 기생충이 살고 있는 것이다.

'다른 일도 바빠!'라는 말로 합리화하지만, 결국 검증하지 않고 계속해서 방법만 만들어내고 검증하지 않은 상태가 이어지기에 일도 쌓이는거다. 일이 쉽사리 풀리지 않을 때는 시간을 확보해 충분한 테스트 환경을 만들어내고 그 테스트 환경으로부터 다시 생각해봐야 한다.

내가 세운 가설에 문제는 없는지, 내가 만든 테스트 케이스에 오류는 없는지, 다른 측면에서 테스트 케이스는 없는지...

직관력과 통찰력은 슈퍼 파워가 아니다. 아이언맨이 가진 Jarvis 같은 도구일 뿐이다. 도구는 이용과 숙련의 대상이지 믿음의 대상이 아니다.

자바 성능 튜닝: 자바 성능 향상을 위한 완벽 가이드란 책을 보고...

'...란 책을 보고'라는 제목을 붙였지만, 사실 24쪽까지 읽은게 전부다.

난 이 책을 사서 읽기 위해 34,000원이라는 돈을 지불했다. 좋은 책을 읽기 위한 34,000원이라는 돈은 사실 푼돈에 불과하다. 30,000원도 안되는 좋은 책들이 이 세상에는 널렸다.

우리 나라의 IT 서적들은 크게 4가지 종류로 나뉜다. 첫째, 한국인 저자가 직접 썼고 내용도 좋은 책. 둘째, 한국인 저자가 썼으나 좋지 못한 책. 셋째, 외쿡 살람이 쓴 좋은 책. 넷째, 외쿡 살람이 썼으나 좋지 못한 책.

난 외쿡 살람이 쓴 좋은 책을 가장 선호한다. 물론 한국인 저자가 쓴 책 중에도 좋은 책들이 많다. 그 중에서도 조영호님이 쓴 객체 지향의 사실과 오해라는 책은 내가 읽은 어떤 OOP 책 중에서도 최고다. 아무튼, 내가 외쿡 살람이 쓴 좋은 책을 선호하는 이유는 이 포스트에서 중요한 내용은 아니므로 넘어가자.

거꾸로 내가 제일 싫어하는 책은? 한국 살람이 쓴 안좋은 책? 아니다. 안 좋은 책을 쓰는 이유는 많을 것이다. 문장 능력 자체가 떨어진다던가, 책이 다루는 주제에 대한 지식이 책을 쓸만큼 갖춰져 있지 않다던가... 뭐 여러 가지 이유가 있을텐데, 물론 저러한 이유들도 납득하기 쉽지 않다. 허나 내가 제일 싫어하는 책은, '외쿡 살람이 쓴 좋은 책을 한쿡 살람이 이상하게 번역한 책'이다.

'자바 성능 튜닝: 자바 성능 향상을 위한 완벽 가이드'란 책이 딱 그런 책이다. 살때는 몰랐는데 이 책의 제목이 이젠 마음에 안든다. 원제는 Java Performance: The definitive guide다. 자바 성능 완벽 가이드라고 하는게 맞다. 왜냐하면 긴 이름이 마음에 안들기도 하지만, 책의 내용 상 튜닝에만 초점을 맞춘게 아니라, 중점은 성능에 있다. 이는 책에서도 거듭 강조한 부분이고 역자가 제대로 변역한 부분에도 분명 나와있다. 그러므로, 원제처럼 자바 성능이라고 하는 것이 맞다.

내가 이 책을 받아들고 19페이지를 읽을 때쯤 원서를 다운로드 받았다. 이유는 딱 하나.

이게 무슨 뜻이야?

비판하는 몇 가지 증거를 제시하겠다.

이 번역서의 21페이지, 첫 단락을 보면 이렇게 나와있다.

여기서 세 번째 위험 요소는 테스트의 입력 값 범위다. 임의의 수를 선택하는 데 코드를 사용하는 방법을 나타낼 필요는 없다. 이와 같은 경우 (음수일 때) 테스트 중인 메서드에 대한 호출 대신 바로 예외 처리를 한다. 가장 큰 피보나치 수는 두 배로 나타날 수 있으므로 1476보다 큰 파라미터 값이 들어오면 예외 처리한다.

원서의 내용은 다음과 같다.

The third pitfall here is the input range of the test: selecting arbitrary random values isn’t necessarily representative of how the code will be used. In this case, an exception will be immediately thrown on half of the calls to the method under test (anything with a negative value). An exception will also be thrown anytime the input parameter is greater than 1476, since that is the largest Fibonacci number that can be represented in a double.

이 단락을 이해하려면 맥락을 이해해야 한다. 두 가지 코드를 볼 것이다.

먼저,

for (int i = 0; i < nLoops; i++) {  
  l = fibImpl1(random.nextInteger());
}

여기서 fibImpl1 메서드는 피보나치 수를 구하는 함수로 한 개의 정수형 인자를 받는다. 그런데 컴파일러의 똑똑함을 극복(?)하기 위해서 균일한 값이 아닌 랜덤 값을 입력시키고자 난수 발생기로 정수를 가져와 입력하는 코드다.

그리고,

int[] input = new int[nLoops];  
for (int i = 0; i < nLoops; i++) {  
  input[i] = random.nextInt();
}
long then = System.currentTimeMillis();  
for (int i = 0; i < nLoops; i++) {  
  try {
    l = fibImpl1(input[i]);
  } catch (IllegalArgumentException iae) {
  }
}
long now = System.currentTimeMillis();  

위 코드는 첫번째 코드를 실제적인 성능 측정을 위해 개선한 것이다. 랜덤 값 발생 비용이 측정에 포함되므로 이를 제외하기 위한 코드 분리를 추가한 것이다.

원서의 문장에서 selecting arbitary random values라고 말한 부분이 바로 이것을 두고 말한 것이다. 즉, 해당 메서드를 실제 사용할 때 랜덤 값 생성은 포함되지 않으므로, 입력 값의 범위를 정의해주어야 한다는 것이 이 단락 그리고 절의 핵심이다.

그래서, 임의의 수를 선택하는 데 코드를 사용하는 방법을 나타낼 필요는 없다.라고 번역한 것은 문맥을 고려하지 않고 그냥 번역한 것이다. 이는 랜덤 값 선택이 반드시 코드 사용 방법을 나타내는 것은 아니다.라고 변역되거나 좀 더 의역하여 랜덤 값 선택은 해당 메서드의 내용이 아니다.라고 변역해야 한다.

그 다음 문장 이와 같은 경우 (음수일 때) 테스트 중인 메서드에 대한 호출 대신 바로 예외 처리를 한다.은 심지어 내용을 완전히 왜곡한 것도 모자라 수동태를 능동태로 옮겨버렸다. 원서의 문장 In this case, an exception will be immediately thrown on half of the calls to the method under test (anything with a negative value).에서 핵심은 on half of the calls다. 이 문장에서 말하고자 하는 내용은 테스트 대상 메서드에 대한 호출 중 절반(음수 값을 가진 모든 호출)에 대해 예외가 즉시 발생한다.는 것이다. 즉, random.nextInt() 메서드가 음수 값을 발생시킬 확률이 50% 정도는 될 것이므로 호출의 절반이 예외를 발생시킬 것이라고 말하고 있는 것이다.

위 단락에서 마지막 문장 가장 큰 피보나치 수는 두 배로 나타날 수 있으므로 1476보다 큰 파라미터 값이 들어오면 예외 처리한다.는 이 책의 정수(?)를 보여준다. 이 문장을 설명하려면 코드 하나를 더 봐야 한다(코드의 내용은 일단 무시하자).

public double fibImplSlow(int n) {  
  if (n < 0) throw new IllegalArgumentException("Must be > 0"); 
  if (n > 1476) throw new ArithmeticException("Must be < 1476");
  return verySlowImpl(n);
}

나는 이러한 번역이 왜 나왔는지를 역으로 추적해봄으로써 올바른 번역을 소개하고자 한다.

첫번째로, '가장 큰 피보나치 수는 두 배로 나타날 수 있다'라는 문장이 도대체 무엇을 의미하는걸까? 이것은 원서의 문장 중 뒤에 that절을 봐야한다. 대충 직역하자면, '그것이 가장 큰 피보나치 숫자이고, double로 표현될 수 있다' 정도일텐데, 여기서 역자는 a double를 두배라고 번역했다. 그렇게 번역하려면 원래의 문장에서 in a doubleto be doubled가 되어야 한다. 관사 a는 도대체 어디로 날려먹은걸까? 여기서 a double은 우리가 흔히(?) 얘기하는 double 변수, 즉 배정밀도 변수를 말한다. 갑자기 왠 double? 일단 다음으로 넘어가자.

두번째로, 1476이다. 1476이라는 숫자는 위 코드에서 경계값으로 예외 조건이다. 내 생각에 저자는 이 1476이라는 숫자가 무엇을 의미하는지 몰랐던 것 같다. 만약 알았다면 첫번째 내용을 이해했어야 한다. 만약 피보나치 메서드에 1476이 대입된다면 코드의 내용 상 아마도 그에 대한 피보나치 값이 나올 것이라는 것을 추정할 수 있다. 그렇다면 1477은 왜 안되는 걸까?

피보나치 수는 입력 값이 커질 수록 급격하게 증가한다. 내 기억으로 long이나 double이 아닌 int로 구현할 경우 입력값 한계가 300도 안됐던걸로 기억한다. 1476가 입력됐을 때 double 변수에 담을 수 있는 최대의 피보나치 수가 결과로 나온다. 즉, 1477이 입력되면 overflow가 발생할 것이고 결과는 음수가 나온다. 이를 사전에 막기위한 조치가 바로 if (n > 1476)이다.

이 책의 구현 방식으로는 사실 1476은 고사하고 50만 입력해도 엄청 오래 걸린다. 이 책의 구현 방식은 피보나치 수에 대한 알고리즘을 그대로 구현한 것으로 꼬리 재귀를 만족시키지 못한다. 예제에서 50을 입력하도록 되어 있는데, 이 부분이 원서의 수준을 의심케 하는 대목이다. 19페이지를 보면 내가 의심하는 이유를 알 것이다. 내가 직접 해본 바로는 책에서 말하는 내용을 증명할 수 없었다.

그러므로 원래의 문장 An exception will also be thrown anytime the input parameter is greater than 1476, since that is the largest Fibonacci number that can be represented in a double.또한, 1476이 한 개의 double 변수로 나타낼 수 있는 가장 큰 피보나치 수를 만들어내므로 입력 파라메터가 그 수보다 크면 예외가 발생한다.라고 번역되어야 한다.

다음은 내가 결국 이 책을 덮게 만든 문장(24페이지)이다.

벤치마크 내에서 전반적으로 시간 차이가 나서 루프를 많이 도는 동안에는 몇 초 후에 측정되지만 각 반복 직후에는 나노초 후에 측정되곤 한다.

이 문장이 도저히 이해가 안가 다시 원서를 봤다.

The overall time difference in a benchmark such as the one discussed here may be measured in seconds for a large number of loops, but the per-iteration difference is often measured in nano‐ seconds.

그 뒷문장은 다음과 같다(원서와 번역서의 문장을 같이 보여주겠다).

Yes, nanoseconds add up, and “death by 1,000 cuts” is a frequent performance issue.(이 나노초들이 더해져서 "가랑비에 옷 젖게 되는" 경우는 흔한 성능상의 이슈다)

뒷 문장은 그런대로 번역이 되었다. 그런데... 앞 문장을 왜 저렇게 번역했을까?

이 문장에서 의도하는 바를 대충 번역하자면, 여기서 거론되고 있는 벤치마크처럼 하나의 벤치마크 상의 전체 시간 차는 많은 회수의 루프의 경우에 초단위로 측정되지만, 이터레이션 당(즉 루프 한번) 시간 차는 대게 나노 초로 측정될 것이다. 정도가 될 것 같다. 그래야 뒷문장에서 말하는 나노초의 합산과 death by 1,000 cuts로 인한 상습적인 성능 이슈가 설명된다.

번역서의 의미로는 도저히 의미 파악도 안되고 문제의 문장과 그 뒷문장이 연결되지도 않는다.

이들 외에도 지적할 부분들이 있지만, 더해봐야 시간 낭비다(이미...).

내가 이러한 오역서들에 짜증이 나는 이유는 잘못된 정보 전달이 제일 크다. 책을 볼 때 의심 내지 비평을 하면서 보는 자세는 당연히 필요하지만, 이 책은 정보서다. 정보서라는 것은 정확한 정보만을 명확하게 전달해야 한다. 물론 환경이나 여러 가지 이유로 똑같이 재현이 안되거나 일부 틀린 내용이 있을 수는 있다. 허나 이 번역서에서 보여준 실수는 실수가 아니다. 역자는 그 내용이 어떤 내용인지도 모르고 책을 번역했음이 분명하다.

나는 내가 책 읽는 속도가 느리다고 생각했었는데(사실 좀 답답하긴 하다), 오늘에서야 그 의심을 좀 거둘 수 있을거 같다. 이 책의 24페이지까지 읽는데 무려 1시간 20분이 걸렸는데, 문장을 보는 내내 턱턱 막히고 그 의미를 다시 찾아보고 원서와 비교하다보니 그랬다. 이는 정보서적으로써는 0점이다.

생각보다 엄청 긴 글이 됐는데, 역자에게는 죄송스러운 마음이 든다. 뭐 사실 이 포스트를 얼마나 많은 사람이 보겠냐마는... 책 한권을 번역한다는게(이 책이 무려 500페이지가 넘는다) 엄청난 일이라는 것을 나도 대충 알기에 이렇게 비판하는 것이 그리 속시원하지만은 않다. 하지만, 번역서는 원서의 명성은 둘째치고 있는 그대로의 정보를 전달하기 위해 정성을 들여야 한다는 점은 포기하지 못하겠다.

혹자가 이런 말을 할까 하여 한마디 덧붙인다.

번역이 좀 틀릴 수도 있는거고, 번역한 거 자체도 고마워해야 하는거 아닌가요?

예전에 웹에 어떤 문서를 번역한 내용이 틀렸다라는 것을 알려주면서(상대방에게는 지적으로 들렸을 수도? 아니 사실 지적 맞다) 들은 말이다. 그 때는 그냥 별거 아니라 생각하고 넘겼는데, 지금은 제대로 말해주고 싶다.

번역서라고 해도 원저자의 의도를 훼손할 권리는 없는 것이고, 돈을 받고 판매를 하는 책에 대한 의무가 있는 겁니다. 그리고 설령 어떤 내용을 번역하여 무료로 웹에 올렸다고 해도 오역에 대한 비판 내지 충고는 들을 자세가 되어 있어야 합니다.

Algorithm 강의를 듣다가...

최근 Coursera에서 Data structures and algorithms 라는 강의를 듣기 시작했는데, 첫주 과제의 문제를 풀다가 이런 생각을 하게 됐다.

큰 데이터셋을 테스트하라고? 귀찮은데 그냥 제공해주면 어디 덧나니? 하아...

그 뒤 설명에는 표준 입력을 받는 방식으로 테스트하면 큰 데이터셋을 테스트하기 어려우니 대신 파일을 받아서 파일의 내용을 이용하라고 되어 있었다.

귀찮... 아 일단 다음으로 넘어가자.

문제 지시 사항이 여러 절로 되어 있는데 다음 절로 넘어가자마자 이런 글이 보였다.

You are probably wondering why we did not provide you with the 5th out of 17 test datasets that brought down your program.

그러니까, '프로그램이 제대로 실행되지 않을텐데 왜 그 데이터를 제공하지 않는지 궁금해 할지도 모르겠다'라는거다. 어?! 뭐야, 이 자식! 날 꿰뚫어 보는 거 같아 기분이 나빴다... 그런데 뒤이어서,

The reason is that nobody will provide you with the test cases in real life!

아... 딱 한마디 문장으로... 더 정확히는 딱 두 단어 real life로 날 반성하게 만들었다. 그래, 실제 삶에서 나한테 테스트 데이터 따위를 친절하게 넘겨주는 사람은 없었지...

앞으로는 테스트 작성은 물론 테스트 데이터를 만들어내는 일로 귀차니즘을 느낀다면 이 포스트를 다시 꺼내보리라.