Home

데이터에 현혹되지 않고, 데이터를 잘 활용할수 있는 14가지 룰


요즘 데이터에 관련된 부분을 생각하고 고민하다보면서 데이터를 바라보는 시각에 도움되는 글이 있어서 원저자의 허락하에 번역해 봅니다. 원저는 14 rules for data-driven, not data-deluded, marketing"입니다.
이 글을 쓴 SCOTT BRINKER는 년도별 "Marketing Technology Landscape Supergraphic"으로도 유명합니다.

1. 데이터 기반보다는 고객 기반

데이터 기반 마케팅은 분명 좋다. 그러나 (데이터 기반) 마케팅의 목적은 고객을 유치하고 그들에게 더 나은 사용자 경험을 제공하여 그들을 유지시키는 것이다. 결국엔 데이터 기반보다 고객 기반 마케팅이 더 낫다. 고객과 데이터 기반 마케팅은 상호 배타적이진 않다. 다르게 말하면, 데이터 기반은 고객 기반을 위한 수단에 지나지 않는다. 데이터로 인해 미궁의 속으로 들어갔을 땐 생각을 멈추고 "고객에게 어떻게 좋은가?"를 물어봐라. 데이터보다는 고객의 목소리가 더 중요한 법이니깐.

2. 모든 데이터는 동일하게 만들어지지 않는다.

데이터는 논쟁을 종료시킨다는 말이 있다. 적어도 절대적 진실적(정확성)이었을때만 그렇다는 이야기다. 하지만, 데이터는 마음만 먹으면 어떤 주장에도 뒷바침할 수 있는 자료를 찾게 해준다. 다만, 중요한 것은 데이터의 정확성과 관련성의 차이다. "In A Big Data World, Marketers Know Shockingly Little About Us"를 생각해 보자.
좀더 나은 교훈은: 가장 정확하고 관련성이 높은 데이터가 논쟁을 종식시킨다. 하지만, 논쟁이 끝난 다음엔 결단이 기다린다. 즉, 데이터 분석은 단지 결단을 위한 조그만 수단일뿐이다.

3. 데이터는 단순한 역사에 불과하다.

데이터는 일어난 것을 우리에게 말해준다. 일어날 것에 대해서는 말해주지 않는다. 그렇다. 하지만, 우리는 데이터의 역사로부터 배워야 한다. 그리고 과거 데이터로부터 정확한 미래를 예측가능해야 한다. 그러나 세계는 끊임없이 변하고, 데이터가 나타내는 역사의 상황은 오늘, 내일의 환경과도 엄연히 다르다. 중요한 건, 블랙스완(과거의 경험에 의한 판단이 행동의 기준이 되어서는 안된다는 것)의 경고처럼, 그 방법이 될 순 있어서 항상 될 수 있는 건 아니다.

4. 데이터는 항상 불완전하다.

물론 구체적인 하나의 데이터셋은 완전할 순 있다. 지난 3년동안 분기별 판매액을 나에게 줘라는 것은 완전한 데이터 셋이다. 하지만, 진공상태에서 존재하는 것은 아무것도 없다. 당신이 데이터를 가지고 의사결정을 할 때 데이터가 의사 결정에 관련된 유일한 것은 있을 수 없다. 의사 결정에 관련된 더 많은 데이터가 있어야 한다. 하지만, 모든 것을 손에 넣을 순 없다. 이것을 인정하면 우리가 가지고 있는 데이터로 그림은 그릴수 있지만 기껏해야 인상파 화가의 그림 정도이다. 종종 그것은 현대 미술일 수도 있다. 즉, 모든 데이터를 손에 넣을 수 없기 때문에 때로는 주어진 데이터속에서 의사 결정을 할 수 있는 용기가 필요하다.
그리고 나의 데이터에서 의사 결정할 카드가 적다는 것은 인식할 필요가 있다. 그럼 불완전한 데이터에서 많은 시간을 투자해 의사 결정에 필요할 카드를 많이 만들 수 있게 된다.

5. 데이터는 객관적이다. 그러나, 그 집합이나 해석은 주관적이다.

데이터는 객관적이라는 위험한 환상을 우리에게 준다. 객관적으로 모든 사람은 스프레드시트의 같은 데이터를 바라본다. 내가 순 추천 고객 지수 7을 보여줬다면, 내가 보여준 7에 대해서는 이견이 없다. 그러나, 무슨 데이터를 수집했고, 언제 어떻게 수집했는지, 누구로부터 수집한지에 대한 데이터에 대한 주관적인 선택이 포함되어 있다. 다른 한편으로는 우리가 어떻게 해석했는지도 주관적이다.
"The Hidden Biases in Big Data"라는 기사에서 많은 좋은 사례를 보여준다. "data fundamentalism(데이터 원리주의)"라는 화려한 문구로 소개되곤 한다. 데이터는 항상 객관적이라는 환상은 버리는 것이 낫다.

6. 하나의 데이터셋은 무한한 스토리를 만들수 있다.

마케터들은 스토리텔러이다. 그것은 일반적으로 좋은 일이다. 데이터는 스토리를 더욱 설득력있게 만든다. 하지만, 우리는 데이터를 주관적으로 해석할 수 있기 때문에 우리는 주위가 원하는 거의 모든 이야기를 만들수 있기도 하다. 인정하지만, 몇몇 스토리는 다른 것들보다 더 신뢰한다. 이것은 퍼지 라인이며 귀납의 문제(반복되는 경험의 패턴에 따른다는 사실에 촛점을 맞추고, 과거로부터 미래를 추론(Reasoning) 하는 것으로 규정되지만, 넓은 의미에서는, 관찰된 것을 기반으로 해서 아직 관찰되지 않은 것에 대한 결론에 이르는 것을 포함된다. 귀납법을 위한 틀로서 확률이론을 사용하는 베이즈 정리(Bayes' Theorem) 등이 있다.)이다.
그리고 이는 수세기 동안 과학자나 철학자들에게 성가시게 해왔다. 말할 필요도 없이 쉬운 답은 없다. 그러나 관점을 유지하는데 도움이 된다: 데이터 주위에서 나온 어떤 이야기는 그것에 대해 이야기할 수 있는 유일한 이야기는 결코 없다.

7. 전략은 선택의 문제이고, 좋은 데이터는 그 선택을 도와준다.

좋은 전략은 선택을 하기 위한 프레임워크이다. 그리고 좋은 데이터는 좋은 선택을 지원해주는 정보이다. 우리의 전략을 추구하는데 있어서 선택에 도움이 되지 못하는 데이터는 방해의 산물이다.

8. 실험은 인과관계를 발견하는 최고의 수단이다.

상관 관계가 곧 인과 관계로 이어지지 않는다. 데이터 과학자라면 누구나 알고 있는 일이지만, 그러나 마케터들은 더 많은 고객이 우리와 함께 더 많은 사업을 수행하게 되는 인과 관계를 알고 싶어한다. 데이터를 통해 있음직한 상관 관계가 발견되었을 경우 우리는 어떻게 해야할까? 우리는 통제된 실험을 실행한다. 실질적으로 가능한 만큼의 모든 변수를 가지고, 증명 또는 우리의 가설을 반증할 수 있는 대안을 테스트한다. Google은 매년 10,000건의 실험을 수행한다. 그것은 우리가 만들수 있는 가장 강력한 데이터이다. 이것은 Big Data보다 Big Test를 더 크게해야 하는이유이기도 하다.

9. 대시보드 뿐만 아니라, 앞 유리도 보자.

정량적으로 우리는 운전하는 동안 속도계 등의 대시보드(데이터)를 봐야하고, 정성적으로 앞 유리의 시야(경험)도 봐야한다. 물론 둘다 해야한다. 또한 실제로 운전할 때 정량(데이터)과 정성(경험)의 균형을 자연스럽게 유지해야 한다. 우리는 마케팅에서도 똑같은 균형을 위해 노력해야 한다.

10. 데이터의 정확성과 관련성은 시간과 함께 쇠퇴된다.

특히나 마케팅 영역에서는 데이터의 유통기한은 짧다. 내가 새로운 자동차를 검색하는 그 주에 나에게 자동차 광고를 타겟팅하는 것은 의미가 있지만, 6개월 후에 자동차 마케팅 대상에서 나를 식별해준 데이터는 단지 과거의 화석에 지나지 않는다. 내가 자동차 마케팅 대상에 포함된 것을 안다는 것은 약간의 가치가 있을지도 모른다.
하지만 나는 여전히 가치가 없다고 생각하고 있다. 가치가 없는것보다 더 나쁘다. 왜냐하면, 나에게 나쁜 의사 결정을 유도하기 때문이다. 정확성과 관련성은 데이터를 가치있게 만들지만, 속성은 시간이 지남에따라 변한다.

11. 데이터는 탐색(exploration, why?)나 확인(confirmation, what?) 작업에 유용할 수 있다.

데이터는 탐색과 확인시에는 서로 다르게 접근해야 한다. 탐색시에는 새로운 가설에 영감을 얻을 수 있는 패턴, 인사이트나 아이디어, 발견할 것들을 찾는다. 확인시에는 어떤 일이 일어날지 안일어날지, 혹은 어느 정도 일어날지에 대해 검증하는 것이다. 그러나 통제된 실험에서 테스트한 가설이 확인되지 않았다면, 확인은 무엇이 일어났고 왜 안됐는지 말해주어야 한다. 같은 데이터로 하나의 환경에서 확인에 사용될 수도 있고 또 다른 환경에서 탐색에도 사용될 수 있다. 다만 당신이 하고 있는 것에 대해서는 알아야 한다.

12. 하나의 시계를 가진 사람은 지금의 시간을 알 수 있지만, 두 시계를 가진 사람은 알 수 없다.

이 법칙은 마케터들이 알아야할 7가지 기술 법칙들 중의 하나인 Segal의 법칙(정보량이 너무 많으면 정보들이 돌아가는 것을 모르게 된다)으로 알려져 있다. 세상엔 혼돈의 데이터가 많이 있다. 준비된 두개의 다른 웹 분석 패키지들로부터 지표를 얻고자하는 사람은 정확성을 입증할 수 있다. 이 다른 두 툴은 동일 현상을 다르게 측정한다. 왜 두 툴은 서로 다른 가치있는 통찰력을 보여주는지에 대한 이해 - 즉, 중요한 차이에 대한 이해력을 갖추는데 투자하는 것은 가치가 있을수도 있다. 하지만, 매번 모순을 추적하기에는 수확 체감이 있다. 많은 경우 완벽하게 정확한 데이터는 필요없다. 하지만 충분히 정확한 데이터는 좋은 의사 결정을 낳게 한다.

13. 모델은 현실이 아니다.

데이터는 주장을 표현하기 위한 것이고 현실성은 없다. 기껏해야 현실은 반영하지만, 데이터는 변형되기도 쉽다. 과학자이면서 철할자인 Alfred Korzybski가 "지도는 영토가 아니다"라는 말을 했다. 재해석해보면, 물리적 세계(영토)를 반영한 가상적인 공간이 지도인것처럼, 만드는 사람의 심리적 상태나 개인적인 지식의 편차에 의해 지도의 모형은 변형될 수 있다는 의미로 받아들여질 수 있다. 지도나 데이터처럼 실제적인 것이 아님에도 불구하고 사람들은 사용하길 원한다.

위대한 통계학자 George E. P. Box는 "모든 모델들은 잘못된 것이지만, 일부 유용한 것도 있다."고 말했다. 그러나, 데이터가 나타내는 것에 대해 정확성에 대한 긍정적인 의구심을 유지하기 위해 신중을 기해야 한다. 실제로 우리들은 데이터가 현실과 다르다는 것을 알리는 의미로, 임계치를 벗어나는 등의 신호에 대해 알람 주기를 원할 것이다. 그리고 "지도와 지형이 다를 경우 지형을 우선시해라."라는 스위스 군대의 격언을 업급하면서 현실(고객)의 목소리가 중요시하고 있다.

14. 데이터 시각화는 명확하게 하기도 하지만, 혼선을 주기도 하고, 집중을 분산시키기도 한다.

차트, 그래프, 인포그래픽 등의 데이터 시각화는 강력하지만, 양날의 검이기도 하다. 시각화는 지금까지 우리 인간에게 데이터에서 패턴을 발견하는데 가장 효과적인 방법이다. 불행하게도 의도적이든, 우연이든 패턴은 실제로 정확하지 않은 것을 보여줄 수 있다. 데이터 시각화는 과학이며 그 자체가 예술이다. 다양한 각도와 인사이트력이 필요하다.

비주얼에 대한 문학적 스킬을 배울려면 Stephen Few, Kaiser Fung, Edward Tufte, Nathan Yau, Fernanda Viégas and Martin Wattenberg의 글을 읽으면 좋다. 그들은 여러분들의 데이터 인사이트에 많은 도움을 줄 것이다.
Tags : ,

대용량 파일 다운로드는 Axel

Axel은 멀티 커넥션 타입의 다운로더이다. wget이나 curl과 같이 다양한 대응은 할 수 없지만, 하나의 파일을 여러 연결(기본 연결은 4)을 통해 다운로드를 실행하기 때문에 대용량 파일의 다운로드에서는 wget이나 curl에 비해 많이 빠르다.

다운로드 속도를 올리고 싶으시면 Axel을 사용해 보세요.

설치

- Mac OSX
$ brew install axel

- CentOS
$ rpm -ivh http://pkgs.repoforge.org/axel/axel-2.4-1.el6.rf.x86_64.rpm

- Ubuntu
$ apt-get install axel

다운로드 테스트

아래 다운로드 URL http://goo.gl/0Z7WC4http://centos.mirror.cdnetworks.com/7.2.1511/isos/x86_64/CentOS-7-x86_64-Minimal-1511.iso 입니다.

- Wget
[mimul]% wget http://goo.gl/0Z7WC4
--2016-07-06 10:24:44--  http://goo.gl/0Z7WC4
Resolving centos.mirror.cdnetworks.com... 14.0.101.165
Connecting to centos.mirror.cdnetworks.com|14.0.101.165|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 632291328 (603M) [application/octet-stream]
Saving to: 'CentOS-7-x86_64-Minimal-1511.iso'

CentOS-7-x86_64-Minimal-1511.iso  100%[=====>] 603.00M  5.37MB/s   in 2m 7s

10:26:52(4.75 MB/s) - 'CentOS-7-x86_64-Minimal-1511.iso' saved [632291328/632291328]

- Curl
[mimul]% curl -o CentOS7.iso http://goo.gl/0Z7WC4
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
 --:--:-- --:--:-- --:--:--     0*   Trying 14.0.101.165...
* Connected to centos.mirror.cdnetworks.com (14.0.101.165) port 80 (#0)
> GET /7.2.1511/isos/x86_64/CentOS-7-x86_64-Minimal-1511.iso HTTP/1.1
> Host: centos.mirror.cdnetworks.com
> User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
> Accept: */*
> Referer:
>
< HTTP/1.1 200 OK
< Date: Wed, 06 Jul 2016 01:30:21 GMT
< Server: Apache
< Last-Modified: Wed, 09 Dec 2015 23:03:23 GMT
< ETag: "f05035e-25b00000-5267f18d704c0"
< Accept-Ranges: bytes
< Content-Length: 632291328
< Content-Type: application/octet-stream
<
{ [16137 bytes data]
100  603M  100  603M    0     0  5252k      0  0:01:57  0:01:57 --:--:-- 5371k
* Connection #0 to host centos.mirror.cdnetworks.com left intact

- Axel
[mimul]% axel -a -n 10 -o CentOS7.iso http://goo.gl/0Z7WC4
Initializing download: http://goo.gl/0Z7WC4
File size: 632291328 bytes
Opening output file CentOS7.iso
Starting download

Connection 8 finished                                                          ]
Connection 9 finished                                                          ]
Connection 3 finished                                                          ]
Connection 7 finished                                                          ]
Connection 6 finished                                                          ]
Connection 5 finished                                                          ]
Connection 1 finished                                                          ]
Connection 2 finished                                                          ]
Connection 0 finished                                                          ]
[100%] [..................................................] [   8.6MB/s] [00:00]

Downloaded 603.0 megabytes in 1:10 seconds. (8778.65 KB/s)


632메가 CentOS-7-x86_64-Minimal-1511.iso 파일을 다운로드 받는데 wget은 2분 7초, curl은 1분 57초, axel은 1분 10초가 걸렸습니다. axel의 분할 다운로드 기능 때문에 wget, curl보다 빠른거 같습니다. 시간이 급할때는 axel로 다운로드해 보세요.
Tags :

2016년 6월 기준 유니콘 기업 분석

유니콘 기업이란 상장되지 않았고 기업 평가액이 10억달러 이상인 기업을 말합니다. 유니콘 기업의 통계자료를 분석해 보면 핫한 기업 및 산업 분야를 알 수 있고, 연도별로, 국가별로 유니콘 숫자, 유니콘이 되기까지 얼마의 기간이 필요했으며, 유니콘 투자회사들 등을 알 수 있을 거 같습니다.

데이터 입수

전세계 유니콘의 데이터 정보는 기업 정보 데이터베이스를 많이 가지고 있는 Cbinsights의 The Unicorn List라는 페이지에서 구할 수 있습니다. 이 페이지를 긁어서 DB화했고, 시각화는 저희 제품(U2)를 통해 시각화해 했습니다.

저희 제품 데모 사이트는 여기입니다. 필요하신 분은 이메일로 필요한 사유를 보내주시면 테스트 계정을 보내드리겠습니다. 보내실 이메일은 오른쪽 프로파일 이미지 아래에 있습니다. ^^

기업가치 평가액 상위 10개 기업


우버, 샤오미, Airbnb 순으로 나오고, 아래 회사 소개를 보시면 좀 더 자세하게 설명해 두었습니다.

국가별 유니콘 정보

- 국가별 유니콘 개수


미국이 96개, 기업가치 합이 3400억불 정도로 가장 높았습니다. 그리고 건수로 미국과 미국 이외의 지역과 비율을 보면 57.8%대 42.2%로 미국이 미국을 제외한 지역보다 많은 유니콘을 보유하고 있고, 기업 가치 평가액으로 비교를 하면 56.9%대 43.1%로 비율이 비슷합니다.

- 유니콘이 많이 속한 상위 10개 국가

미국이 96개, 중국 32개, 인도 7개 순으로 많네요. 중국의 부상이 무섭습니다.

연도별 유니콘 탄생 건수


2014년 40개사, 2015년에는 80개사로 제일 많고, 현재까지의 전체 유니콘 건수는 166개사인 걸 감안하면 2014년과 2015년에 탄생된 유니콘 수가 전체 67.5%에 해당됩니다. 두해에 가장 많은 투자를 받은 것으로 붐 업 되었다고 봐도 될 거 같습니다.

Unicorn 기업이 되기까지 걸린 시간


Uber는 3년 조금 모자라게 걸렸고, 샤오미나 Airbnb는 5년 정도 걸렸네요.

유니콘의 산업별 분포

- 산업별 유니콘의 분포

eCommerce/Marketplace가 37개로 가장 많고 그 다음으로 Internet Software & Services 26개, Fintech 20개, Big Data가 14개, Healthcare가 10개 등의 순으로 나왔습니다.

- 연도별 산업 분야 유니콘 숫자

년도별로 산업별 유니콘 탄생 숫자를 살펴보면, 2015년이 가장 많은 증가수를 기록하고 있네요. 그리고, 2014년도부터 Healthcare, Fintech가 많이 등장했다는 것만으로도 이 분야가 핫한 분야라 생각할 수 있겠습니다. 국내 투자도 이쪽으로 갈 가능성이 높고, Healthcare, Fintech가 뜨면 아마 백데이터 분야도 같이 수요가 증가될 가능성이 커 빅데이터 기업도 투자될 가능성이 많아집니다.

- 산업별 상위 5개 유니콘들


유니콘 투자자

- 전체 유니콘 투자자 리스트

Sequoia Capital이 17건인데 실제 중국지사(6건) 포함하면 23건으로 제일 많습니다. 그 다음으로 Accel Partners(17건), Insight Venture Partners(10건) 등으로 나타났습니다.

- 상위 10개 투자사들


아시아 지역


아시아쪽은 eCommerce/Marketplace 분야에 17개사로 전체 대비 37% 정도로 많은 수가 포진되어 있습니다.

- 한국의 유니콘

쿠팡(Sequoia Capital, Founder Collective, Wellington Management이 투자), 옐로 모바일(Formation 8이 투자), CJ 게임즈(Tencent가 투자) 3군데며, 그리고 분야는 이커머스와 게임쪽입니다.

유니콘 상위 10개 회사 소개

  1. Uber(2009년 3월 창업) : 주문형 라이드 공유 서비스.
  2. Xiaomi(2010년 4월) : 중국 스마트폰 제조업체. 그외 가전, 통신 등 다양한 제품 제공.
  3. Airbnb(2008년 8월) : 객실 임대 중개 서비스.
  4. Palantir Technologies(2004년) : 피터틸이 창업자중에 한명이며, 데이터 분석 서비스를 주로하는 회사로, 피터틸이 Paypal 시절에 돈 세탁 방지를 위해 트랜젝션을 패턴화하여 부정행위를 발견하는 기술에 CIA와 공동 개발해 확장한 것이 Palantir의 핵심 기술이다. FBI, CIA, NSA 등 미국 부처와 계약해 서비스해 주고 있는 테러리즘 분석 대행 서비스가 주요 사업이며, 최근에는 은행의 자산 관리 및 의약품의 개발, 재고 관리의 최적화와 매출을 증가시키기 위한 구매 행동 분석 서비스 등을 제공해 사업 영역을 확대하고 있다. 재무 비공개.
  5. Didi Kuaidi(2012년 9월) : 중국판 Uber. 택시 배차 서비스.
  6. Lufax : 중국 FinTech 기업. 돈을 빌리는 사람과 투자하는 사람을 연결해주는 대출 중개 서비스 제공.
  7. China Internet Plus Holding(2005년 10월) : 중국 소셜 커머스.
  8. Snapchat(2011년 5월) : SNS 서비스. 사진 공유, 메세지.
  9. WeWork(2011년 2월) : 사무 공간 운영.
  10. Flipkart(2007년 9월) : 인도의 Amazon. E-Commerce 기업.
그 뒤로 SpaceX(2002년 6월), Pinterest(2009년 1월), Dropbox(2007년 6월), DJI Innovations(2006년 11월)가 있습니다.

Self-Service BI 제품 U2를 소개

와이즈에코에서 만든 Self-Service BI 제품 U2를 소개합니다.
SQL On Hadoop(Tajo, Impala, Presto, Hive), RDB(Oracle, Postgresql)의 데이터를 다양한 리포트로 자동 퍼블리싱이 가능한 제품입니다. 제품의 기능은 아래 제품 소개를 살펴봐 주시면 감사하겠습니다.

U2 제품 소개

U2 product For Wiseeco

U2 제품 소개 동영상

[번역] 객체지향 프로그래밍으로 유틸리티 클래스를 대체하자.

유틸리티 클래스를 안써본 사람은 거의 없을겁니다. 유틸리티 클래스가 좋다, 나쁘다는 논쟁꺼리지만, 객체지향의 관점에서 볼때 그래도 생각해볼 꺼리가 된다고 생각해, OOP Alternative to Utility Classes"라는 아티클을 저자 허락하에 번역해 봅니다.

유틸리티 클래스(또는 헬퍼 클래스)는 정적 메소드만을 가지고 있고, 상태를 내포하지 않는 "구조"이다. Apache Commons의 StringUtils, IOUtils, FileUtils과 Guava의 Iterables, Iterators, 그리고 JDK7의 Files 등이 유틸리티 클래스가 좋은 예다.

유틸리티 클래스는 많은 곳에서 사용되는 공통 기능을 제공하기 때문에, 이런 설계 방법은 Java(또는 C#, Ruby 등) 세계에서는 매우 인기있다.

여기엔, DRY 원칙을 따르고 중복을 피하는 것을 원한다. 그래서 유틸리티 클래스에 공통 코드를 넣고, 필요에 따라 재사용한다.
// This is a terrible design, don't reuse
public class NumberUtils {
  public static int max(int a, int b) {
    return a > b ? a : b;
  }
}
정말, 이것이 편리한 기술인가?

유틸리티 클래스는 악이다

그러나, 객체 지향의 세계에서 유틸리티 클래스는 아주 나쁜(심하게 나쁘다고 생각하는 사람도 있을지도 모른다) 방법이다.

이 주제에 대해서는 많은 논란이 있다. 일부 들면, Nick Malik의 헬퍼 클래스는 악인가?", Simon Hart의 왜 헬퍼클래스, 싱글톤, 유틸리티 클래스는 대체로 나쁘낙?", Marshal Ward의 유틸리티 클래스를 피하기", Dhaval Dalal의 유틸 클래스를 죽여라!", Rob Bagby의 "헬퍼 클래스는 문제의 징후다."

게다가, StackExchange에는 유틸리티 클래스에 대한 질문이 몇가지 있다. 예를 들어, 유틸리티 클래스가 악이라면 공통 코드를 어디에 두어야 하나?", 유틸리티 클래스는 악이다" 등이다.

이러한 논쟁을 요약해보면, 유틸리티 클래스는 적절한 객체가 아니라는 것이다. 그래서 객체 지향의 세계에선 적합하지 않다. 유틸리티 클래스는 당시 사람들이 기능 분할 패러다임에 익숙해져 있었기 때문에 절차적 언어에서 계승되었다.

여러분이 이 주장에 동의하고 유틸리티 클래스를 사용하는 것을 중지하고 싶어한다것을 가정하고 유틸리티 클래스를 어떻게 적절한 객체로 대체하는지를 예를 들면서 보여주겠다.

절차적 프로그램의 예

예를 들어, 텍스트 파일을 읽고, 행단위로 분할하고, 각 라인을 손질(공백제거 등)하고, 그 결과를 다른 파일에 저장하고 싶다고 한다. 이것은 Apache Commons의 FileUtils과 함께 구현되어 있다.
void transform(File in, File out) {
  Collection src = FileUtils.readLines(in, "UTF-8");
  Collection dest = new ArrayList<>(src.size());
  for (String line : src) {
    dest.add(line.trim());
  }
  FileUtils.writeLines(out, dest, "UTF-8");
}
위의 코드는 예뻐 보인다. 그러나, 이것은 절차적 프로그래밍이며, 객체 지향이 아니다. 코드의 각 라인에서 데이터(byte와 bit)를 조작하고 컴퓨터의 어디에서 데이터를 가지고, 어디에 쓸 것인지를 명시적으로 지시하고 있다. 즉, 실행 절차를 정의하고 있다.

객체 지향적 대안

객체 지향 패러다임에서는 객체를 인스턴스화하여 합성해야(컴포즈) 한다. 이것은 객체가 언제, 어떻게 객체 자신이 원하는 방식으로 데이터를 관리해야하기 때문이다. 추가적인 정적 메소드를 호출하는 대신, 요구하는 행동을 제공할 수 있는 객체를 생성해야 한다.
public class Max implements Number {
  private final int a;
  private final int b;
  public Max(int x, int y) {
    this.a = x;
    this.b = y;
  }
  @Override
  public int intValue() {
    return this.a > this.b ? this.a : this.b;
  }
}
다음은 절차적 메소드 호출:
int max = NumberUtils.max(10, 5);
다음은 객체지향적인 방법이 된다.
int max = new Max(10, 5).intValue();
둘 다 같은가? 아님 그렇지도 않은가? 좀 더 읽어 주었으면 한다.

데이터 구조 대신 객체

저라면 위와 같은 파일 변환 기능을 객체 지향 방식으로 다음과 같이 설계한다.
void transform(File in, File out) {
  Collection src = new Trimmed(
    new FileLines(new UnicodeFile(in))
  );
  Collection dest = new FileLines(
    new UnicodeFile(out)
  );
  dest.addAll(src);
}
FileLines는 Collection을 구현하고, 파일의 읽기및 쓰기 함수를 내포하고 있다. FileLines 인스턴스는 문자열의 컬렉션으로 정확하게 작동하고 모든 I/O 처리를 은폐하고 있다. 이 인스턴스를 반복하면 파일이 읽혀진다. 이 인스턴스에 addAll()하면 파일에 기록된다.

Trimmed도 Collection을 구현하고, 문자열 컬렉션을 내포하고 있다(Decorator 패턴). 한행이 검색될 때마다 트림된다.

Trimmed이나, FileLines, UnicodeFile은 파일 변환에 기능에 참여하는 모든 클래스는 작지만, 각각 자신의 하나의 기능을 담당하는, 즉 단일 책임 원칙에 완벽하게 따르고 있다.

우리 측, 즉 라이브러리의 사용자에서 보면 이것은 그렇게 중요하지 않을지도 모르지만, 라이브러리 개발자에서 보면 중요하다. 80개 이상의 메소드를 가진 3000라인의 유틸리티 클래스인 FileUtils의 readLines()보다 FileLines의 클래스가 개발과 유지 보수, 단위 테스트가 더 쉽다. 심각하게, 그 소스 코드를 봐라.

객체 지향 접근 방식은 지연 실행을 가능하게 한다. in 파일은 데이터가 필요할 때까지 읽지 않는다. I/O 오류로 out을 여는데 실패했다면 파일은 터치조차 되지 않는다. 모든 것은 addAll()을를 호출한 다음에 시작된다.

두번째 조각의 마지막 줄을 제외한 모든 라인은 작은 객체를 인스턴스화하고 큰 객체를 합성하고 있다. 이 객체 합성은 데이터 변환을 일으키지 않기 때문에 CPU 비용은 오히려 낮다.

또한 첫번째 스크립트가 O(n)으로 움직이는 반면, 두번째 스크립트는 분명히 O(1)의 계산량으로 움직인다. 이런 이유는 첫번째 스크립트에서는 데이터에 대한 절차적 접근을 했기 때문이다.

객체 지향의 세계에서는 데이터라는 것은 없다. 객체와 그 행위만이 있다!

넋두리

오늘은 블로그 글을 쓰고 싶어서 그동안 메모해 두었던것들, 생각나는 것들, 책 읽은 것들을 정리, 인용해서 채워나갑니다. ^^

Leadership and Management -- The Two Creations(리더십과 관리능력, 두 개의 창의성)

회사를 경영하는 것은 참 쉽지 않네요. 오늘은 초심으로 돌아가보고자 The 7 Habits of Highly Effective People"라는 책을 들었는데, 그중에서 예전에는 몰라는데 지금 보니 기록해보고 싶은 글귀가 있어 메모를 해 봅니다.

You can quickly grasp the important difference between the two if you envision a group of producers cutting their way through the jungle with machetes. They're the producers, the problem solvers. They're cutting through the undergrowth, clearing it out.
The managers are behind them, sharpening their machetes, writing policy and procedure manuals, holding muscle development programs, bringing in improved technologies, and setting up working schedules and compensation programs for machete wielders.
The leader is the one who climbs the tallest tree, surveys the entire situation, and yells, "Wrong jungle!"
But how do the busy, efficient producers and managers often respond? "Shut up! We're making progress."
As individuals, groups, and businesses, we're often so busy cutting through the undergrowth we don't even realize we're in the wrong jungle. And the rapidly changing environment in which we live makes effective leadership more critical than it has ever been -- in every aspect of independent and interdependent life.

정글 속에서 도끼로 길을 개척하는 작업팀을 생각해 보면, 리더십과 관리능력의 차이를 곧 알 것이다. 작업 팀은 생산에 종사하고 현장에서 문제를 해결하는 사람들이다. 그들은 실제로 덤불을 베어 길을 개척해 나간다. 관리의 역할은 후방에 있으며, 도끼의 날을 예리하게 갈고, 정책및 절차 매뉴얼을 만들고, 근육 강화 훈련을 개발하고, 새로운 기술을 도입하며, 작업 일정 및 보상 체계를 만든다. 리더의 역할은 정글에서 가장 높은 나무에 올라가, 전체를 둘러보고, "이 정글이 아니잖아!"라고 외친다.

하지만, 현실은 바쁘면서도 효율적인 우리 작업자와 관리자는 종종 어떻게 반응합니까? "닥쳐! 우리 지금 만들고 있잖으냐? 개인, 그룹, 기업으로서, 우리는 정글속 덤불을 절단하는데 너무 바쁜 나머지 우리가 잘못된 정글에서 작업하고 있다는 것은 인지하지 못하고 있다. 지금 우리는 아주 급변하는 환경속에 살고 있어서, 무엇보다도 지금까지 해왔던것보다 더 효과적인 리더십이 중요하다.

"The 7 Habits of Highly Effective People" 중에서.

개발 문화에 대해

요즘 Github에 스타트업에서도 필요한 개발 문화를 만들기 위해 우선 필요한 것들을 이것 저것 적고 있는데 문화를 만들기 또한, 쉽지 않네요.

요즘, 스트트업들은 Cloud, Slack, GitHub, Intellij, Mac등과 같은 도구들을 잘 지원해 주고 또, 일반적으로 잘 다루고 있기도 합니다만. 그런다고 개발이 잘 진행되지는 않는것 같습니다. 뭔가 도구를 도입하면 문제가 해결한다는 것은 착각이고, 정작 중요한 것은 개발 조직의 구조와 관리, 그리고 리더십 등 올바르게 움직이게 하는 것인데, 이런 것이 기업 문화가 아닌가 생각됩니다.

바라봐야할 곳

문제 해결을 위해서, 문제보다, 문제 해결 노력보다 먼저 방법을 생각해 버리는 경우가 많아지고 있습니다. 일 예로, 서비스를 하나 구상하고 있는데, 제가 가지고 있는 기술중에서 방법들을 엮어보려는 시도를 하는 모습을 자주 봅니다. 도구와 방법에 이끌려 창의력을 옥죄고 있고 자신이 가지고 있는 방법이나 기술이 만능이라고 암묵적으로 생각해서 그런걸까요?

또, 소프트웨어는 목적을 달성하기 위한 도구이기는 하지만, 소프트웨어가 목적 자체를 바꿀 수는 없을까요? 같은 생각을 해야할 것 같아요. 그리고 목적 달성에 굳이 소프트웨어가 아닐 수도 있다는 생각도 중요한거 같습니다.

[번역] 왜 Null이 나쁜가?

우리가 개발을 하면서 항상 마주하는게 NULL 체크인데(잊고 있을수도 있지만), NULL 체크를 해야하는지 말아야하는지 등의 부담을 개발자에게 전가시키는 것은 안좋은 방법이라고 알고 있다. 그래서 왜 Null의 반환이 안좋은지를 잘 이해시킨 포스트가 있어 소개한다. "Why NULL is Bad?".

Java에서 NULL을 사용하는 아주 단순한 예이다.
public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    return null;
  }
  return new Employee(id);
}

이 메서드 무엇이 잘못되었는가? 객체 대신 NULL을 반환할 수 있다는 것이 잘못된 것이다. NULL은 객체 지향 패러다임의 끔찍한 관습이며, 온 힘을 다해 피해야할 것 중에 하나이다. 이에 대해서는 많은 의견이 이미 발표되어 있다. 예를 들어, Tony Hoare의 Null References, The Billion Dollar Mistake와 David West의 저서 Object Thinking에서 전반적으로 언급되고 있다.

여기에서 모든 논거를 정리해 NULL의 사용을 피하고 적절한 객체 지향 구조로 바꾸는 방법의 예를 소개하고 싶다.

기본적으로 NULL을 대신할 수 있는 것은 두가지가 있다.

하나는 Null Object 디자인 패턴이다. (가장 좋은 방법은 하나의 불변 객체로 만드는 것이다.)
public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    return Employee.NOBODY;
  }
  return Employee(id);
}

또 하나는 객체를 돌려줄 수 없는 경우에 예외를 던지고 fail-fast하는 방법이다.
public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    throw new EmployeeNotFoundException(name);
  }
  return Employee(id);
}

자 그럼, NULL을 반대하는 논거를 살펴 보자.

위 Tony Hoare의 발표와 David West의 저서 이외에, 나는 이 포스트를 쓰기 전에 다음의 책이나 글들을 읽었다.

Ad-hoc(임기응변적인) 오류 처리
입력으로 객체를 받은 경우는 항상 그것이 NULL 아닌지, 또한 유효한 객체 참조 여부를 확인하지 않으면 안된다. 그것을 확인하는 것을 잊어버리면 NullPointerException(NPE)으로 인해 런타임때 실행을 중지시켜 버릴 우려가 있다. 따라서 여러분의 로직은 복수의 확인 처리나 if/then/else 분기에 코드가 오염되어 버린다.
// this is a terrible design, don't reuse
Employee employee = dept.getByName("Jeffrey");
if (employee == null) {
  System.out.println("can't find an employee");
  System.exit(-1);
} else {
  employee.transferTo(dept2);
}

이것은 C나 다른 명령문을 늘어 놓는 절차형 언어에 있어서 예외적인 상황에 대응하는 방법이다. OOP는 주로 이런 임시 오류 처리의 블록을 없앨 목적으로 예외 처리를 도입했다. OOP에서는 예외 처리를 어플리케이션 레벨에서 에러 핸들러에 맡기는 것으로, 코드를 매우 깨끗하고 간결하게 해준다.
dept.getByName("Jeffrey").transferTo(dept2);

NULL 참조는 절차적 언어에서 계승된 것이라고 인식하고 Null 객체 또는 예외를 대신에 사용한다.

Ambiguous Semantic(모호한 의도)
위 메소드의 목적을 분명하게 전달하기 위해서 getByName()은 getByNameOrNullIfNotFound()라고 명명되어져야 한다. 이와 같은 이름을 가진 객체 또는 NULL을 반환하는 모든 함수가 있어야 된다. 그렇지 않다면 누군가가 모호한 코드를 읽게 된다. 그래서 코드의 의도를 명확하게 하기 위해 함수에 긴 이름을 붙이게 한다.

이 모호성을 제거하기 위해 함수는 실제 객체를 반환하거나 혹은 Null 객체를 반환하거나 예외를 반환한다.

성능을 고려하면 NULL을 반환해야하는 경우도 있다라고 주장하는 사람이 있을지도 모른다. 예를 들어, Java의 Map 인터페이스의 get() 메서드는 지정된 요소가 없는 경우 NULL을 반환한다.
Employee employee = employees.get("Jeffrey");
if (employee == null) {
  throw new EmployeeNotFoundException();
}
return employee;

이 코드는 Map이 NULL을 사용하고 있는 덕분에 map을 한번밖에 검색하지 않는다. 만약 Map의 get()이 요소가 발견되지 않을 때에 예외를 던지도록 하면 다음과 같은 코드가 된다.
if (!employees.containsKey("Jeffrey")) { // first search
  throw new EmployeeNotFoundException();
}
return employees.get("Jeffrey"); // second search

분명히, 이 방법은 처음보다 2배 느리다. 그런데, 어떻게 하라고?

Map 인터페이스는 (저자을 공격하는 것은 아니지만) 설계에 문제가 있다. 그 get() 메소드는 Iterator를 반환해야 한다. 그러한 경우는 다음과 같은 코드가 된다.
Iterator found = Map.search("Jeffrey");
if (!found.hasNext()) {
  throw new EmployeeNotFoundException();
}
return found.next();

참고로 C++ 표준 라이브러리의 map::find() 함수는 이렇게 설계되어 있다.

컴퓨터 사고 vs. 객체 사고
Java의 객체는 데이터 구조를 가리키는 포인터로 NULL은 아무것도 가리키지 않는 포인터(Intel x86 프로세서에서는 0x00000000)임을 아는 사람에게는 if (employee == null)이라는 문장은 이해할 수 있다.

그러나 만약 우리가 객체가 되었다고 생각하면 이 문장은 상당히 의미가 없는 것이 된다. 객체 관점에서 위 코드는 다음과 같다.
- 여보세요, 소프트웨어 부서입니까?
- 예.
- Jeffrey랑 이야기하고 싶습니다.
- 잠시만 기다려주십시오...
- 여보세요?
- 당신은 NULL입니까?

대화의 마지막 질문이 이상하지 않은가?

대신에 만약 Jeffrey와 연결을 요청한 후 전화가 끊어지면 우리에게 문제(예외)가 발생했다는 것을 안다. 이 시점에서 다시 한번 전화해보거나 Jeffrey에게로 연결되지 않아서 큰 일을 못하게 되면 상사에게 보고한다.

또는, Jeffrey는 아니지만 소프트웨어 부서의 사람에게 대략적인 질문에 대답할 수 있는 사람에게 전화를 할 수도 있고, Jeffrey 밖에 모르는 내용이라면 거부할지도 모른다(Null Object).

지연 실패(Slow Failing)
빠른 실패 대신에 위의 코드는 천천히 죽이려 한다. 중간에 다른 객체를 죽이면서. 문제가 발생했기 때문에 예외 처리를 빨리 시작해야 한다고 주위에 알리는 대신, 클라이언트부터 오류를 숨기고 있다.

이 논의는 앞에 기술한 "임시 오류 처리(Ad-hoc Error Handling)"에 가깝다.

코드는 가능한 한 허술한 것이 좋다. 필요할 때 멈춰야 한다.

메소드는 다루는 데이터에 대해서 가능한 한 엄격하게 만들어져야 한다. 주어진 데이터가 불충분하거나 메소드의 사용 방법에 위배되면 예외를 던지도록 해야한다.

그렇지 않으면, 공통적인 행위를 하거나 모든 호출에서 항상 예외를 던지는 Null Object를 반환한다.
public Employee getByName(String name) {
  int id = database.find(name);
  Employee employee;
  if (id == 0) {
    employee = new Employee() {
      @Override
      public String name() {
        return "anonymous";
      }
      @Override
      public void transferTo(Department dept) {
        throw new AnonymousEmployeeException(
          "I can't be transferred, I'm anonymous"
        );
      }
    };
  } else {
    employee = Employee(id);
  }
  return employee;
}

가변적이면서 불완전한 객체
일반적으로 객체는 불변으로 설계하는 것이 바람직하다. 이것은 객체룰 인스턴스화할 때 필요한 모든 정보를 받고 그 수명 주기 전반에 걸쳐 상태를 바꾸지 않는다는 것을 의미한다.

NULL은 지연 로딩을 할 때 종종 사용되는 객체를 불완전, 가변객체를 만든다. 다음이 그 예다.
public class Department {
  private Employee found = null;
  public synchronized Employee manager() {
    if (this.found == null) {
      this.found = new Employee("Jeffrey");
    }
    return this.found;
  }
}

이 기술은 널리 사용되고는 있지만, OOP의 안티 패턴이다. 주된 이유는 실행 환경의 성능 문제 책임을 객체에 전가했기 때문이다. 본래 그것은 Employee 객체가 걱정해야 할 부분은 아니다.

객체가 자신의 상태를 관리하고 자신의 역할에 대한 행동을 공개하는 대신 반환 캐시를 신경써야 한다. 이것이 지연로드의 의미이다.

캐시는 employee(직원)이 사무실에서 하는 일이 아니지 않나?

해결책? 지연로드는 위의 예 같은 원시적인 방법으로는 사용하지 마라. 대신 캐시 문제를 어플리케이션의 다른 레이어에 옮겨라.

예를 들어, Java라면, AOP를 사용할 수 있다. 예를 들어, jcabi-aspects는 @Cacheable 어노테이션이 메소드의 반환 값을 캐쉬하고 있다.
import com.jcabi.aspects.Cacheable;
public class Department {
  @Cacheable(forever = true)
  public Employee manager() {
    return new Employee("Jacky Brown");
  }
}

나의 이 분석에 납득하고 더이상 NULL을 쓰기를 끝내길 바란다.
Tags : , ,